mirror of
				https://gitlab.com/freepascal.org/lazarus/lazarus.git
				synced 2025-10-31 01:04:50 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			997 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
			
		
		
	
	
			997 lines
		
	
	
		
			32 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: TFpInternalBreakpoint) 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: TFpInternalBreakpoint;
 | |
|     FIsSet: boolean;
 | |
|   public
 | |
|     procedure DoContinue(AProcess: TDbgProcess; AThread: TDbgThread); override;
 | |
|     procedure ResolveEvent(var AnEvent: TFPDEvent; out Handled, Finished: boolean); override;
 | |
|   end;
 | |
| 
 | |
|   { TDbgControllerStepOutInstructionCmd }
 | |
| 
 | |
|   TDbgControllerStepOutInstructionCmd = class(TDbgControllerCmd)
 | |
|   private
 | |
|     FHiddenBreakpoint: TFpInternalBreakpoint;
 | |
|     FIsSet: boolean;
 | |
|     FProcess: TDbgProcess;
 | |
|     FThread: TDbgThread;
 | |
|     FStepCount: Integer;
 | |
|     FStepOut: Boolean;
 | |
|   protected
 | |
|     procedure SetReturnAdressBreakpoint(AProcess: TDbgProcess);
 | |
|   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: TFpInternalBreakpoint;
 | |
|     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: TFpInternalBreakpoint;
 | |
|     FLocation: TDBGPtrArray;
 | |
|     FProcess: TDbgProcess;
 | |
|   public
 | |
|     constructor Create(AController: TDbgController; ALocation: TDBGPtrArray);
 | |
|     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;
 | |
|     FForceNewConsoleWin: boolean;
 | |
|     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;
 | |
|     function GetCurrentThreadId: Integer;
 | |
|     procedure SetCurrentThreadId(AValue: Integer);
 | |
|     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 CurrentThreadId: Integer read GetCurrentThreadId write SetCurrentThreadId;
 | |
|     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 ForceNewConsoleWin: boolean read FForceNewConsoleWin write FForceNewConsoleWin; // windows only
 | |
|     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
 | |
| 
 | |
| { TDbgControllerStepOutInstructionCmd }
 | |
| 
 | |
| procedure TDbgControllerStepOutInstructionCmd.SetReturnAdressBreakpoint(AProcess: TDbgProcess);
 | |
| var
 | |
|   AStackPointerValue, StepOutStackPos, ReturnAddress: TDBGPtr;
 | |
| begin
 | |
|   AStackPointerValue:=FController.CurrentThread.GetStackBasePointerRegisterValue;
 | |
|   StepOutStackPos:=AStackPointerValue+DBGPTRSIZE[FController.FCurrentProcess.Mode];
 | |
| 
 | |
|   if AProcess.ReadAddress(StepOutStackPos, ReturnAddress) then
 | |
|   begin
 | |
|     FProcess := AProcess;
 | |
|     if not AProcess.HasBreak(ReturnAddress) then
 | |
|       FHiddenBreakpoint := AProcess.AddBreak(ReturnAddress)
 | |
|   end
 | |
|   else
 | |
|   begin
 | |
|     AProcess.Log('Failed to read return-address from stack');
 | |
|   end;
 | |
| 
 | |
|   FIsSet:=true;
 | |
| end;
 | |
| 
 | |
| procedure TDbgControllerStepOutInstructionCmd.DoContinue(AProcess: TDbgProcess; AThread: TDbgThread);
 | |
| var
 | |
|   CodeBin: array[0..20] of byte;
 | |
|   p: pointer;
 | |
|   ADump,
 | |
|   AStatement: string;
 | |
| begin
 | |
|   FThread := AThread;
 | |
|   FProcess := AProcess;
 | |
|   if FIsSet then
 | |
|     // When a breanpoint has already been set on the return-adress, just continue
 | |
|     AProcess.Continue(AProcess, AThread, false)
 | |
|   else if FStepCount < 12 then
 | |
|   begin
 | |
|     // During the prologue and epiloge of a procedure the call-stack might not been
 | |
|     // setup already. To avoid problems in these cases, start with a few (max
 | |
|     // 12) single steps.
 | |
|     Inc(FStepCount);
 | |
|     if AProcess.ReadData(AThread.GetInstructionPointerRegisterValue,sizeof(CodeBin),CodeBin) then
 | |
|     begin
 | |
|       p := @CodeBin;
 | |
|       Disassemble(p, AProcess.Mode=dm64, ADump, AStatement);
 | |
