mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-01 09:43:42 +02:00
774 lines
24 KiB
ObjectPascal
774 lines
24 KiB
ObjectPascal
unit FPDbgController;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes,
|
|
SysUtils,
|
|
Maps,
|
|
LazLogger,
|
|
DbgIntfBaseTypes,
|
|
FpDbgDisasX86,
|
|
FpDbgClasses;
|
|
|
|
type
|
|
|
|
TOnCreateProcessEvent = procedure(var continue: boolean) of object;
|
|
TOnHitBreakpointEvent = procedure(var continue: boolean; const Breakpoint: TDbgBreakpoint) of object;
|
|
TOnExceptionEvent = procedure(var continue: boolean; const ExceptionClass, ExceptionMessage: string) of object;
|
|
TOnProcessExitEvent = procedure(ExitCode: DWord) of object;
|
|
|
|
TDbgController = class;
|
|
|
|
{ TDbgControllerCmd }
|
|
|
|
TDbgControllerCmd = class
|
|
protected
|
|
FController: TDbgController;
|
|
public
|
|
constructor Create(AController: TDbgController); virtual;
|
|
procedure DoContinue(AProcess: TDbgProcess; AThread: TDbgThread); virtual; abstract;
|
|
procedure ResolveEvent(var AnEvent: TFPDEvent; out Handled, Finished: boolean); virtual; abstract;
|
|
end;
|
|
|
|
{ TDbgControllerContinueCmd }
|
|
|
|
TDbgControllerContinueCmd = class(TDbgControllerCmd)
|
|
public
|
|
procedure DoContinue(AProcess: TDbgProcess; AThread: TDbgThread); override;
|
|
procedure ResolveEvent(var AnEvent: TFPDEvent; out Handled, Finished: boolean); override;
|
|
end;
|
|
|
|
{ TDbgControllerStepIntoInstructionCmd }
|
|
|
|
TDbgControllerStepIntoInstructionCmd = class(TDbgControllerCmd)
|
|
public
|
|
procedure DoContinue(AProcess: TDbgProcess; AThread: TDbgThread); override;
|
|
procedure ResolveEvent(var AnEvent: TFPDEvent; out Handled, Finished: boolean); override;
|
|
end;
|
|
|
|
{ TDbgControllerStepOverInstructionCmd }
|
|
|
|
TDbgControllerStepOverInstructionCmd = class(TDbgControllerCmd)
|
|
private
|
|
FHiddenBreakpoint: TDbgBreakpoint;
|
|
FIsSet: boolean;
|
|
public
|
|
procedure DoContinue(AProcess: TDbgProcess; AThread: TDbgThread); override;
|
|
procedure ResolveEvent(var AnEvent: TFPDEvent; out Handled, Finished: boolean); override;
|
|
end;
|
|
|
|
{ TDbgControllerStepOverLineCmd }
|
|
|
|
TDbgControllerStepOverLineCmd = class(TDbgControllerStepOverInstructionCmd)
|
|
private
|
|
FInfoStored: boolean;
|
|
FStoredStackFrame: TDBGPtr;
|
|
public
|
|
procedure DoContinue(AProcess: TDbgProcess; AThread: TDbgThread); override;
|
|
procedure ResolveEvent(var AnEvent: TFPDEvent; out Handled, Finished: boolean); override;
|
|
end;
|
|
|
|
{ TDbgControllerStepIntoLineCmd }
|
|
|
|
TDbgControllerStepIntoLineCmd = class(TDbgControllerCmd)
|
|
private
|
|
FInfoStored: boolean;
|
|
FStoredStackFrame: TDBGPtr;
|
|
FInto: boolean;
|
|
FHiddenWatchpointInto: integer;
|
|
FHiddenWatchpointOut: integer;
|
|
FHiddenWatchpointOutStackbase: integer;
|
|
FLastStackPointerValue: TDBGPtr;
|
|
FLastStackBaseValue: TDBGPtr;
|
|
FAssumedProcStartStackPointer: TDBGPtr;
|
|
FHiddenBreakpoint: TDbgBreakpoint;
|
|
FInstCount: integer;
|
|
public
|
|
constructor Create(AController: TDbgController); override;
|
|
destructor Destroy; override;
|
|
procedure DoContinue(AProcess: TDbgProcess; AThread: TDbgThread); override;
|
|
procedure ResolveEvent(var AnEvent: TFPDEvent; out Handled, Finished: boolean); override;
|
|
end;
|
|
|
|
{ TDbgControllerRunToCmd }
|
|
|
|
TDbgControllerRunToCmd = class(TDbgControllerCmd)
|
|
private
|
|
FHiddenBreakpoint: TDbgBreakpoint;
|
|
FLocation: TDBGPtr;
|
|
FProcess: TDbgProcess;
|
|
public
|
|
constructor Create(AController: TDbgController; ALocation: TDBGPtr);
|
|
procedure DoContinue(AProcess: TDbgProcess; AThread: TDbgThread); override;
|
|
procedure ResolveEvent(var AnEvent: TFPDEvent; out Handled, Finished: boolean); override;
|
|
end;
|
|
|
|
{ TDbgController }
|
|
|
|
TDbgController = class
|
|
private
|
|
FEnvironment: TStrings;
|
|
FExecutableFilename: string;
|
|
FNextOnlyStopOnStartLine: boolean;
|
|
FOnCreateProcessEvent: TOnCreateProcessEvent;
|
|
FOnDebugInfoLoaded: TNotifyEvent;
|
|
FOnExceptionEvent: TOnExceptionEvent;
|
|
FOnHitBreakpointEvent: TOnHitBreakpointEvent;
|
|
FOnLog: TOnLog;
|
|
FOnProcessExitEvent: TOnProcessExitEvent;
|
|
FProcessMap: TMap;
|
|
FPDEvent: TFPDEvent;
|
|
FParams: TStringList;
|
|
FConsoleTty: string;
|
|
FRedirectConsoleOutput: boolean;
|
|
FWorkingDirectory: string;
|
|
procedure SetEnvironment(AValue: TStrings);
|
|
procedure SetExecutableFilename(AValue: string);
|
|
procedure SetOnLog(AValue: TOnLog);
|
|
procedure DoOnDebugInfoLoaded(Sender: TObject);
|
|
procedure SetParams(AValue: TStringList);
|
|
protected
|
|
FMainProcess: TDbgProcess;
|
|
FCurrentProcess: TDbgProcess;
|
|
FCurrentThread: TDbgThread;
|
|
FCommand: TDbgControllerCmd;
|
|
procedure Log(const AString: string; const ALogLevel: TFPDLogLevel = dllDebug);
|
|
procedure Log(const AString: string; const Options: array of const; const ALogLevel: TFPDLogLevel = dllDebug);
|
|
function GetProcess(const AProcessIdentifier: THandle; out AProcess: TDbgProcess): Boolean;
|
|
public
|
|
constructor Create; virtual;
|
|
destructor Destroy; override;
|
|
procedure InitializeCommand(ACommand: TDbgControllerCmd);
|
|
function Run: boolean;
|
|
procedure Stop;
|
|
procedure StepIntoInstr;
|
|
procedure StepOverInstr;
|
|
procedure Next;
|
|
procedure Step;
|
|
procedure StepOut;
|
|
function Pause: boolean;
|
|
procedure ProcessLoop;
|
|
procedure SendEvents(out continue: boolean);
|
|
|
|
property ExecutableFilename: string read FExecutableFilename write SetExecutableFilename;
|
|
property OnLog: TOnLog read FOnLog write SetOnLog;
|
|
property CurrentProcess: TDbgProcess read FCurrentProcess;
|
|
property CurrentThread: TDbgThread read FCurrentThread;
|
|
property MainProcess: TDbgProcess read FMainProcess;
|
|
property Params: TStringList read FParams write SetParams;
|
|
property Environment: TStrings read FEnvironment write SetEnvironment;
|
|
property WorkingDirectory: string read FWorkingDirectory write FWorkingDirectory;
|
|
property RedirectConsoleOutput: boolean read FRedirectConsoleOutput write FRedirectConsoleOutput;
|
|
property ConsoleTty: string read FConsoleTty write FConsoleTty;
|
|
// With this parameter set a 'next' will only stop if the current
|
|
// instruction is the first instruction of a line according to the
|
|
// debuginfo.
|
|
// Due to a bug in fpc's debug-info, the line info for the first instruction
|
|
// of a line, sometimes points the the prior line. This setting hides the
|
|
// results of that bug. It seems like it that GDB does something similar.
|
|
property NextOnlyStopOnStartLine: boolean read FNextOnlyStopOnStartLine write FNextOnlyStopOnStartLine;
|
|
|
|
property OnCreateProcessEvent: TOnCreateProcessEvent read FOnCreateProcessEvent write FOnCreateProcessEvent;
|
|
property OnHitBreakpointEvent: TOnHitBreakpointEvent read FOnHitBreakpointEvent write FOnHitBreakpointEvent;
|
|
property OnProcessExitEvent: TOnProcessExitEvent read FOnProcessExitEvent write FOnProcessExitEvent;
|
|
property OnExceptionEvent: TOnExceptionEvent read FOnExceptionEvent write FOnExceptionEvent;
|
|
property OnDebugInfoLoaded: TNotifyEvent read FOnDebugInfoLoaded write FOnDebugInfoLoaded;
|
|
end;
|
|
|
|
implementation
|
|
|
|
{ TDbgControllerRunToCmd }
|
|
|
|
constructor TDbgControllerRunToCmd.Create(AController: TDbgController; ALocation: TDBGPtr);
|
|
begin
|
|
inherited create(AController);
|
|
FLocation:=ALocation;
|
|
end;
|
|
|
|
procedure TDbgControllerRunToCmd.DoContinue(AProcess: TDbgProcess; AThread: TDbgThread);
|
|
begin
|
|
FProcess := AProcess;
|
|
if not assigned(FHiddenBreakpoint) and not AProcess.HasBreak(FLocation) then
|
|
FHiddenBreakpoint := AProcess.AddBreak(FLocation);
|
|
|
|
AProcess.Continue(AProcess, AThread, False);
|
|
end;
|
|
|
|
procedure TDbgControllerRunToCmd.ResolveEvent(var AnEvent: TFPDEvent; out
|
|
Handled, Finished: boolean);
|
|
begin
|
|
Handled := false;
|
|
Finished := (AnEvent<>deInternalContinue);
|
|
if Finished and assigned(FHiddenBreakpoint) then
|
|
begin
|
|
FProcess.RemoveBreak(FLocation);
|
|
FHiddenBreakpoint.Free;
|
|
end;
|
|
end;
|
|
|
|
{ TDbgControllerStepIntoLineCmd }
|
|
|
|
constructor TDbgControllerStepIntoLineCmd.Create(AController: TDbgController);
|
|
begin
|
|
inherited Create(AController);
|
|
FHiddenWatchpointInto:=-1;
|
|
FHiddenWatchpointOut:=-1;
|
|
FHiddenWatchpointOutStackbase:=-1;
|
|
end;
|
|
|
|
destructor TDbgControllerStepIntoLineCmd.Destroy;
|
|
begin
|
|
if assigned(FHiddenBreakpoint) then
|
|
begin
|
|
FController.CurrentProcess.RemoveBreak(FHiddenBreakpoint.Location);
|
|
FreeAndNil(FHiddenBreakpoint);
|
|
end;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TDbgControllerStepIntoLineCmd.DoContinue(AProcess: TDbgProcess; AThread: TDbgThread);
|
|
|
|
var
|
|
CodeBin: array[0..20] of byte;
|
|
p: pointer;
|
|
ADump,
|
|
AStatement: string;
|
|
ALocation: TDBGPtr;
|
|
|
|
begin
|
|
if not FInfoStored then
|
|
begin
|
|
FInfoStored:=true;
|
|
FStoredStackFrame:=AProcess.GetStackBasePointerRegisterValue;
|
|
AThread.StoreStepInfo;
|
|
end;
|
|
|
|
if not FInto then
|
|
begin
|
|
if AProcess.ReadData(aProcess.GetInstructionPointerRegisterValue,sizeof(CodeBin),CodeBin) then
|
|
begin
|
|
p := @CodeBin;
|
|
Disassemble(p, AProcess.Mode=dm64, ADump, AStatement);
|
|
if (copy(AStatement,1,4)='call') then
|
|
begin
|
|
FInto := true;
|
|
FInstCount := 0;
|
|
|
|
ALocation := AProcess.GetInstructionPointerRegisterValue+(PtrUInt(p)-PtrUInt(@codebin));
|
|
if not AProcess.HasBreak(ALocation) then
|
|
FHiddenBreakpoint := AProcess.AddBreak(ALocation);
|
|
|
|
AProcess.Continue(AProcess, AThread, true);
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
AProcess.Continue(AProcess, AThread, (FHiddenWatchpointInto=-1) and (FHiddenWatchpointOut=-1));
|
|
end;
|
|
|
|
procedure TDbgControllerStepIntoLineCmd.ResolveEvent(var AnEvent: TFPDEvent; out Handled, Finished: boolean);
|
|
|
|
var
|
|
AStackPointerValue: TDBGPtr;
|
|
AStackBasePointerValue: TDBGPtr;
|
|
|
|
procedure SetHWBreakpoints;
|
|
var
|
|
OutStackPos: TDBGPtr;
|
|
StackBasePos: TDBGPtr;
|
|
IntoStackPos: TDBGPtr;
|
|
begin
|
|
IntoStackPos:=AStackPointerValue-DBGPTRSIZE[FController.FCurrentProcess.Mode];
|
|
OutStackPos:=FAssumedProcStartStackPointer;
|
|
StackBasePos:=AStackBasePointerValue;
|
|
|
|
FHiddenWatchpointInto := FController.FCurrentThread.AddWatchpoint(IntoStackPos);
|
|
FHiddenWatchpointOut := FController.FCurrentThread.AddWatchpoint(OutStackPos);
|
|
FHiddenWatchpointOutStackbase := FController.FCurrentThread.AddWatchpoint(StackBasePos);
|
|
end;
|
|
|
|
begin
|
|
if (FHiddenWatchpointOut<>-1) and FController.FCurrentThread.RemoveWatchpoint(FHiddenWatchpointOut) then
|
|
FHiddenWatchpointOut:=-1;
|
|
if (FHiddenWatchpointInto<>-1) and FController.FCurrentThread.RemoveWatchpoint(FHiddenWatchpointInto) then
|
|
FHiddenWatchpointInto:=-1;
|
|
if (FHiddenWatchpointOutStackbase<>-1) and FController.FCurrentThread.RemoveWatchpoint(FHiddenWatchpointOutStackbase) then
|
|
FHiddenWatchpointOutStackbase:=-1;
|
|
|
|
AStackPointerValue:=FController.CurrentProcess.GetStackPointerRegisterValue;
|
|
AStackBasePointerValue:=FController.CurrentProcess.GetStackBasePointerRegisterValue;
|
|
|
|
Handled := false;
|
|
Finished := not (AnEvent in [deInternalContinue, deLoadLibrary]);
|
|
if (AnEvent=deBreakpoint) and (not assigned(FController.FCurrentProcess.CurrentBreakpoint) or (FController.FCurrentProcess.CurrentBreakpoint=FHiddenBreakpoint)) then
|
|
begin
|
|
if FController.FCurrentThread.CompareStepInfo<>dcsiNewLine then
|
|
begin
|
|
AnEvent:=deInternalContinue;
|
|
Finished:=false;
|
|
inc(FInstCount);
|
|
|
|
if FInto then
|
|
begin
|
|
if (FController.CurrentProcess.CurrentBreakpoint=FHiddenBreakpoint) then
|
|
begin
|
|
FInto:=false;
|
|
FInstCount:=0;
|
|
FController.CurrentProcess.RemoveBreak(FHiddenBreakpoint.Location);
|
|
FreeAndNil(FHiddenBreakpoint);
|
|
end
|
|
else
|
|
begin
|
|
if FInstCount=1 then
|
|
FAssumedProcStartStackPointer:=AStackPointerValue;
|
|
if (AStackBasePointerValue<>FLastStackBaseValue) or (AStackPointerValue<>FLastStackPointerValue) then
|
|
FInstCount:=1;
|
|
|
|
if FInstCount>4 then
|
|
SetHWBreakpoints;
|
|
end;
|
|
end
|
|
else
|
|
FInstCount := 0;
|
|
end
|
|
else
|
|
begin
|
|
// Also check if the current instruction is at the start of a new
|
|
// sourceline. (Dwarf only)
|
|
// Don't do this while stepping into a procedure, only when stepping out.
|
|
// This because when stepping out of a procedure, the first asm-instruction
|
|
// could still be part of the instruction-line that made the call to the
|
|
// procedure in the first place.
|
|
if ((FStoredStackFrame<AStackBasePointerValue) or (FController.NextOnlyStopOnStartLine))
|
|
and not FController.FCurrentThread.IsAtStartOfLine then
|
|
begin
|
|
Finished:=false;
|
|
AnEvent:=deInternalContinue;
|
|
end;
|
|
end;
|
|
end;
|
|
FLastStackPointerValue:=AStackPointerValue;
|
|
FLastStackBaseValue:=AStackBasePointerValue;
|
|
end;
|
|
|
|
{ TDbgControllerStepOverLineCmd }
|
|
|
|
procedure TDbgControllerStepOverLineCmd.DoContinue(AProcess: TDbgProcess; AThread: TDbgThread);
|
|
begin
|
|
if not FInfoStored then
|
|
begin
|
|
FInfoStored:=true;
|
|
AThread.StoreStepInfo;
|
|
FStoredStackFrame:=AProcess.GetStackBasePointerRegisterValue;
|
|
end;
|
|
|
|
inherited DoContinue(AProcess, AThread);
|
|
end;
|
|
|
|
procedure TDbgControllerStepOverLineCmd.ResolveEvent(var AnEvent: TFPDEvent; out Handled, Finished: boolean);
|
|
begin
|
|
inherited ResolveEvent(AnEvent, Handled, Finished);
|
|
if (AnEvent=deBreakpoint) and not assigned(FController.CurrentProcess.CurrentBreakpoint) then
|
|
begin
|
|
if (FController.FCurrentThread.CompareStepInfo<>dcsiNewLine) or
|
|
(not FController.FCurrentThread.IsAtStartOfLine and
|
|
(FController.NextOnlyStopOnStartLine or (FStoredStackFrame < FController.CurrentProcess.GetStackBasePointerRegisterValue))) then
|
|
begin
|
|
AnEvent:=deInternalContinue;
|
|
FHiddenBreakpoint:=nil;
|
|
FIsSet:=false;
|
|
Finished:=false;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ TDbgControllerStepOverInstructionCmd }
|
|
|
|
procedure TDbgControllerStepOverInstructionCmd.DoContinue(AProcess: TDbgProcess; AThread: TDbgThread);
|
|
|
|
var
|
|
CodeBin: array[0..20] of byte;
|
|
p: pointer;
|
|
ADump,
|
|
AStatement: string;
|
|
CallInstr: boolean;
|
|
ALocation: TDbgPtr;
|
|
|
|
begin
|
|
if FIsSet then
|
|
AProcess.Continue(AProcess, AThread, false)
|
|
else
|
|
begin
|
|
CallInstr:=false;
|
|
if AProcess.ReadData(aProcess.GetInstructionPointerRegisterValue,sizeof(CodeBin),CodeBin) then
|
|
begin
|
|
p := @CodeBin;
|
|
Disassemble(p, AProcess.Mode=dm64, ADump, AStatement);
|
|
if copy(AStatement,1,4)='call' then
|
|
CallInstr:=true;
|
|
end;
|
|
|
|
if CallInstr then
|
|
begin
|
|
ALocation := AProcess.GetInstructionPointerRegisterValue+(PtrUInt(p)-PtrUInt(@codebin));
|
|
if not AProcess.HasBreak(ALocation) then
|
|
FHiddenBreakpoint := AProcess.AddBreak(ALocation);
|
|
end;
|
|
FIsSet:=true;
|
|
AProcess.Continue(AProcess, AThread, not CallInstr);
|
|
end;
|
|
end;
|
|
|
|
procedure TDbgControllerStepOverInstructionCmd.ResolveEvent(
|
|
var AnEvent: TFPDEvent; out Handled, Finished: boolean);
|
|
begin
|
|
Handled := false;
|
|
Finished := not (AnEvent in [deInternalContinue, deLoadLibrary]);
|
|
if Finished then
|
|
begin
|
|
if assigned(FHiddenBreakpoint) then
|
|
begin
|
|
FController.FCurrentProcess.RemoveBreak(FHiddenBreakpoint.Location);
|
|
FHiddenBreakpoint.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ TDbgControllerStepIntoInstructionCmd }
|
|
|
|
procedure TDbgControllerStepIntoInstructionCmd.DoContinue(
|
|
AProcess: TDbgProcess; AThread: TDbgThread);
|
|
begin
|
|
AProcess.Continue(AProcess, AThread, True);
|
|
end;
|
|
|
|
procedure TDbgControllerStepIntoInstructionCmd.ResolveEvent(
|
|
var AnEvent: TFPDEvent; out Handled, Finished: boolean);
|
|
begin
|
|
Handled := false;
|
|
Finished := (AnEvent<>deInternalContinue);
|
|
end;
|
|
|
|
{ TDbgControllerContinueCmd }
|
|
|
|
procedure TDbgControllerContinueCmd.DoContinue(AProcess: TDbgProcess; AThread: TDbgThread);
|
|
begin
|
|
AProcess.Continue(AProcess, AThread, False);
|
|
end;
|
|
|
|
procedure TDbgControllerContinueCmd.ResolveEvent(var AnEvent: TFPDEvent; out
|
|
Handled, Finished: boolean);
|
|
begin
|
|
Handled := false;
|
|
Finished := (AnEvent<>deInternalContinue);
|
|
end;
|
|
|
|
{ TDbgControllerCmd }
|
|
|
|
constructor TDbgControllerCmd.Create(AController: TDbgController);
|
|
begin
|
|
FController := AController;
|
|
end;
|
|
|
|
{ TDbgController }
|
|
|
|
procedure TDbgController.DoOnDebugInfoLoaded(Sender: TObject);
|
|
begin
|
|
if Assigned(FOnDebugInfoLoaded) then
|
|
FOnDebugInfoLoaded(Self);
|
|
end;
|
|
|
|
procedure TDbgController.SetParams(AValue: TStringList);
|
|
begin
|
|
if FParams=AValue then Exit;
|
|
FParams.Assign(AValue);
|
|
end;
|
|
|
|
procedure TDbgController.SetExecutableFilename(AValue: string);
|
|
begin
|
|
if FExecutableFilename=AValue then Exit;
|
|
FExecutableFilename:=AValue;
|
|
end;
|
|
|
|
procedure TDbgController.SetEnvironment(AValue: TStrings);
|
|
begin
|
|
if FEnvironment=AValue then Exit;
|
|
FEnvironment.Assign(AValue);
|
|
end;
|
|
|
|
procedure TDbgController.SetOnLog(AValue: TOnLog);
|
|
begin
|
|
if FOnLog=AValue then Exit;
|
|
FOnLog:=AValue;
|
|
end;
|
|
|
|
destructor TDbgController.Destroy;
|
|
begin
|
|
FMainProcess.Free;
|
|
FProcessMap.Free;
|
|
FParams.Free;
|
|
FEnvironment.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TDbgController.InitializeCommand(ACommand: TDbgControllerCmd);
|
|
begin
|
|
if assigned(FCommand) then
|
|
raise exception.create('Prior command not finished yet.');
|
|
{$ifdef DBG_FPDEBUG_VERBOSE}
|
|
log('Initialized command '+ACommand.ClassName, dllDebug);
|
|
{$endif DBG_FPDEBUG_VERBOSE}
|
|
FCommand := ACommand;
|
|
end;
|
|
|
|
function TDbgController.Run: boolean;
|
|
begin
|
|
result := False;
|
|
if assigned(FMainProcess) then
|
|
begin
|
|
Log('The debuggee is already running', dllInfo);
|
|
Exit;
|
|
end;
|
|
|
|
if FExecutableFilename = '' then
|
|
begin
|
|
Log('No filename given to execute.', dllInfo);
|
|
Exit;
|
|
end;
|
|
|
|
if not FileExists(FExecutableFilename) then
|
|
begin
|
|
Log('File %s does not exist.',[FExecutableFilename], dllInfo);
|
|
Exit;
|
|
end;
|
|
|
|
FCurrentProcess := OSDbgClasses.DbgProcessClass.StartInstance(FExecutableFilename, Params, Environment, WorkingDirectory, FConsoleTty , @Log, RedirectConsoleOutput);
|
|
if assigned(FCurrentProcess) then
|
|
begin
|
|
FProcessMap.Add(FCurrentProcess.ProcessID, FCurrentProcess);
|
|
Log('Got PID: %d, TID: %d', [FCurrentProcess.ProcessID, FCurrentProcess.ThreadID]);
|
|
result := true;
|
|
end;
|
|
end;
|
|
|
|
procedure TDbgController.Stop;
|
|
begin
|
|
if assigned(FMainProcess) then
|
|
FMainProcess.TerminateProcess
|
|
else
|
|
raise Exception.Create('Failed to stop debugging. No main process.');
|
|
end;
|
|
|
|
procedure TDbgController.StepIntoInstr;
|
|
begin
|
|
InitializeCommand(TDbgControllerStepIntoInstructionCmd.Create(self));
|
|
end;
|
|
|
|
procedure TDbgController.StepOverInstr;
|
|
begin
|
|
InitializeCommand(TDbgControllerStepOverInstructionCmd.Create(self));
|
|
end;
|
|
|
|
procedure TDbgController.Next;
|
|
begin
|
|
InitializeCommand(TDbgControllerStepOverLineCmd.Create(self));
|
|
end;
|
|
|
|
procedure TDbgController.Step;
|
|
begin
|
|
InitializeCommand(TDbgControllerStepIntoLineCmd.Create(self));
|
|
end;
|
|
|
|
procedure TDbgController.StepOut;
|
|
begin
|
|
//FCurrentThread.StepOut;
|
|
end;
|
|
|
|
function TDbgController.Pause: boolean;
|
|
begin
|
|
result := FCurrentProcess.Pause;
|
|
end;
|
|
|
|
procedure TDbgController.ProcessLoop;
|
|
|
|
var
|
|
AProcessIdentifier: THandle;
|
|
AThreadIdentifier: THandle;
|
|
AExit: boolean;
|
|
IsHandled: boolean;
|
|
IsFinished: boolean;
|
|
|
|
begin
|
|
AExit:=false;
|
|
repeat
|
|
if assigned(FCurrentProcess) and not assigned(FMainProcess) then
|
|
FMainProcess:=FCurrentProcess
|
|
else
|
|
begin
|
|
if not assigned(FCommand) then
|
|
begin
|
|
{$ifdef DBG_FPDEBUG_VERBOSE}
|
|
log('Continue process without command.', dllDebug);
|
|
{$endif DBG_FPDEBUG_VERBOSE}
|
|
FCurrentProcess.Continue(FCurrentProcess, FCurrentThread, False)
|
|
end
|
|
else
|
|
begin
|
|
{$ifdef DBG_FPDEBUG_VERBOSE}
|
|
log('Continue process with command '+FCommand.ClassName, dllDebug);
|
|
{$endif DBG_FPDEBUG_VERBOSE}
|
|
FCommand.DoContinue(FCurrentProcess, FCurrentThread);
|
|
end;
|
|
end;
|
|
if not FCurrentProcess.WaitForDebugEvent(AProcessIdentifier, AThreadIdentifier) then Continue;
|
|
|
|
FCurrentProcess := nil;
|
|
FCurrentThread := nil;
|
|
if not GetProcess(AProcessIdentifier, FCurrentProcess) then
|
|
begin
|
|
// A second/third etc process has been started.
|
|
FCurrentProcess := OSDbgClasses.DbgProcessClass.Create('', AProcessIdentifier, AThreadIdentifier, OnLog);
|
|
FProcessMap.Add(AProcessIdentifier, FCurrentProcess);
|
|
Continue;
|
|
end;
|
|
|
|
if FCurrentProcess<>FMainProcess then
|
|
// Just continue the process. Only the main-process is being debugged.
|
|
Continue;
|
|
|
|
if not FCurrentProcess.GetThread(AThreadIdentifier, FCurrentThread) then
|
|
FCurrentThread := FCurrentProcess.AddThread(AThreadIdentifier);
|
|
|
|
FPDEvent:=FCurrentProcess.ResolveDebugEvent(FCurrentThread);
|
|
{$ifdef DBG_FPDEBUG_VERBOSE}
|
|
log('Process stopped with event %s. IP=%s, SP=%s, BSP=%s.', [FPDEventNames[FPDEvent],
|
|
FCurrentProcess.FormatAddress(FCurrentProcess.GetInstructionPointerRegisterValue),
|
|
FCurrentProcess.FormatAddress(FCurrentProcess.GetStackPointerRegisterValue),
|
|
FCurrentProcess.FormatAddress(FCurrentProcess.GetStackBasePointerRegisterValue)], dllDebug);
|
|
{$endif DBG_FPDEBUG_VERBOSE}
|
|
if assigned(FCommand) then
|
|
begin
|
|
FCommand.ResolveEvent(FPDEvent, IsHandled, IsFinished);
|
|
{$ifdef DBG_FPDEBUG_VERBOSE}
|
|
if IsFinished then
|
|
log('Command %s is finished. (IsHandled=%s)', [FCommand.ClassName, BoolToStr(IsHandled)], dllDebug)
|
|
else
|
|
log('Command %s is not finished. (IsHandled=%s)', [FCommand.ClassName, BoolToStr(IsHandled)], dllDebug);
|
|
{$endif DBG_FPDEBUG_VERBOSE}
|
|
end
|
|
else
|
|
begin
|
|
IsHandled:=false;
|
|
IsFinished:=false;
|
|
end;
|
|
if not IsHandled then
|
|
begin
|
|
{ case FPDEvent of
|
|
deLoadLibrary :
|
|
begin
|
|
if FCurrentProcess.GetLib(FCurrentProcess.LastEventProcessIdentifier, ALib)
|
|
and (GImageInfo <> iiNone)
|
|
then begin
|
|
WriteLN('Name: ', ALib.Name);
|
|
//if GImageInfo = iiDetail
|
|
//then DumpPEImage(Proc.Handle, Lib.BaseAddr);
|
|
end;
|
|
if GBreakOnLibraryLoad
|
|
then GState := dsPause;
|
|
|
|
end;
|
|
end; }{case}
|
|
end;
|
|
if IsFinished then
|
|
FreeAndNil(FCommand);
|
|
AExit:=true;
|
|
until AExit;
|
|
end;
|
|
|
|
procedure TDbgController.SendEvents(out continue: boolean);
|
|
begin
|
|
case FPDEvent of
|
|
deCreateProcess:
|
|
begin
|
|
FCurrentProcess.LoadInfo;
|
|
if not FCurrentProcess.DbgInfo.HasInfo then
|
|
Log('No Dwarf-debug information available. The debugger will not function properly. [CurrentProcess='+dbgsname(FCurrentProcess)+',DbgInfo='+dbgsname(FCurrentProcess.DbgInfo)+']',dllInfo);
|
|
|
|
DoOnDebugInfoLoaded(self);
|
|
|
|
continue:=true;
|
|
if assigned(OnCreateProcessEvent) then
|
|
OnCreateProcessEvent(continue);
|
|
end;
|
|
deBreakpoint:
|
|
begin
|
|
continue:=false;
|
|
if assigned(OnHitBreakpointEvent) then
|
|
OnHitBreakpointEvent(continue, FCurrentProcess.CurrentBreakpoint);
|
|
end;
|
|
deExitProcess:
|
|
begin
|
|
if FCurrentProcess = FMainProcess then FMainProcess := nil;
|
|
|
|
if assigned(OnProcessExitEvent) then
|
|
OnProcessExitEvent(FCurrentProcess.ExitCode);
|
|
|
|
FProcessMap.Delete(FCurrentProcess.ProcessID);
|
|
FCurrentProcess.Free;
|
|
FCurrentProcess := nil;
|
|
continue := false;
|
|
end;
|
|
deException:
|
|
begin
|
|
continue:=false;
|
|
if assigned(OnExceptionEvent) then
|
|
OnExceptionEvent(continue, FCurrentProcess.ExceptionClass, FCurrentProcess.ExceptionMessage );
|
|
end;
|
|
deLoadLibrary:
|
|
begin
|
|
continue:=true;
|
|
end;
|
|
deInternalContinue:
|
|
begin
|
|
continue := true;
|
|
end;
|
|
else
|
|
raise exception.create('Unknown debug controler state');
|
|
end;
|
|
end;
|
|
|
|
procedure TDbgController.Log(const AString: string; const ALogLevel: TFPDLogLevel);
|
|
begin
|
|
if assigned(FOnLog) then
|
|
FOnLog(AString, ALogLevel)
|
|
else
|
|
DebugLn(AString);
|
|
end;
|
|
|
|
procedure TDbgController.Log(const AString: string;
|
|
const Options: array of const; const ALogLevel: TFPDLogLevel);
|
|
begin
|
|
Log(Format(AString, Options), ALogLevel);
|
|
end;
|
|
|
|
function TDbgController.GetProcess(const AProcessIdentifier: THandle; out AProcess: TDbgProcess): Boolean;
|
|
begin
|
|
Result := FProcessMap.GetData(AProcessIdentifier, AProcess) and (AProcess <> nil);
|
|
end;
|
|
|
|
constructor TDbgController.Create;
|
|
begin
|
|
FParams := TStringList.Create;
|
|
FEnvironment := TStringList.Create;
|
|
FProcessMap := TMap.Create(itu4, SizeOf(TDbgProcess));
|
|
FNextOnlyStopOnStartLine := true;
|
|
end;
|
|
|
|
end.
|
|
|