mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-04 03:23:58 +02:00
646 lines
16 KiB
ObjectPascal
646 lines
16 KiB
ObjectPascal
{ $Id$ }
|
|
{ ----------------------------------------------
|
|
GDBDebugger.pp - Debugger class forGDB
|
|
----------------------------------------------
|
|
|
|
@created(Wed Feb 28st WET 2001)
|
|
@lastmod($Date$)
|
|
@author(Marc Weustink <marc@@lazarus.dommelstein.net>)
|
|
|
|
This unit contains the Commandline debugger class for the GDB
|
|
debugger.
|
|
|
|
|
|
***************************************************************************
|
|
* *
|
|
* This source is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
* This code is distributed in the hope that it will be useful, but *
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
|
* General Public License for more details. *
|
|
* *
|
|
* A copy of the GNU General Public License is available on the World *
|
|
* Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
|
|
* obtain it by writing to the Free Software Foundation, *
|
|
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
* *
|
|
***************************************************************************
|
|
}
|
|
unit GDBDebugger;
|
|
|
|
{$mode objfpc}
|
|
{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, Process, Debugger, CmdLineDebugger;
|
|
|
|
|
|
type
|
|
TGDBProgramInfo = record
|
|
State: TDBGState;
|
|
BreakPoint: Integer; // ID of Breakpoint hit
|
|
Signal: Integer; // Signal no if we hit one
|
|
SignalText: String; // Signal text if we hit one
|
|
end;
|
|
|
|
|
|
TGDBDebugger = class(TCmdLineDebugger)
|
|
private
|
|
FHasSymbols: Boolean;
|
|
function FindBreakpoint(const ABreakpoint: Integer): TDBGBreakPoint;
|
|
procedure GDBRun;
|
|
procedure GDBPause;
|
|
procedure GDBStart;
|
|
procedure GDBStop;
|
|
procedure GDBStepOver;
|
|
procedure GDBStepInto;
|
|
procedure GDBRunTo(const ASource: String; const ALine: Integer);
|
|
procedure GDBJumpTo(const ASource: String; const ALine: Integer);
|
|
function SendCommand(const ACommand: String;
|
|
Values: array of const): TStrings; // internally used by breakpoints and watches
|
|
procedure RunCommand(const ACommand: String);
|
|
function GetLocation: TDBGLocationRec;
|
|
function GetProgramInfo(const AHandleResult: Boolean): TGDBProgramInfo;
|
|
protected
|
|
function CreateBreakPoints: TDBGBreakPoints; override;
|
|
function CreateWatches: TDBGWatches; override;
|
|
function GetSupportedCommands: TDBGCommands; override;
|
|
procedure SetFileName(const AValue: String); override;
|
|
function RequestCommand(const ACommand: TDBGCommand; const AParams: array of const): Boolean; override;
|
|
public
|
|
constructor Create; {override;}
|
|
destructor Destroy; override;
|
|
|
|
procedure Init; override; // Initializes external debugger
|
|
procedure Done; override; // Kills external debugger
|
|
// internal testing
|
|
procedure TestCmd(const ACommand: String); override;
|
|
end;
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
SysUtils;
|
|
|
|
type
|
|
TGDBBreakPoint = class(TDBGBreakPoint)
|
|
private
|
|
FBreakID: Integer;
|
|
procedure SetBreakPoint;
|
|
protected
|
|
procedure DoActionChange; override;
|
|
procedure DoEnableChange; override;
|
|
procedure DoExpressionChange; override;
|
|
procedure DoStateChange; override;
|
|
procedure SetLocation(const ASource: String; const ALine: Integer); override;
|
|
public
|
|
constructor Create(ACollection: TCollection); override;
|
|
destructor Destroy; override;
|
|
procedure Hit;
|
|
end;
|
|
|
|
TGDBWatch = class(TDBGWatch)
|
|
private
|
|
protected
|
|
procedure DoEnableChange; override;
|
|
function GetValue: String; override;
|
|
function GetValid: Boolean; override;
|
|
procedure SetExpression(const AValue: String); override;
|
|
procedure SetValue(const AValue: String); override;
|
|
public
|
|
end;
|
|
|
|
{ =========================================================================== }
|
|
{ TGDBDebugger }
|
|
{ =========================================================================== }
|
|
|
|
constructor TGDBDebugger.Create;
|
|
begin
|
|
inherited Create;
|
|
end;
|
|
|
|
function TGDBDebugger.CreateBreakPoints: TDBGBreakPoints;
|
|
begin
|
|
Result := TDBGBreakPoints.Create(Self, TGDBBreakPoint);
|
|
end;
|
|
|
|
function TGDBDebugger.CreateWatches: TDBGWatches;
|
|
begin
|
|
Result := TDBGWatches.Create(Self, TGDBWatch);
|
|
end;
|
|
|
|
destructor TGDBDebugger.Destroy;
|
|
begin
|
|
inherited;
|
|
end;
|
|
|
|
procedure TGDBDebugger.Done;
|
|
begin
|
|
if State = dsRun then GDBPause;
|
|
SendCmdLn('quit', False);
|
|
inherited Done;
|
|
end;
|
|
|
|
function TGDBDebugger.FindBreakpoint(const ABreakpoint: Integer): TDBGBreakPoint;
|
|
var
|
|
n: Integer;
|
|
begin
|
|
if ABreakpoint <> 0
|
|
then
|
|
for n := 0 to Breakpoints.Count - 1 do
|
|
begin
|
|
Result := Breakpoints[n];
|
|
if TGDBBreakpoint(Result).FBreakID = ABreakpoint
|
|
then Exit;
|
|
end;
|
|
Result := nil;
|
|
end;
|
|
|
|
procedure TGDBDebugger.GDBJumpTo(const ASource: String; const ALine: Integer);
|
|
begin
|
|
end;
|
|
|
|
procedure TGDBDebugger.GDBPause;
|
|
begin
|
|
SendBreak(TargetProcess.Handle);
|
|
end;
|
|
|
|
procedure TGDBDebugger.GDBRun;
|
|
var
|
|
dState: TDBGState;
|
|
begin
|
|
case State of
|
|
dsIdle, dsStop: begin
|
|
GDBStart;
|
|
dState := GetProgramInfo(False).State;
|
|
if dState = dsPause
|
|
then begin
|
|
RunCommand('cont');
|
|
end
|
|
else begin
|
|
DoCurrent(GetLocation);
|
|
SetState(dState);
|
|
end;
|
|
end;
|
|
dsPause: begin
|
|
RunCommand('cont');
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TGDBDebugger.GDBRunTo(const ASource: String; const ALine: Integer);
|
|
begin
|
|
if State in [dsRun, dsError] then Exit;
|
|
|
|
SendCmdLn('tbreak %s:%d', [ASource, ALine], True);
|
|
GDBRun;
|
|
end;
|
|
|
|
procedure TGDBDebugger.GDBStart;
|
|
var
|
|
loc: TDBGLocationRec;
|
|
dState: TDBGState;
|
|
StopAdress: Integer;
|
|
//idx: Integer;
|
|
S: String;
|
|
begin
|
|
if State in [dsIdle, dsStop]
|
|
then begin
|
|
CreateTargetProcess(FileName);
|
|
|
|
SendCommand('attach %d', [TargetProcess.Handle]);
|
|
TargetProcess.Resume;
|
|
SendCmdLn('cont', True);
|
|
|
|
S := SendCommand('tbreak main', [TargetProcess.Handle]).Text;
|
|
|
|
|
|
if Pos('Breakpoint', S) > 0
|
|
then begin
|
|
// TargetProcess.Resume;
|
|
SetState(dsRun);
|
|
StopAdress := -1;
|
|
end
|
|
else begin
|
|
// No symbols or no main
|
|
// Try to set a TBreak at first adress
|
|
S := SendCommand('info files', [TargetProcess.Handle]).Text;
|
|
StopAdress := StrToIntDef(StripLN('$' + GetPart('Entry point: 0x', '', S)), -1);
|
|
if StopAdress <> -1
|
|
then begin
|
|
S := SendCommand('x/2i 0x%x', [StopAdress]).Text;
|
|
GetLine(S); //ignore first line
|
|
S := GetPart('0x', ':', S);
|
|
StopAdress := StrToIntDef(StripLN('$' + GetPart('', ' ', S)), -1);
|
|
if StopAdress <> -1
|
|
then begin
|
|
SendCommand('tbreak *0x%x', [StopAdress]);
|
|
end;
|
|
end;
|
|
|
|
// TargetProcess.Resume;
|
|
SetState(dsRun);
|
|
end;
|
|
|
|
repeat
|
|
SendCmdLn('cont', True);
|
|
loc := GetLocation;
|
|
dState := GetProgramInfo(False).State;
|
|
until (loc.FuncName = 'main') or (Integer(loc.Adress) = StopAdress) or (dState <> dsPause);
|
|
end;
|
|
end;
|
|
|
|
procedure TGDBDebugger.GDBStepInto;
|
|
begin
|
|
case State of
|
|
dsIdle, dsStop: begin
|
|
GDBStart;
|
|
GetProgramInfo(True);
|
|
end;
|
|
dsPause: begin
|
|
RunCommand('step');
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TGDBDebugger.GDBStepOver;
|
|
begin
|
|
case State of
|
|
dsIdle, dsStop: begin
|
|
GDBStart;
|
|
GetProgramInfo(True);
|
|
end;
|
|
dsPause: begin
|
|
RunCommand('next');
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TGDBDebugger.GDBStop;
|
|
var
|
|
dState: TDBGState;
|
|
begin
|
|
if State = dsRun
|
|
then begin
|
|
GDBPause;
|
|
// wait till pause is executed
|
|
SendCmdLn('', True);
|
|
end;
|
|
|
|
dState := GetProgramInfo(False).State;
|
|
if dState = dsPause
|
|
then begin
|
|
SendCmdLn('kill', True);
|
|
dState := GetProgramInfo(False).State;
|
|
end;
|
|
|
|
if dState = dsStop
|
|
then KillTargetProcess;
|
|
SetState(dState);
|
|
end;
|
|
|
|
function TGDBDebugger.GetLocation: TDBGLocationRec;
|
|
var
|
|
n, idx: Integer;
|
|
NoSrc: Boolean;
|
|
S: String;
|
|
begin
|
|
Result.Adress := nil;
|
|
Result.FuncName := '';
|
|
Result.SrcFile := '';
|
|
Result.SrcLine := -1;
|
|
|
|
SendCmdLn('info frame', True);
|
|
for n := 0 to OutputLines.Count - 1 do
|
|
begin
|
|
S := OutputLines[n];
|
|
idx := Pos('eip = 0x', S);
|
|
if idx = 0 then Continue;
|
|
|
|
// Get addr
|
|
Delete(S, 1, idx + 7);
|
|
idx := Pos('in', S);
|
|
if idx = 0 then Break;
|
|
Result.Adress := Pointer(StrToIntDef('$' + Copy(S, 1, idx - 2), 0));
|
|
|
|
// get function
|
|
Delete(S, 1, idx + 2);
|
|
idx := Pos(' (', S);
|
|
NoSrc := (idx = 0);
|
|
if NoSrc
|
|
then idx := Pos(';',S);
|
|
Result.FuncName := Copy(S, 1, idx - 1);
|
|
if NoSrc then Break;
|
|
|
|
// getsource info
|
|
Delete(S, 1, idx + 1);
|
|
idx := Pos(':', S);
|
|
if idx = 0 then Break;
|
|
Result.SrcFile := Copy(S, 1, idx - 1);
|
|
Delete(S, 1, idx);
|
|
idx := Pos(')', S);
|
|
if idx = 0 then Break;
|
|
Result.SrcLine := StrToIntDef(Copy(S, 1, idx - 1), 0);
|
|
end;
|
|
end;
|
|
|
|
function TGDBDebugger.GetProgramInfo(const AHandleResult: Boolean): TGDBProgramInfo;
|
|
var
|
|
S, Signal: String;
|
|
BreakPoint: TGDBBreakPoint;
|
|
begin
|
|
// Loop since we might have hit a non-break breakpoint
|
|
while True do
|
|
begin
|
|
Result.Breakpoint := 0;
|
|
Result.Signal := 0;
|
|
Result.SignalText := '';
|
|
Result.State := dsNone;
|
|
|
|
SendCmdLn('info program', True);
|
|
S := OutputLines.Text;
|
|
if Pos('stopped', S) > 0
|
|
then begin
|
|
Result.State := dsPause;
|
|
if Pos('breakpoint ', S) > 0
|
|
then begin
|
|
Result.Breakpoint := StrToIntDef(GetPart('breakpoint ', '.', S), 0);
|
|
end
|
|
else if Pos('signal ', S) > 0
|
|
then begin
|
|
Signal := GetPart('signal ', ',', S);
|
|
// TODO: translate to id
|
|
Result.SignalText := GetPart(' ', '.', S);
|
|
end;
|
|
end
|
|
else if Pos('not being run', S) > 0
|
|
then Result.State := dsStop;
|
|
|
|
if AHandleResult
|
|
then begin
|
|
if Result.Breakpoint <> 0
|
|
then begin
|
|
BreakPoint := TGDBBreakPoint(FindBreakPoint(Result.Breakpoint));
|
|
if BreakPoint <> nil
|
|
then begin
|
|
BreakPoint.Hit;
|
|
|
|
if not (bpaStop in BreakPoint.Actions)
|
|
then begin
|
|
SendCmdLn('cont', True);
|
|
Continue;
|
|
end;
|
|
end;
|
|
end;
|
|
SetState(Result.State);
|
|
DoCurrent(GetLocation);
|
|
|
|
if Result.SignalText <> ''
|
|
then DoException(Result.Signal, Result.SignalText);
|
|
end;
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
function TGDBDebugger.GetSupportedCommands: TDBGCommands;
|
|
begin
|
|
Result := [dcRun, dcPause, dcStop, dcStepOver, dcStepInto, dcRunTo, dcJumpto, dcBreak{, dcWatch}]
|
|
end;
|
|
|
|
procedure TGDBDebugger.Init;
|
|
begin
|
|
WaitPrompt := '(gdb) ';
|
|
if DebugProcess = nil
|
|
then begin
|
|
CreateDebugProcess('/usr/bin/gdb -silent');
|
|
SendCmdLn('', True);
|
|
end;
|
|
SendCmdLn('set confirm off', True);
|
|
inherited Init;
|
|
end;
|
|
|
|
function TGDBDebugger.RequestCommand(const ACommand: TDBGCommand; const AParams: array of const): Boolean;
|
|
begin
|
|
case ACommand of
|
|
dcRun: GDBRun;
|
|
dcPause: GDBPause;
|
|
dcStop: GDBStop;
|
|
dcStepOver: GDBStepOver;
|
|
dcStepInto: GDBStepInto;
|
|
dcRunTo: GDBRunTo(String(APArams[0].VAnsiString), APArams[1].VInteger);
|
|
dcJumpto: GDBJumpTo(String(APArams[0].VAnsiString), APArams[1].VInteger);
|
|
end;
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TGDBDebugger.RunCommand(const ACommand: String);
|
|
begin
|
|
SetState(dsRun);
|
|
SendCmdLn(ACommand, True);
|
|
GetProgramInfo(True);
|
|
end;
|
|
|
|
function TGDBDebugger.SendCommand(const ACommand: String;
|
|
Values: array of const): TStrings;
|
|
begin
|
|
SendCmdLn(ACommand, Values, True);
|
|
Result := OutputLines;
|
|
end;
|
|
|
|
procedure TGDBDebugger.SetFileName(const AValue: String);
|
|
begin
|
|
if AValue <> FileName
|
|
then begin
|
|
GDBStop;
|
|
if AValue <> ''
|
|
then begin
|
|
SendCmdLn('file %s', [AValue], True);
|
|
FHasSymbols := Pos('no debugging symbols', OutputLines.Text) = 0;
|
|
if not FHasSymbols
|
|
then WriteLN('WARNING: File ''',AValue, ''' has no debug symbols');
|
|
end;
|
|
inherited;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure TGDBDebugger.TestCmd(const ACommand: String);
|
|
begin
|
|
SetState(dsRun);
|
|
inherited TestCmd(ACommand);
|
|
GetProgramInfo(True);
|
|
end;
|
|
|
|
{ =========================================================================== }
|
|
{ TGDBBreakPoint }
|
|
{ =========================================================================== }
|
|
|
|
constructor TGDBBreakPoint.Create(ACollection: TCollection);
|
|
begin
|
|
inherited Create(ACollection);
|
|
FBreakID := 0;
|
|
end;
|
|
|
|
destructor TGDBBreakPoint.Destroy;
|
|
begin
|
|
if FBreakID <> 0
|
|
then begin
|
|
TGDBDebugger(Debugger).SendCommand('delete %d', [FBreakID]);
|
|
end;
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TGDBBreakPoint.DoActionChange;
|
|
begin
|
|
Changed(False);
|
|
end;
|
|
|
|
procedure TGDBBreakPoint.DoEnableChange;
|
|
const
|
|
CMD: array[Boolean] of String = ('disable', 'enable');
|
|
begin
|
|
if FBreakID = 0 then Exit;
|
|
|
|
TGDBDebugger(Debugger).SendCommand('%s %d', [CMD[Enabled], FBreakID]);
|
|
Changed(false);
|
|
end;
|
|
|
|
procedure TGDBBreakPoint.DoExpressionChange;
|
|
begin
|
|
Changed(False);
|
|
end;
|
|
|
|
procedure TGDBBreakPoint.DoStateChange;
|
|
begin
|
|
inherited DoStateChange;
|
|
if (Debugger.State = dsStop)
|
|
and (FBreakID = 0)
|
|
then SetBreakpoint;
|
|
end;
|
|
|
|
procedure TGDBBreakPoint.Hit;
|
|
begin
|
|
SetHitCount(HitCount + 1);
|
|
|
|
if bpaEnableGroup in Actions
|
|
then; //TODO
|
|
if bpaDisableGroup in Actions
|
|
then; //TODO
|
|
end;
|
|
|
|
procedure TGDBBreakPoint.SetBreakpoint;
|
|
var
|
|
idx: Integer;
|
|
S: String;
|
|
begin
|
|
S := TGDBDebugger(Debugger).SendCommand('break %s:%d', [Source, Line])[0];
|
|
idx := Pos(' at', S);
|
|
if idx >0
|
|
then begin
|
|
FBreakID := StrToIntDef(Copy(S, 12, idx - 12), 0);
|
|
end;
|
|
SetValid(FBreakID <> 0);
|
|
DoEnableChange;
|
|
end;
|
|
|
|
|
|
procedure TGDBBreakPoint.SetLocation(const ASource: String; const ALine: Integer);
|
|
begin
|
|
if (FSource = ASource) and (FLine = ALine) then exit;
|
|
inherited;
|
|
if TGDBDebugger(Debugger).State in [dsStop, dsPause, dsIdle]
|
|
then SetBreakpoint;
|
|
end;
|
|
|
|
{ =========================================================================== }
|
|
{ TGDBWatch }
|
|
{ =========================================================================== }
|
|
|
|
procedure TGDBWatch.DoEnableChange;
|
|
begin
|
|
end;
|
|
|
|
function TGDBWatch.GetValue: String;
|
|
begin
|
|
if (Debugger.State in [dsStop, dsPause, dsIdle])
|
|
and Valid
|
|
then begin
|
|
end
|
|
else Result := inherited GetValue;
|
|
end;
|
|
|
|
function TGDBWatch.GetValid: Boolean;
|
|
begin
|
|
Result:=false;
|
|
end;
|
|
|
|
procedure TGDBWatch.SetExpression(const AValue: String);
|
|
begin
|
|
if (AValue <> Expression)
|
|
and (Debugger.State in [dsStop, dsPause, dsIdle])
|
|
then begin
|
|
//TGDBDebugger(Debugger).SendCmdLn('', True);
|
|
end;
|
|
end;
|
|
|
|
procedure TGDBWatch.SetValue(const AValue: String);
|
|
begin
|
|
end;
|
|
|
|
end.
|
|
{ =============================================================================
|
|
$Log$
|
|
Revision 1.11 2003/05/27 20:58:12 mattias
|
|
implemented enable and deleting breakpoint in breakpoint dlg
|
|
|
|
Revision 1.10 2003/05/23 14:12:51 mattias
|
|
implemented restoring breakpoints
|
|
|
|
Revision 1.9 2002/05/10 06:57:48 lazarus
|
|
MG: updated licenses
|
|
|
|
Revision 1.8 2002/03/09 02:03:59 lazarus
|
|
MWE:
|
|
* Upgraded gdb debugger to gdb/mi debugger
|
|
* Set default value for autpopoup
|
|
* Added Clear popup to debugger output window
|
|
|
|
Revision 1.7 2002/02/22 13:37:19 lazarus
|
|
MG: fixed saving undo limit
|
|
|
|
Revision 1.6 2002/02/20 23:33:24 lazarus
|
|
MWE:
|
|
+ Published OnClick for TMenuItem
|
|
+ Published PopupMenu property for TEdit and TMemo (Doesn't work yet)
|
|
* Fixed debugger running twice
|
|
+ Added Debugger output form
|
|
* Enabled breakpoints
|
|
|
|
Revision 1.5 2002/02/06 08:58:29 lazarus
|
|
MG: fixed compiler warnings and asking to create non existing files
|
|
|
|
Revision 1.4 2002/02/05 23:16:48 lazarus
|
|
MWE: * Updated tebugger
|
|
+ Added debugger to IDE
|
|
|
|
Revision 1.3 2001/11/12 19:28:23 lazarus
|
|
MG: fixed create, virtual constructors makes no sense
|
|
|
|
Revision 1.2 2001/11/06 23:59:13 lazarus
|
|
MWE: + Initial breakpoint support
|
|
+ Added exeption handling on process.free
|
|
|
|
Revision 1.1 2001/11/05 00:12:51 lazarus
|
|
MWE: First steps of a debugger.
|
|
|
|
|
|
}
|