|       if (copy(AStatement,1,4)='call') then
 | |
|       begin
 | |
|         // Stop with the single-steps, set an hidden breakpoint at the return
 | |
|         // address and continue.
 | |
|         SetReturnAdressBreakpoint(AProcess);
 | |
|         AProcess.Continue(AProcess, AThread, False);
 | |
|       end
 | |
|       else if (copy(AStatement,1,3)='ret') then
 | |
|       begin
 | |
|         // Do one more single-step, and we're finished.
 | |
|         FStepOut := True;
 | |
|         AProcess.Continue(AProcess, AThread, True);
 | |
|       end
 | |
|       else
 | |
|         AProcess.Continue(AProcess, AThread, True);
 | |
|     end
 | |
|     else
 | |
|       AProcess.Continue(AProcess, AThread, True);
 | |
|   end
 | |
|   else
 | |
|   begin
 | |
|     // Enough with the single-stepping, set an hidden breakpoint at the return
 | |
|     // address, and continue.
 | |
|     SetReturnAdressBreakpoint(AProcess);
 | |
|     AProcess.Continue(AProcess, AThread, False);
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| procedure TDbgControllerStepOutInstructionCmd.ResolveEvent(var AnEvent: TFPDEvent; out Handled, Finished: boolean);
 | |
| begin
 | |
|   Handled := false;
 | |
|   Finished := false;
 | |
| 
 | |
|   if FStepOut then
 | |
|     // During single-stepping a 'ret' instruction was encountered. So we're just
 | |
|     // finished.
 | |
|     Finished := true
 | |
|   else if FIsSet then
 | |
|     Finished := not (AnEvent in [deInternalContinue, deLoadLibrary])
 | |
|   else if (AnEvent in [deBreakpoint]) and not FProcess.HasBreak(FThread.GetInstructionPointerRegisterValue) then
 | |
|     // Single-stepping, so continue silently.
 | |
|     AnEvent := deInternalContinue;
 | |
| 
 | |
|   if Finished then
 | |
|   begin
 | |
|     AnEvent := deFinishedStep;
 | |
|     if Assigned(FHiddenBreakpoint) then begin
 | |
|       FProcess.RemoveBreak(FHiddenBreakpoint);
 | |
|       FHiddenBreakpoint.Free;
 | |
|     end;
 | |
|   end;
 | |
| end;
 | |
| 
 | |
| { TDbgControllerRunToCmd }
 | |
| 
 | |
| constructor TDbgControllerRunToCmd.Create(AController: TDbgController; ALocation: TDBGPtrArray);
 | |
| begin
 | |
|   inherited create(AController);
 | |
|   FLocation:=ALocation;
 | |
| end;
 | |
| 
 | |
| procedure TDbgControllerRunToCmd.DoContinue(AProcess: TDbgProcess; AThread: TDbgThread);
 | |
| begin
 | |
|   FProcess := AProcess;
 | |
|   if not assigned(FHiddenBreakpoint) then // and not AProcess.HasBreak(FLocation)
 | |
|     FHiddenBreakpoint := AProcess.AddBreak(FLocation)
 | |
|   else
 | |
|     FProcess.Log('TDbgControllerRunToCmd.DoContinue: Breakpoint already used');
 | |
| 
 | |
|   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(FHiddenBreakpoint);
 | |
|     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);
 | |
|     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:=AThread.GetStackBasePointerRegisterValue;
 | |
|     AThread.StoreStepInfo;
 | |
|   end;
 | |
| 
 | |
|   if not FInto then
 | |
|   begin
 | |
|     if AProcess.ReadData(AThread.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 := AThread.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.CurrentThread.GetStackPointerRegisterValue;
 | |
|   AStackBasePointerValue:=FController.CurrentThread.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);
 | |
|           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:=AThread.GetStackBasePointerRegisterValue;
 | |
|   end;
 | |
| 
 | |
|   inherited DoContinue(AProcess, AThread);
 | |
| end;
 | |
| 
 | |
| procedure TDbgControllerStepOverLineCmd.ResolveEvent(var AnEvent: TFPDEvent; out Handled, Finished: boolean);
 | |
| var
 | |
|   OrigEvent: TFPDEvent;
 | |
| begin
 | |
|   OrigEvent := AnEvent;
 | |
|   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.CurrentThread.GetStackBasePointerRegisterValue))) then
 | |
|     begin
 | |
|       AnEvent:=deInternalContinue;
 | |
|       FHiddenBreakpoint:=nil;
 | |
|       FIsSet:=false;
 | |
