(* This program 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, 3 or any later version of the License (at your option). This program 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. You should have received a copy of the GNU General Public License along with this program. If not, see . *) (* settings set target.output-path /tmp/out.txt *) unit LldbDebugger; {$mode objfpc}{$H+} interface uses Classes, SysUtils, strutils, math, // LazUtils LazClasses, LazFileUtils, LazLoggerBase, LazStringUtils, Maps, // DebuggerIntf DbgIntfDebuggerBase, DbgIntfBaseTypes, // CmdLineDebuggerBase DebugProcess, // LazDebuggerLldb LldbInstructions, LldbHelper; type (* * Commands *) TLldbDebugger = class; TLldbDebuggerCommand = class; { TLldbDebuggerCommandQueue } TLldbDebuggerCommandQueue = class(TRefCntObjList) private FDebugger: TLldbDebugger; FLockQueueRun: Integer; function Get(Index: Integer): TLldbDebuggerCommand; procedure Put(Index: Integer; const AValue: TLldbDebuggerCommand); private FRunningCommand: TLldbDebuggerCommand; procedure Run; // Call Debugger.OnIdle // set IsIdle protected procedure CommandFinished(ACommand: TLldbDebuggerCommand); public constructor Create(ADebugger: TLldbDebugger); destructor Destroy; override; procedure CancelAll; procedure CancelForRun; procedure LockQueueRun; procedure UnLockQueueRun; property Items[Index: Integer]: TLldbDebuggerCommand read Get write Put; default; procedure QueueCommand(AValue: TLldbDebuggerCommand); procedure DoLineDataReceived(var ALine: String); property RunningCommand: TLldbDebuggerCommand read FRunningCommand; end; { TLldbDebuggerCommand } TLldbDebuggerCommand = class(TRefCountedObject) private FCancelableForRun: Boolean; FOwner: TLldbDebugger; FIsRunning: Boolean; function GetDebuggerState: TDBGState; function GetCommandQueue: TLldbDebuggerCommandQueue; function GetInstructionQueue: TLldbInstructionQueue; protected procedure DoLineDataReceived(var ALine: String); virtual; procedure DoExecute; virtual; abstract; procedure DoCancel; virtual; procedure Finished; procedure InstructionSucceeded(AnInstruction: TObject); procedure InstructionFailed(AnInstruction: TObject); procedure QueueInstruction(AnInstruction: TLldbInstruction); procedure SetDebuggerState(const AValue: TDBGState); property Debugger: TLldbDebugger read FOwner; property CommandQueue: TLldbDebuggerCommandQueue read GetCommandQueue; property InstructionQueue: TLldbInstructionQueue read GetInstructionQueue; property DebuggerState: TDBGState read GetDebuggerState; public constructor Create(AOwner: TLldbDebugger); destructor Destroy; override; procedure Execute; procedure Cancel; property CancelableForRun: Boolean read FCancelableForRun write FCancelableForRun; end; { TLldbDebuggerCommandInit } TLldbDebuggerCommandInit = class(TLldbDebuggerCommand) private FGotLLDB: Boolean; protected procedure DoExecute; override; procedure DoLineDataReceived(var ALine: String); override; end; { TLldbDebuggerCommandRun } TLldbDebuggerCommandRun = class(TLldbDebuggerCommand) private type TExceptionInfoCommand = (exiReg0, exiReg2, exiClass, exiMsg); TExceptionInfoCommands = set of TExceptionInfoCommand; private FMode: (cmRun, cmRunToCatch, cmRunAfterCatch, cmRunToTmpBrk); FState: (crRunning, crReadingThreads, crStopped, crStoppedRaise, crDone); FNextStepAction: TLldbInstructionProcessStepAction; FWaitToResume: Boolean; FCurBrkId, FTmpBreakId: Integer; FUnknownStopReason: String; FThreadInstr: TLldbInstructionThreadList; FCurrentExceptionInfo: record FHasCommandData: TExceptionInfoCommands; // cleared in Setstate FObjAddress, FFramePtr: TDBGPtr; FExceptClass: String; FExceptMsg: String; end; FFramePtrAtStart: TDBGPtr; FFramesDescending: Boolean; procedure ThreadInstructionSucceeded(Sender: TObject); procedure ExceptionReadReg0Success(Sender: TObject); procedure ExceptionReadReg2Success(Sender: TObject); procedure ExceptionReadClassSuccess(Sender: TObject); procedure ExceptionReadMsgSuccess(Sender: TObject); procedure CatchesStackInstructionFinished(Sender: TObject); procedure SearchFpStackInstructionFinished(Sender: TObject); procedure SearchExceptFpStackInstructionFinished(Sender: TObject); procedure TempBreakPointSet(Sender: TObject); procedure RunInstructionSucceeded(AnInstruction: TObject); procedure ResetStateToRun; procedure SetNextStepCommand(AStepAction: TLldbInstructionProcessStepAction); procedure ResumeWithNextStepCommand; procedure SetTempBreakPoint(AnAddr: TDBGPtr); procedure DeleteTempBreakPoint; Procedure SetDebuggerLocation(AnAddr, AFrame: TDBGPtr; AFuncName, AFile, AFullFile: String; SrcLine: integer); protected FStepAction: TLldbInstructionProcessStepAction; procedure DoLineDataReceived(var ALine: String); override; procedure DoCancel; override; procedure DoInitialExecute; virtual; abstract; procedure DoExecute; override; public constructor Create(AOwner: TLldbDebugger); destructor Destroy; override; end; { TLldbDebuggerCommandRunStep } TLldbDebuggerCommandRunStep = class(TLldbDebuggerCommandRun) private protected procedure DoInitialExecute; override; public constructor Create(AOwner: TLldbDebugger; AStepAction: TLldbInstructionProcessStepAction); end; { TLldbDebuggerCommandRunLaunch } TLldbDebuggerCommandRunLaunch = class(TLldbDebuggerCommandRun) private FRunInstr: TLldbInstruction; FLaunchWarnings: String; procedure CollectDwarfLoadErrors(Sender: TObject); procedure ExceptBreakInstructionFinished(Sender: TObject); procedure LaunchInstructionSucceeded(Sender: TObject); procedure TargetCreated(Sender: TObject); protected procedure DoInitialExecute; override; constructor Create(AOwner: TLldbDebugger); end; { TLldbDebuggerCommandStop } TLldbDebuggerCommandStop = class(TLldbDebuggerCommand) private procedure StopInstructionSucceeded(Sender: TObject); protected procedure DoExecute; override; end; { TLldbDebuggerCommandLocals } TLldbDebuggerCommandLocals = class(TLldbDebuggerCommand) private FLocals: TLocals; FLocalsInstr: TLldbInstructionLocals; procedure DoLocalsFreed(Sender: TObject); procedure LocalsInstructionFinished(Sender: TObject); protected procedure DoExecute; override; public constructor Create(AOwner: TLldbDebugger; ALocals: TLocals); destructor Destroy; override; end; { TLldbDebuggerCommandEvaluate } TLldbDebuggerCommandEvaluate = class(TLldbDebuggerCommand) private FInstr: TLldbInstructionExpression; FWatchValue: TWatchValue; FExpr: String; FFlags: TDBGEvaluateFlags; FCallback: TDBGEvaluateResultCallback; procedure DoWatchFreed(Sender: TObject); procedure EvalInstructionFailed(Sender: TObject); procedure EvalInstructionSucceeded(Sender: TObject); protected procedure DoExecute; override; public // TODO: Pass FCurrentStackFrame to create constructor Create(AOwner: TLldbDebugger; AWatchValue: TWatchValue); constructor Create(AOwner: TLldbDebugger; AnExpr: String; AFlags: TDBGEvaluateFlags; ACallback: TDBGEvaluateResultCallback); destructor Destroy; override; end; { TlldbInternalBreakPoint } TlldbInternalBreakPoint = class private FDwarfLoadErrors: String; FName: String; FBeforePrologue: Boolean; FId: Integer; FDebugger: TLldbDebugger; FOnFail: TNotifyEvent; FOnFinish: TNotifyEvent; procedure BreakSetSuccess(Sender: TObject); procedure DoFailed(Sender: TObject); procedure DoFinished(Sender: TObject); procedure QueueInstruction(AnInstr: TLldbInstruction); public constructor Create(AName: String; ADebugger: TLldbDebugger; ABeforePrologue: Boolean = False); destructor Destroy; override; procedure Enable; procedure Disable; procedure Remove; property BreakId: Integer read FId; property OnFail: TNotifyEvent read FOnFail write FOnFail; property OnFinish: TNotifyEvent read FOnFinish write FOnFinish; property DwarfLoadErrors: String read FDwarfLoadErrors; end; { TLldbDebuggerCommandRegister } TLldbDebuggerCommandRegister = class(TLldbDebuggerCommand) private FRegisters: TRegisters; procedure RegisterInstructionFinished(Sender: TObject); protected procedure DoExecute; override; procedure DoCancel; override; public constructor Create(AOwner: TLldbDebugger; ARegisters: TRegisters); destructor Destroy; override; property Registers: TRegisters read FRegisters; end; (* * Debugger *) { TLldbDebugger } { TLldbDebuggerProperties } TLldbDebuggerProperties = class(TDebuggerProperties) private FLaunchNewTerminal: Boolean; FSkipGDBDetection: Boolean; FIgnoreLaunchWarnings: Boolean; public constructor Create; override; procedure Assign(Source: TPersistent); override; published property LaunchNewTerminal: Boolean read FLaunchNewTerminal write FLaunchNewTerminal default False; property SkipGDBDetection: Boolean read FSkipGDBDetection write FSkipGDBDetection default False; property IgnoreLaunchWarnings: Boolean read FIgnoreLaunchWarnings write FIgnoreLaunchWarnings default False; end; TLldbDebugger = class(TDebuggerIntf) private FDebugProcess: TDebugProcess; FDebugInstructionQueue: TLldbInstructionQueue; FCommandQueue: TLldbDebuggerCommandQueue; FInIdle: Boolean; FCurrentLocation: TDBGLocationRec; FCurrentStackFrame: Integer; FCurrentThreadId: Integer; FCurrentThreadFramePtr: TDBGPtr; FBreakErrorBreak: TlldbInternalBreakPoint; FRunErrorBreak: TlldbInternalBreakPoint; FExceptionBreak: TlldbInternalBreakPoint; FPopExceptStack, FCatchesBreak, FReRaiseBreak: TlldbInternalBreakPoint; FTargetWidth: Byte; FTargetRegisters: array[0..2] of String; FLldbMissingBreakSetDisable: Boolean; FExceptionInfo: record FReg0Cmd, FReg2Cmd, FExceptClassCmd, FExceptMsgCmd: String; FAtExcepiton: Boolean; // cleared in Setstate end; (* DoAfterLineReceived is called after DebugInstruction.ProcessInputFromDbg (but not if ProcessInputFromDbg already handled the Line) DoAfterLineReceived will first call CommandQueue.DoLineDataReceived *) procedure DoAfterLineReceived(var ALine: String); // procedure DoBeforeLineReceived(var ALine: String); // Before DebugInstruction.ProcessInputFromDbg procedure DoCmdLineDebuggerTerminated(Sender: TObject); procedure DoLineSentToDbg(Sender: TObject; ALine: String); function LldbRun: Boolean; function LldbStep(AStepAction: TLldbInstructionProcessStepAction): Boolean; function LldbStop: Boolean; function LldbPause: Boolean; function LldbEvaluate(const AExpression: String; EvalFlags: TDBGEvaluateFlags; ACallback: TDBGEvaluateResultCallback): Boolean; function LldbEnvironment(const AVariable: String; const ASet: Boolean): Boolean; procedure TerminateLldb; // Kills external debugger protected procedure DoBeforeLaunch; virtual; procedure DoAfterLaunch(var LaunchWarnings: string); virtual; procedure DoBeginReceivingLines(Sender: TObject); procedure DoEndReceivingLines(Sender: TObject); procedure LockRelease; override; procedure UnlockRelease; override; procedure QueueCommand(const ACommand: TLldbDebuggerCommand); procedure DoState(const OldState: TDBGState); override; //procedure DoBeforeState(const OldState: TDBGState); override; procedure SetErrorState(const AMsg: String; const AInfo: String = ''); function DoExceptionHit(AExcClass, AExcMsg: String): Boolean; function DoBreakpointHit(BrkId: Integer): Boolean; property CurrentThreadId: Integer read FCurrentThreadId; property CurrentStackFrame: Integer read FCurrentStackFrame; property CurrentLocation: TDBGLocationRec read FCurrentLocation; property DebugInstructionQueue: TLldbInstructionQueue read FDebugInstructionQueue; property CommandQueue: TLldbDebuggerCommandQueue read FCommandQueue; protected function CreateBreakPoints: TDBGBreakPoints; override; function CreateLocals: TLocalsSupplier; override; //function CreateLineInfo: TDBGLineInfo; override; function CreateRegisters: TRegisterSupplier; override; function CreateCallStack: TCallStackSupplier; override; //function CreateDisassembler: TDBGDisassembler; override; function CreateWatches: TWatchesSupplier; override; function CreateThreads: TThreadsSupplier; override; function GetTargetWidth: Byte; override; function GetIsIdle: Boolean; override; class function GetSupportedCommands: TDBGCommands; override; //function GetCommands: TDBGCommands; override; function RequestCommand(const ACommand: TDBGCommand; const AParams: array of const; const ACallback: TMethod): Boolean; override; public class function CreateProperties: TDebuggerProperties; override; // Creates debuggerproperties class function Caption: String; override; class function ExePaths: String; override; class function ExePathsMruGroup: TDebuggerClass; override; constructor Create(const AExternalDebugger: String); override; destructor Destroy; override; procedure Init; override; // Initializes external debugger procedure Done; override; // Kills external debugger class function RequiredCompilerOpts(ATargetCPU, ATargetOS: String ): TDebugCompilerRequirements; override; function GetLocation: TDBGLocationRec; override; // function GetProcessList({%H-}AList: TRunningProcessInfoList): boolean; override; function NeedReset: Boolean; override; procedure TestCmd(const ACommand: String); override; end; procedure Register; implementation var DBG_VERBOSE: PLazLoggerLogGroup; type {%region ***** ***** Threads ***** } { TLldbDebuggerCommandThreads } TLldbDebuggerCommandThreads = class(TLldbDebuggerCommand) private procedure ThreadInstructionSucceeded(Sender: TObject); protected constructor Create(AOwner: TLldbDebugger); procedure DoExecute; override; end; { TLldbThreads } TLldbThreads = class(TThreadsSupplier) private FThreadFramePointers: Array of TDBGPtr; function GetDebugger: TLldbDebugger; protected procedure DoStateEnterPause; override; procedure ReadFromThreadInstruction(Instr: TLldbInstructionThreadList; ACurrentId: Integer = -1); public procedure RequestMasterData; override; procedure ChangeCurrentThread(ANewId: Integer); override; function GetFramePointerForThread(AnId: Integer): TDBGPtr; property Debugger: TLldbDebugger read GetDebugger; end; {%endregion ^^^^^ Threads ^^^^^ } {%region ***** ***** CallStack ***** } { TLldbDebuggerCommandCallStack } TLldbDebuggerCommandCallStack = class(TLldbDebuggerCommand) private FCurrentCallStack: TCallStackBase; procedure DoCallstackFreed(Sender: TObject); procedure StackInstructionFinished(Sender: TObject); protected procedure DoExecute; override; public constructor Create(AOwner: TLldbDebugger; ACurrentCallStack: TCallStackBase); destructor Destroy; override; property CurrentCallStack: TCallStackBase read FCurrentCallStack; end; { TLldbCallStack } TLldbCallStack = class(TCallStackSupplier) protected //procedure Clear; procedure DoThreadChanged; procedure ParentRequestEntries(ACallstack: TCallStackBase); public procedure RequestAtLeastCount(ACallstack: TCallStackBase; ARequiredMinCount: Integer); override; procedure UpdateCurrentIndex; override; procedure RequestCurrent(ACallstack: TCallStackBase); override; procedure RequestEntries(ACallstack: TCallStackBase); override; end; {%endregion ^^^^^ CallStack ^^^^^ } {%region ***** ***** Locals ***** } { TLldbLocals } TLldbLocals = class(TLocalsSupplier) public procedure RequestData(ALocals: TLocals); override; end; {%endregion ^^^^^ Locals ^^^^^ } {%region ***** ***** Watches ***** } { TLldbWatches } TLldbWatches = class(TWatchesSupplier) private protected procedure InternalRequestData(AWatchValue: TWatchValue); override; public end; {%endregion ^^^^^ Watches ^^^^^ } {%region ***** ***** BreakPoint ***** } { TLldbBreakPoint } TLldbBreakPoint = class(TDBGBreakPoint) private FBreakID: Integer; FCurrentInstruction: TLldbInstruction; FNeededChanges: TDbgBpChangeIndicators; procedure InstructionSetBreakFinished(Sender: TObject); procedure InstructionUpdateBreakFinished(Sender: TObject); procedure SetBreakPoint; procedure ReleaseBreakPoint; procedure UpdateProperties(AChanged: TDbgBpChangeIndicators); procedure DoCurrentInstructionFinished; procedure CancelCurrentInstruction; protected procedure DoStateChange(const AOldState: TDBGState); override; procedure DoPropertiesChanged(AChanged: TDbgBpChangeIndicators); override; public // constructor Create(ACollection: TCollection); override; destructor Destroy; override; // procedure DoLogExpression(const AnExpression: String); override; end; { TLldbBreakPoints } TLldbBreakPoints = class(TDBGBreakPoints) protected function FindById(AnId: Integer): TLldbBreakPoint; end; {%endregion ^^^^^ BreakPoint ^^^^^ } {%region ***** ***** Register ***** } { TLldbRegisterSupplier } TLldbRegisterSupplier = class(TRegisterSupplier) public procedure Changed; procedure RequestData(ARegisters: TRegisters); override; end; { TLldbDebuggerProperties } constructor TLldbDebuggerProperties.Create; begin inherited Create; FLaunchNewTerminal := False; FSkipGDBDetection := False; FIgnoreLaunchWarnings := False; end; procedure TLldbDebuggerProperties.Assign(Source: TPersistent); begin inherited Assign(Source); if Source is TLldbDebuggerProperties then begin FLaunchNewTerminal := TLldbDebuggerProperties(Source).FLaunchNewTerminal; FSkipGDBDetection := TLldbDebuggerProperties(Source).FSkipGDBDetection; FIgnoreLaunchWarnings := TLldbDebuggerProperties(Source).FIgnoreLaunchWarnings; end; end; { TLldbDebuggerCommandRun } procedure TLldbDebuggerCommandRun.CatchesStackInstructionFinished(Sender: TObject); var Instr: TLldbInstruction; r: TStringArray; Id, line: Integer; IsCur: Boolean; addr, stack, frame: TDBGPtr; func, filename, fullfile, d: String; Arguments: TStringList; begin r := TLldbInstructionStackTrace(Sender).Res; if Length(r) < 1 then begin SetDebuggerState(dsPause); Finished; exit; end; ParseNewFrameLocation(r[0], Id, IsCur, addr, stack, frame, func, Arguments, filename, fullfile, line, d); Arguments.Free; if addr = 0 then begin SetDebuggerState(dsPause); Finished; exit; end; if FMode = cmRunToTmpBrk then begin if (FFramesDescending and (frame > FFramePtrAtStart)) or ((not FFramesDescending) and (frame < FFramePtrAtStart)) then begin ResetStateToRun; SetNextStepCommand(saContinue); exit; end; DeleteTempBreakPoint; // except stepped out below temp brkpoint end; SetTempBreakPoint(Addr); ResetStateToRun; FMode := cmRunAfterCatch; SetNextStepCommand(saContinue); end; procedure TLldbDebuggerCommandRun.TempBreakPointSet(Sender: TObject); begin FTmpBreakId := TLldbInstructionBreakSet(Sender).BreakId; end; procedure TLldbDebuggerCommandRun.SearchFpStackInstructionFinished( Sender: TObject); var r: TStringArray; fr: Integer; Id, line: Integer; IsCur: Boolean; addr, stack, frame, prev: TDBGPtr; func, filename, fullfile, d: String; Arguments: TStringList; begin r := TLldbInstructionStackTrace(Sender).Res; if Length(r) < 1 then begin SetDebuggerState(dsPause); Finished; exit; end; fr := 0; prev := 0; repeat ParseNewFrameLocation(r[fr], Id, IsCur, addr, stack, frame, func, Arguments, filename, fullfile, line, d); Arguments.Free; if fr = 0 then begin FFramesDescending := frame > FFramePtrAtStart; if (FState = crStoppedRaise) and (Length(r) >= 2) then begin inc(fr); Continue; end; end; if not( (frame = 0) or ((fr > 0) and (frame = {%H-}prev)) ) then begin if frame = FFramePtrAtStart then break; if (prev <> 0) and ( ( (fr < prev) and not(FFramePtrAtStart < fr) ) or ( (fr > prev) and not(FFramePtrAtStart > fr) ) ) then begin SetDebuggerState(dsPause); Finished; exit; end; prev := frame; end; inc(fr); until fr >= Length(r); if (fr >= Length(r)) or (addr = 0) then begin SetDebuggerState(dsPause); Finished; exit; end; SetTempBreakPoint(Addr); ResetStateToRun; FMode := cmRunToTmpBrk; SetNextStepCommand(saContinue); end; procedure TLldbDebuggerCommandRun.SearchExceptFpStackInstructionFinished( Sender: TObject); var r: TStringArray; fr: Integer; Id, line: Integer; IsCur: Boolean; addr, stack, frame: TDBGPtr; func, filename, fullfile, d: String; Arguments: TStringList; begin r := TLldbInstructionStackTrace(Sender).Res; if Length(r) < 2 then begin SetDebuggerState(dsPause); Finished; exit; end; fr := 1; repeat ParseNewFrameLocation(r[fr], Id, IsCur, addr, stack, frame, func, Arguments, filename, fullfile, line, d); Arguments.Free; if frame = FCurrentExceptionInfo.FFramePtr then begin SetDebuggerLocation(Addr, Frame, Func, filename, FullFile, Line); break; end; inc(fr); until fr >= Length(r); SetDebuggerState(dsPause); Debugger.DoCurrent(Debugger.FCurrentLocation); Finished; end; procedure TLldbDebuggerCommandRun.ThreadInstructionSucceeded(Sender: TObject); begin FState := crStopped; end; procedure TLldbDebuggerCommandRun.ExceptionReadReg0Success(Sender: TObject); var v: String; i: SizeInt; begin v := TLldbInstructionReadExpression(Sender).Res; i := pos(' = ', v); if i > 1 then delete(v, 1, i+2); FCurrentExceptionInfo.FObjAddress := StrToInt64Def(v, 0); Include(FCurrentExceptionInfo.FHasCommandData, exiReg0); end; procedure TLldbDebuggerCommandRun.ExceptionReadReg2Success(Sender: TObject); var v: String; i: SizeInt; begin v := TLldbInstructionReadExpression(Sender).Res; i := pos(' = ', v); if i > 1 then delete(v, 1, i+2); FCurrentExceptionInfo.FFramePtr := StrToInt64Def(v, 0); Include(FCurrentExceptionInfo.FHasCommandData, exiReg2); end; procedure TLldbDebuggerCommandRun.ExceptionReadClassSuccess(Sender: TObject); var s: String; i: SizeInt; l: Integer; begin // (char * ) $2 = 0x005c18d0 "\tException" // (char *) $10 = 0x00652d44 "\x04TXXX" s := TLldbInstructionReadExpression(Sender).Res; i := pos('"', s); l := 255; if i > 0 then begin if s[i+1] = '\' then begin inc(i, 2); if s[i] = 'x' then begin l := StrToIntDef('$'+copy(s, i+1, 2), 255); inc(i, 2); end else begin case s[i] of 'a': l := 7; 'b': l := 8; 't': l := 9; 'n': l := 10; 'v': l := 11; 'f': l := 12; 'r': l := 13; 'e': l := 27; 's': l := 32; '\': l := 92; 'd': l := 127; end; end; end else begin inc(i, 1); l := ord(s[i]); end; s := copy(s, i+1, Min(l, Length(s)-i-1)); end; FCurrentExceptionInfo.FExceptClass := s; Include(FCurrentExceptionInfo.FHasCommandData, exiClass); end; procedure TLldbDebuggerCommandRun.ExceptionReadMsgSuccess(Sender: TObject); var s: String; i: SizeInt; begin s := TLldbInstructionReadExpression(Sender).Res; i := pos('"', s); if i > 0 then s := copy(s, i+1, Length(s)-i-1); FCurrentExceptionInfo.FExceptMsg := s; Include(FCurrentExceptionInfo.FHasCommandData, exiMsg); end; procedure TLldbDebuggerCommandRun.DoLineDataReceived(var ALine: String); const MaxStackSearch = 99; procedure ContinueRunning; var Instr: TLldbInstruction; begin if FStepAction = saContinue then begin DeleteTempBreakPoint; ResetStateToRun; FMode := cmRun; SetNextStepCommand(saContinue); exit; end; if FFramePtrAtStart = 0 then begin SetDebuggerState(dsPause); Finished; exit; end; if FTmpBreakId = 0 then begin FMode := cmRunToTmpBrk; FState := crRunning; // Ignore the STEP 3 / frame Instr := TLldbInstructionStackTrace.Create(MaxStackSearch, 0, Debugger.FCurrentThreadId); Instr.OnFinish := @SearchFpStackInstructionFinished; QueueInstruction(Instr); Instr.ReleaseReference; exit; end; ResetStateToRun; FMode := cmRunToTmpBrk; SetNextStepCommand(saContinue); //case FStepAction of // saInsIn: SetDebuggerState(dsPause); // saInsOver, saOver: ; // saInto: ; // saOut: ; //end; end; function GetBreakPointId(AReason: String): Integer; var i: Integer; begin i := pos('.', AReason); if i = 0 then i := Length(AReason)+1; Result := StrToIntDef(copy(AReason, 12, i-12), -1); debugln(DBG_VERBOSE, ['DoBreakPointHit ', AReason, ' / ', Result]); end; procedure DoException; var ExcClass, ExcMsg: String; CanContinue: Boolean; Instr: TLldbInstructionStackTrace; ExceptItem: TBaseException; begin if exiClass in FCurrentExceptionInfo.FHasCommandData then ExcClass := FCurrentExceptionInfo.FExceptClass else ExcClass := ''; // TODO: move to IDE if exiMsg in FCurrentExceptionInfo.FHasCommandData then ExcMsg := FCurrentExceptionInfo.FExceptMsg else ExcMsg := ''; // TODO: move to IDE ExceptItem := Debugger.Exceptions.Find(ExcClass); if (ExceptItem <> nil) and (ExceptItem.Enabled) then begin FState := crStoppedRaise; ContinueRunning; exit; end; CanContinue := Debugger.DoExceptionHit(ExcClass, ExcMsg); if CanContinue then begin FState := crStoppedRaise; ContinueRunning; exit; end else begin Debugger.FExceptionInfo.FAtExcepiton := True; if (exiReg2 in FCurrentExceptionInfo.FHasCommandData) and (FCurrentExceptionInfo.FFramePtr <> 0) then begin FState := crRunning; // ensure command is not finished early Instr := TLldbInstructionStackTrace.Create(MaxStackSearch, 0, Debugger.FCurrentThreadId); Instr.OnFinish := @SearchExceptFpStackInstructionFinished; QueueInstruction(Instr); Instr.ReleaseReference; exit; end; Debugger.FCurrentLocation.SrcLine := -1; SetDebuggerState(dsPause); // after GetLocation => dsPause may run stack, watches etc end; end; procedure DoRunError; var CanContinue: Boolean; ErrNo: Integer; ExceptName: String; ExceptItem: TBaseException; begin ErrNo := 0; if exiReg0 in FCurrentExceptionInfo.FHasCommandData then ErrNo := FCurrentExceptionInfo.FObjAddress; ErrNo := ErrNo and $FFFF; ExceptName := Format('RunError(%d)', [ErrNo]); ExceptItem := Debugger.Exceptions.Find(ExceptName); if (ExceptItem <> nil) and (ExceptItem.Enabled) then begin FState := crStoppedRaise; ContinueRunning; exit; end; Debugger.DoException(deRunError, ExceptName, Debugger.FCurrentLocation, Debugger.RunErrorText[ErrNo], CanContinue); if CanContinue then begin FState := crStoppedRaise; ContinueRunning; exit; end; SetDebuggerState(dsPause); // after GetLocation => dsPause may run stack, watches etc end; procedure DoUnknownStopReason(AStopReason: String); var CanContinue: Boolean; begin Debugger.DoException(deExternal, Format('Debugger stopped with reason: %s', [AStopReason]), Debugger.FCurrentLocation, '', CanContinue); SetDebuggerState(dsPause); // after GetLocation => dsPause may run stack, watches etc end; procedure DoCatchesHit; var Instr: TLldbInstruction; begin FState := crRunning; // Ignore the STEP 3 / frame Instr := TLldbInstructionStackTrace.Create(1, 1, Debugger.FCurrentThreadId); Instr.OnFinish := @CatchesStackInstructionFinished; QueueInstruction(Instr); Instr.ReleaseReference; end; procedure DoStopTemp; begin if (FMode in [cmRunAfterCatch, cmRunToTmpBrk]) and (Debugger.FCurrentLocation.SrcLine = 0) then begin DeleteTempBreakPoint; ResetStateToRun; FMode := cmRun; SetNextStepCommand(saOver); exit; end; SetDebuggerState(dsPause); end; procedure DoBreakPointHit(BrkId: Integer); var BreakPoint: TLldbBreakPoint; CanContinue: Boolean; begin CanContinue := Debugger.DoBreakpointHit(BrkId); if CanContinue then begin // Important trigger State => as snapshot is taken in TDebugManager.DebuggerChangeState SetDebuggerState(dsInternalPause); // TODO: need location? ContinueRunning; end else begin SetDebuggerState(dsPause); end; end; var Instr: TLldbInstruction; found: TStringArray; AnId, SrcLine, i: Integer; AnIsCurrent: Boolean; AnAddr, stack, frame: TDBGPtr; AFuncName, AFile, AReminder, AFullFile, s, Name: String; AnArgs: TStringList; begin (* When the debuggee stops (pause), the following will be received: // for EXCEPTIONS ONLY (less the spaces between * ) ) p/x $eax (unsigned int) $1 = 0x04dfd920 p ((char *** )$eax)[0][3] (char * ) $2 = 0x005c18d0 "\tException" p ((char ** )$eax)[1] (char * ) $3 = 0x00000000 // Hit breakpoint Process 10992 stopped // thread list => but thread ID may be wrong (maybe look for "reason", instead of leading * * thread #1: tid=0x1644: 0x00409e91, 0x0158FF38, 0x0158FF50 &&//FULL: \FPC\SVN\fixes_3_0\rtl\win32\..\inc\except.inc &&//SHORT: except.inc &&//LINE: 185 &&//MOD: project1.exe &&//FUNC: fpc_raiseexception(OBJ=0x038f5a90, ANADDR=0x00401601, AFRAME=0x0158ff58) <<&&//FRAME" thread #2: tid=0x27bc: 0x77abf8dc, 0x0557FE64, 0x0557FEC8 &&//FULL: &&//SHORT: &&//LINE: &&//MOD: ntdll.dll &&//FUNC: NtDelayExecution <<&&//FRAME" Process 10992 stopped // thread (correct) and frame * thread #1, stop reason = breakpoint 6.1 frame #0: 0x0042b855 &&//FULL: \tmp\New Folder (2)\unit1.pas &&//SHORT: unit1.pas &&//LINE: 54 &&//MOD: project1.exe &&//FUNC: FORMCREATE(this=0x04c81248, SENDER=0x04c81248) <<&&//FRAME ... stop reason = Exception 0xc0000005 encountered at address 0x42e067 ... stop reason = EXC_BAD_ACCESS (code=1, address=0x4) ... stop reason = step over ... stop reason = step in ... stop reason = instruction step over ... google stop reason = signal / trace / watchpoint *) {%region exception } s := TrimLeft(ALine); Instr := nil; if StrStartsWith(s, Debugger.FExceptionInfo.FReg0Cmd, True) then begin Instr := TLldbInstructionReadExpression.Create; Instr.OnSuccess := @ExceptionReadReg0Success; end else if StrStartsWith(s, Debugger.FExceptionInfo.FReg2Cmd, True) then begin Instr := TLldbInstructionReadExpression.Create; Instr.OnSuccess := @ExceptionReadReg2Success; end else if StrStartsWith(s, Debugger.FExceptionInfo.FExceptClassCmd, True) then begin Instr := TLldbInstructionReadExpression.Create; Instr.OnSuccess := @ExceptionReadClassSuccess; end else if StrStartsWith(s, Debugger.FExceptionInfo.FExceptMsgCmd, True) then begin Instr := TLldbInstructionReadExpression.Create; Instr.OnSuccess := @ExceptionReadMsgSuccess; end; if Instr <> nil then begin ALine := ''; debugln(DBG_VERBOSE, ['Reading exception info']); assert(InstructionQueue.RunningInstruction = nil, 'InstructionQueue.RunningInstruction = nil'); QueueInstruction(Instr); Instr.ReleaseReference; exit; end; {%endregion exception } // STEP 1: Process 10992 stopped if (FState = crRunning) and StrMatches(ALine, ['Process ', 'stopped']) then begin FState := crReadingThreads; debugln(DBG_VERBOSE, ['Reading thread info']); FThreadInstr := TLldbInstructionThreadListReader.Create(); FThreadInstr.OnSuccess := @ThreadInstructionSucceeded; QueueInstruction(FThreadInstr); exit; end; // STEP 2: * thread #1, stop reason = breakpoint 6.1 if StrMatches(ALine, ['* thread #', ', stop reason = ', ''], found) then begin FState := crStopped; debugln(DBG_VERBOSE, ['Reading stopped thread']); SetDebuggerLocation(0, 0, '', '', '', 0); if StrStartsWith(found[1], 'breakpoint ') then begin FCurBrkId := GetBreakPointId(found[1]) //end else //if StrStartsWith(found[1], 'watchpoint ') then begin end else begin FUnknownStopReason := ''; if not( ( StrStartsWith(found[1], 'step ') or StrStartsWith(found[1], 'instruction step ') ) and ( StrContains(found[1], ' in') or StrContains(found[1], ' over') or StrContains(found[1], ' out') ) ) then FUnknownStopReason := found[1]; FCurBrkId := -1; end; ParseNewThreadLocation(ALine, AnId, AnIsCurrent, Name, AnAddr, Stack, Frame, AFuncName, AnArgs, AFile, AFullFile, SrcLine, AReminder); AnArgs.Free; Debugger.FCurrentThreadId := AnId; Debugger.FCurrentStackFrame := 0; SetDebuggerLocation(AnAddr, Frame, AFuncName, AFile, AFullFile, SrcLine); InstructionQueue.SetKnownThreadAndFrame(Debugger.FCurrentThreadId, 0); Debugger.Threads.CurrentThreads.CurrentThreadId := Debugger.FCurrentThreadId; // set again from thread list if StrStartsWith(found[1], 'breakpoint ') then begin if FCurBrkId = Debugger.FExceptionBreak.BreakId then DoException else if FCurBrkId = Debugger.FRunErrorBreak.BreakId then DoRunError // location = frame with fp // see gdbmi else if FCurBrkId = Debugger.FBreakErrorBreak.BreakId then DoRunError // location = frame(1) // see gdbmi else if (FCurBrkId = Debugger.FCatchesBreak.BreakId) or (FCurBrkId = Debugger.FPopExceptStack.BreakId) then DoCatchesHit else if FCurBrkId = Debugger.FReRaiseBreak.BreakId then begin FState := crStoppedRaise; ContinueRunning; end else if FCurBrkId = FTmpBreakId then DoStopTemp else DoBreakPointHit(FCurBrkId); end else if FUnknownStopReason <> '' then begin DoUnknownStopReason(FUnknownStopReason); end else SetDebuggerState(dsPause); if (FState = crRunning) then exit; if DebuggerState in [dsPause, dsInternalPause, dsStop] then Debugger.DoCurrent(Debugger.FCurrentLocation); FState := crDone; ALine := ''; exit; end; if (FState = crRunning) then exit; // STEP 3: frame #0: 0x0042b855 &&//FULL: \tmp\New Folder (2)\unit1.pas &&//SHORT: unit1.pas &&//LINE: 54 &&//MOD: project1.exe &&//FUNC: FORMCREATE(this=0x04c81248, SENDER=0x04c81248) <<&&//FRAME if ParseNewFrameLocation(ALine, AnId, AnIsCurrent, AnAddr, stack, frame, AFuncName, AnArgs, AFile, AFullFile, SrcLine, AReminder) then begin AnArgs.Free; if FState = crReadingThreads then begin FState := crStopped; // did not execute "thread list" / thread cmd reader has read "stop reason" for i := 0 to length(FThreadInstr.Res) - 1 do DoLineDataReceived(FThreadInstr.Res[i]); end; DeleteTempBreakPoint; Finished; end; if (LeftStr(ALine, 8) = 'Process ') and (pos('exited with status = ', ALine) > 0) then begin DeleteTempBreakPoint; Finished; exit; // handle in main debugger end; end; procedure TLldbDebuggerCommandRun.DoCancel; begin InstructionQueue.CancelAllForCommand(Self); // in case there still are any DeleteTempBreakPoint; // Must not call Finished; => would cancel DeleteTempBreakPoint; if CommandQueue.RunningCommand = Self then CommandQueue.CommandFinished(Self); end; procedure TLldbDebuggerCommandRun.DoExecute; begin if FWaitToResume then ResumeWithNextStepCommand else DoInitialExecute; end; procedure TLldbDebuggerCommandRun.RunInstructionSucceeded(AnInstruction: TObject ); begin FCurrentExceptionInfo.FHasCommandData := []; end; procedure TLldbDebuggerCommandRun.ResetStateToRun; begin FState := crRunning; FCurBrkId := 0; if FThreadInstr <> nil then begin FThreadInstr.ReleaseReference; FThreadInstr := nil; end; FCurrentExceptionInfo.FHasCommandData := []; end; procedure TLldbDebuggerCommandRun.SetNextStepCommand( AStepAction: TLldbInstructionProcessStepAction); begin FNextStepAction := AStepAction; {$IFDEF LLDB_SKIP_SNAP} ResumeWithNextStepCommand; exit; {$ENDIF} FWaitToResume := True; // Run the queue, before continue CommandQueue.QueueCommand(Self); CommandQueue.CommandFinished(Self); //CommandQueue.FRunningCommand := nil; //CommandQueue.Run; end; procedure TLldbDebuggerCommandRun.ResumeWithNextStepCommand; var Instr: TLldbInstructionProcessStep; begin if FNextStepAction in [saOver, saInto, saOut, saInsOver] then Debugger.FReRaiseBreak.Enable else Debugger.FReRaiseBreak.Disable; if FMode in [cmRunToCatch, cmRunToTmpBrk] then begin Debugger.FCatchesBreak.Enable; Debugger.FPopExceptStack.Enable; Instr := TLldbInstructionProcessStep.Create(saContinue); end else begin Debugger.FCatchesBreak.Disable; Debugger.FPopExceptStack.Disable; Instr := TLldbInstructionProcessStep.Create(FNextStepAction, Debugger.FCurrentThreadId); end; Instr.OnFinish := @RunInstructionSucceeded; QueueInstruction(Instr); Instr.ReleaseReference; if DebuggerState <> dsRun then SetDebuggerState(dsRun); end; procedure TLldbDebuggerCommandRun.SetTempBreakPoint(AnAddr: TDBGPtr); var Instr: TLldbInstructionBreakSet; begin Instr := TLldbInstructionBreakSet.Create(AnAddr); Instr.OnFinish := @TempBreakPointSet; QueueInstruction(Instr); Instr.ReleaseReference; end; procedure TLldbDebuggerCommandRun.DeleteTempBreakPoint; var Instr: TLldbInstruction; begin if FTmpBreakId = 0 then exit; Instr := TLldbInstructionBreakDelete.Create(FTmpBreakId); QueueInstruction(Instr); Instr.ReleaseReference; FTmpBreakId := 0; end; procedure TLldbDebuggerCommandRun.SetDebuggerLocation(AnAddr, AFrame: TDBGPtr; AFuncName, AFile, AFullFile: String; SrcLine: integer); begin Debugger.FCurrentThreadFramePtr := AFrame; Debugger.FCurrentLocation.Address := AnAddr; Debugger.FCurrentLocation.FuncName := AFuncName; Debugger.FCurrentLocation.SrcFile := AFile; Debugger.FCurrentLocation.SrcFullName := AFullFile; Debugger.FCurrentLocation.SrcLine := SrcLine; end; constructor TLldbDebuggerCommandRun.Create(AOwner: TLldbDebugger); begin AOwner.FExceptionInfo.FAtExcepiton := False; FState := crRunning; FMode := cmRun; FFramePtrAtStart := AOwner.FCurrentThreadFramePtr; inherited Create(AOwner); end; destructor TLldbDebuggerCommandRun.Destroy; begin FThreadInstr.ReleaseReference; inherited Destroy; end; { TLldbDebuggerCommandLocals } procedure TLldbDebuggerCommandLocals.LocalsInstructionFinished(Sender: TObject ); var n: String; i: Integer; begin if FLocals <> nil then begin FLocals.Clear; for i := 0 to FLocalsInstr.Res.Count - 1 do begin n := FLocalsInstr.Res.Names[i]; FLocals.Add(n, FLocalsInstr.Res.Values[n]); end; FLocals.SetDataValidity(ddsValid); end; ReleaseRefAndNil(FLocalsInstr); Finished; end; procedure TLldbDebuggerCommandLocals.DoLocalsFreed(Sender: TObject); begin FLocals := nil; if FLocalsInstr <> nil then begin FLocalsInstr.OnFinish := nil; FLocalsInstr.Cancel; ReleaseRefAndNil(FLocalsInstr); Finished; end; end; procedure TLldbDebuggerCommandLocals.DoExecute; begin if FLocals = nil then begin Finished; exit; end; if FLocalsInstr <> nil then begin FLocalsInstr.OnFinish := nil; ReleaseRefAndNil(FLocalsInstr); end; FLocalsInstr := TLldbInstructionLocals.Create(FLocals.ThreadId, FLocals.StackFrame); FLocalsInstr.OnFinish := @LocalsInstructionFinished; QueueInstruction(FLocalsInstr); end; constructor TLldbDebuggerCommandLocals.Create(AOwner: TLldbDebugger; ALocals: TLocals); begin FLocals := ALocals; FLocals.AddFreeNotification(@DoLocalsFreed); CancelableForRun := True; inherited Create(AOwner); end; destructor TLldbDebuggerCommandLocals.Destroy; begin if FLocalsInstr <> nil then begin FLocalsInstr.OnFinish := nil; ReleaseRefAndNil(FLocalsInstr); end; if FLocals <> nil then FLocals.RemoveFreeNotification(@DoLocalsFreed); inherited Destroy; end; {%endregion ^^^^^ Register ^^^^^ } {%region ***** ***** Threads ***** } { TLldbDebuggerCommandThreads } procedure TLldbDebuggerCommandThreads.ThreadInstructionSucceeded(Sender: TObject); begin TLldbThreads(Debugger.Threads).ReadFromThreadInstruction(TLldbInstructionThreadList(Sender)); Finished; end; constructor TLldbDebuggerCommandThreads.Create(AOwner: TLldbDebugger); begin CancelableForRun := True; inherited Create(AOwner); end; procedure TLldbDebuggerCommandThreads.DoExecute; var Instr: TLldbInstructionThreadList; begin Instr := TLldbInstructionThreadList.Create(); Instr.OnFinish := @ThreadInstructionSucceeded; QueueInstruction(Instr); Instr.ReleaseReference; end; { TLldbThreads } function TLldbThreads.GetDebugger: TLldbDebugger; begin Result := TLldbDebugger(inherited Debugger); end; procedure TLldbThreads.DoStateEnterPause; begin inherited DoStateEnterPause; Changed; end; procedure TLldbThreads.ReadFromThreadInstruction( Instr: TLldbInstructionThreadList; ACurrentId: Integer); var i, j, line: Integer; s, func, filename, name, d, fullfile: String; found, foundFunc, foundArg: TStringArray; TId, CurThrId: LongInt; CurThr: Boolean; Arguments: TStringList; addr, stack, frame: TDBGPtr; te: TThreadEntry; begin CurrentThreads.Clear; SetLength(FThreadFramePointers, Length(Instr.Res)); for i := 0 to Length(Instr.Res) - 1 do begin s := Instr.Res[i]; ParseNewThreadLocation(s, TId, CurThr, name, addr, stack, frame, func, Arguments, filename, fullfile, line, d); FThreadFramePointers[i] := frame; if CurThr then CurThrId := TId; te := CurrentThreads.CreateEntry( addr, Arguments, func, filename, fullfile, line, TId, name, '' ); CurrentThreads.Add(te); te.Free; Arguments.Free; end; if ACurrentId >= 0 then CurThrId := ACurrentId; CurrentThreads.CurrentThreadId := CurThrId; CurrentThreads.SetValidity(ddsValid); end; procedure TLldbThreads.RequestMasterData; var Cmd: TLldbDebuggerCommandThreads; RunCmd: TLldbDebuggerCommand; Instr: TLldbInstructionThreadList; begin if not (Debugger.State in [dsPause, dsInternalPause]) then exit; RunCmd := Debugger.CommandQueue.RunningCommand; if RunCmd is TLldbDebuggerCommandRun then begin Instr := TLldbDebuggerCommandRun(RunCmd).FThreadInstr; if (Instr <> nil) and Instr.IsSuccess then begin // FThreadInstr, may have the wrong thread marked. Use Debugger.FCurrentThreadId (which should not have changed since the RunCommand set it) ReadFromThreadInstruction(TLldbInstructionThreadList(Instr), Debugger.FCurrentThreadId); exit; end; end; Cmd := TLldbDebuggerCommandThreads.Create(Debugger); Debugger.QueueCommand(Cmd); Cmd.ReleaseReference; end; procedure TLldbThreads.ChangeCurrentThread(ANewId: Integer); begin if Debugger = nil then Exit; if not(Debugger.State in [dsPause, dsInternalPause]) then exit; Debugger.FCurrentThreadId := ANewId; Debugger.FCurrentThreadFramePtr := GetFramePointerForThread(ANewId); if CurrentThreads <> nil then CurrentThreads.CurrentThreadId := ANewId; TLldbCallStack(Debugger.CallStack).DoThreadChanged; end; function TLldbThreads.GetFramePointerForThread(AnId: Integer): TDBGPtr; begin if (AnId < 0) or (AnId >= Length(FThreadFramePointers)) then exit(0); Result := FThreadFramePointers[AnId]; end; {%endregion ^^^^^ Threads ^^^^^ } {%region ***** ***** CallStack ***** } { TLldbDebuggerCommandCallStack } procedure TLldbDebuggerCommandCallStack.StackInstructionFinished(Sender: TObject ); var Instr: TLldbInstructionStackTrace absolute Sender; i, FId, line: Integer; e: TCallStackEntry; found, foundArg: TStringArray; Arguments: TStringList; It: TMapIterator; s, func, filename, d, fullfile: String; IsCur: Boolean; addr, stack, frame: TDBGPtr; begin if FCurrentCallStack = nil then begin Finished; exit; end; It := TMapIterator.Create(FCurrentCallStack.RawEntries); for i := 0 to Length(Instr.Res) - 1 do begin s := Instr.Res[i]; ParseNewFrameLocation(s, FId, IsCur, addr, stack, frame, func, Arguments, filename, fullfile, line, d); if It.Locate(FId) then begin e := TCallStackEntry(It.DataPtr^); e.Init(addr, Arguments, func, filename, fullfile, line); end; Arguments.Free; end; It.Free; TLldbCallStack(Debugger.CallStack).ParentRequestEntries(FCurrentCallStack); Finished; end; procedure TLldbDebuggerCommandCallStack.DoCallstackFreed(Sender: TObject); begin FCurrentCallStack := nil; //TODO cancel end; procedure TLldbDebuggerCommandCallStack.DoExecute; var StartIdx, EndIdx: Integer; Instr: TLldbInstructionStackTrace; begin if FCurrentCallStack = nil then begin Finished; exit; end; StartIdx := Max(FCurrentCallStack.LowestUnknown, 0); EndIdx := FCurrentCallStack.HighestUnknown; if EndIdx < StartIdx then begin Finished; exit; end; Instr := TLldbInstructionStackTrace.Create(EndIdx+1, FCurrentCallStack.ThreadId); Instr.OnFinish := @StackInstructionFinished; QueueInstruction(Instr); Instr.ReleaseReference; end; constructor TLldbDebuggerCommandCallStack.Create(AOwner: TLldbDebugger; ACurrentCallStack: TCallStackBase); begin inherited Create(AOwner); FCurrentCallStack := ACurrentCallStack; FCurrentCallStack.AddFreeNotification(@DoCallstackFreed); CancelableForRun := True; end; destructor TLldbDebuggerCommandCallStack.Destroy; begin if FCurrentCallStack <> nil then FCurrentCallStack.RemoveFreeNotification(@DoCallstackFreed); inherited Destroy; end; { TLldbCallStack } procedure TLldbCallStack.DoThreadChanged; var tid, idx: Integer; cs: TCallStackBase; begin if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then begin exit; end; TLldbDebugger(Debugger).FCurrentStackFrame := 0; tid := Debugger.Threads.CurrentThreads.CurrentThreadId; cs := TCallStackBase(CurrentCallStackList.EntriesForThreads[tid]); idx := cs.CurrentIndex; // CURRENT if idx < 0 then idx := 0; TLldbDebugger(Debugger).FCurrentStackFrame := idx; cs.CurrentIndex := idx; end; procedure TLldbCallStack.ParentRequestEntries(ACallstack: TCallStackBase); begin inherited RequestEntries(ACallstack); end; procedure TLldbCallStack.RequestAtLeastCount(ACallstack: TCallStackBase; ARequiredMinCount: Integer); begin if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then begin ACallstack.SetCurrentValidity(ddsInvalid); Exit; end; ACallstack.Count := ARequiredMinCount + 1; // TODO: get data, and return correct result ACallstack.SetCountValidity(ddsValid); end; procedure TLldbCallStack.UpdateCurrentIndex; var tid, idx: Integer; cs: TCallStackBase; begin if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then begin exit; end; tid := Debugger.Threads.CurrentThreads.CurrentThreadId; // FCurrentThreadId ? cs := TCallStackBase(CurrentCallStackList.EntriesForThreads[tid]); idx := cs.NewCurrentIndex; // NEW-CURRENT if TLldbDebugger(Debugger).FCurrentStackFrame = idx then Exit; TLldbDebugger(Debugger).FCurrentStackFrame := idx; if cs <> nil then begin cs.CurrentIndex := idx; cs.SetCurrentValidity(ddsValid); end; end; procedure TLldbCallStack.RequestCurrent(ACallstack: TCallStackBase); begin if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then begin ACallstack.SetCurrentValidity(ddsInvalid); Exit; end; if ACallstack.ThreadId = TLldbDebugger(Debugger).FCurrentThreadId then ACallstack.CurrentIndex := TLldbDebugger(Debugger).FCurrentStackFrame else ACallstack.CurrentIndex := 0; // will be used, if thread is changed ACallstack.SetCurrentValidity(ddsValid); end; procedure TLldbCallStack.RequestEntries(ACallstack: TCallStackBase); var Cmd: TLldbDebuggerCommandCallStack; begin if not (Debugger.State in [dsPause, dsInternalPause]) then exit; Cmd := TLldbDebuggerCommandCallStack.Create(TLldbDebugger(Debugger), ACallstack); TLldbDebugger(Debugger).QueueCommand(Cmd); Cmd.ReleaseReference; end; {%endregion ^^^^^ CallStack ^^^^^ } { TLldbLocals } procedure TLldbLocals.RequestData(ALocals: TLocals); var Cmd: TLldbDebuggerCommandLocals; begin if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then Exit; Cmd := TLldbDebuggerCommandLocals.Create(TLldbDebugger(Debugger), ALocals); TLldbDebugger(Debugger).QueueCommand(Cmd); Cmd.ReleaseReference; end; { TLldbWatches } procedure TLldbWatches.InternalRequestData(AWatchValue: TWatchValue); var Cmd: TLldbDebuggerCommandEvaluate; begin if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then Exit; Cmd := TLldbDebuggerCommandEvaluate.Create(TLldbDebugger(Debugger), AWatchValue); TLldbDebugger(Debugger).QueueCommand(Cmd); Cmd.ReleaseReference; end; { TLldbBreakPoint } procedure TLldbBreakPoint.SetBreakPoint; var i: Integer; s: String; Instr: TLldbInstruction; en: Boolean; begin debugln(DBG_VERBOSE, ['TLldbBreakPoint.SetBreakPoint ']); if FCurrentInstruction <> nil then begin if (FBreakID <> 0) or (not FCurrentInstruction.IsRunning) then begin // Can be a queued SetBreak => replace // Or an Update => don't care, ReleaseBreakpoint will be called CancelCurrentInstruction; end else begin // already running a SetBreakPoint FNeededChanges := FNeededChanges + [ciLocation]; // wait for instruction to finish // need ID to del exit; end; end; if (FBreakID <> 0) then ReleaseBreakPoint; en := Enabled; if TLldbDebugger(Debugger).FLldbMissingBreakSetDisable and (not en) and (Kind <> bpkData) then begin en := True; FNeededChanges := FNeededChanges + [ciEnabled]; end; case Kind of bpkSource: begin i := LastPos(PathDelim, Source); if i > 0 then s := Copy(Source, i+1, Length(Source)) else s := Source; Instr := TLldbInstructionBreakSet.Create(s, Line, not en, Expression); end; bpkAddress: begin Instr := TLldbInstructionBreakSet.Create(Address, not en, Expression); end; bpkData: begin if not Enabled then // do not set, if not enabled exit; // TODO: scope // TODO: apply , Expression, not Enabled Instr := TLldbInstructionWatchSet.Create(WatchData, WatchKind); if Expression <> '' then FNeededChanges := FNeededChanges + [ciCondition]; end; end; Instr.OnFinish := @InstructionSetBreakFinished; TLldbDebugger(Debugger).FDebugInstructionQueue.QueueInstruction(Instr); FCurrentInstruction := Instr; end; procedure TLldbBreakPoint.InstructionSetBreakFinished(Sender: TObject); var nc: TDbgBpChangeIndicators; begin DoCurrentInstructionFinished; if TLldbInstructionBreakOrWatchSet(Sender).LldbNoDisableError then begin TLldbDebugger(Debugger).FLldbMissingBreakSetDisable := True; FNeededChanges := FNeededChanges + [ciLocation] end; if TLldbInstructionBreakOrWatchSet(Sender).IsSuccess then begin FBreakID := TLldbInstructionBreakOrWatchSet(Sender).BreakId; if FNeededChanges * [ciDestroy, ciLocation] = [] then SetValid(TLldbInstructionBreakOrWatchSet(Sender).State); end else SetValid(vsInvalid); nc := FNeededChanges; FNeededChanges := []; MarkPropertiesChanged(nc); end; procedure TLldbBreakPoint.InstructionUpdateBreakFinished(Sender: TObject); var nc: TDbgBpChangeIndicators; begin DoCurrentInstructionFinished; nc := FNeededChanges; FNeededChanges := []; MarkPropertiesChanged(nc); end; procedure TLldbBreakPoint.ReleaseBreakPoint; var Instr: TLldbInstruction; begin CancelCurrentInstruction; if FBreakID <= 0 then exit; SetHitCount(0); case Kind of bpkSource, bpkAddress: Instr := TLldbInstructionBreakDelete.Create(FBreakID); bpkData: Instr := TLldbInstructionWatchDelete.Create(FBreakID); end; FBreakID := 0; // Allow a new location to be set immediately //Instr.OwningCommand := Self; // if it needs to be cancelled TLldbDebugger(Debugger).FDebugInstructionQueue.QueueInstruction(Instr); Instr.ReleaseReference; end; procedure TLldbBreakPoint.UpdateProperties(AChanged: TDbgBpChangeIndicators); var Instr: TLldbInstruction; begin assert(AChanged * [ciEnabled, ciCondition] <> [], 'break.UpdateProperties() AChanged * [ciEnabled, ciCondition] <> []'); if (FCurrentInstruction <> nil) then begin FNeededChanges := FNeededChanges + AChanged; exit; end; if FBreakID = 0 then // SetBreakPoint may have failed / nothing to do exit; case Kind of bpkSource, bpkAddress: if ciCondition in AChanged then Instr := TLldbInstructionBreakModify.Create(FBreakID, not Enabled, Expression) else Instr := TLldbInstructionBreakModify.Create(FBreakID, not Enabled); bpkData: begin if Enabled <> (FBreakID <> 0) then begin if Enabled then SetBreakPoint // will else ReleaseBreakPoint; exit; end; if ciCondition in AChanged then Instr := TLldbInstructionWatchModify.Create(FBreakID, Expression); end; end; TLldbDebugger(Debugger).FDebugInstructionQueue.QueueInstruction(Instr); Instr.OnFinish := @InstructionUpdateBreakFinished; FCurrentInstruction := Instr; end; procedure TLldbBreakPoint.DoCurrentInstructionFinished; begin if FCurrentInstruction <> nil then begin FCurrentInstruction.OnFinish := nil; ReleaseRefAndNil(FCurrentInstruction); end; end; procedure TLldbBreakPoint.CancelCurrentInstruction; begin if FCurrentInstruction <> nil then begin FCurrentInstruction.OnFinish := nil; FCurrentInstruction.Cancel; ReleaseRefAndNil(FCurrentInstruction); end; end; procedure TLldbBreakPoint.DoStateChange(const AOldState: TDBGState); begin inherited DoStateChange(AOldState); case DebuggerState of dsRun: if AOldState = dsInit then begin // Disabled data breakpoints: wait until enabled // Disabled other breakpoints: Give to LLDB to see if they are valid SetBreakPoint end; dsStop: begin if FBreakID > 0 then ReleaseBreakpoint; end; end; end; procedure TLldbBreakPoint.DoPropertiesChanged(AChanged: TDbgBpChangeIndicators); begin FNeededChanges := []; if not (DebuggerState in [dsPause, dsInternalPause, dsRun]) then exit; if ciDestroy in AChanged then begin ReleaseBreakPoint; DoCurrentInstructionFinished; exit; end; if AChanged * [ciLocation, ciCreated] <> [] then SetBreakPoint else UpdateProperties(AChanged); end; destructor TLldbBreakPoint.Destroy; begin DoCurrentInstructionFinished; inherited Destroy; end; { TLldbBreakPoints } function TLldbBreakPoints.FindById(AnId: Integer): TLldbBreakPoint; var i: Integer; begin for i := 0 to Count - 1 do begin Result := TLldbBreakPoint(Items[i]); if Result.FBreakID = AnId then exit; end; Result := nil; end; {%region ***** ***** Register ***** } { TLldbDebuggerCommandRegister } procedure TLldbDebuggerCommandRegister.RegisterInstructionFinished( Sender: TObject); var Instr: TLldbInstructionRegister absolute Sender; RegVal: TRegisterValue; n: String; i: Integer; begin if not Instr.IsSuccess then begin if FRegisters.DataValidity in [ddsRequested, ddsEvaluating] then FRegisters.DataValidity := ddsInvalid; exit; end; FRegisters.DataValidity := ddsEvaluating; for i := 0 to Instr.Res.Count - 1 do begin n := Instr.Res.Names[i]; RegVal := FRegisters.EntriesByName[n]; RegVal.Value := Instr.Res.Values[n]; RegVal.DataValidity := ddsValid; end; FRegisters.DataValidity := ddsValid; Finished; end; procedure TLldbDebuggerCommandRegister.DoExecute; var Instr: TLldbInstructionRegister; begin // TODO: store thread/frame when command is created Instr := TLldbInstructionRegister.Create(FRegisters.ThreadId, FRegisters.StackFrame); Instr.OnFinish := @RegisterInstructionFinished; QueueInstruction(Instr); Instr.ReleaseReference; end; procedure TLldbDebuggerCommandRegister.DoCancel; begin if FRegisters <> nil then FRegisters.DataValidity := ddsInvalid; inherited DoCancel; end; constructor TLldbDebuggerCommandRegister.Create(AOwner: TLldbDebugger; ARegisters: TRegisters); begin FRegisters := ARegisters; FRegisters.AddReference; CancelableForRun := True; inherited Create(AOwner); end; destructor TLldbDebuggerCommandRegister.Destroy; begin ReleaseRefAndNil(FRegisters); inherited Destroy; end; { TLldbRegisterSupplier } procedure TLldbRegisterSupplier.Changed; begin if CurrentRegistersList <> nil then CurrentRegistersList.Clear; end; procedure TLldbRegisterSupplier.RequestData(ARegisters: TRegisters); var Cmd: TLldbDebuggerCommandRegister; begin if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause, dsStop]) then exit; Cmd := TLldbDebuggerCommandRegister.Create(TLldbDebugger(Debugger), ARegisters); TLldbDebugger(Debugger).QueueCommand(Cmd); Cmd.ReleaseReference; end; {%endregion ^^^^^ Register ^^^^^ } { TLldbDebuggerCommandQueue } function TLldbDebuggerCommandQueue.Get(Index: Integer): TLldbDebuggerCommand; begin Result := TLldbDebuggerCommand(inherited Get(Index)); end; procedure TLldbDebuggerCommandQueue.Put(Index: Integer; const AValue: TLldbDebuggerCommand); begin inherited Put(Index, AValue); end; procedure TLldbDebuggerCommandQueue.QueueCommand(AValue: TLldbDebuggerCommand); begin debugln(DBG_VERBOSE, ['CommandQueue.QueueCommand ', AValue.ClassName]); Insert(Count, AValue); Run; end; procedure TLldbDebuggerCommandQueue.DoLineDataReceived(var ALine: String); begin if FRunningCommand <> nil then FRunningCommand.DoLineDataReceived(ALine); end; procedure TLldbDebuggerCommandQueue.Run; begin if (FRunningCommand <> nil) or (FLockQueueRun > 0) then exit; {$IFnDEF LLDB_SKIP_IDLE} if Count = 0 then begin if Assigned(FDebugger.OnIdle) and (not FDebugger.FInIdle) then begin FDebugger.FInIdle := True; LockQueueRun; FDebugger.OnIdle(Self); UnLockQueueRun; FDebugger.FInIdle := False; end; exit; end; {$ENDIF} FRunningCommand := Items[0]; FRunningCommand.AddReference; Delete(0); DebugLnEnter(DBG_VERBOSE, ['||||||||>>> CommandQueue.Run ', FRunningCommand.ClassName, ', ', dbgs(fDebugger.State), ' Cnt:',Count]); FRunningCommand.Execute; // debugger and queue may get destroyed at the end of execute end; procedure TLldbDebuggerCommandQueue.CommandFinished( ACommand: TLldbDebuggerCommand); begin if FRunningCommand = ACommand then begin DebugLnExit(DBG_VERBOSE, ['||||||||<<< CommandQueue.Run ', FRunningCommand.ClassName, ', ', dbgs(fDebugger.State), ' Cnt:',Count]); ReleaseRefAndNil(FRunningCommand); end//; else DebugLn(DBG_VERBOSE, ['|||||||| TLldbDebuggerCommandQueue.CommandFinished >> unknown ???', ', ', dbgs(fDebugger.State), ' Cnt:',Count]); if not(FDebugger.State in [dsError, dsDestroying, dsNone]) then Run; end; constructor TLldbDebuggerCommandQueue.Create(ADebugger: TLldbDebugger); begin FDebugger := ADebugger; inherited Create; end; destructor TLldbDebuggerCommandQueue.Destroy; begin while Count > 0 do Delete(0); if FRunningCommand <> nil then begin DebugLnExit(DBG_VERBOSE, ['<<< CommandQueue.Run (Destroy)', FRunningCommand.ClassName, ', ', fDebugger.State]); ReleaseRefAndNil(FRunningCommand); end; inherited Destroy; end; procedure TLldbDebuggerCommandQueue.CancelAll; var i: Integer; begin i := Count - 1; while i >= 0 do begin Items[i].Cancel; dec(i); if i > Count then i := Count - 1; end; if FRunningCommand <> nil then FRunningCommand.Cancel; end; procedure TLldbDebuggerCommandQueue.CancelForRun; var i: Integer; begin i := Count - 1; while i >= 0 do begin if Items[i].CancelableForRun then Items[i].Cancel; dec(i); if i > Count then i := Count - 1; end; if (FRunningCommand <> nil) and (FRunningCommand.CancelableForRun) then FRunningCommand.Cancel; end; procedure TLldbDebuggerCommandQueue.LockQueueRun; begin inc(FLockQueueRun); debugln(DBG_VERBOSE, ['TLldbDebuggerCommandQueue.LockQueueRun ',FLockQueueRun]); end; procedure TLldbDebuggerCommandQueue.UnLockQueueRun; begin debugln(DBG_VERBOSE, ['TLldbDebuggerCommandQueue.UnLockQueueRun ',FLockQueueRun]); dec(FLockQueueRun); if FLockQueueRun = 0 then Run; end; { TLldbDebuggerCommand } function TLldbDebuggerCommand.GetDebuggerState: TDBGState; begin Result := Debugger.State; end; procedure TLldbDebuggerCommand.InstructionSucceeded(AnInstruction: TObject); begin Finished; end; procedure TLldbDebuggerCommand.InstructionFailed(AnInstruction: TObject); begin SetDebuggerState(dsError); Finished; end; procedure TLldbDebuggerCommand.Finished; begin InstructionQueue.CancelAllForCommand(Self); // in case there still are any CommandQueue.CommandFinished(Self); end; function TLldbDebuggerCommand.GetCommandQueue: TLldbDebuggerCommandQueue; begin Result := Debugger.FCommandQueue; end; function TLldbDebuggerCommand.GetInstructionQueue: TLldbInstructionQueue; begin Result := Debugger.FDebugInstructionQueue; end; procedure TLldbDebuggerCommand.QueueInstruction(AnInstruction: TLldbInstruction); begin AnInstruction.OwningCommand := Self; InstructionQueue.QueueInstruction(AnInstruction); end; procedure TLldbDebuggerCommand.SetDebuggerState(const AValue: TDBGState); begin Debugger.SetState(AValue); end; constructor TLldbDebuggerCommand.Create(AOwner: TLldbDebugger); begin FOwner := AOwner; inherited Create; AddReference; end; destructor TLldbDebuggerCommand.Destroy; begin if InstructionQueue <> nil then InstructionQueue.CancelAllForCommand(Self); inherited Destroy; end; procedure TLldbDebuggerCommand.Execute; var d: TLldbDebugger; begin FIsRunning := True; d := Debugger; try AddReference; d.LockRelease; DoExecute; // may call Finished and Destroy Self finally d.UnlockRelease; ReleaseReference; end; end; procedure TLldbDebuggerCommand.Cancel; begin AddReference; Debugger.CommandQueue.Remove(Self); // current running command is not on queue // dec refcount, may call destroy if FIsRunning then DoCancel; // should call CommandQueue.CommandFinished ReleaseReference; end; procedure TLldbDebuggerCommand.DoLineDataReceived(var ALine: String); begin // end; procedure TLldbDebuggerCommand.DoCancel; begin // end; { TLldbDebuggerCommandInit } procedure TLldbDebuggerCommandInit.DoExecute; const FRAME_INFO = '${frame.pc}, {${frame.sp}}, {${frame.fp}}' + ' &&//FULL: {${line.file.fullpath}} &&//SHORT: {${line.file.basename}} &&//LINE: {${line.number}}' + ' &&//MOD: {${module.file.basename}} &&//FUNC: {${function.name-with-args}}' + ' <<&&//FRAME'; var Instr: TLldbInstruction; begin Instr := TLldbInstructionSettingSet.Create('frame-format', '"frame #${frame.index}: ' + FRAME_INFO + '\n"' ); QueueInstruction(Instr); Instr.ReleaseReference; Instr := TLldbInstructionSettingSet.Create('thread-format', '"thread #${thread.index}: tid=${thread.id%tid}: ' + FRAME_INFO + //'{, activity = ''${thread.info.activity.name}''}{, ${thread.info.trace_messages} messages}' + '{, stop reason = ${thread.stop-reason}}' + //'{\nReturn value: ${thread.return-value}}{\nCompleted expression: ${thread.completed-expression}}' + '\n"' ); QueueInstruction(Instr); Instr.ReleaseReference; // Not all versions of lldb have this Instr := TLldbInstructionSettingSet.Create('thread-stop-format', '"thread #${thread.index}: tid=${thread.id%tid}: ' + FRAME_INFO + //'{, activity = ''${thread.info.activity.name}''}{, ${thread.info.trace_messages} messages}' + '{, stop reason = ${thread.stop-reason}}' + //'{\nReturn value: ${thread.return-value}}{\nCompleted expression: ${thread.completed-expression}}' + '\n"' ); QueueInstruction(Instr); Instr.ReleaseReference; Instr := TLldbInstructionTargetStopHook.Create('thread list'); QueueInstruction(Instr); Instr.ReleaseReference; Instr := TLldbInstructionSettingSet.Create('stop-line-count-after', '0'); QueueInstruction(Instr); Instr.ReleaseReference; Instr := TLldbInstructionSettingSet.Create('stop-line-count-before', '0'); QueueInstruction(Instr); Instr.ReleaseReference; Instr := TLldbInstructionSettingSet.Create('stop-disassembly-count', '0'); Instr.OnFinish := @InstructionSucceeded; QueueInstruction(Instr); Instr.ReleaseReference; end; procedure TLldbDebuggerCommandInit.DoLineDataReceived(var ALine: String); begin inherited DoLineDataReceived(ALine); if FGotLLDB then exit; if TLldbDebuggerProperties(Debugger.GetProperties).SkipGDBDetection then FGotLLDB := True else if StrContains(UpperCase(ALine), 'LLDB') then FGotLLDB := True else if StrContains(UpperCase(ALine), '(GDB)') then Debugger.SetErrorState('GDB detected', 'The external debugger identified itself as GDB. The IDE expected LLDB.'); end; { TLldbDebuggerCommandRunStep } procedure TLldbDebuggerCommandRunStep.DoInitialExecute; begin SetNextStepCommand(FStepAction); end; constructor TLldbDebuggerCommandRunStep.Create(AOwner: TLldbDebugger; AStepAction: TLldbInstructionProcessStepAction); var AtExcepiton: Boolean; begin AtExcepiton := AOwner.FExceptionInfo.FAtExcepiton; FStepAction := AStepAction; inherited Create(AOwner); if AtExcepiton and (AStepAction in [saOver, saInto, saOut]) then FMode := cmRunToCatch; end; { TLldbDebuggerCommandRunLaunch } procedure TLldbDebuggerCommandRunLaunch.TargetCreated(Sender: TObject); var TargetInstr: TLldbInstructionTargetCreate absolute Sender; Instr: TLldbInstruction; found: TStringArray; begin if not TargetInstr.IsSuccess then begin SetDebuggerState(dsError); Finished; end; CollectDwarfLoadErrors(Sender); If StrMatches(TargetInstr.Res, [''{}, '','('{}, ')',''], found) then begin if (found[1] = 'i386') or (found[1] = 'i686') then begin DebugLn(DBG_VERBOSE, ['Target 32 bit: ', found[1]]); Debugger.FTargetWidth := 32; Debugger.FTargetRegisters[0] := '$eax'; Debugger.FTargetRegisters[1] := '$edx'; Debugger.FTargetRegisters[2] := '$ecx'; end else if (found[1] = '(x86_64)') or (found[1] = 'x86_64') then begin DebugLn(DBG_VERBOSE, ['Target 64 bit: ', found[1]]); Debugger.FTargetWidth := 64; // target list gives more detailed result. But until remote debugging is added, use the current system {$IFDEF MSWindows} Debugger.FTargetRegisters[0] := '$rcx'; Debugger.FTargetRegisters[1] := '$rdx'; Debugger.FTargetRegisters[2] := '$r8'; {$ELSE} Debugger.FTargetRegisters[0] := '$rdi'; Debugger.FTargetRegisters[1] := '$rsi'; Debugger.FTargetRegisters[2] := '$rdx'; {$ENDIF} end else found := nil; end else found := nil; if found = nil then begin DebugLn(DBG_VERBOSE, ['Target bitness UNKNOWN']); // use architecture of IDE {$IFDEF cpu64} Debugger.FTargetWidth := 64; {$IFDEF MSWindows} Debugger.FTargetRegisters[0] := '$rcx'; Debugger.FTargetRegisters[1] := '$rdx'; Debugger.FTargetRegisters[2] := '$r8'; {$ELSE} Debugger.FTargetRegisters[0] := '$rdi'; Debugger.FTargetRegisters[1] := '$rsi'; Debugger.FTargetRegisters[2] := '$rdx'; {$ENDIF} {$ELSE} Debugger.FTargetWidth := 32; Debugger.FTargetRegisters[0] := '$eax'; Debugger.FTargetRegisters[1] := '$edx'; Debugger.FTargetRegisters[2] := '$ecx'; {$ENDIF} end; if Trim(Debugger.Arguments) <> '' then Instr := TLldbInstructionSettingSet.Create('target.run-args', Debugger.Arguments) else Instr := TLldbInstructionSettingClear.Create('target.run-args'); Instr.OnFinish := @CollectDwarfLoadErrors; QueueInstruction(Instr); Instr.ReleaseReference; Debugger.FBreakErrorBreak.OnFinish := @CollectDwarfLoadErrors; Debugger.FRunErrorBreak.OnFinish := @CollectDwarfLoadErrors; Debugger.FExceptionBreak.OnFinish := @ExceptBreakInstructionFinished; Debugger.FBreakErrorBreak.Enable; Debugger.FRunErrorBreak.Enable; Debugger.FExceptionBreak.Enable; end; procedure TLldbDebuggerCommandRunLaunch.CollectDwarfLoadErrors(Sender: TObject); begin if Sender is TlldbInternalBreakPoint then begin FLaunchWarnings := FLaunchWarnings + TlldbInternalBreakPoint(Sender).DwarfLoadErrors; TlldbInternalBreakPoint(Sender).OnFinish := nil; end else FLaunchWarnings := FLaunchWarnings + TLldbInstruction(Sender).DwarfLoadErrors; end; procedure TLldbDebuggerCommandRunLaunch.ExceptBreakInstructionFinished(Sender: TObject ); var Instr: TLldbInstruction; BrkId: Integer; begin CollectDwarfLoadErrors(Sender); Debugger.FBreakErrorBreak.OnFinish := nil; Debugger.FExceptionInfo.FReg0Cmd := ''; Debugger.FExceptionInfo.FReg2Cmd := ''; Debugger.FExceptionInfo.FExceptClassCmd := ''; Debugger.FExceptionInfo.FExceptMsgCmd := ''; BrkId := Debugger.FExceptionBreak.BreakId; if BrkId > 0 then begin Debugger.FExceptionInfo.FReg0Cmd := 'p/x ' + Debugger.FTargetRegisters[0]; Debugger.FExceptionInfo.FReg2Cmd := 'p/x ' + Debugger.FTargetRegisters[2]; Debugger.FExceptionInfo.FExceptClassCmd := 'p ((char ***)' + Debugger.FTargetRegisters[0] + ')[0][3]'; Debugger.FExceptionInfo.FExceptMsgCmd := 'p ((char **)' + Debugger.FTargetRegisters[0] + ')[1]'; // 'p ((EXCEPTION *)' + Debugger.FTargetRegisters[0] + ')->FMESSAGE' Instr := TLldbInstructionBreakAddCommands.Create(BrkId, [ Debugger.FExceptionInfo.FReg0Cmd, Debugger.FExceptionInfo.FReg2Cmd, Debugger.FExceptionInfo.FExceptClassCmd, Debugger.FExceptionInfo.FExceptMsgCmd ]); Instr.OnFinish := @CollectDwarfLoadErrors; QueueInstruction(Instr); Instr.ReleaseReference; end; BrkId := Debugger.FRunErrorBreak.BreakId; if BrkId > 0 then begin Debugger.FExceptionInfo.FReg0Cmd := 'p/x ' + Debugger.FTargetRegisters[0]; Instr := TLldbInstructionBreakAddCommands.Create(BrkId, [Debugger.FExceptionInfo.FReg0Cmd]); Instr.OnFinish := @CollectDwarfLoadErrors; QueueInstruction(Instr); Instr.ReleaseReference; end; BrkId := Debugger.FBreakErrorBreak.BreakId; if BrkId > 0 then begin Debugger.FExceptionInfo.FReg0Cmd := 'p/x ' + Debugger.FTargetRegisters[0]; Instr := TLldbInstructionBreakAddCommands.Create(BrkId, [Debugger.FExceptionInfo.FReg0Cmd]); Instr.OnFinish := @CollectDwarfLoadErrors; QueueInstruction(Instr); Instr.ReleaseReference; end; SetDebuggerState(dsRun); // the state change allows breakpoints to be set, before the run command is issued. FRunInstr := TLldbInstructionProcessLaunch.Create(TLldbDebuggerProperties(Debugger.GetProperties).LaunchNewTerminal); FRunInstr.OnSuccess := @LaunchInstructionSucceeded; FRunInstr.OnFailure := @InstructionFailed; QueueInstruction(FRunInstr); FRunInstr.ReleaseReference; end; procedure TLldbDebuggerCommandRunLaunch.LaunchInstructionSucceeded(Sender: TObject); begin CollectDwarfLoadErrors(Sender); Debugger.DoAfterLaunch(FLaunchWarnings); if (not TLldbDebuggerProperties(Debugger.GetProperties).IgnoreLaunchWarnings) and (FLaunchWarnings <> '') and assigned(Debugger.OnFeedback) then begin case Debugger.OnFeedback(self, Format('The debugger encountered some errors/warnings while launching the target application.%0:s' + 'Press "Ok" to continue debugging.%0:s' + 'Press "Stop" to end the debug session.', [LineEnding]), FLaunchWarnings, ftWarning, [frOk, frStop] ) of frOk: begin end; frStop: begin Debugger.Stop; end; end; end; RunInstructionSucceeded(Sender); end; procedure TLldbDebuggerCommandRunLaunch.DoInitialExecute; var Instr: TLldbInstruction; begin Instr := TLldbInstructionTargetCreate.Create(Debugger.FileName); Instr.OnSuccess := @TargetCreated; Instr.OnFailure := @InstructionFailed; QueueInstruction(Instr); Instr.ReleaseReference; end; constructor TLldbDebuggerCommandRunLaunch.Create(AOwner: TLldbDebugger); begin FStepAction := saContinue; inherited Create(AOwner); end; { TLldbDebuggerCommandStop } procedure TLldbDebuggerCommandStop.StopInstructionSucceeded(Sender: TObject); begin if DebuggerState <> dsIdle then SetDebuggerState(dsStop); end; procedure TLldbDebuggerCommandStop.DoExecute; var Instr: TLldbInstruction; begin Instr := TLldbInstructionProcessKill.Create(); Instr.OnSuccess := @StopInstructionSucceeded; Instr.OnFailure := @InstructionFailed; QueueInstruction(Instr); Instr.ReleaseReference; Instr := TLldbInstructionTargetDelete.Create(); Instr.OnFailure := @InstructionFailed; QueueInstruction(Instr); Instr.ReleaseReference; Instr := TLldbInstructionTargetDelete.Create(); Instr.OnSuccess := @InstructionSucceeded; Instr.OnFailure := @InstructionFailed; QueueInstruction(Instr); Instr.ReleaseReference; end; { TLldbDebuggerCommandEvaluate } procedure TLldbDebuggerCommandEvaluate.EvalInstructionSucceeded(Sender: TObject ); begin if FWatchValue <> nil then begin FWatchValue.Value := FInstr.Res; //FWatchValue.TypeInfo := TypeInfo; FWatchValue.Validity := ddsValid; end else if FCallback <> nil then FCallback(Debugger, True, FInstr.Res, nil); FInstr.ReleaseReference; Finished; end; procedure TLldbDebuggerCommandEvaluate.EvalInstructionFailed(Sender: TObject); begin if FWatchValue <> nil then FWatchValue.Validity := ddsError else if FCallback <> nil then FCallback(Debugger, False, '', nil); FInstr.ReleaseReference; Finished; end; procedure TLldbDebuggerCommandEvaluate.DoWatchFreed(Sender: TObject); begin FWatchValue := nil; end; procedure TLldbDebuggerCommandEvaluate.DoExecute; begin if FWatchValue <> nil then FInstr := TLldbInstructionExpression.Create(FWatchValue.Expression, FWatchValue.ThreadId, FWatchValue.StackFrame) else // todo: only if FCallback ? FInstr := TLldbInstructionExpression.Create(FExpr, Debugger.FCurrentThreadId, Debugger.FCurrentStackFrame); FInstr.OnSuccess := @EvalInstructionSucceeded; FInstr.OnFailure := @EvalInstructionFailed; QueueInstruction(FInstr); end; constructor TLldbDebuggerCommandEvaluate.Create(AOwner: TLldbDebugger; AWatchValue: TWatchValue); begin FWatchValue := AWatchValue; FWatchValue.AddFreeNotification(@DoWatchFreed); CancelableForRun := True; inherited Create(AOwner); end; constructor TLldbDebuggerCommandEvaluate.Create(AOwner: TLldbDebugger; AnExpr: String; AFlags: TDBGEvaluateFlags; ACallback: TDBGEvaluateResultCallback); begin FExpr := AnExpr; FFlags := AFlags; FCallback := ACallback; CancelableForRun := True; inherited Create(AOwner); end; destructor TLldbDebuggerCommandEvaluate.Destroy; begin if FWatchValue <> nil then FWatchValue.RemoveFreeNotification(@DoWatchFreed); inherited Destroy; end; { TlldbInternalBreakPoint } procedure TlldbInternalBreakPoint.QueueInstruction(AnInstr: TLldbInstruction); begin AnInstr.OnFinish := @DoFinished; FDebugger.DebugInstructionQueue.QueueInstruction(AnInstr); AnInstr.ReleaseReference; end; procedure TlldbInternalBreakPoint.BreakSetSuccess(Sender: TObject); begin FId := TLldbInstructionBreakSet(Sender).BreakId; end; procedure TlldbInternalBreakPoint.DoFailed(Sender: TObject); begin if FId = 0 then FId := -1; if OnFail <> nil then OnFail(Self); end; procedure TlldbInternalBreakPoint.DoFinished(Sender: TObject); begin FDwarfLoadErrors := TLldbInstruction(Sender).DwarfLoadErrors; if OnFinish <> nil then OnFinish(Self); end; constructor TlldbInternalBreakPoint.Create(AName: String; ADebugger: TLldbDebugger; ABeforePrologue: Boolean); begin FName := AName; FDebugger := ADebugger; FBeforePrologue := ABeforePrologue; FId := 0; inherited Create; end; destructor TlldbInternalBreakPoint.Destroy; begin Remove; inherited Destroy; end; procedure TlldbInternalBreakPoint.Enable; var Instr: TLldbInstruction; begin if FId = 0 then begin Instr := TLldbInstructionBreakSet.Create(FName, False, FBeforePrologue); Instr.OnSuccess := @BreakSetSuccess; Instr.OnFailure := @DoFailed; QueueInstruction(Instr); exit; end; if FId < 0 then begin DoFailed(nil); exit; end; Instr := TLldbInstructionBreakModify.Create(FId, False); Instr.OnFailure := @DoFailed; QueueInstruction(Instr); end; procedure TlldbInternalBreakPoint.Disable; var Instr: TLldbInstruction; begin if FId <= 0 then exit; Instr := TLldbInstructionBreakModify.Create(FId, True); Instr.OnFailure := @DoFailed; QueueInstruction(Instr); end; procedure TlldbInternalBreakPoint.Remove; var Instr: TLldbInstruction; begin if FId <= 0 then exit; Instr := TLldbInstructionBreakDelete.Create(FId); QueueInstruction(Instr); FId := 0; end; { TLldbDebugger } function TLldbDebugger.LldbRun: Boolean; var Cmd: TLldbDebuggerCommandRunLaunch; begin DebugLn(DBG_VERBOSE, '*** Run'); Result := True; if State in [dsPause, dsInternalPause, dsRun] then begin // dsRun in case of exception CommandQueue.CancelForRun; LldbStep(saContinue); exit; end; if State in [dsNone, dsIdle, dsStop] then SetState(dsInit); FInIdle := False; DoBeforeLaunch; Cmd := TLldbDebuggerCommandRunLaunch.Create(Self); QueueCommand(Cmd); Cmd.ReleaseReference; end; procedure TLldbDebugger.DoAfterLineReceived(var ALine: String); var Instr: TLldbInstruction; begin if ALine = '' then exit; FCommandQueue.DoLineDataReceived(ALine); if ALine = '' then exit; // Process 8888 exited with status = 0 (0x00000000) if (LeftStr(ALine, 8) = 'Process ') and (pos('exited with status = ', ALine) > 0) then begin // todo: target delete if State <> dsIdle then SetState(dsStop); ALine := ''; Instr := TLldbInstructionTargetDelete.Create(); FDebugInstructionQueue.QueueInstruction(Instr); Instr.ReleaseReference; exit; end; end; procedure TLldbDebugger.DoBeforeLineReceived(var ALine: String); begin DoDbgOutput(ALine); end; procedure TLldbDebugger.DoBeginReceivingLines(Sender: TObject); begin LockRelease; end; procedure TLldbDebugger.DoCmdLineDebuggerTerminated(Sender: TObject); begin SetState(dsError); end; procedure TLldbDebugger.DoLineSentToDbg(Sender: TObject; ALine: String); begin DoDbgOutput('>> '+ALine); end; procedure TLldbDebugger.DoEndReceivingLines(Sender: TObject); begin UnlockRelease; end; function TLldbDebugger.LldbStep(AStepAction: TLldbInstructionProcessStepAction ): Boolean; var Cmd: TLldbDebuggerCommandRunStep; begin Result := True; CommandQueue.CancelForRun; Cmd := TLldbDebuggerCommandRunStep.Create(Self, AStepAction); QueueCommand(Cmd); Cmd.ReleaseReference; end; function TLldbDebugger.LldbStop: Boolean; var Cmd: TLldbDebuggerCommandStop; begin DebugLn(DBG_VERBOSE, '*** Stop'); Result := True; CommandQueue.CancelAll; Cmd := TLldbDebuggerCommandStop.Create(Self); QueueCommand(Cmd); Cmd.ReleaseReference; end; function TLldbDebugger.LldbPause: Boolean; var Instr: TLldbInstruction; begin Result := True; Instr := TLldbInstructionProcessInterrupt.Create(); FDebugInstructionQueue.QueueInstruction(Instr); Instr.ReleaseReference; end; function TLldbDebugger.LldbEvaluate(const AExpression: String; EvalFlags: TDBGEvaluateFlags; ACallback: TDBGEvaluateResultCallback): Boolean; var Cmd: TLldbDebuggerCommandEvaluate; begin Cmd := TLldbDebuggerCommandEvaluate.Create(Self, AExpression, EvalFlags, ACallback); QueueCommand(Cmd); Cmd.ReleaseReference; Result := True; end; function TLldbDebugger.LldbEnvironment(const AVariable: String; const ASet: Boolean): Boolean; var Instr: TLldbInstruction; s: String; begin debugln(DBG_VERBOSE, ['-----------------------------------------', AVariable]); if ASet then Instr := TLldbInstructionSettingSet.Create('target.env-vars', AVariable, False, True) else begin s := AVariable; Instr := TLldbInstructionSettingRemove.Create('target.env-vars', GetPart([], ['='], s, False, False)); end; FDebugInstructionQueue.QueueInstruction(Instr); Instr.ReleaseReference; Result := True; end; procedure TLldbDebugger.TerminateLldb; begin if FDebugProcess.DebugProcessRunning then begin FDebugProcess.SendCmdLn('process kill'); FDebugProcess.SendCmdLn('quit'); Sleep(100); end; FDebugInstructionQueue.OnDebuggerTerminated := nil; // TODO: use a flag to prevent this FDebugProcess.StopDebugProcess; FDebugInstructionQueue.OnDebuggerTerminated := @DoCmdLineDebuggerTerminated; end; procedure TLldbDebugger.DoBeforeLaunch; begin // end; procedure TLldbDebugger.DoAfterLaunch(var LaunchWarnings: string); begin // end; procedure TLldbDebugger.LockRelease; begin inherited LockRelease; end; procedure TLldbDebugger.UnlockRelease; begin inherited UnlockRelease; end; procedure TLldbDebugger.QueueCommand(const ACommand: TLldbDebuggerCommand); begin FCommandQueue.QueueCommand(ACommand); end; procedure TLldbDebugger.SetErrorState(const AMsg: String; const AInfo: String); begin inherited SetErrorState(AMsg, AInfo); end; procedure TLldbDebugger.DoState(const OldState: TDBGState); begin inherited DoState(OldState); if (State = dsError) then TerminateLldb; end; function TLldbDebugger.DoExceptionHit(AExcClass, AExcMsg: String): Boolean; begin if Assigned(EventLogHandler) then EventLogHandler.LogCustomEvent(ecDebugger, etExceptionRaised, Format('Exception class "%s" at $%.' + IntToStr(TargetWidth div 4) + 'x with message "%s"', [AExcClass, FCurrentLocation.Address, AExcMsg])); if Assigned(OnException) then OnException(Self, deInternal, AExcClass, FCurrentLocation, AExcMsg, Result) // TODO: Location else Result := True; // CanContinue end; function TLldbDebugger.DoBreakpointHit(BrkId: Integer): Boolean; var BreakPoint: TLldbBreakPoint; begin if (BrkId >= 0) then BreakPoint := TLldbBreakPoints(BreakPoints).FindById(BrkId) else BreakPoint := nil; if Assigned(EventLogHandler) then EventLogHandler.LogEventBreakPointHit(Breakpoint, FCurrentLocation); if BreakPoint <> nil then begin if (BreakPoint.Valid = vsPending) then BreakPoint.SetPendingToValid(vsValid); try BreakPoint.AddReference; // Important: The Queue must be unlocked // BreakPoint.Hit may evaluate stack and expressions // SetDebuggerState may evaluate data for Snapshot Result := False; // Result; BreakPoint.Hit(Result); finally BreakPoint.ReleaseReference; end; end else if (State = dsRun) then begin debugln(DBG_VERBOSE, ['********** WARNING: breakpoint hit, but nothing known about it ABreakId=', BrkId]); end; end; function TLldbDebugger.CreateBreakPoints: TDBGBreakPoints; begin Result := TLldbBreakPoints.Create(Self, TLldbBreakPoint); end; function TLldbDebugger.CreateLocals: TLocalsSupplier; begin Result := TLldbLocals.Create(Self); end; function TLldbDebugger.CreateRegisters: TRegisterSupplier; begin Result := TLldbRegisterSupplier.Create(Self); end; function TLldbDebugger.CreateCallStack: TCallStackSupplier; begin Result := TLldbCallStack.Create(Self); end; function TLldbDebugger.CreateWatches: TWatchesSupplier; begin Result := TLldbWatches.Create(Self); end; function TLldbDebugger.CreateThreads: TThreadsSupplier; begin Result := TLldbThreads.Create(Self); end; function TLldbDebugger.GetTargetWidth: Byte; begin Result := FTargetWidth; end; function TLldbDebugger.GetIsIdle: Boolean; begin Result := FInIdle or ( (CommandQueue.Count = 0) and (CommandQueue.RunningCommand = nil) ); end; class function TLldbDebugger.GetSupportedCommands: TDBGCommands; begin Result := [dcRun, dcStop, dcStepOver, dcStepInto, dcStepOut, dcEvaluate, dcStepOverInstr, dcStepIntoInstr, dcPause, dcEnvironment]; // Result := [dcStepTo, dcAttach, dcDetach, dcJumpto, // dcBreak, dcWatch, dcLocal, dcEvaluate, dcModify, // dcSetStackFrame, dcDisassemble // ]; end; function TLldbDebugger.RequestCommand(const ACommand: TDBGCommand; const AParams: array of const; const ACallback: TMethod): Boolean; var EvalFlags: TDBGEvaluateFlags; begin LockRelease; try case ACommand of dcRun: Result := LldbRun; dcPause: Result := LldbPause; dcStop: Result := LldbStop; dcStepOver: Result := LldbStep(saOver); dcStepInto: Result := LldbStep(saInto); dcStepOut: Result := LldbStep(saOut); dcStepOverInstr: Result := LldbStep(saInsOver); dcStepIntoInstr: Result := LldbStep(saInsIn); dcEvaluate: begin EvalFlags := []; if high(AParams) >= 1 then EvalFlags := TDBGEvaluateFlags(AParams[1].VInteger); Result := LldbEvaluate(String(AParams[0].VAnsiString), EvalFlags, TDBGEvaluateResultCallback(ACallback)); end; // dcStepTo: Result := GDBRunTo(String(AParams[0].VAnsiString), AParams[1].VInteger); // dcJumpto: Result := GDBJumpTo(String(AParams[0].VAnsiString), AParams[1].VInteger); // dcAttach: Result := GDBAttach(String(AParams[0].VAnsiString)); // dcDetach: Result := GDBDetach; // dcModify: Result := GDBModify(String(AParams[0].VAnsiString), String(AParams[1].VAnsiString)); dcEnvironment: Result := LldbEnvironment(String(AParams[0].VAnsiString), AParams[1].VBoolean); // dcDisassemble: Result := GDBDisassemble(AParams[0].VQWord^, AParams[1].VBoolean, TDbgPtr(AParams[2].VPointer^), // String(AParams[3].VPointer^), String(AParams[4].VPointer^), // String(AParams[5].VPointer^), Integer(AParams[6].VPointer^)) // {%H-}; end; finally UnlockRelease; end; end; class function TLldbDebugger.CreateProperties: TDebuggerProperties; begin Result := TLldbDebuggerProperties.Create; end; class function TLldbDebugger.Caption: String; begin Result := 'LLDB Debugger (Alpha)'; end; class function TLldbDebugger.ExePaths: String; begin {$IFdef MSWindows} Result := ''; {$ELSE} Result := '/usr/bin/lldb'; {$ENDIF} end; class function TLldbDebugger.ExePathsMruGroup: TDebuggerClass; begin Result := TLldbDebugger; end; constructor TLldbDebugger.Create(const AExternalDebugger: String); begin inherited Create(AExternalDebugger); FDebugProcess := TDebugProcess.Create(AExternalDebugger); FDebugProcess.OnLineSent := @DoLineSentToDbg; FDebugInstructionQueue := TLldbInstructionQueue.Create(FDebugProcess); FDebugInstructionQueue.OnBeginLinesReceived := @DoBeginReceivingLines; FDebugInstructionQueue.OnEndLinesReceived := @DoEndReceivingLines; FDebugInstructionQueue.OnBeforeHandleLineReceived := @DoBeforeLineReceived; FDebugInstructionQueue.OnAfterHandleLineReceived := @DoAfterLineReceived; FDebugInstructionQueue.OnDebuggerTerminated := @DoCmdLineDebuggerTerminated; FCommandQueue := TLldbDebuggerCommandQueue.Create(Self); FBreakErrorBreak := TlldbInternalBreakPoint.Create('fpc_break_error', Self, True); FRunErrorBreak := TlldbInternalBreakPoint.Create('fpc_runerror', Self, True); FExceptionBreak := TlldbInternalBreakPoint.Create('fpc_raiseexception', Self, True); FPopExceptStack := TlldbInternalBreakPoint.Create('fpc_popaddrstack', Self); FCatchesBreak := TlldbInternalBreakPoint.Create('fpc_catches', Self); FReRaiseBreak := TlldbInternalBreakPoint.Create('fpc_reraise', Self); end; destructor TLldbDebugger.Destroy; begin debugln(DBG_VERBOSE, ['!!!!!!!!!!!!!!! TLldbDebugger.Destroy ']); FBreakErrorBreak.Remove; FRunErrorBreak.Remove; FExceptionBreak.Remove; FPopExceptStack.Remove; FCatchesBreak.Remove; FReRaiseBreak.Remove; FDebugInstructionQueue.LockQueueRun; inherited Destroy; FBreakErrorBreak.Destroy; FRunErrorBreak.Destroy; FExceptionBreak.Destroy; FPopExceptStack.Destroy; FCatchesBreak.Destroy; FReRaiseBreak.Destroy; FCommandQueue.Destroy; FDebugInstructionQueue.Destroy; FDebugProcess.Destroy; end; procedure TLldbDebugger.Init; var Cmd: TLldbDebuggerCommandInit; begin FDebugProcess.CreateDebugProcess('', Environment); inherited Init; Cmd := TLldbDebuggerCommandInit.Create(Self); QueueCommand(Cmd); Cmd.ReleaseReference; end; procedure TLldbDebugger.Done; begin DebugLnEnter(DBG_VERBOSE, '!!! TLldbDebugger.Done;'); // TODO: cancel all commands TerminateLldb; inherited Done; DebugLnExit(DBG_VERBOSE, '!!! TLldbDebugger.Done;'); end; class function TLldbDebugger.RequiredCompilerOpts(ATargetCPU, ATargetOS: String ): TDebugCompilerRequirements; begin Result:=[dcrDwarfOnly]; end; function TLldbDebugger.GetLocation: TDBGLocationRec; begin Result := FCurrentLocation; end; function TLldbDebugger.NeedReset: Boolean; begin Result := true; end; procedure TLldbDebugger.TestCmd(const ACommand: String); begin FDebugProcess.SendCmdLn(ACommand); end; procedure Register; begin RegisterDebugger(TLldbDebugger); end; initialization DBG_VERBOSE := DebugLogger.FindOrRegisterLogGroup('DBG_VERBOSE' {$IFDEF DBG_VERBOSE} , True {$ENDIF} ); end.