|       Finished:=false;
 | |
|     end;
 | |
|   end;
 | |
|   if not Finished then
 | |
|     AnEvent := OrigEvent;
 | |
| 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(AThread.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 := AThread.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
 | |
|     AnEvent := deFinishedStep;
 | |
|     if assigned(FHiddenBreakpoint) then
 | |
|     begin
 | |
|       FController.FCurrentProcess.RemoveBreak(FHiddenBreakpoint);
 | |
|       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;
 | |
| 
 | |
| function TDbgController.GetCurrentThreadId: Integer;
 | |
| begin
 | |
|   Result := FCurrentThread.ID;
 | |
| end;
 | |
| 
 | |
| procedure TDbgController.SetCurrentThreadId(AValue: Integer);
 | |
| var
 | |
|   ExistingThread: TDbgThread;
 | |
| begin
 | |
|   if FCurrentThread.ID = AValue then Exit;
 | |
| 
 | |
|   if not FCurrentProcess.GetThread(AValue, ExistingThread) then begin
 | |
|     debugln(['SetCurrentThread() unknown thread id: ', AValue]);
 | |
|     // raise ...
 | |
|     exit;
 | |
|   end;
 | |
|   FCurrentThread := ExistingThread;
 | |
| end;
 | |
| 
 | |
| procedure TDbgController.SetOnLog(AValue: TOnLog);
 | |
| begin
 | |
|   if FOnLog=AValue then Exit;
 | |
|   FOnLog:=AValue;
 | |
| end;
 | |
| 
 | |
| destructor TDbgController.Destroy;
 | |
| var
 | |
|   it: TMapIterator;
 | |
|   p: TDbgProcess;
 | |
| begin
 | |
|   if Assigned(FMainProcess) then begin
 | |
|     FProcessMap.Delete(FMainProcess.ProcessID);
 | |
|     FMainProcess.Free;
 | |
|   end;
 | |
| 
 | |
|   it := TMapIterator.Create(FProcessMap);
 | |
|   while not it.EOM do begin
 | |
|     it.GetData(p);
 | |
|     p.Free;
 | |
|     it.Next;
 | |
|   end;
 | |
|   it.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;
 | |
| var
 | |
|   Flags: TStartInstanceFlags;
 | |
| 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;
 | |
| 
 | |
|   Flags := [];
 | |
|   if RedirectConsoleOutput then Include(Flags, siRediretOutput);
 | |
|   if ForceNewConsoleWin then Include(Flags, siForceNewConsole);
 | |
|   FCurrentProcess := OSDbgClasses.DbgProcessClass.StartInstance(FExecutableFilename, Params, Environment, WorkingDirectory, FConsoleTty , @Log, Flags);
 | |
|   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
 | |
|   InitializeCommand(TDbgControllerStepOutInstructionCmd.Create(self));
 | |
| end;
 | |
| 
 | |
| function TDbgController.Pause: boolean;
 | |
| begin
 | |
|   result := FCurrentProcess.Pause;
 | |
| end;
 | |
| 
 | |
| procedure TDbgController.ProcessLoop;
 | |
| 
 | |
| var
 | |
|   AProcessIdentifier: THandle;
 | |
|   AThreadIdentifier: THandle;
 | |
|   AExit: boolean;
 | |
|   IsHandled: boolean;
 | |
|   IsFinished: boolean;
 | |
|   EventProcess: TDbgProcess;
 | |
|   DummyThread: TDbgThread;
 | |
|   ctid: Integer;
 | |
| 
 | |
| begin
 | |
|   AExit:=false;
 | |
|   repeat
 | |
|     if assigned(FCurrentProcess) and not assigned(FMainProcess) then
 | |
|       FMainProcess:=FCurrentProcess
 | |
|     else
 | |
|     begin
 | |
|       ctid := 0;
 | |
|       if FCurrentThread <> nil then
 | |
|         ctid := FCurrentThread.ID;
 | |
|       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;
 | |
| 
 | |
|         // TODO: replace the dangling pointer with the next best value....
 | |
|         // There is still a race condition, for another thread to access it...
 | |
|         if (ctid <> 0) and not FCurrentProcess.GetThread(ctid, DummyThread) then
 | |
|           FCurrentThread := nil;
 | |
|     end;
 | |
|     if not FCurrentProcess.WaitForDebugEvent(AProcessIdentifier, AThreadIdentifier) then Continue;
 | |
| 
 | |
|     (* Do not change CurrentProcess/Thread,
 | |
|        unless the debugger can actually controll/debug those processes
 | |
|        - If FCurrentProcess is not set to FMainProcess then Pause will fail
 | |
|           (because a process that is not debugged, can not be paused,
 | |
|            and if it were debugged, *all* debugged processes may need to be paused)
 | |
|        - The LazFpDebugger may try to access FCurrentThread. If that is nil, it may crash.
 | |
|          e.g. TFPThreads.RequestMasterData
 | |
| 
 | |
|        This may need 3 threads: main, user-selected (thread win), current-event
 | |
| 
 | |
|        deExitProcess relies on only the main process receiving this.
 | |
| 
 | |
|     *)
 | |
|     //FCurrentProcess := nil;
 | |
|     //FCurrentThread := nil;
 | |
|     EventProcess := nil;
 | |
| //    if not GetProcess(AProcessIdentifier, FCurrentProcess) then
 | |
|     if not GetProcess(AProcessIdentifier, EventProcess) then
 | |
|       begin
 | |
|       // A second/third etc process has been started.
 | |
|       (* A process was created/forked
 | |
|          However the debugger currently does not attach to it on all platforms
 | |
|            so maybe other processes should be ignored?
 | |
|            It seems on windows/linux it does NOT attach.
 | |
|            On Mac, it may attempt to attach.
 | |
|          If the process is not debugged, it may not receive an deExitProcess
 | |
|       *)
 | |
|       (* As above, currently do not change those variables,
 | |
|          just continue the process-loop (as "FCurrentProcess<>FMainProcess" would do)
 | |
|       *)
 | |
|       //FCurrentProcess := OSDbgClasses.DbgProcessClass.Create('', AProcessIdentifier, AThreadIdentifier, OnLog);
 | |
|       //FProcessMap.Add(AProcessIdentifier, FCurrentProcess);
 | |
| 
 | |
|       Continue; // ***** This will swallow all FPDEvent for unknow processes *****
 | |
|       end;
 | |
| 
 | |
|     if EventProcess<>FMainProcess then
 | |
|     //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);
 | |
| 
 | |
|     (* TODO: ExitThread **********
 | |
|        at least the winprocess handles exitthread in the next line.
 | |
|        this will remove CurrentThread form the list of threads
 | |
|        CurrentThread is then destroyed in the next call to continue....
 | |
|     *)
 | |
|     FPDEvent:=FCurrentProcess.ResolveDebugEvent(FCurrentThread);
 | |
|     {$ifdef DBG_FPDEBUG_VERBOSE}
 | |
|     log('Process stopped with event %s. IP=%s, SP=%s, BSP=%s.', [FPDEventNames[FPDEvent],
 | |
|                                                                 FCurrentProcess.FormatAddress(FCurrentThread.GetInstructionPointerRegisterValue),
 | |
|                                                                 FCurrentProcess.FormatAddress(FCurrentThread.GetStackPointerRegisterValue),
 | |
|                                                                 FCurrentProcess.FormatAddress(FCurrentThread.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;
 | |
| 
 | |
|     AExit:=true;
 | |
|     if not IsHandled then
 | |
|     begin
 | |
|      case FPDEvent of
 | |
|        deInternalContinue: AExit := False;
 | |
| {        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);
 | |
|   until AExit;
 | |
| end;
 | |
| 
 | |
| procedure TDbgController.SendEvents(out continue: boolean);
 | |
| begin
 | |
|   case FPDEvent of
 | |
|     deCreateProcess:
 | |
|       begin
 | |
|       (* Only events for the main process get here / See ProcessLoop *)
 | |
|         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;
 | |
|     deFinishedStep:
 | |
|       begin
 | |
|         continue:=false;
 | |
|         // if there is a breakpoint at the stepping end, execute its actions
 | |
|         if assigned(OnHitBreakpointEvent) and Assigned(FCurrentProcess.CurrentBreakpoint) then
 | |
|           OnHitBreakpointEvent(continue, FCurrentProcess.CurrentBreakpoint);
 | |
|         // but do not continue
 | |
|         continue:=false;
 | |
|       end;
 | |
|     deBreakpoint:
 | |
|       begin
 | |
|         continue:=false;
 | |
|         if assigned(OnHitBreakpointEvent) then
 | |
|           OnHitBreakpointEvent(continue, FCurrentProcess.CurrentBreakpoint);
 | |
|       end;
 | |
|     deExitProcess:
 | |
|       begin
 | |
|       (* Only events for the main process get here / See ProcessLoop *)
 | |
|         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.
 | |
| 
 | 
