lazarus/components/lazdebuggergdbmi/gdbmidebugger.pp
maxim c9cd3c3901 Merged revision(s) 57743 #61d41ced28 from trunk:
GDBMiDebugger: fix range check / used wrong variable
........

git-svn-id: branches/fixes_1_8@57939 -
2018-05-14 22:40:12 +00:00

12902 lines
409 KiB
ObjectPascal

{ $Id$ }
{ ----------------------------------------------
GDBDebugger.pp - Debugger class forGDB
----------------------------------------------
@created(Wed Feb 23rd WET 2002)
@lastmod($Date$)
@author(Marc Weustink <marc@@lazarus.dommelstein.net>)
This unit contains debugger class for the GDB/MI debugger.
***************************************************************************
* *
* This source is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This code is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* General Public License for more details. *
* *
* A copy of the GNU General Public License is available on the World *
* Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
* obtain it by writing to the Free Software Foundation, *
* Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. *
* *
***************************************************************************
}
unit GDBMIDebugger;
{$mode objfpc}
{$H+}
{$ifndef VER2}
{$define disassemblernestedproc}
{$endif VER2}
{$ifdef disassemblernestedproc}
{$modeswitch nestedprocvars}
{$endif disassemblernestedproc}
{$IFDEF linux} {$DEFINE DBG_ENABLE_TERMINAL} {$ENDIF}
interface
uses
{$IFdef MSWindows}
Windows,
{$ENDIF}
{$IFDEF UNIX}
Unix,BaseUnix,termio,
{$ENDIF}
Classes, SysUtils, strutils, math, Variants,
// LCL
Controls, Dialogs, Forms,
LCLProc,
// LazUtils
FileUtil, LazUTF8, LazClasses, LazLoggerBase, Maps,
// IdeIntf
BaseIDEIntf,
{$IFDEF Darwin}
LazFileUtils,
{$ENDIF}
DebugUtils, GDBTypeInfo, GDBMIDebugInstructions, GDBMIMiscClasses,
DbgIntfBaseTypes, DbgIntfDebuggerBase, GdbmiStringConstants;
type
TGDBMIProgramInfo = record
State: TDBGState;
BreakPoint: Integer; // ID of Breakpoint hit
Signal: Integer; // Signal no if we hit one
SignalText: String; // Signal text if we hit one
end;
// The internal ExecCommand of the new Commands (object queue)
TGDBMICommandFlag = (
cfCheckState, // Copy CmdResult to DebuggerState, EXCEPT dsError,dsNone (e.g copy dsRun, dsPause, dsStop, dsIdle)
cfCheckError, // Copy CmdResult to DebuggerState, ONLY if dsError
cfTryAsync, // try with " &"
cfNoThreadContext,
cfNoStackContext,
//used for old commands, TGDBMIDebuggerSimpleCommand.Create
cfscIgnoreState, // ignore the result state of the command
cfscIgnoreError // ignore errors
);
TGDBMICommandFlags = set of TGDBMICommandFlag;
TGDBMICallback = procedure(const AResult: TGDBMIExecResult; const ATag: PtrInt) of object;
TGDBMIPauseWaitState = (pwsNone, pwsInternal, pwsExternal);
TGDBMITargetFlag = (
tfHasSymbols, // Debug symbols are present
tfRTLUsesRegCall, // the RTL is compiled with RegCall calling convention
tfClassIsPointer, // with dwarf class names are pointer. with stabs they are not
tfExceptionIsPointer, // Can happen, if stabs and dwarf are mixed
tfFlagHasTypeObject,
tfFlagHasTypeException,
tfFlagHasTypeShortstring,
//tfFlagHasTypePShortString,
tfFlagHasTypePointer,
tfFlagHasTypeByte
//tfFlagHasTypeChar
);
TGDBMITargetFlags = set of TGDBMITargetFlag;
TGDBMIDebuggerFlags = set of (
dfImplicidTypes, // Debugger supports implicit types (^Type)
dfForceBreak, // Debugger supports insertion of not yet known brekpoints
dfForceBreakDetected,
dfSetBreakFailed,
dfSetBreakPending
);
// Target info
TGDBMITargetInfo = record
TargetPID: Integer;
TargetFlags: TGDBMITargetFlags;
TargetCPU: String;
TargetOS: String;
TargetRegisters: array[0..2] of String;
TargetPtrSize: Byte; // size in bytes
TargetIsBE: Boolean;
end;
PGDBMITargetInfo = ^TGDBMITargetInfo;
TConvertToGDBPathType = (cgptNone, cgptCurDir, cgptExeName);
TGDBMIDebuggerFilenameEncoding = (
gdfeNone, gdfeDefault, gdfeEscSpace, gdfeQuote
);
TGDBMIDebuggerStartBreak = (
gdsbDefault, gdsbEntry, gdsbMainAddr, gdsbMain, gdsbAddZero
);
TGDBMIUseNoneMiRunCmdsState = (
gdnmNever, gdnmAlways, gdnmFallback
);
TGDBMIWarnOnSetBreakpointError = (
gdbwNone, gdbwAll, gdbwUserBreakPoint, gdbwExceptionsAndRunError
);
{ TGDBMIDebuggerPropertiesBase }
TGDBMIDebuggerPropertiesBase = class(TDebuggerProperties)
private
FDisableForcedBreakpoint: Boolean;
FDisableLoadSymbolsForLibraries: Boolean;
FEncodeCurrentDirPath: TGDBMIDebuggerFilenameEncoding;
FEncodeExeFileName: TGDBMIDebuggerFilenameEncoding;
{$IFDEF UNIX}
FConsoleTty: String;
{$ENDIF}
FGDBOptions: String;
FInternalStartBreak: TGDBMIDebuggerStartBreak;
FMaxDisplayLengthForString: Integer;
FTimeoutForEval: Integer;
FUseAsyncCommandMode: Boolean;
FUseNoneMiRunCommands: TGDBMIUseNoneMiRunCmdsState;
FWarnOnSetBreakpointError: TGDBMIWarnOnSetBreakpointError;
FWarnOnInternalError: Boolean;
FWarnOnTimeOut: Boolean;
procedure SetMaxDisplayLengthForString(AValue: Integer);
procedure SetTimeoutForEval(const AValue: Integer);
procedure SetWarnOnTimeOut(const AValue: Boolean);
public
constructor Create; override;
procedure Assign(Source: TPersistent); override;
public
property Debugger_Startup_Options: String read FGDBOptions write FGDBOptions;
{$IFDEF UNIX}
property ConsoleTty: String read FConsoleTty write FConsoleTty;
{$ENDIF}
property MaxDisplayLengthForString: Integer read FMaxDisplayLengthForString write SetMaxDisplayLengthForString;
property TimeoutForEval: Integer read FTimeoutForEval write SetTimeoutForEval;
property WarnOnTimeOut: Boolean read FWarnOnTimeOut write SetWarnOnTimeOut;
property WarnOnInternalError: Boolean read FWarnOnInternalError write FWarnOnInternalError;
property EncodeCurrentDirPath: TGDBMIDebuggerFilenameEncoding
read FEncodeCurrentDirPath write FEncodeCurrentDirPath default gdfeDefault;
property EncodeExeFileName: TGDBMIDebuggerFilenameEncoding
read FEncodeExeFileName write FEncodeExeFileName default gdfeDefault;
property InternalStartBreak: TGDBMIDebuggerStartBreak
read FInternalStartBreak write FInternalStartBreak default gdsbDefault;
property UseAsyncCommandMode: Boolean read FUseAsyncCommandMode write FUseAsyncCommandMode;
property UseNoneMiRunCommands: TGDBMIUseNoneMiRunCmdsState
read FUseNoneMiRunCommands write FUseNoneMiRunCommands default gdnmFallback;
property DisableLoadSymbolsForLibraries: Boolean read FDisableLoadSymbolsForLibraries
write FDisableLoadSymbolsForLibraries default False;
property DisableForcedBreakpoint: Boolean read FDisableForcedBreakpoint
write FDisableForcedBreakpoint default False;
property WarnOnSetBreakpointError: TGDBMIWarnOnSetBreakpointError read FWarnOnSetBreakpointError
write FWarnOnSetBreakpointError default gdbwAll;
end;
TGDBMIDebuggerProperties = class(TGDBMIDebuggerPropertiesBase)
published
property Debugger_Startup_Options;
{$IFDEF UNIX}
property ConsoleTty;
{$ENDIF}
property MaxDisplayLengthForString;
property TimeoutForEval;
property WarnOnTimeOut;
property WarnOnInternalError;
property EncodeCurrentDirPath;
property EncodeExeFileName;
property InternalStartBreak;
property UseAsyncCommandMode;
property UseNoneMiRunCommands;
property DisableLoadSymbolsForLibraries;
property DisableForcedBreakpoint;
//property WarnOnSetBreakpointError;
end;
TGDBMIDebugger = class;
TGDBMIDebuggerCommand = class;
{ TGDBMIDebuggerInstruction }
TGDBMIDebuggerInstruction = class(TGDBInstruction)
private
FCmd: TGDBMIDebuggerCommand;
FFullCmdReply: String;
FHasResult: Boolean;
FInLogWarning: Boolean;
FLogWarnings: String;
FResultData: TGDBMIExecResult;
protected
function ProcessInputFromGdb(const AData: String): Boolean; override;
function GetTimeOutVerifier: TGDBInstruction; override;
procedure Init; override;
public
procedure HandleNoGdbRunning; override;
procedure HandleReadError; override;
procedure HandleTimeOut; override;
property ResultData: TGDBMIExecResult read FResultData;
property HasResult: Boolean read FHasResult; // seen a "^foo" msg from gdb
property FullCmdReply: String read FFullCmdReply;
property LogWarnings: String read FLogWarnings;
property Cmd: TGDBMIDebuggerCommand read FCmd write FCmd;
end;
{ TGDBMIDbgInstructionQueue }
TGDBMIDbgInstructionQueue = class(TGDBInstructionQueue)
protected
procedure HandleGdbDataBeforeInstruction(var AData: String; var SkipData: Boolean;
const TheInstruction: TGDBInstruction); override;
function Debugger: TGDBMIDebugger; reintroduce;
end;
{ TGDBMIDebuggerCommand }
TGDBMIDebuggerCommandState =
( dcsNone, // Initial State
dcsQueued, // [None] => Queued behind other commands
dcsExecuting, // [None, Queued] => currently running
// Final States, those lead to the object being freed, unless it still is referenced (Add/Release-Reference)
dcsFinished, // [Executing] => Finished Execution
dcsCanceled, // [Queued] => Never Executed
// Flags, for Seenstates
dcsInternalRefReleased // The internal reference has been released
);
TGDBMIDebuggerCommandStates = set of TGDBMIDebuggerCommandState;
TGDBMIDebuggerCommandProperty = (dcpCancelOnRun);
TGDBMIDebuggerCommandProperts = set of TGDBMIDebuggerCommandProperty;
TGDBMIExecCommandType =
( ectNone,
ectContinue, // -exec-continue
ectRun, // -exec-run
ectRunTo, // -exec-until [Source, Line]
ectStepOver, // -exec-next
ectStepOut, // -exec-finish
ectStepInto, // -exec-step
// not yet used
ectStepOverInstruction, // -exec-next-instruction
ectStepIntoInstruction, // -exec-step-instruction
ectReturn // -exec-return (step out immediately, skip execution)
);
TGDBMIBreakpointReason = (gbrBreak, gbrWatchTrigger, gbrWatchScope);
TGDBMIProcessResultOpt = (
prNoLeadingTab, // Do not require/strip the leading #9
prKeepBackSlash, // Workaround, backslash may have been removed already
// for structures
prStripAddressFromString,
prMakePrintAble
);
TGDBMIProcessResultOpts = set of TGDBMIProcessResultOpt;
TGDBMICommandContextKind = (ccNotRequired, ccUseGlobal, ccUseLocal);
TGDBMICommandContext = record
ThreadContext: TGDBMICommandContextKind;
ThreadId: Integer;
StackContext: TGDBMICommandContextKind;
StackFrame: Integer;
end;
TGDBMIDebuggerCommand = class(TRefCountedObject)
private
FDefaultTimeOut: Integer;
FLastExecwasTimeOut: Boolean;
FOnCancel: TNotifyEvent;
FOnDestroy: TNotifyEvent;
FOnExecuted: TNotifyEvent;
FPriority: Integer;
FProcessResultTimedOut: Boolean;
FProperties: TGDBMIDebuggerCommandProperts;
FQueueRunLevel: Integer;
FState : TGDBMIDebuggerCommandState;
FSeenStates: TGDBMIDebuggerCommandStates;
FLastExecCommand: String;
FLastExecResult: TGDBMIExecResult;
FLogWarnings, FFullCmdReply: String;
FGotStopped: Boolean; // used in ProcessRunning
function GetDebuggerProperties: TGDBMIDebuggerPropertiesBase;
function GetDebuggerState: TDBGState;
function GetTargetInfo: PGDBMITargetInfo;
protected
FTheDebugger: TGDBMIDebugger; // Set during Execute
FContext: TGDBMICommandContext;
function ContextThreadId: Integer; // does not check validy, only ccUseGlobal or ccUseLocal
function ContextStackFrame: Integer; // does not check validy, only ccUseGlobal or ccUseLocal
procedure CopyGlobalContextToLocal;
procedure SetDebuggerState(const AValue: TDBGState);
procedure SetDebuggerErrorState(const AMsg: String; const AInfo: String = '');
function ErrorStateMessage: String; virtual;
function ErrorStateInfo: String; virtual;
property DebuggerState: TDBGState read GetDebuggerState;
property DebuggerProperties: TGDBMIDebuggerPropertiesBase read GetDebuggerProperties;
property TargetInfo: PGDBMITargetInfo read GetTargetInfo;
protected
procedure SetCommandState(NewState: TGDBMIDebuggerCommandState);
procedure DoStateChanged({%H-}OldState: TGDBMIDebuggerCommandState); virtual;
procedure DoLockQueueExecute; virtual;
procedure DoUnLockQueueExecute; virtual;
procedure DoLockQueueExecuteForInstr; virtual;
procedure DoUnLockQueueExecuteForInstr; virtual;
function DoExecute: Boolean; virtual; abstract;
procedure DoOnExecuted;
procedure DoCancel; virtual;
procedure DoOnCanceled;
property SeenStates: TGDBMIDebuggerCommandStates read FSeenStates;
property QueueRunLevel: Integer read FQueueRunLevel write FQueueRunLevel; // if queue is nested
protected
// ExecuteCommand does execute direct. It does not use the queue
function ExecuteCommand(const ACommand: String;
AFlags: TGDBMICommandFlags = [];
ATimeOut: Integer = -1
): Boolean; overload;
function ExecuteCommand(const ACommand: String;
out AResult: TGDBMIExecResult;
AFlags: TGDBMICommandFlags = [];
ATimeOut: Integer = -1
): Boolean; overload;
function ExecuteCommand(const ACommand: String; const AValues: array of const;
AFlags: TGDBMICommandFlags;
ATimeOut: Integer = -1
): Boolean; overload;
function ExecuteCommand(const ACommand: String; const AValues: array of const;
out AResult: TGDBMIExecResult;
AFlags: TGDBMICommandFlags = [];
ATimeOut: Integer = -1
): Boolean; overload;
procedure DoTimeoutFeedback;
function ProcessGDBResultStruct(S: String; Opts: TGDBMIProcessResultOpts = []): String; // Must have at least one flag for structs
function ProcessGDBResultText(S: String; Opts: TGDBMIProcessResultOpts = []): String;
function GetStackDepth(MaxDepth: integer): Integer;
function FindStackFrame(FP: TDBGPtr; StartAt, MaxDepth: Integer): Integer;
function GetFrame(const AIndex: Integer): String;
function GetText(const ALocation: TDBGPtr): String; overload;
function GetText(const AExpression: String; const AValues: array of const): String; overload;
function GetChar(const AExpression: String; const AValues: array of const): String; overload;
function GetFloat(const AExpression: String; const AValues: array of const): String;
function GetWideText(const ALocation: TDBGPtr): String;
function GetGDBTypeInfo(const AExpression: String; FullTypeInfo: Boolean = False;
AFlags: TGDBTypeCreationFlags = [];
{%H-}AFormat: TWatchDisplayFormat = wdfDefault;
ARepeatCount: Integer = 0): TGDBType;
function GetClassName(const AClass: TDBGPtr): String; overload;
function GetClassName(const AExpression: String; const AValues: array of const): String; overload;
function GetInstanceClassName(const AInstance: TDBGPtr): String; overload;
function GetInstanceClassName(const AExpression: String; const AValues: array of const): String; overload;
function GetData(const ALocation: TDbgPtr): TDbgPtr; overload;
function GetData(const AExpression: String; const AValues: array of const): TDbgPtr; overload;
function GetStrValue(const AExpression: String; const AValues: array of const): String;
function GetIntValue(const AExpression: String; const AValues: array of const): Integer;
function GetPtrValue(const AExpression: String; const AValues: array of const; {%H-}ConvertNegative: Boolean = False): TDbgPtr;
function CheckHasType(TypeName: String; TypeFlag: TGDBMITargetFlag): TGDBMIExecResult;
function PointerTypeCast: string;
function FrameToLocation(const AFrame: String = ''): TDBGLocationRec;
procedure ProcessFrame(ALocation: TDBGLocationRec; ASeachStackForSource: Boolean = True); overload;
procedure ProcessFrame(const AFrame: String = ''; ASeachStackForSource: Boolean = True); overload;
procedure DoDbgEvent(const ACategory: TDBGEventCategory; const AEventType: TDBGEventType; const AText: String);
property LastExecResult: TGDBMIExecResult read FLastExecResult;
property DefaultTimeOut: Integer read FDefaultTimeOut write FDefaultTimeOut;
property ProcessResultTimedOut: Boolean read FProcessResultTimedOut; // single gdb command, took to long.Used to trigger timeout detection
property LastExecwasTimeOut: Boolean read FLastExecwasTimeOut; // timeout, was confirmed (additional commands send and returned)
public
constructor Create(AOwner: TGDBMIDebugger);
destructor Destroy; override;
// DoQueued: Called if queued *behind* others
procedure DoQueued;
// DoFinished: Called after processing is done
// defaults to Destroy the object
procedure DoFinished;
function Execute: Boolean;
procedure Cancel;
function KillNow: Boolean; virtual;
function DebugText: String; virtual;
property State: TGDBMIDebuggerCommandState read FState;
property OnExecuted: TNotifyEvent read FOnExecuted write FOnExecuted;
property OnCancel: TNotifyEvent read FOnCancel write FOnCancel;
property OnDestroy: TNotifyEvent read FOnDestroy write FOnDestroy;
property Priority: Integer read FPriority write FPriority;
property Properties: TGDBMIDebuggerCommandProperts read FProperties write FProperties;
end;
{ TGDBMIDebuggerCommandList }
TGDBMIDebuggerCommandList = class(TRefCntObjList)
private
function Get(Index: Integer): TGDBMIDebuggerCommand;
procedure Put(Index: Integer; const AValue: TGDBMIDebuggerCommand);
public
property Items[Index: Integer]: TGDBMIDebuggerCommand read Get write Put; default;
end;
{%region ***** TGDBMIDebuggerCommands ***** }
{ TGDBMIDebuggerSimpleCommand }
// not to be used for anything that runs/steps the app
TGDBMIDebuggerSimpleCommand = class(TGDBMIDebuggerCommand)
private
FCommand: String;
FFlags: TGDBMICommandFlags;
FCallback: TGDBMICallback;
FTag: PtrInt;
FResult: TGDBMIExecResult;
protected
function DoExecute: Boolean; override;
public
constructor Create(AOwner: TGDBMIDebugger;
const ACommand: String;
const AValues: array of const;
const AFlags: TGDBMICommandFlags;
const ACallback: TGDBMICallback;
const ATag: PtrInt);
function DebugText: String; override;
property Result: TGDBMIExecResult read FResult;
end;
{ TGDBMIDebuggerCommandInitDebugger }
TGDBMIDebuggerCommandInitDebugger = class(TGDBMIDebuggerCommand)
protected
FSuccess: Boolean;
function DoExecute: Boolean; override;
public
property Success: Boolean read FSuccess;
end;
{ TGDBMIDebuggerChangeFilenameBase }
TGDBMIDebuggerChangeFilenameBase = class(TGDBMIDebuggerCommand)
protected
FErrorMsg: String;
function DoChangeFilename: Boolean;
function DoSetPascal: Boolean;
end;
{ TGDBMIDebuggerCommandChangeFilename }
TGDBMIDebuggerCommandChangeFilename = class(TGDBMIDebuggerChangeFilenameBase)
private
FSuccess: Boolean;
FFileName: String;
protected
function DoExecute: Boolean; override;
public
constructor Create(AOwner: TGDBMIDebugger; AFileName: String);
property Success: Boolean read FSuccess;
property ErrorMsg: String read FErrorMsg;
end;
{ TGDBMIDebuggerCommandExecuteBase }
TGDBMIDebuggerCommandExecuteBase = class(TGDBMIDebuggerChangeFilenameBase)
private
FCanKillNow, FDidKillNow: Boolean;
protected
function ProcessRunning(out AStoppedParams: String; out AResult: TGDBMIExecResult; ATimeOut: Integer = 0): Boolean;
function ParseBreakInsertError(var AText: String; out AnId: Integer): Boolean;
function ProcessStopped(const {%H-}AParams: String; const {%H-}AIgnoreSigIntState: Boolean): Boolean; virtual;
public
constructor Create(AOwner: TGDBMIDebugger);
function KillNow: Boolean; override;
end;
{ TGDBMIDebuggerCommandStartBase }
TGDBMIDebuggerCommandStartBase = class(TGDBMIDebuggerCommandExecuteBase)
protected
procedure SetTargetInfo(const AFileType: String);
function CheckFunction(const AFunction: String): Boolean;
procedure RetrieveRegcall;
procedure CheckAvailableTypes;
procedure DetectForceableBreaks;
procedure CommonInit; // Before any run/exec
end;
{ TGDBMIDebuggerCommandStartDebugging }
TGDBMIDebuggerCommandStartDebugging = class(TGDBMIDebuggerCommandStartBase)
private
FContinueCommand: TGDBMIDebuggerCommand;
FSuccess: Boolean;
protected
function DoExecute: Boolean; override;
function GdbRunCommand: String; virtual;
public
constructor Create(AOwner: TGDBMIDebugger; AContinueCommand: TGDBMIDebuggerCommand);
destructor Destroy; override;
function DebugText: String; override;
property ContinueCommand: TGDBMIDebuggerCommand read FContinueCommand;
property Success: Boolean read FSuccess;
end;
{ TGDBMIDebuggerCommandAttach }
TGDBMIDebuggerCommandAttach = class(TGDBMIDebuggerCommandStartBase)
private
FProcessID: String;
FSuccess: Boolean;
protected
function DoExecute: Boolean; override;
public
constructor Create(AOwner: TGDBMIDebugger; AProcessID: String);
function DebugText: String; override;
property Success: Boolean read FSuccess;
end;
{ TGDBMIDebuggerCommandDetach }
TGDBMIDebuggerCommandDetach = class(TGDBMIDebuggerCommand)
protected
function DoExecute: Boolean; override;
end;
{ TGDBMIDebuggerCommandExecute }
TGDBMIDebuggerCommandExecute = class(TGDBMIDebuggerCommandExecuteBase)
private
FNextExecQueued: Boolean;
FResult: TGDBMIExecResult;
FExecType: TGDBMIExecCommandType;
FCurrentExecCmd: TGDBMIExecCommandType;
FCurrentExecArg: String;
FRunToSrc: String;
FRunToLine: Integer;
FStepBreakPoint: Integer;
protected
procedure DoLockQueueExecute; override;
procedure DoUnLockQueueExecute; override;
function ProcessStopped(const AParams: String; const AIgnoreSigIntState: Boolean): Boolean; override;
{$IFDEF MSWindows}
function FixThreadForSigTrap: Boolean;
{$ENDIF}
function DoExecute: Boolean; override;
public
constructor Create(AOwner: TGDBMIDebugger; const ExecType: TGDBMIExecCommandType);
constructor Create(AOwner: TGDBMIDebugger; const ExecType: TGDBMIExecCommandType; Args: array of const);
function DebugText: String; override;
property Result: TGDBMIExecResult read FResult;
property NextExecQueued: Boolean read FNextExecQueued;
end;
{ TGDBMIDebuggerCommandKill }
TGDBMIDebuggerCommandKill = class(TGDBMIDebuggerCommand)
protected
function DoExecute: Boolean; override;
end;
{%endregion}
{ TGDBMIInternalBreakPoint }
TGDBMIInternalBreakPoint = class
private type
TClearOpt = (coClearIfSet, coKeepIfSet);
TBlockOpt = (boNone, boBlock, boUnblock);
TInternalBreakLocation = (iblNamed, iblAddrOfNamed, iblCustomAddr,
iblAddOffset, iblFileLine);
TInternalBreakData = record
BreakGdbId: Integer;
BreakAddr: TDBGPtr;
BreakFunction: String;
BreakFile: String;
BreakLine: String;
end;
private
FBreaks: array[TInternalBreakLocation] of TInternalBreakData;
(* F...ID: -1 not set, -2 blocked
*)
FEnabled: Boolean;
FName: string; // The (function) name of the location "main" or "FPC_RAISE"
FMainAddrFound: TDBGPtr; // The address found for this named location
FUseForceFlag: Boolean;
function BreakSet(ACmd: TGDBMIDebuggerCommand; ABreakLoc: String;
ALoc: TInternalBreakLocation;
AClearIfSet: TClearOpt): Boolean;
function GetBreakAddr(ALoc: TInternalBreakLocation): TDBGPtr;
function GetBreakFile(ALoc: TInternalBreakLocation): String;
function GetBreakId(ALoc: TInternalBreakLocation): Integer;
function GetBreakLine(ALoc: TInternalBreakLocation): String;
function GetInfoAddr(ACmd: TGDBMIDebuggerCommand): TDBGPtr;
function HasBreakAtAddr(AnAddr: TDBGPtr): Boolean;
function HasBreakWithId(AnId: Integer): Boolean;
procedure InternalSetAddr(ACmd: TGDBMIDebuggerCommand; ALoc: TInternalBreakLocation;
AnAddr: TDBGPtr);
protected
procedure Clear(ACmd: TGDBMIDebuggerCommand; ALoc: TInternalBreakLocation;
ABlock: TBlockOpt = boNone);
property BreakId[ALoc: TInternalBreakLocation]: Integer read GetBreakId;
property BreakAddr[ALoc: TInternalBreakLocation]: TDBGPtr read GetBreakAddr;
property BreakFile[ALoc: TInternalBreakLocation]: String read GetBreakFile;
property BreakLine[ALoc: TInternalBreakLocation]: String read GetBreakLine;
public
constructor Create(AName: string);
procedure SetBoth(ACmd: TGDBMIDebuggerCommand);
procedure SetByName(ACmd: TGDBMIDebuggerCommand);
procedure SetByAddr(ACmd: TGDBMIDebuggerCommand; SetNamedOnFail: Boolean = False);
procedure SetAtCustomAddr(ACmd: TGDBMIDebuggerCommand; AnAddr: TDBGPtr);
procedure SetAtLineOffs(ACmd: TGDBMIDebuggerCommand; AnOffset: integer);
procedure SetAtFileLine(ACmd: TGDBMIDebuggerCommand; AFile, ALine: String);
procedure Clear(ACmd: TGDBMIDebuggerCommand);
function ClearId(ACmd: TGDBMIDebuggerCommand; AnId: Integer): Boolean;
// a blocked id can not be set, until after the next clear (clear all)
function ClearAndBlockId(ACmd: TGDBMIDebuggerCommand; AnId: Integer): Boolean;
function MatchAddr(AnAddr: TDBGPtr): boolean;
function MatchId(AnId: Integer): boolean;
function IsBreakSet: boolean;
function BreakSetCount: Integer;
procedure EnableOrSetByAddr(ACmd: TGDBMIDebuggerCommand; SetNamedOnFail: Boolean = False);
procedure Enable(ACmd: TGDBMIDebuggerCommand);
procedure Disable(ACmd: TGDBMIDebuggerCommand);
property MainAddrFound: TDBGPtr read FMainAddrFound;
property UseForceFlag: Boolean read FUseForceFlag write FUseForceFlag;
property Enabled: Boolean read FEnabled;
end;
{ TGDBMIWatches }
TGDBMIDebuggerParentFrameCache = record
ThreadId: Integer;
ParentFPList: Array of
record
fp, parentfp: string; // empty=unknown / '-'=evaluated-no-data
end;
end;
PGDBMIDebuggerParentFrameCache = ^TGDBMIDebuggerParentFrameCache;
TGDBMIWatches = class(TWatchesSupplier)
private
FCommandList: TList;
FParentFPList: Array of TGDBMIDebuggerParentFrameCache;
FParentFPListChangeStamp: Integer;
procedure DoEvaluationDestroyed(Sender: TObject);
protected
function GetParentFPList(AThreadId: Integer): PGDBMIDebuggerParentFrameCache;
procedure DoStateChange(const AOldState: TDBGState); override;
procedure Changed;
procedure Clear;
function ForceQueuing: Boolean;
procedure InternalRequestData(AWatchValue: TWatchValue); override;
property ParentFPListChangeStamp: Integer read FParentFPListChangeStamp;
public
constructor Create(const ADebugger: TDebuggerIntf);
destructor Destroy; override;
end;
{ TGDBMILocals }
TGDBMILocals = class(TLocalsSupplier)
private
FCommandList: TList;
procedure CancelEvaluation; deprecated;
procedure DoEvaluationDestroyed(Sender: TObject);
protected
procedure CancelAllCommands;
function ForceQueuing: Boolean;
public
procedure Changed;
constructor Create(const ADebugger: TDebuggerIntf);
destructor Destroy; override;
procedure RequestData(ALocals: TLocals); override;
end;
{ TGDBMIDebugger }
TGDBMIDebugger = class(TGDBMICmdLineDebugger) // TODO: inherit from TDebugger direct
private
FInstructionQueue: TGDBMIDbgInstructionQueue;
FCommandQueue: TGDBMIDebuggerCommandList;
FCurrentCommand: TGDBMIDebuggerCommand;
FCommandQueueExecLock: Integer;
FCommandProcessingLock: Integer;
FMainAddrBreak: TGDBMIInternalBreakPoint;
FBreakAtMain: TDBGBreakPoint;
FBreakErrorBreak: TGDBMIInternalBreakPoint;
FRunErrorBreak: TGDBMIInternalBreakPoint;
FExceptionBreak: TGDBMIInternalBreakPoint;
FPopExceptStack, FCatchesBreak, FReRaiseBreak: TGDBMIInternalBreakPoint;
FPauseWaitState: TGDBMIPauseWaitState;
FStoppedReason: (srNone, srRaiseExcept, srReRaiseExcept, srPopExceptStack, srCatches);
FInExecuteCount: Integer;
FInIdle: Boolean;
FRunQueueOnUnlock: Boolean;
FDebuggerFlags: TGDBMIDebuggerFlags;
FSourceNames: TStringList; // Objects[] -> TMap[Integer|Integer] -> TDbgPtr
FReleaseLock: Integer;
FInProcessStopped: Boolean; // paused, but maybe state run
FCommandNoneMiState: Array [TGDBMIExecCommandType] of Boolean;
FCommandAsyncState: Array [TGDBMIExecCommandType] of Boolean;
FCurrentCmdIsAsync: Boolean;
FAsyncModeEnabled: Boolean;
FWasDisableLoadSymbolsForLibraries: Boolean;
// Internal Current values
FCurrentStackFrame, FCurrentThreadId: Integer; // User set values
FCurrentStackFrameValid, FCurrentThreadIdValid: Boolean; // Internal (update for every temporary change)
FCurrentLocation: TDBGLocationRec;
// GDB info (move to ?)
FGDBVersion: String;
FGDBVersionMajor, FGDBVersionMinor, FGDBVersionRev: Integer;
FGDBCPU: String;
FGDBPtrSize: integer; // PointerSize of the GDB-cpu
FGDBOS: String;
// Target info (move to record ?)
FTargetInfo: TGDBMITargetInfo;
FThreadGroups: TStringList;
FTypeRequestCache: TGDBPTypeRequestCache;
FMaxLineForUnitCache: TStringList;
procedure DoPseudoTerminalRead(Sender: TObject);
// Implementation of external functions
function GDBEnvironment(const AVariable: String; const ASet: Boolean): Boolean;
function GDBEvaluate(const AExpression: String; var AResult: String;
out ATypeInfo: TGDBType; EvalFlags: TDBGEvaluateFlags): Boolean;
function GDBModify(const AExpression, ANewValue: String): Boolean;
procedure GDBModifyDone(const {%H-}AResult: TGDBMIExecResult; const {%H-}ATag: PtrInt);
function GDBRun: Boolean;
function GDBPause(const AInternal: Boolean): Boolean;
function GDBStop: Boolean;
function GDBStepOver: Boolean;
function GDBStepInto: Boolean;
function GDBStepOverInstr: Boolean;
function GDBStepIntoInstr: Boolean;
function GDBStepOut: Boolean;
function GDBRunTo(const ASource: String; const ALine: Integer): Boolean;
function GDBJumpTo(const {%H-}ASource: String; const {%H-}ALine: Integer): Boolean;
function GDBAttach(AProcessID: String): Boolean;
function GDBDetach: Boolean;
function GDBDisassemble(AAddr: TDbgPtr; ABackward: Boolean; out ANextAddr: TDbgPtr;
out ADump, AStatement, AFile: String; out ALine: Integer): Boolean;
deprecated;
function GDBSourceAdress(const ASource: String; ALine, {%H-}AColumn: Integer; out AAddr: TDbgPtr): Boolean;
// prevent destruction while nested in any call
procedure LockRelease;
procedure UnlockRelease;
// ---
procedure ClearSourceInfo;
function FindBreakpoint(const ABreakpoint: Integer): TDBGBreakPoint;
// All ExecuteCommand functions are wrappers for the real (full) implementation
// ExecuteCommandFull is never called directly
function ExecuteCommand(const ACommand: String; const AValues: array of const; const AFlags: TGDBMICommandFlags): Boolean; overload;
function ExecuteCommand(const ACommand: String; const AValues: array of const; const AFlags: TGDBMICommandFlags; var AResult: TGDBMIExecResult): Boolean; overload;
function ExecuteCommandFull(const ACommand: String; const AValues: array of const; const AFlags: TGDBMICommandFlags; const ACallback: TGDBMICallback; const ATag: PtrInt; var AResult: TGDBMIExecResult): Boolean; overload;
procedure RunQueue;
procedure CancelAllQueued;
procedure CancelBeforeRun;
procedure CancelAfterStop;
procedure RunQueueASync;
procedure RemoveRunQueueASync;
procedure DoRunQueueFromASync({%H-}Data: PtrInt);
function StartDebugging(AContinueCommand: TGDBMIExecCommandType): Boolean;
function StartDebugging(AContinueCommand: TGDBMIExecCommandType; AValues: array of const): Boolean;
function StartDebugging(AContinueCommand: TGDBMIDebuggerCommand = nil): Boolean;
procedure TerminateGDB;
protected
FNeedStateToIdle, FNeedReset: Boolean;
{$IFDEF MSWindows}
FPauseRequestInThreadID: Cardinal;
{$ENDIF}
{$IFDEF DBG_ENABLE_TERMINAL}
FPseudoTerminal: TPseudoTerminal;
procedure ProcessWhileWaitForHandles; override;
{$ENDIF}
procedure QueueExecuteLock;
procedure QueueExecuteUnlock;
procedure QueueCommand(const ACommand: TGDBMIDebuggerCommand; ForceQueue: Boolean = False);
procedure UnQueueCommand(const ACommand: TGDBMIDebuggerCommand);
function ConvertToGDBPath(APath: string; ConvType: TConvertToGDBPathType = cgptNone): string;
function ChangeFileName: Boolean; override;
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 GetSupportedCommands: TDBGCommands; override;
function GetCommands: TDBGCommands; override;
function GetTargetWidth: Byte; override;
procedure InterruptTarget; virtual;
function ParseInitialization: Boolean; virtual;
function CreateCommandInit: TGDBMIDebuggerCommandInitDebugger; virtual;
function CreateCommandStartDebugging(AContinueCommand: TGDBMIDebuggerCommand): TGDBMIDebuggerCommandStartDebugging; virtual;
function RequestCommand(const ACommand: TDBGCommand; const AParams: array of const): Boolean; override;
property CurrentCmdIsAsync: Boolean read FCurrentCmdIsAsync;
property CurrentCommand: TGDBMIDebuggerCommand read FCurrentCommand;
procedure ClearCommandQueue;
function GetIsIdle: Boolean; override;
procedure ResetStateToIdle; override;
procedure DoState(const OldState: TDBGState); override;
procedure DoBeforeState(const OldState: TDBGState); override;
function LineEndPos(const s: string; out LineEndLen: integer): integer; override;
procedure DoThreadChanged;
property TargetPID: Integer read FTargetInfo.TargetPID;
property TargetPtrSize: Byte read FTargetInfo.TargetPtrSize;
property TargetFlags: TGDBMITargetFlags read FTargetInfo.TargetFlags write FTargetInfo.TargetFlags;
property PauseWaitState: TGDBMIPauseWaitState read FPauseWaitState;
property DebuggerFlags: TGDBMIDebuggerFlags read FDebuggerFlags;
procedure DoRelease; override; // Destroy self (or schedule)
procedure DoUnknownException(Sender: TObject; AnException: Exception);
procedure DoNotifyAsync(Line: String);
procedure DoDbgBreakpointEvent(ABreakpoint: TDBGBreakPoint; Location: TDBGLocationRec;
AReason: TGDBMIBreakpointReason;
AOldVal: String = ''; ANewVal: String = '');
procedure AddThreadGroup(const S: String);
procedure RemoveThreadGroup(const {%H-}S: String);
function ParseLibraryLoaded(const S: String): String;
function ParseLibraryUnLoaded(const S: String): String;
function ParseThread(const S, EventText: String): String;
property CurrentStackFrame: Integer read FCurrentStackFrame;
property CurrentThreadId: Integer read FCurrentThreadId;
property CurrentStackFrameValid: Boolean read FCurrentStackFrameValid;
property CurrentThreadIdValid: Boolean read FCurrentThreadIdValid;
function CreateTypeRequestCache: TGDBPTypeRequestCache; virtual;
property TypeRequestCache: TGDBPTypeRequestCache read FTypeRequestCache;
public
class function CreateProperties: TDebuggerProperties; override; // Creates debuggerproperties
class function Caption: String; override;
class function ExePaths: String; override;
constructor Create(const AExternalDebugger: String); override;
destructor Destroy; override;
procedure Init; override; // Initializes external debugger
procedure Done; override; // Kills external debugger
function GetLocation: TDBGLocationRec; override;
function GetProcessList({%H-}AList: TRunningProcessInfoList): boolean; override;
//LockCommandProcessing is more than just QueueExecuteLock
//LockCommandProcessing also takes care to run the queue, if unlocked and not already running
procedure LockCommandProcessing; override;
procedure UnLockCommandProcessing; override;
property AsyncModeEnabled: Boolean read FAsyncModeEnabled;
// internal testing
procedure TestCmd(const ACommand: String); override;
function NeedReset: Boolean; override;
end;
{%region ***** TGDBMINameValueList and Parsers ***** }
{ TGDBMINameValueBasedList }
TGDBMINameValueBasedList = class
protected
FNameValueList: TGDBMINameValueList;
procedure PreParse; virtual; abstract;
public
constructor Create;
constructor Create(const AResultValues: String);
constructor Create(AResult: TGDBMIExecResult);
destructor Destroy; override;
procedure Init(AResultValues: string);
procedure Init(AResult: TGDBMIExecResult);
end;
{ TGDBMIMemoryDumpResultList }
TGDBMIMemoryDumpResultList = class(TGDBMINameValueBasedList)
private
FAddr: TDBGPtr;
function GetItem(Index: Integer): TPCharWithLen;
function GetItemNum(Index: Integer): Integer;
function GetItemTxt(Index: Integer): string;
protected
procedure PreParse; override;
public
// Expected input format: 1 row with hex values
function Count: Integer;
property Item[Index: Integer]: TPCharWithLen read GetItem;
property ItemTxt[Index: Integer]: string read GetItemTxt;
property ItemNum[Index: Integer]: Integer read GetItemNum;
property Addr: TDBGPtr read FAddr;
function AsText(AStartOffs, ACount: Integer; AAddrWidth: Integer): string;
end;
{%endregion *^^^* TGDBMINameValueList and Parsers *^^^* }
procedure Register;
implementation
var
DBGMI_QUEUE_DEBUG, DBGMI_STRUCT_PARSER, DBG_VERBOSE, DBG_WARNINGS,
DBG_DISASSEMBLER, DBG_THREAD_AND_FRAME: PLazLoggerLogGroup;
const
GDBMIBreakPointReasonNames: Array[TGDBMIBreakpointReason] of string =
('Breakpoint', 'Watchpoint', 'Watchpoint (scope)');
GDBMIExecCommandMap: array [TGDBMIExecCommandType] of string =
( '', // ectNone
'-exec-continue', // ectContinue,
'-exec-run', // ectRun,
'-exec-until', // ectRunTo, // [Source, Line]
'-exec-next', // ectStepOver,
'-exec-finish', // ectStepOut,
'-exec-step', // ectStepInto,
'-exec-next-instruction', // ectStepOverInstruction,
'-exec-step-instruction', // ectStepIntoInstruction,
'-exec-return' // ectReturn // (step out immediately, skip execution)
);
GDBMIExecCommandMapNoneMI: array [TGDBMIExecCommandType] of string =
( '', // ectNone
'continue', // ectContinue,
'run', // ectRun,
'until', // ectRunTo, // [Source, Line]
'next', // ectStepOver,
'finish', // ectStepOut,
'step', // ectStepInto,
'nexti', // ectStepOverInstruction,
'stepi', // ectStepIntoInstruction,
'return' // ectReturn // (step out immediately, skip execution)
);
type
THackDBGType = class(TGDBType) end;
const
// priorities for commands
GDCMD_PRIOR_IMMEDIATE = 999; // run immediate (request without callback)
GDCMD_PRIOR_LINE_INFO = 100; // Line info should run asap
GDCMD_PRIOR_DISASS = 30; // Run before watches
GDCMD_PRIOR_USER_ACT = 10; // set/change/remove brkpoint
GDCMD_PRIOR_THREAD = 5; // Run before watches, stack or locals
GDCMD_PRIOR_STACK = 2; // Run before watches
GDCMD_PRIOR_LOCALS = 1; // Run before watches (also registers etc)
type
{%region ***** Locals ***** }
{ TGDBMIDebuggerCommandLocals }
TGDBMIDebuggerCommandLocals = class(TGDBMIDebuggerCommand)
private
FLocals: TLocals;
protected
procedure DoLockQueueExecute; override;
procedure DoUnLockQueueExecute; override;
procedure DoLockQueueExecuteForInstr; override;
procedure DoUnLockQueueExecuteForInstr; override;
function DoExecute: Boolean; override;
public
constructor Create(AOwner: TGDBMIDebugger; ALocals: TLocals);
destructor Destroy; override;
function DebugText: String; override;
end;
{%endregion ^^^^^ Locals ^^^^^ }
{%region ***** LineSymbolInfo ***** }
{ TGDBMIDebuggerCommandLineSymbolInfo }
TGDBMIDebuggerCommandLineSymbolInfo = class(TGDBMIDebuggerCommand)
private
FResult: TGDBMIExecResult;
FSource: string;
protected
function DoExecute: Boolean; override;
public
constructor Create(AOwner: TGDBMIDebugger; Source: string);
function DebugText: String; override;
property Result: TGDBMIExecResult read FResult;
property Source: string read FSource;
end;
{ TGDBMILineInfo }
TGDBMILineInfo = class(TDBGLineInfo)
private
FSourceIndex: TStringList;
FRequestedSources: TStringList;
FSourceMaps: array of record
Source: String;
Map: TMap;
end;
FGetLineSymbolsCmdObj: TGDBMIDebuggerCommandLineSymbolInfo;
procedure DoGetLineSymbolsDestroyed(Sender: TObject);
procedure ClearSources;
procedure AddInfo(const ASource: String; const AResult: TGDBMIExecResult);
procedure DoGetLineSymbolsFinished(Sender: TObject);
protected
function GetSource(const AIndex: integer): String; override;
procedure DoStateChange(const {%H-}AOldState: TDBGState); override;
public
constructor Create(const ADebugger: TDebuggerIntf);
destructor Destroy; override;
function Count: Integer; override;
function GetAddress(const AIndex: Integer; const ALine: Integer): TDbgPtr; override;
function GetInfo({%H-}AAdress: TDbgPtr; out {%H-}ASource, {%H-}ALine, {%H-}AOffset: Integer): Boolean; override;
function IndexOf(const ASource: String): integer; override;
procedure Request(const ASource: String); override;
procedure Cancel(const ASource: String); override;
end;
{%endregion ^^^^^ LineSymbolInfo ^^^^^ }
{%region ***** BreakPoints ***** }
{ TGDBMIDebuggerCommandBreakPointBase }
TGDBMIDebuggerCommandBreakPointBase = class(TGDBMIDebuggerCommand)
protected
function ExecCheckLineInUnit(ASource: string; ALine: Integer): Boolean;
function ExecBreakDelete(ABreakId: Integer): Boolean;
function ExecBreakEnabled(ABreakId: Integer; AnEnabled: Boolean): Boolean;
function ExecBreakCondition(ABreakId: Integer; AnExpression: string): Boolean;
end;
{ TGDBMIDebuggerCommandBreakInsert }
TGDBMIDebuggerCommandBreakInsert = class(TGDBMIDebuggerCommandBreakPointBase)
private
FKind: TDBGBreakPointKind;
FAddress: TDBGPtr;
FSource: string;
FLine: Integer;
FEnabled: Boolean;
FExpression: string;
FReplaceId: Integer;
FAddr: TDBGPtr;
FBreakID: Integer;
FHitCnt: Integer;
FValid: Boolean;
FWatchData: String;
FWatchKind: TDBGWatchPointKind;
FWatchScope: TDBGWatchPointScope;
protected
function ExecBreakInsert(out ABreakId, AHitCnt: Integer; out AnAddr: TDBGPtr): Boolean;
function DoExecute: Boolean; override;
public
constructor Create(AOwner: TGDBMIDebugger; ASource: string; ALine: Integer;
AEnabled: Boolean; AnExpression: string; AReplaceId: Integer); overload;
constructor Create(AOwner: TGDBMIDebugger; AAddress: TDBGPtr;
AEnabled: Boolean; AnExpression: string; AReplaceId: Integer); overload;
constructor Create(AOwner: TGDBMIDebugger; AData: string; AScope: TDBGWatchPointScope;
AKind: TDBGWatchPointKind; AEnabled: Boolean; AnExpression: string; AReplaceId: Integer); overload;
function DebugText: String; override;
property Kind: TDBGBreakPointKind read FKind write FKind;
property Address: TDBGPtr read FAddress write FAddress;
property Source: string read FSource write FSource;
property Line: Integer read FLine write FLine;
property WatchData: String read FWatchData write FWatchData;
property WatchScope: TDBGWatchPointScope read FWatchScope write FWatchScope;
property WatchKind: TDBGWatchPointKind read FWatchKind write FWatchKind;
property Enabled: Boolean read FEnabled write FEnabled;
property Expression: string read FExpression write FExpression;
property ReplaceId: Integer read FReplaceId write FReplaceId;
// result values
property Addr: TDBGPtr read FAddr;
property BreakID: Integer read FBreakID;
property HitCnt: Integer read FHitCnt;
property Valid: Boolean read FValid;
end;
{ TGDBMIDebuggerCommandBreakRemove }
TGDBMIDebuggerCommandBreakRemove = class(TGDBMIDebuggerCommandBreakPointBase)
private
FBreakId: Integer;
protected
function DoExecute: Boolean; override;
public
constructor Create(AOwner: TGDBMIDebugger; ABreakId: Integer);
function DebugText: String; override;
end;
{ TGDBMIDebuggerCommandBreakUpdate }
TGDBMIDebuggerCommandBreakUpdate = class(TGDBMIDebuggerCommandBreakPointBase)
private
FBreakID: Integer;
FEnabled: Boolean;
FExpression: string;
FUpdateEnabled: Boolean;
FUpdateExpression: Boolean;
protected
function DoExecute: Boolean; override;
public
constructor Create(AOwner: TGDBMIDebugger; ABreakId: Integer);
constructor Create(AOwner: TGDBMIDebugger; ABreakId: Integer; AnEnabled: Boolean);
constructor Create(AOwner: TGDBMIDebugger; ABreakId: Integer; AnExpression: string);
constructor Create(AOwner: TGDBMIDebugger; ABreakId: Integer; AnEnabled: Boolean; AnExpression: string);
function DebugText: String; override;
property UpdateEnabled: Boolean read FUpdateEnabled write FUpdateEnabled;
property UpdateExpression: Boolean read FUpdateExpression write FUpdateExpression;
property Enabled: Boolean read FEnabled write FEnabled;
property Expression: string read FExpression write FExpression;
end;
{ TGDBMIBreakPoint ***** BreakPoints ***** }
TGDBMIBreakPointUpdateFlag = (bufSetBreakPoint, bufEnabled, bufCondition);
TGDBMIBreakPointUpdateFlags = set of TGDBMIBreakPointUpdateFlag;
TGDBMIBreakPoint = class(TDBGBreakPoint)
private
FParsedExpression: String;
FCurrentCmd: TGDBMIDebuggerCommandBreakPointBase;
FUpdateFlags: TGDBMIBreakPointUpdateFlags;
procedure SetBreakPoint;
procedure ReleaseBreakPoint;
procedure UpdateProperties(AFlags: TGDBMIBreakPointUpdateFlags);
procedure DoCommandDestroyed(Sender: TObject);
procedure DoCommandExecuted(Sender: TObject);
protected
FBreakID: Integer;
procedure DoEndUpdate; override;
procedure DoEnableChange; override;
procedure DoExpressionChange; override;
procedure DoStateChange(const AOldState: TDBGState); override;
procedure MakeInvalid;
procedure SetAddress(const AValue: TDBGPtr); override;
public
constructor Create(ACollection: TCollection); override;
destructor Destroy; override;
procedure DoLogExpression(const AnExpression: String); override;
procedure SetLocation(const ASource: String; const ALine: Integer); override;
procedure SetWatch(const AData: String; const AScope: TDBGWatchPointScope;
const AKind: TDBGWatchPointKind); override;
end;
{ TGDBMIBreakPoints }
TGDBMIBreakPoints = class(TDBGBreakPoints)
protected
function FindById(AnId: Integer): TGDBMIBreakPoint;
end;
{%endregion ^^^^^ BreakPoints ^^^^^ }
{%region ***** Register ***** }
TStringArray = Array of string;
TGDBMIRegisterSupplier = class;
{ TGDBMIDebuggerCommandRegisterUpdate }
TGDBMIDebuggerCommandRegisterUpdate = class(TGDBMIDebuggerCommand)
private
FRegisters: TRegisters;
FGDBMIRegSupplier: TGDBMIRegisterSupplier;
protected
function DoExecute: Boolean; override;
procedure DoCancel; override;
public
constructor Create(AOwner: TGDBMIDebugger; AGDBMIRegSupplier: TGDBMIRegisterSupplier; ARegisters: TRegisters);
destructor Destroy; override;
//function DebugText: String; override;
end;
{ TGDBMIRegisterSupplier }
TGDBMIRegisterSupplier = class(TRegisterSupplier)
private
FRegNamesCache: TStringArray;
protected
procedure DoStateChange(const AOldState: TDBGState); override;
public
procedure Changed;
procedure RequestData(ARegisters: TRegisters); override;
end;
{%endregion ^^^^^ Register ^^^^^ }
{%region ***** Watches ***** }
{ TGDBMIDebuggerCommandEvaluate }
TGDBMIDebuggerCommandEvaluate = class(TGDBMIDebuggerCommand)
private
FEvalFlags: TDBGEvaluateFlags;
FExpression: String;
FDisplayFormat: TWatchDisplayFormat;
FWatchValue: TWatchValue;
FTextValue: String;
FTypeInfo: TGDBType;
FValidity: TDebuggerDataState;
FTypeInfoAutoDestroy: Boolean;
FLockFlag: Boolean;
function GetTypeInfo: TGDBType;
procedure DoWatchFreed(Sender: TObject);
protected
procedure DoLockQueueExecute; override;
procedure DoUnLockQueueExecute; override;
procedure DoLockQueueExecuteForInstr; override;
procedure DoUnLockQueueExecuteForInstr; override;
function DoExecute: Boolean; override;
function SelectContext: Boolean;
procedure UnSelectContext;
public
constructor Create(AOwner: TGDBMIDebugger; AExpression: String; ADisplayFormat: TWatchDisplayFormat);
constructor Create(AOwner: TGDBMIDebugger; AWatchValue: TWatchValue);
destructor Destroy; override;
function DebugText: String; override;
property Expression: String read FExpression;
property EvalFlags: TDBGEvaluateFlags read FEvalFlags write FEvalFlags;
property DisplayFormat: TWatchDisplayFormat read FDisplayFormat;
property TextValue: String read FTextValue;
property TypeInfo: TGDBType read GetTypeInfo;
property TypeInfoAutoDestroy: Boolean read FTypeInfoAutoDestroy write FTypeInfoAutoDestroy;
end;
{%endregion ^^^^^ Watches ^^^^^ }
{%region ***** Stack ***** }
TGDBMINameValueListArray = array of TGDBMINameValueList;
{ TGDBMIDebuggerCommandStack }
TGDBMIDebuggerCommandStack = class(TGDBMIDebuggerCommand)
private
procedure DoCallstackFreed(Sender: TObject);
protected
FCallstack: TCallStackBase;
procedure DoLockQueueExecute; override;
procedure DoUnLockQueueExecute; override;
procedure DoLockQueueExecuteForInstr; override;
procedure DoUnLockQueueExecuteForInstr; override;
public
constructor Create(AOwner: TGDBMIDebugger; ACallstack: TCallStackBase);
destructor Destroy; override;
property Callstack: TCallStackBase read FCallstack;
end;
{ TGDBMIDebuggerCommandStackFrames }
TGDBMIDebuggerCommandStackFrames = class(TGDBMIDebuggerCommandStack)
protected
function DoExecute: Boolean; override;
end;
{ TGDBMIDebuggerCommandStackDepth }
TGDBMIDebuggerCommandStackDepth = class(TGDBMIDebuggerCommandStack)
private
FDepth: Integer;
FLimit: Integer;
protected
function DoExecute: Boolean; override;
public
constructor Create(AOwner: TGDBMIDebugger; ACallstack: TCallStackBase);
function DebugText: String; override;
property Depth: Integer read FDepth;
property Limit: Integer read FLimit write FLimit;
end;
{ TGDBMICallStack }
TGDBMICallStack = class(TCallStackSupplier)
private
FCommandList: TList;
FDepthEvalCmdObj: TGDBMIDebuggerCommandStackDepth;
FLimitSeen: Integer;
procedure DoDepthCommandExecuted(Sender: TObject);
//procedure DoFramesCommandExecuted(Sender: TObject);
procedure DoCommandDestroyed(Sender: TObject);
protected
procedure Clear;
procedure DoThreadChanged;
public
constructor Create(const ADebugger: TDebuggerIntf);
destructor Destroy; override;
procedure RequestCount(ACallstack: TCallStackBase); override;
procedure RequestAtLeastCount(ACallstack: TCallStackBase; ARequiredMinCount: Integer); override;
procedure RequestCurrent(ACallstack: TCallStackBase); override;
procedure RequestEntries(ACallstack: TCallStackBase); override;
procedure UpdateCurrentIndex; override;
end;
{%endregion ^^^^^ Stack ^^^^^ }
{%region ***** Disassembler ***** }
const
(* Some values to calculate how many bytes to disassemble for a given amount of lines
Those values are only guesses *)
// Max possible len of a statement in byte. Only used for up to 5 lines
DAssBytesPerCommandMax = 24;
// Maximum alignment between to procedures (for detecion of gaps, after dis-ass with source)
DAssBytesPerCommandAlign = 16;
type
{ TGDBMIDisassembleResultList }
TGDBMIDisassembleResultList = class(TGDBMINameValueBasedList)
private
FCount: Integer;
FHasSourceInfo: Boolean;
FItems: array of record
AsmEntry: TPCharWithLen;
SrcFile: TPCharWithLen;
SrcLine: TPCharWithLen;
ParsedInfo: TDisassemblerEntry;
end;
HasItemPointerList: Boolean;
ItemPointerList: Array of PDisassemblerEntry;
function GetItem(Index: Integer): PDisassemblerEntry;
function GetLastItem: PDisassemblerEntry;
procedure ParseItem(Index: Integer);
procedure SetCount(const AValue: Integer);
procedure SetItem(Index: Integer; const AValue: PDisassemblerEntry);
procedure SetLastItem(const AValue: PDisassemblerEntry);
protected
procedure PreParse; override;
public
property Count: Integer read FCount write SetCount;
property HasSourceInfo: Boolean read FHasSourceInfo;
property Item[Index: Integer]: PDisassemblerEntry read GetItem write SetItem;
property LastItem: PDisassemblerEntry read GetLastItem write SetLastItem;
function SortByAddress: Boolean;
public
// only valid as long a src object exists, and not modified
constructor CreateSubList(ASource: TGDBMIDisassembleResultList; AStartIdx, ACount: Integer);
procedure InitSubList(ASource: TGDBMIDisassembleResultList; AStartIdx, ACount: Integer);
end;
{ TGDBMIDisassembleResultFunctionIterator }
TGDBMIDisassembleResultFunctionIterator = class
private
FCurIdx: Integer;
FIndexOfLocateAddress: Integer;
FOffsetOfLocateAddress: Integer;
FIndexOfCounterAddress: Integer;
FList: TGDBMIDisassembleResultList;
FStartedAtIndex: Integer;
FStartIdx, FMaxIdx: Integer;
FLastSubListEndAddr: TDBGPtr;
FAddressToLocate, FAddForLineAfterCounter: TDBGPtr;
FSublistNumber: Integer;
public
constructor Create(AList: TGDBMIDisassembleResultList; AStartIdx: Integer;
ALastSubListEndAddr: TDBGPtr;
AnAddressToLocate, AnAddForLineAfterCounter: TDBGPtr);
function EOL: Boolean;
function NextSubList(var AResultList: TGDBMIDisassembleResultList): Boolean;
// Current SubList
function IsFirstSubList: Boolean;
function CurrentFixedAddr(AOffsLimit: Integer): TDBGPtr; // Addr[0] - Offs[0]
// About the next SubList
function NextStartAddr: TDBGPtr;
function NextStartOffs: Integer;
// Overall
function CountLinesAfterCounterAddr: Integer; // count up to Start of Current SubList
property CurrentIndex: Integer read FCurIdx;
property NextIndex: Integer read FStartIdx;
property SublistNumber: Integer read FSublistNumber; // running count of sublists found
property StartedAtIndex: Integer read FStartedAtIndex;
property IndexOfLocateAddress: Integer read FIndexOfLocateAddress;
property OffsetOfLocateAddress: Integer read FOffsetOfLocateAddress;
property IndexOfCounterAddress: Integer read FIndexOfCounterAddress;
property List: TGDBMIDisassembleResultList read FList;
end;
{ TGDBMIDebuggerCommandDisassemble }
TGDBMIDisAssAddrRange = record
FirstAddr, LastAddr: TDBGPtr;
end;
TGDBMIDebuggerCommandDisassemble = class(TGDBMIDebuggerCommand)
private
FEndAddr: TDbgPtr;
FLinesAfter: Integer;
FLinesBefore: Integer;
FOnProgress: TNotifyEvent;
FStartAddr: TDbgPtr;
FKnownRanges: TDBGDisassemblerEntryMap;
FRangeIterator: TDBGDisassemblerEntryMapIterator;
FMemDumpsNeeded: array of TGDBMIDisAssAddrRange;
procedure DoProgress;
{$ifndef disassemblernestedproc}
function AdjustToKnowFunctionStart(var AStartAddr: TDisassemblerAddress): Boolean;
function DoDisassembleRange(AnEntryRanges: TDBGDisassemblerEntryMap; AFirstAddr, ALastAddr: TDisassemblerAddress; StopAfterAddress: TDBGPtr; StopAfterNumLines: Integer): Boolean;
function ExecDisassmble(AStartAddr, AnEndAddr: TDbgPtr; WithSrc: Boolean;
AResultList: TGDBMIDisassembleResultList = nil;
ACutBeforeEndAddr: Boolean = False): TGDBMIDisassembleResultList;
function OnCheckCancel: boolean;
{$endif}
protected
function DoExecute: Boolean; override;
public
constructor Create(AOwner: TGDBMIDebugger; AKnownRanges: TDBGDisassemblerEntryMap;
AStartAddr, AEndAddr: TDbgPtr; ALinesBefore, ALinesAfter: Integer);
destructor Destroy; override;
function DebugText: String; override;
property StartAddr: TDbgPtr read FStartAddr write FStartAddr;
property EndAddr: TDbgPtr read FEndAddr write FEndAddr;
property LinesBefore: Integer read FLinesBefore write FLinesBefore;
property LinesAfter: Integer read FLinesAfter write FLinesAfter;
property OnProgress: TNotifyEvent read FOnProgress write FOnProgress;
end;
TGDBMIDisassembler = class(TDBGDisassembler)
private
FDisassembleEvalCmdObj: TGDBMIDebuggerCommandDisassemble;
FLastExecAddr, FCancelledAddr: TDBGPtr;
FIsCancelled: Boolean;
procedure DoDisassembleExecuted(Sender: TObject);
procedure DoDisassembleProgress(Sender: TObject);
procedure DoDisassembleDestroyed(Sender: TObject);
protected
function PrepareEntries(AnAddr: TDbgPtr; ALinesBefore, ALinesAfter: Integer): Boolean; override;
function HandleRangeWithInvalidAddr(ARange: TDBGDisassemblerEntryRange;AnAddr:
TDbgPtr; var ALinesBefore, ALinesAfter: Integer): boolean; override;
public
procedure Clear; override;
function PrepareRange(AnAddr: TDbgPtr; ALinesBefore, ALinesAfter: Integer): Boolean; override;
end;
{%endregion ^^^^^ Disassembler ^^^^^ }
{%region ***** Threads ***** }
{ TGDBMIDebuggerCommandThreads }
TGDBMIDebuggerCommandThreads = class(TGDBMIDebuggerCommand)
private
FCurrentThreadId: Integer;
FCurrentThreads: TThreads;
FSuccess: Boolean;
FThreads: Array of TThreadEntry;
function GetThread(AnIndex: Integer): TThreadEntry;
protected
function DoExecute: Boolean; override;
public
constructor Create(AOwner: TGDBMIDebugger);
destructor Destroy; override;
//function DebugText: String; override;
function Count: Integer;
property Threads[AnIndex: Integer]: TThreadEntry read GetThread;
property CurrentThreadId: Integer read FCurrentThreadId;
property Success: Boolean read FSuccess;
property CurrentThreads: TThreads read FCurrentThreads write FCurrentThreads;
end;
{ TGDBMIThreads }
TGDBMIThreads = class(TThreadsSupplier)
private
FGetThreadsCmdObj: TGDBMIDebuggerCommandThreads;
function GetDebugger: TGDBMIDebugger;
procedure ThreadsNeeded;
procedure CancelEvaluation;
procedure DoThreadsDestroyed(Sender: TObject);
procedure DoThreadsFinished(Sender: TObject);
protected
property Debugger: TGDBMIDebugger read GetDebugger;
procedure DoCleanAfterPause; override;
public
destructor Destroy; override;
procedure RequestMasterData; override;
procedure ChangeCurrentThread(ANewId: Integer); override;
end;
{%endregion ^^^^^ Threads ^^^^^ }
{ TGDBStringIterator }
TGDBStringIterator=class
protected
FDataSize: Integer;
FReadPointer: Integer;
FParsableData: String;
public
constructor Create(const AParsableData: String);
function ParseNext(out ADecomposable: Boolean; out APayload: String; out ACharStopper: Char): Boolean;
end;
TGDBMIExceptionInfo = record
ObjAddr: String;
Name: String;
end;
{ =========================================================================== }
{ Some win32 stuff }
{ =========================================================================== }
{$IFdef MSWindows}
var
DebugBreakAddr: Pointer = nil;
// use our own version. Win9x doesn't support this, so it is a nice check
_CreateRemoteThread: function(hProcess: THandle; lpThreadAttributes: Pointer; dwStackSize: DWORD; lpStartAddress: TFNThreadStartRoutine; lpParameter: Pointer; dwCreationFlags: DWORD; var lpThreadId: DWORD): THandle; stdcall = nil;
procedure InitWin32;
var
hMod: THandle;
begin
// Check if we already are initialized
if DebugBreakAddr <> nil then Exit;
// normally you would load a lib, but since kernel32 is
// always loaded we can use this (and we don't have to free it
hMod := GetModuleHandle(kernel32);
if hMod = 0 then Exit; //????
DebugBreakAddr := GetProcAddress(hMod, 'DebugBreak');
Pointer(_CreateRemoteThread) := GetProcAddress(hMod, 'CreateRemoteThread');
end;
{$ENDIF}
{ =========================================================================== }
{ Helpers }
{ =========================================================================== }
function CpuNameToPtrSize(const CpuName: String): Integer;
begin
//'x86', 'i386', 'i486', 'i586', 'i686',
//'ia64', 'x86_64', 'powerpc', aarch64
//'sparc', 'arm'
Result := 4;
if (LowerCase(CpuName) = 'ia64') or (LowerCase(CpuName) = 'x86_64') or (LowerCase(CpuName) = 'aarch64')
then Result := 8;
end;
{ TGDBMIDebuggerCommandRegisterUpdate }
function TGDBMIDebuggerCommandRegisterUpdate.DoExecute: Boolean;
procedure UpdateFormat(AFormat: TRegisterDisplayFormat);
const
// rdDefault, rdHex, rdBinary, rdOctal, rdDecimal, rdRaw
FormatChar : array [TRegisterDisplayFormat] of string =
('N', 'x', 't', 'o', 'd', 'r');
var
i, idx: Integer;
Num: QWord;
List, ValList: TGDBMINameValueList;
Item: PGDBMINameValue;
RegVal: TRegisterValue;
RegValObj: TRegisterDisplayValue;
t: String;
NumErr: word;
R: TGDBMIExecResult;
begin
if (not ExecuteCommand('-data-list-register-values %s', [FormatChar[AFormat]], R)) or
(R.State = dsError)
then begin
for i := 0 to FRegisters.Count - 1 do
if FRegisters[i].DataValidity in [ddsRequested, ddsEvaluating] then
FRegisters[i].DataValidity := ddsInvalid;
Exit;
end;
ValList := TGDBMINameValueList.Create('');
List := TGDBMINameValueList.Create(R, ['register-values']);
for i := 0 to List.Count - 1 do
begin
Item := List.Items[i];
ValList.Init(Item^.Name);
idx := StrToIntDef(Unquote(ValList.Values['number']), -1);
if (idx < 0) or (idx > High(FGDBMIRegSupplier.FRegNamesCache)) then Continue;
RegVal := FRegisters.EntriesByName[FGDBMIRegSupplier.FRegNamesCache[idx]];
if (RegVal.DataValidity = ddsValid) and (RegVal.HasValueFormat[AFormat]) then continue;
t := Unquote(ValList.Values['value']);
RegValObj := RegVal.ValueObjFormat[AFormat];
if (AFormat in [rdDefault, rdRaw]) or (RegValObj.SupportedDispFormats = [AFormat]) then
RegValObj.SetAsText(t);
Val(t, Num, NumErr);
if NumErr <> 0 then
RegValObj.SetAsText(t)
else
begin
RegValObj.SetAsNum(Num, FTheDebugger.TargetPtrSize);
RegValObj.AddFormats([rdBinary, rdDecimal, rdOctal, rdHex]);
end;
if AFormat = RegVal.DisplayFormat then
RegVal.DataValidity := ddsValid;
end;
FreeAndNil(List);
FreeAndNil(ValList);
end;
var
R: TGDBMIExecResult;
List: TGDBMINameValueList;
i, idx: Integer;
ChangedRegList: TGDBMINameValueList;
begin
Result := True;
if FRegisters.DataValidity = ddsEvaluating then // in process
exit;
FContext.ThreadContext := ccUseLocal;
FContext.StackContext := ccUseLocal;
FContext.ThreadId := FRegisters.ThreadId;
FContext.StackFrame := FRegisters.StackFrame;
FGDBMIRegSupplier.BeginUpdate;
try
if length(FGDBMIRegSupplier.FRegNamesCache) = 0 then begin
if (not ExecuteCommand('-data-list-register-names', R, [cfNoThreadContext, cfNoStackContext])) or
(R.State = dsError)
then begin
if FRegisters.DataValidity in [ddsRequested, ddsEvaluating] then
FRegisters.DataValidity := ddsInvalid;
exit;
end;
List := TGDBMINameValueList.Create(R, ['register-names']);
SetLength(FGDBMIRegSupplier.FRegNamesCache, List.Count);
for i := 0 to List.Count - 1 do
FGDBMIRegSupplier.FRegNamesCache[i] := UnQuote(List.GetString(i));
FreeAndNil(List);
end;
if FRegisters.DataValidity = ddsRequested then begin
ChangedRegList := nil;
if (FRegisters.StackFrame = 0) and // need modified, run before all others
ExecuteCommand('-data-list-changed-registers', R, [cfscIgnoreError]) and
(R.State <> dsError)
then
ChangedRegList := TGDBMINameValueList.Create(R, ['changed-registers']);
// Need all registers
FRegisters.DataValidity := ddsEvaluating;
UpdateFormat(rdDefault);
FRegisters.DataValidity := ddsValid;
if ChangedRegList <> nil then begin
for i := 0 to ChangedRegList.Count - 1 do begin
idx := StrToIntDef(Unquote(ChangedRegList.GetString(i)), -1);
if (idx < 0) or (idx > High(FGDBMIRegSupplier.FRegNamesCache)) then Continue;
FRegisters.EntriesByName[FGDBMIRegSupplier.FRegNamesCache[idx]].Modified := True;
end;
FreeAndNil(ChangedRegList);
end;
end;
// check for individual updates / displayformat
for i := 0 to FRegisters.Count - 1 do begin
if not FRegisters[i].HasValue then
UpdateFormat(FRegisters[i].DisplayFormat);
end;
finally
FGDBMIRegSupplier.EndUpdate;
end;
end;
procedure TGDBMIDebuggerCommandRegisterUpdate.DoCancel;
begin
if FRegisters.DataValidity in [ddsRequested, ddsEvaluating] then
FRegisters.DataValidity := ddsInvalid;
inherited DoCancel;
end;
constructor TGDBMIDebuggerCommandRegisterUpdate.Create(AOwner: TGDBMIDebugger;
AGDBMIRegSupplier: TGDBMIRegisterSupplier; ARegisters: TRegisters);
begin
inherited Create(AOwner);
FGDBMIRegSupplier := AGDBMIRegSupplier;
FRegisters := ARegisters;
FRegisters.AddReference;
end;
destructor TGDBMIDebuggerCommandRegisterUpdate.Destroy;
begin
inherited Destroy;
FRegisters.ReleaseReference;
end;
{ TGDBMIRegisterSupplier }
procedure TGDBMIRegisterSupplier.DoStateChange(const AOldState: TDBGState);
begin
if not( (AOldState in [dsPause, dsInternalPause]) and (Debugger.State in [dsPause, dsInternalPause]) )
then
SetLength(FRegNamesCache, 0);
inherited DoStateChange(AOldState);
end;
procedure TGDBMIRegisterSupplier.Changed;
begin
if CurrentRegistersList <> nil
then CurrentRegistersList.Clear;
end;
procedure TGDBMIRegisterSupplier.RequestData(ARegisters: TRegisters);
var
ForceQueue: Boolean;
Cmd: TGDBMIDebuggerCommandRegisterUpdate;
begin
if (Debugger = nil) or not(Debugger.State in [dsPause, dsStop]) then
exit;
Cmd := TGDBMIDebuggerCommandRegisterUpdate.Create(TGDBMIDebugger(Debugger), Self, ARegisters);
//Cmd.OnExecuted := @DoGetRegisterNamesFinished;
//Cmd.OnDestroy := @DoGetRegisterNamesDestroyed;
Cmd.Priority := GDCMD_PRIOR_LOCALS;
Cmd.Properties := [dcpCancelOnRun];
ForceQueue := (TGDBMIDebugger(Debugger).FCurrentCommand <> nil)
and (TGDBMIDebugger(Debugger).FCurrentCommand is TGDBMIDebuggerCommandExecute)
and (not TGDBMIDebuggerCommandExecute(TGDBMIDebugger(Debugger).FCurrentCommand).NextExecQueued)
and (Debugger.State <> dsInternalPause);
TGDBMIDebugger(Debugger).QueueCommand(Cmd, ForceQueue);
end;
{ TGDBMIDebuggerChangeFilenameBase }
function TGDBMIDebuggerChangeFilenameBase.DoChangeFilename: Boolean;
var
R: TGDBMIExecResult;
List: TGDBMINameValueList;
S: String;
begin
Result := False;
FContext.ThreadContext := ccNotRequired;
FContext.StackContext := ccNotRequired;
//Cleanup our own breakpoints
FTheDebugger.FExceptionBreak.Clear(Self);
FTheDebugger.FBreakErrorBreak.Clear(Self);
FTheDebugger.FRunErrorBreak.Clear(Self);
FTheDebugger.FPopExceptStack.Clear(Self);
FTheDebugger.FCatchesBreak.Clear(Self);
FTheDebugger.FReRaiseBreak.Clear(Self);
if DebuggerState = dsError then Exit;
S := FTheDebugger.ConvertToGDBPath(FTheDebugger.FileName, cgptExeName);
Result := ExecuteCommand('-file-exec-and-symbols %s', [S], R);
if not Result then exit;
{$IFDEF darwin}
if (R.State = dsError) and (FTheDebugger.FileName <> '')
then begin
S := FTheDebugger.FileName + '/Contents/MacOS/' + ExtractFileNameOnly(FTheDebugger.FileName);
S := FTheDebugger.ConvertToGDBPath(S, cgptExeName);
Result := ExecuteCommand('-file-exec-and-symbols %s', [S], R);
if not Result then exit;
end;
{$ENDIF}
if (R.State = dsError) and (FTheDebugger.FileName <> '')
then begin
List := TGDBMINameValueList.Create(R);
FErrorMsg := DeleteEscapeChars((List.Values['msg']));
List.Free;
Result := False;
Exit;
end;
end;
function TGDBMIDebuggerChangeFilenameBase.DoSetPascal: Boolean;
begin
Result := True;
FContext.ThreadContext := ccNotRequired;
FContext.StackContext := ccNotRequired;
// Force setting language
// Setting extensions dumps GDB (bug #508)
Result := ExecuteCommand('-gdb-set language pascal', [], [cfCheckError]);
Result := Result and (DebuggerState <> dsError);
(*
ExecuteCommand('-gdb-set extension-language .lpr pascal', False);
if not FHasSymbols then Exit; // file-exec-and-symbols not allways result in no symbols
ExecuteCommand('-gdb-set extension-language .lrs pascal', False);
ExecuteCommand('-gdb-set extension-language .dpr pascal', False);
ExecuteCommand('-gdb-set extension-language .pas pascal', False);
ExecuteCommand('-gdb-set extension-language .pp pascal', False);
ExecuteCommand('-gdb-set extension-language .inc pascal', False);
*)
end;
{ TGDBMIDbgInstructionQueue }
procedure TGDBMIDbgInstructionQueue.HandleGdbDataBeforeInstruction(var AData: String;
var SkipData: Boolean; const TheInstruction: TGDBInstruction);
procedure DoConsoleStream(Line: String);
begin
// check for symbol info
if Pos('no debugging symbols', Line) > 0
then begin
Debugger.TargetFlags := Debugger.TargetFlags - [tfHasSymbols];
Debugger.DoDbgEvent(ecDebugger, etDefault, Format(gdbmiEventLogNoSymbols, [Debugger.FileName]));
end;
end;
procedure DoLogStream(const Line: String);
begin
// check for symbol info
if Pos('No symbol table is loaded. Use the \"file\" command.', Line) > 0
then begin
Debugger.TargetFlags := Debugger.TargetFlags - [tfHasSymbols];
Debugger.DoDbgEvent(ecDebugger, etDefault,
Format(gdbmiEventLogNoSymbols, [Debugger.FileName]));
end;
// check internal error
if (Pos('internal-error:', LowerCase(Line)) > 0) or
(Pos('internal to gdb has been detected', LowerCase(Line)) > 0) or
(Pos('further debugging may prove unreliable', LowerCase(Line)) > 0)
then begin
Debugger.DoDbgEvent(ecDebugger, etDefault,
Format(gdbmiEventLogGDBInternalError, [AData]));
if TGDBMIDebuggerProperties(Debugger.GetProperties).WarnOnInternalError
then begin
if Debugger.OnFeedback(Debugger,
Format(gdbmiGDBInternalError, [LineEnding]),
Format(gdbmiGDBInternalErrorInfo, [LineEnding, Line, TheInstruction.DebugText]),
ftWarning, [frOk, frStop]
) = frStop
then begin
try
Debugger.CancelAllQueued;
finally
Debugger.FNeedReset := True;
Debugger.Stop;
end;
end;
end;
end;
end;
begin
if AData <> ''
then case AData[1] of
'~': DoConsoleStream(AData);
//'@': DoTargetStream(AData);
'&': DoLogStream(AData);
//'*': DoExecAsync(AData);
//'+': DoStatusAsync(AData);
//'=': DoMsgAsync(AData);
end;
inherited HandleGdbDataBeforeInstruction(AData, SkipData, TheInstruction);
end;
function TGDBMIDbgInstructionQueue.Debugger: TGDBMIDebugger;
begin
Result := TGDBMIDebugger(inherited Debugger);
end;
{ TGDBMIDebuggerInstruction }
function TGDBMIDebuggerInstruction.ProcessInputFromGdb(const AData: String): Boolean;
function DoResultRecord(Line: String; CurRes: Boolean): Boolean;
var
ResultClass: String;
OldResult: Boolean;
begin
ResultClass := GetPart('^', ',', Line);
if Line = ''
then begin
if FResultData.Values <> ''
then Include(FResultData.Flags, rfNoMI);
end
else begin
FResultData.Values := Line;
end;
OldResult := CurRes;
Result := True;
case StringCase(ResultClass, ['done', 'running', 'exit', 'error', 'stopped']) of
0: begin // done
end;
1: begin // running
FResultData.State := dsRun;
end;
2: begin // exit
FResultData.State := dsIdle;
end;
3: begin // error
DebugLn(DBG_WARNINGS, 'TGDBMIDebugger.ProcessResult Error: ', Line);
// todo: implement with values
if (pos('msg=', Line) > 0)
and (pos('not being run', Line) > 0)
then FResultData.State := dsStop
else FResultData.State := dsError;
end;
4: begin
FCmd.FGotStopped := True;
//AStoppedParams := Line;
end;
else
//TODO: should that better be dsError ?
if OldResult and (FResultData.State in [dsError, dsStop]) and
(copy(ResultClass,1,6) = 'error"')
then begin
// Gdb 6.3.5 on Mac, does sometime return a 2nd mis-formatted error line
// The line seems truncated, it simply is (note the misplaced quote): ^error"
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: Unknown result class (IGNORING): ', ResultClass);
end
else begin
Result := False;
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: Unknown result class: ', ResultClass);
end;
end;
end;
procedure DoConsoleStream(Line: String);
var
len: Integer;
begin
// Strip surrounding ~" "
len := Length(Line) - 3;
if len < 0 then Exit;
Line := Copy(Line, 3, len);
// strip trailing \n (unless it is escaped \\n)
if (len >= 2) and (Line[len - 1] = '\') and (Line[len] = 'n')
then begin
if len = 2
then Line := LineEnding
else if Line[len - 2] <> '\'
then begin
SetLength(Line, len - 2);
Line := Line + LineEnding;
end;
end;
FResultData.Values := FResultData.Values + Line;
end;
procedure DoTargetStream(const Line: String);
begin
DebugLn(DBG_VERBOSE, '[Debugger] Target output: ', Line);
end;
procedure DoLogStream(const Line: String);
//const
// LogWarning = '&"warning:"';
begin
DebugLn(DBG_VERBOSE, '[Debugger] Log output: ', Line);
if Line = '&"kill\n"'
then FResultData.State := dsStop
else if LeftStr(Line, 8) = '&"Error '
then FResultData.State := dsError;
if LowerCase(copy(Line, 1, length(FLogWarnings))) = FLogWarnings
then FInLogWarning := True;
if FInLogWarning
then FLogWarnings := FLogWarnings + copy(Line, 3, length(Line)-5) + LineEnding;
if Line = '&"\n"' then
FInLogWarning := False;
end;
procedure DoExecAsync(Line: String);
var
S: String;
ct: TThreads;
i: Integer;
t: TThreadEntry;
begin
S := GetPart(['*'], [','], Line);
if S = 'running'
then begin
if (FCmd.FTheDebugger.Threads.CurrentThreads <> nil)
then begin
ct := FCmd.FTheDebugger.Threads.CurrentThreads;
S := GetPart('thread-id="', '"', Line);
if s = 'all' then begin
for i := 0 to ct.Count - 1 do
ct[i].ThreadState := 'running'; // TODO enum?
end
else begin
S := S + ',';
while s <> '' do begin
i := StrToIntDef(GetPart('', ',', s), -1);
if (s <> '') and (s[1] = ',') then delete(s, 1, 1)
else begin
debugln(DBG_WARNINGS, 'GDBMI: Error parsing threads');
break
end;
if i < 0 then Continue;
t := ct.EntryById[i];
if t <> nil then
t.ThreadState := 'running'; // TODO enum?
end;
end;
FCmd.FTheDebugger.Threads.Changed;
end;
FCmd.DoDbgEvent(ecProcess, etProcessStart,
Format(gdbmiEventLogProcessStart, [FCmd.FTheDebugger.FileName]));
end
else
if S = 'stopped' then begin
FCmd.FGotStopped := True;
// StoppedParam ??
end
else
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: Unexpected async-record: ', Line);
end;
procedure DoMsgAsync(Line: String);
var
S: String;
i, x: Integer;
ct: TThreads;
t: TThreadEntry;
begin
S := GetPart('=', ',', Line, False, False);
x := StringCase(S, ['thread-created', 'thread-exited', 'thread-group-started']);
case x of // thread-group-exited // thread-group-added,id="i1"
0,1: begin
i := StrToIntDef(GetPart(',id="', '"', Line, False, False), -1);
if (i > 0) and (FCmd.FTheDebugger.Threads.CurrentThreads <> nil)
then begin
ct := FCmd.FTheDebugger.Threads.CurrentThreads;
t := ct.EntryById[i];
case x of
0: begin
if t = nil then begin
t := ct.CreateEntry(0, nil, '', '', '', 0, i, '', 'unknown');
ct.Add(t);
t.Free;
end
else
debugln(DBG_WARNINGS, 'GDBMI: Duplicate thread');
end;
1: begin
if t <> nil then begin
ct.Remove(t);
end
else
debugln(DBG_WARNINGS, 'GDBMI: Missing thread');
end;
end;
FCmd.FTheDebugger.Threads.Changed;
end;
end;
2: begin // thread-group-started // needed in RunToMain
// Todo, store in seperate field
if FCmd is TGDBMIDebuggerCommandStartDebugging then
FLogWarnings := FLogWarnings + Line + LineEnding;
end;
end;
FCmd.FTheDebugger.DoNotifyAsync(Line);
end;
procedure DoStatusAsync(const Line: String);
begin
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: Unexpected async-record: ', Line);
end;
begin
Result := True;
FFullCmdReply := FFullCmdReply + AData + LineEnding;
if AData = '(gdb) ' then begin
MarkAsSuccess;
exit;
end;
//if (AData = '^exit') and (FCmd = '-gdb-exit') then begin
// // no (gdb) expected
// MarkAsSuccess;
//end;
if AData <> '' then begin
if AData[1] <> '&' then
FInLogWarning := False;
case AData[1] of
'^': FHasResult := DoResultRecord(AData, Result);
'~': DoConsoleStream(AData);
'@': DoTargetStream(AData);
'&': DoLogStream(AData);
'*': DoExecAsync(AData);
'+': DoStatusAsync(AData);
'=': DoMsgAsync(AData);
else
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: Unknown record: ', AData);
end;
end;
{$IFDEF VerboseIDEToDo}{$message warning condition should also check end-of-file reached for process output stream}{$ENDIF}
end;
procedure TGDBMIDebuggerInstruction.HandleNoGdbRunning;
begin
if FHasResult and (Command = '-gdb-exit') then begin
// no (gdb) expected
MarkAsSuccess;
end
else
inherited HandleNoGdbRunning;
end;
procedure TGDBMIDebuggerInstruction.HandleReadError;
begin
if FHasResult and (Command = '-gdb-exit') then begin
// no (gdb) expected
MarkAsSuccess;
end
else
inherited HandleReadError;
end;
procedure TGDBMIDebuggerInstruction.HandleTimeOut;
begin
if FHasResult and (Command = '-gdb-exit') then begin
// no (gdb) expected
MarkAsSuccess;
end
else
inherited HandleTimeOut;
end;
function TGDBMIDebuggerInstruction.GetTimeOutVerifier: TGDBInstruction;
begin
if FHasResult and (Command = '-gdb-exit') then
Result := nil
else
Result := inherited GetTimeOutVerifier;
end;
procedure TGDBMIDebuggerInstruction.Init;
begin
inherited Init;
FHasResult := False;
FResultData.Values := '';
FResultData.Flags := [];
FResultData.State := dsNone;
FFullCmdReply := '';
FLogWarnings := '';
FInLogWarning := False;
end;
{ TGDBMIDebuggerCommandStartBase }
procedure TGDBMIDebuggerCommandStartBase.SetTargetInfo(const AFileType: String);
var
FoundPtrSize, UseWin64ABI: Boolean;
begin
UseWin64ABI := False;
// assume some defaults
TargetInfo^.TargetPtrSize := GetIntValue('sizeof(%s)', [PointerTypeCast]);
FoundPtrSize := (FLastExecResult.State <> dsError) and (TargetInfo^.TargetPtrSize > 0);
if not FoundPtrSize
then TargetInfo^.TargetPtrSize := 4;
TargetInfo^.TargetIsBE := False;
case StringCase(AFileType, [
'efi-app-ia32', 'elf32-i386', 'pei-i386', 'elf32-i386-freebsd',
'elf64-x86-64', 'pei-x86-64',
'mach-o-be',
'mach-o-le',
'pei-arm-little',
'pei-arm-big',
'elf64-littleaarch64',
'elf64-bigaarch64'
], True, False) of
0..3: TargetInfo^.TargetCPU := 'x86';
4: TargetInfo^.TargetCPU := 'x86_64'; //TODO: should we check, PtrSize must be 8, but what if not?
5: begin
TargetInfo^.TargetCPU := 'x86_64'; //TODO: should we check, PtrSize must be 8, but what if not?
UseWin64ABI := True;
end;
6: begin
//mach-o-be
TargetInfo^.TargetIsBE := True;
if FTheDebugger.FGDBCPU <> ''
then TargetInfo^.TargetCPU := FTheDebugger.FGDBCPU
else TargetInfo^.TargetCPU := 'powerpc'; // guess
end;
7: begin
//mach-o-le
if FoundPtrSize then begin
if FTheDebugger.FGDBPtrSize = TargetInfo^.TargetPtrSize
then TargetInfo^.TargetCPU := FTheDebugger.FGDBCPU
else // guess
case TargetInfo^.TargetPtrSize of
4: TargetInfo^.TargetCPU := 'x86'; // guess
8: TargetInfo^.TargetCPU := 'x86_64'; // guess
else TargetInfo^.TargetCPU := 'x86'; // guess
end
end
else begin
if FTheDebugger.FGDBCPU <> ''
then TargetInfo^.TargetCPU := FTheDebugger.FGDBCPU
else TargetInfo^.TargetCPU := 'x86'; // guess
end;
end;
8: begin
TargetInfo^.TargetCPU := 'arm';
end;
9: begin
TargetInfo^.TargetIsBE := True;
TargetInfo^.TargetCPU := 'arm';
end;
10: begin
TargetInfo^.TargetCPU := 'aarch64';
end;
11: begin
TargetInfo^.TargetIsBE := True;
TargetInfo^.TargetCPU := 'aarch64';
end;
else
// Unknown filetype, use GDB cpu
DebugLn(DBG_WARNINGS, '[WARNING] [Debugger.TargetInfo] Unknown FileType: %s, using GDB cpu', [AFileType]);
TargetInfo^.TargetCPU := FTheDebugger.FGDBCPU;
// Todo: check PtrSize and downgrade 64 bit cpu to 32 bit cpu, if required
end;
if not FoundPtrSize
then TargetInfo^.TargetPtrSize := CpuNameToPtrSize(TargetInfo^.TargetCPU);
case StringCase(TargetInfo^.TargetCPU, [
'x86', 'i386', 'i486', 'i586', 'i686',
'ia64', 'x86_64', 'powerpc',
'sparc', 'arm', 'aarch64'
], True, False) of
0..4: begin // x86
TargetInfo^.TargetRegisters[0] := '$eax';
TargetInfo^.TargetRegisters[1] := '$edx';
TargetInfo^.TargetRegisters[2] := '$ecx';
end;
5, 6: begin // ia64, x86_64
if TargetInfo^.TargetPtrSize = 4
then begin
TargetInfo^.TargetRegisters[0] := '$eax';
TargetInfo^.TargetRegisters[1] := '$edx';
TargetInfo^.TargetRegisters[2] := '$ecx';
end
else if UseWin64ABI
then begin
TargetInfo^.TargetRegisters[0] := '$rcx';
TargetInfo^.TargetRegisters[1] := '$rdx';
TargetInfo^.TargetRegisters[2] := '$r8';
end else
begin
TargetInfo^.TargetRegisters[0] := '$rdi';
TargetInfo^.TargetRegisters[1] := '$rsi';
TargetInfo^.TargetRegisters[2] := '$rdx';
end;
end;
7: begin // powerpc
TargetInfo^.TargetIsBE := True;
// alltough darwin can start with r2, it seems that all OS start with r3
// if UpperCase(FTargetInfo.TargetOS) = 'DARWIN'
// then begin
// FTargetInfo.TargetRegisters[0] := '$r2';
// FTargetInfo.TargetRegisters[1] := '$r3';
// FTargetInfo.TargetRegisters[2] := '$r4';
// end
// else begin
TargetInfo^.TargetRegisters[0] := '$r3';
TargetInfo^.TargetRegisters[1] := '$r4';
TargetInfo^.TargetRegisters[2] := '$r5';
// end;
end;
8: begin // sparc
TargetInfo^.TargetIsBE := True;
TargetInfo^.TargetRegisters[0] := '$g1';
TargetInfo^.TargetRegisters[1] := '$o0';
TargetInfo^.TargetRegisters[2] := '$o1';
end;
9: begin // arm
TargetInfo^.TargetRegisters[0] := '$r0';
TargetInfo^.TargetRegisters[1] := '$r1';
TargetInfo^.TargetRegisters[2] := '$r2';
end;
10: begin // aarch64
//TargetInfo^.TargetRegisters[0] := '$r0';
//TargetInfo^.TargetRegisters[1] := '$r1';
//TargetInfo^.TargetRegisters[2] := '$r2';
TargetInfo^.TargetRegisters[0] := '$x0';
TargetInfo^.TargetRegisters[1] := '$x1';
TargetInfo^.TargetRegisters[2] := '$x2';
end;
else
TargetInfo^.TargetRegisters[0] := '';
TargetInfo^.TargetRegisters[1] := '';
TargetInfo^.TargetRegisters[2] := '';
DebugLn(DBG_WARNINGS, '[WARNING] [Debugger] Unknown target CPU: ', TargetInfo^.TargetCPU);
end;
end;
function TGDBMIDebuggerCommandStartBase.CheckFunction(const AFunction: String
): Boolean;
var
R: TGDBMIExecResult;
idx: Integer;
begin
ExecuteCommand('info functions %s', [AFunction], R, [cfCheckState]);
idx := Pos(AFunction, R.Values);
if idx <> 0
then begin
// Strip first
Delete(R.Values, 1, idx + Length(AFunction) - 1);
idx := Pos(AFunction, R.Values);
end;
Result := idx <> 0;
end;
procedure TGDBMIDebuggerCommandStartBase.RetrieveRegcall;
var
R: TGDBMIExecResult;
begin
// Assume it is
Include(TargetInfo^.TargetFlags, tfRTLUsesRegCall);
ExecuteCommand('-data-evaluate-expression FPC_THREADVAR_RELOCATE_PROC', R);
if R.State <> dsError then Exit; // guessed right
// next attempt, posibly no symbols, try functions
if CheckFunction('FPC_CPUINIT') then Exit; // function present --> not 1.0
// this runerror is only defined for < 1.1 ?
if not CheckFunction('$$_RUNERROR$') then Exit;
// We are here in 2 cases
// 1) there are no symbols at all
// We do not have to know the calling convention
// 2) target is compiled with an earlier version than 1.9.2
// params are passes by stack
Exclude(TargetInfo^.TargetFlags, tfRTLUsesRegCall);
end;
procedure TGDBMIDebuggerCommandStartBase.CheckAvailableTypes;
var
HadTimeout: Boolean;
R: TGDBMIExecResult;
begin
// collect timeouts
HadTimeout := False;
// check whether we need class cast dereference
R := CheckHasType('TObject', tfFlagHasTypeObject);
HadTimeout := HadTimeout and LastExecwasTimeOut;
if R.State <> dsError
then begin
if UpperCase(LeftStr(R.Values, 15)) = UpperCase('type = ^TOBJECT')
then include(TargetInfo^.TargetFlags, tfClassIsPointer);
end;
R := CheckHasType('Exception', tfFlagHasTypeException);
HadTimeout := HadTimeout and LastExecwasTimeOut;
if R.State <> dsError
then begin
if UpperCase(LeftStr(R.Values, 17)) = UpperCase('type = ^EXCEPTION')
then include(TargetInfo^.TargetFlags, tfExceptionIsPointer);
end;
CheckHasType('Shortstring', tfFlagHasTypeShortstring);
HadTimeout := HadTimeout and LastExecwasTimeOut;
//CheckHasType('PShortstring', tfFlagHasTypePShortString);
//HadTimeout := HadTimeout and LastExecwasTimeOut;
CheckHasType('pointer', tfFlagHasTypePointer);
HadTimeout := HadTimeout and LastExecwasTimeOut;
CheckHasType('byte', tfFlagHasTypeByte);
HadTimeout := HadTimeout and LastExecwasTimeOut;
//CheckHasType('char', tfFlagHasTypeChar);
//HadTimeout := HadTimeout and LastExecwasTimeOut;
if HadTimeout then DoTimeoutFeedback;
end;
procedure TGDBMIDebuggerCommandStartBase.DetectForceableBreaks;
var
R: TGDBMIExecResult;
List: TGDBMINameValueList;
begin
if DebuggerProperties.DisableForcedBreakpoint then
exit;
if not (dfForceBreakDetected in FTheDebugger.FDebuggerFlags) then begin
// detect if we can insert a not yet known break
ExecuteCommand('-break-insert -f foo', R);
if R.State <> dsError
then begin
Include(FTheDebugger.FDebuggerFlags, dfForceBreak);
List := TGDBMINameValueList.Create(R, ['bkpt']);
ExecuteCommand('-break-delete ' + List.Values['number']);
List.Free;
end
else Exclude(FTheDebugger.FDebuggerFlags, dfForceBreak);
Include(FTheDebugger.FDebuggerFlags, dfForceBreakDetected);
end;
end;
procedure TGDBMIDebuggerCommandStartBase.CommonInit;
var
i: TGDBMIExecCommandType;
begin
for i := low(TGDBMIExecCommandType) to high(TGDBMIExecCommandType) do begin
FTheDebugger.FCommandAsyncState[i] := True;
FTheDebugger.FCommandNoneMiState[i] := DebuggerProperties.UseNoneMiRunCommands = gdnmAlways;
end;
FTheDebugger.FCurrentCmdIsAsync := False;
ExecuteCommand('set print elements %d',
[TGDBMIDebuggerPropertiesBase(FTheDebugger.GetProperties).MaxDisplayLengthForString],
[]);
if DebuggerProperties.DisableLoadSymbolsForLibraries then begin
ExecuteCommand('set auto-solib-add off', [cfscIgnoreState, cfscIgnoreError]);
FTheDebugger.FWasDisableLoadSymbolsForLibraries := True;
end
else begin
// Only unset, if it was set due to this property
if FTheDebugger.FWasDisableLoadSymbolsForLibraries then
ExecuteCommand('set auto-solib-add on', [cfscIgnoreState, cfscIgnoreError]);
FTheDebugger.FWasDisableLoadSymbolsForLibraries := False;
end;
end;
{ TGDBMIDebuggerCommandExecuteBase }
function TGDBMIDebuggerCommandExecuteBase.ProcessRunning(out AStoppedParams: String; out
AResult: TGDBMIExecResult; ATimeOut: Integer): Boolean;
var
InLogWarning: Boolean;
function DoExecAsync(var Line: String): Boolean;
var
S: String;
i: Integer;
ct: TThreads;
t: TThreadEntry;
begin
Result := False;
S := GetPart('*', ',', Line);
case StringCase(S, ['stopped', 'started', 'disappeared', 'running']) of
0: begin // stopped
AStoppedParams := Line;
FGotStopped := True;
end;
1: ; // Known, but undocumented classes
2: FGotStopped := True;
3: begin // running,thread-id="1" // running,thread-id="all"
if (FTheDebugger.Threads.CurrentThreads <> nil)
then begin
ct := FTheDebugger.Threads.CurrentThreads;
S := GetPart('thread-id="', '"', Line);
if s = 'all' then begin
for i := 0 to ct.Count - 1 do
ct[i].ThreadState := 'running'; // TODO enum?
end
else begin
S := S + ',';
while s <> '' do begin
i := StrToIntDef(GetPart('', ',', s), -1);
if (s <> '') and (s[1] = ',') then delete(s, 1, 1)
else begin
debugln(DBG_WARNINGS, 'GDBMI: Error parsing threads');
break
end;
if i < 0 then Continue;
t := ct.EntryById[i];
if t <> nil then
t.ThreadState := 'running'; // TODO enum?
end;
end;
FTheDebugger.Threads.Changed;
end;
end;
else
// Assume targetoutput, strip char and continue
DebugLn(DBG_VERBOSE, '[DBGTGT] *');
Line := S + Line;
Result := True;
end;
end;
procedure DoMsgAsync(var Line: String);
var
S: String;
i, x: Integer;
ct: TThreads;
t: TThreadEntry;
begin
S := GetPart('=', ',', Line, False, False);
x := StringCase(S, ['thread-created', 'thread-exited']);
case x of // thread-group-exited // thread-group-added,id="i1"
0,1: begin
i := StrToIntDef(GetPart(',id="', '"', Line, False, False), -1);
if (i > 0) and (FTheDebugger.Threads.CurrentThreads <> nil)
then begin
ct := FTheDebugger.Threads.CurrentThreads;
t := ct.EntryById[i];
case x of
0: begin
if t = nil then begin
t := FTheDebugger.Threads.CurrentThreads.CreateEntry(0, nil, '', '', '', 0, i, '', 'unknown');
ct.Add(t);
t.Free;
end
else
debugln(DBG_WARNINGS, 'GDBMI: Duplicate thread');
end;
1: begin
if t <> nil then begin
ct.Remove(t);
end
else
debugln(DBG_WARNINGS, 'GDBMI: Missing thread');
end;
end;
FTheDebugger.Threads.Changed;
end;
end;
end;
FTheDebugger.DoNotifyAsync(Line);
end;
procedure DoStatusAsync(const Line: String);
begin
DebugLn(DBG_VERBOSE, '[Debugger] Status output: ', Line);
end;
procedure DoResultRecord(Line: String);
var
ResultClass: String;
begin
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: unexpected result-record: ', Line);
ResultClass := GetPart('^', ',', Line);
if Line = ''
then begin
if AResult.Values <> ''
then Include(AResult.Flags, rfNoMI);
end
else begin
AResult.Values := Line;
end;
//Result := True;
case StringCase(ResultClass, ['done', 'running', 'exit', 'error']) of
0: begin // done
AResult.State := dsIdle; // just indicate a ressult <> dsNone
end;
1: begin // running
AResult.State := dsRun;
end;
2: begin // exit
AResult.State := dsIdle;
end;
3: begin // error
DebugLn(DBG_WARNINGS, 'TGDBMIDebugger.ProcessRunning Error: ', Line);
// todo: implement with values
if (pos('msg=', Line) > 0)
and (pos('not being run', Line) > 0)
then AResult.State := dsStop
else AResult.State := dsError;
end;
else
//TODO: should that better be dsError ?
//Result := False;
AResult.State := dsIdle; // just indicate a ressult <> dsNone
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: Unknown result class: ', ResultClass);
end;
end;
procedure DoConsoleStream(const Line: String);
begin
DebugLn(DBG_VERBOSE, '[Debugger] Console output: ', Line);
end;
procedure DoTargetStream(const Line: String);
begin
DebugLn(DBG_VERBOSE, '[Debugger] Target output: ', Line);
end;
procedure DoLogStream(const Line: String);
const
LogWarning = 'warning:';
var
Warning: String;
begin
DebugLn(DBG_VERBOSE, '[Debugger] Log output: ', Line);
Warning := Line;
if Copy(Warning, 1, 2) = '&"' then
Delete(Warning, 1, 2);
if Copy(Warning, Length(Warning) - 2, 3) = '\n"' then
Delete(Warning, Length(Warning) - 2, 3);
if LowerCase(Copy(Warning, 1, Length(LogWarning))) = LogWarning then
begin
InLogWarning := True;
Delete(Warning, 1, Length(LogWarning));
Warning := MakePrintable(UnEscapeBackslashed(Trim(Warning), [uefOctal, uefTab, uefNewLine]));
DoDbgEvent(ecOutput, etOutputDebugString, Format(gdbmiEventLogDebugOutput, [Warning]));
end;
if InLogWarning then
FLogWarnings := FLogWarnings + Warning + LineEnding;
if Line = '&"\n"' then
InLogWarning := False;
(*
<< TCmdLineDebugger.ReadLn "&"Warning:\n""
<< TCmdLineDebugger.ReadLn "&"Cannot insert breakpoint 11.\n""
<< TCmdLineDebugger.ReadLn "&"Error accessing memory address 0x760: Input/output error.\n""
<< TCmdLineDebugger.ReadLn "&"\n""
<< TCmdLineDebugger.ReadLn "&"warning: Bad debug information detected: Attempt to read 592 bytes from registers.\n""
<< TCmdLineDebugger.ReadLn "^done,stack-args=[frame={level="5",args=[{name="ADDR",value="131"},{name="FUNC",value="']A'#0#131#0#0#0'l'#248#202#7#156#248#202#7#132#245#202#7#140#245#202#7'2kA'#0#6#2#0#0#27#0#0#0'#'#0#0#0'#'#0#0#0" ..(493).. ",{name="PTEXT",value="<value optimized out>"}]},frame={level="8",args=[]},frame={level="9",args=[]}]"
*)
end;
var
S: String;
idx: Integer;
begin
Result := True;
AResult.State := dsNone;
InLogWarning := False;
FGotStopped := False;
FLogWarnings := '';
AStoppedParams := '';
while FTheDebugger.DebugProcessRunning and not(FTheDebugger.State in [dsError, dsDestroying]) do
begin
if ATimeOut > 0 then begin
S := FTheDebugger.ReadLine(ATimeOut);
if FTheDebugger.ReadLineTimedOut then begin
FProcessResultTimedOut := True;
break;
end;
end
else
S := FTheDebugger.ReadLine(50);
if (S = '(gdb) ') or
( (S = '') and FDidKillNow )
then
Break;
while S <> '' do
begin
if S[1] <> '&' then
InLogWarning := False;
case S[1] of
'^': DoResultRecord(S);
'~': DoConsoleStream(S);
'@': DoTargetStream(S);
'&': DoLogStream(S);
'*': if DoExecAsync(S) then Continue;
'+': DoStatusAsync(S);
'=': DoMsgAsync(S);
else
// since target output isn't prefixed (yet?)
// one of our known commands could be part of it.
idx := Pos('*stopped', S);
if idx > 0
then begin
DebugLn(DBG_VERBOSE, '[DBGTGT] ', Copy(S, 1, idx - 1));
Delete(S, 1, idx - 1);
FGotStopped := True;
Continue;
end
else begin
// normal target output
DebugLn(DBG_VERBOSE, '[DBGTGT] ', S);
end;
end;
Break;
end;
if FTheDebugger.FAsyncModeEnabled and FGotStopped then begin
// There should not be a "(gdb) ",
// but some versions print it, as they run none async, after accepting "run &"
S := FTheDebugger.ReadLine(True, 50);
if FTheDebugger.ReadLineTimedOut then break;
if (S = '(gdb) ') then begin
FTheDebugger.ReadLine(50); // read the extra "(gdb) "
break;
end;
// since no command was sent, we can loop
end;
end;
end;
function TGDBMIDebuggerCommandExecuteBase.ParseBreakInsertError(var AText: String; out
AnId: Integer): Boolean;
const
BreaKErrMsg = 'not insert breakpoint ';
WatchErrMsg = 'not insert hardware watchpoint ';
var
i, i2, j: Integer;
begin
Result := False;
AnId := -1;
i := pos(BreaKErrMsg, AText);
if i > 0
then j := i + length(BreaKErrMsg);
i2 := pos(WatchErrMsg, AText);
if (i2 > 0) and ( (i2 < i) or (i < 1) )
then begin
i := i2;
j := i + length(WatchErrMsg);
end;
if i <= 0 then exit;
i2 := j;
while (i2 <= length(AText)) and (AText[i2] in ['0'..'9']) do inc(i2);
if i2 > j then
AnId := StrToIntDef(copy(AText, j, i2-j), -1);
Delete(AText, i, i2 - i);
Result := True;
end;
function TGDBMIDebuggerCommandExecuteBase.ProcessStopped(const AParams: String;
const AIgnoreSigIntState: Boolean): Boolean;
begin
Result := False;
end;
constructor TGDBMIDebuggerCommandExecuteBase.Create(AOwner: TGDBMIDebugger);
begin
FCanKillNow := False;
inherited Create(AOwner);
end;
function TGDBMIDebuggerCommandExecuteBase.KillNow: Boolean;
var
StoppedParams: String;
R: TGDBMIExecResult;
begin
Result := False;
if not FCanKillNow then exit;
// only here, if we are in ProcessRunning
FDidKillNow := True; // interrupt current ProcessRunning
FCanKillNow := False; // Do not allow to re-enter
FTheDebugger.GDBPause(True);
FTheDebugger.CancelAllQueued; // before ProcessStopped
FDidKillNow := False; // allow ProcessRunning
Result := ProcessRunning(StoppedParams, R, 1500);
if ProcessResultTimedOut then begin
// the outer Processrunning should stop, due to process no longer running
FDidKillNow := True;
FTheDebugger.TerminateGDB;
FTheDebugger.FNeedReset:= True;
SetDebuggerState(dsStop);
//FTheDebugger.CancelAllQueued; // stop queued new cmd
Result := True;
exit;
end;
FDidKillNow := True;
if StoppedParams <> ''
then ProcessStopped(StoppedParams, FTheDebugger.PauseWaitState = pwsInternal);
FTheDebugger.FPauseWaitState := pwsNone;
ExecuteCommand('kill', [cfNoThreadContext], 1500);
FTheDebugger.FCurrentStackFrameValid := False;
FTheDebugger.FCurrentThreadIdValid := False;
Result := ExecuteCommand('info program', R, [cfNoThreadContext], 1500);
Result := Result and (Pos('not being run', R.Values) > 0);
if Result
then SetDebuggerState(dsStop);
// Now give the ProcessRunning in the current DoExecute something
//FTheDebugger.SendCmdLn('print 1');
end;
function TGDBMIDebugger.ConvertToGDBPath(APath: string; ConvType: TConvertToGDBPathType = cgptNone): string;
// GDB wants forward slashes in its filenames, even on win32.
var
esc: TGDBMIDebuggerFilenameEncoding;
begin
Result := UTF8ToWinCP(APath);
// no need to process empty filename
if Result = '' then exit;
case ConvType of
cgptNone: esc := gdfeNone;
cgptCurDir:
begin
esc := TGDBMIDebuggerPropertiesBase(GetProperties).FEncodeCurrentDirPath;
//TODO: check FGDBOS
//Unix/Windows can use gdfeEscSpace, but work without too;
{$IFDEF darwin}
if esc = gdfeDefault then
if (FGDBVersionMajor >= 7) and (FGDBVersionMinor >= 0)
then esc := gdfeNone
else esc := gdfeQuote;
{$ELSE}
if esc = gdfeDefault then esc := gdfeNone;
{$ENDIF}
end;
cgptExeName:
begin
esc := TGDBMIDebuggerPropertiesBase(GetProperties).FEncodeExeFileName;
//Unix/Windows can use gdfeEscSpace, but work without too;
{$IFDEF darwin}
if esc = gdfeDefault then
if (FGDBVersionMajor >= 7) and (FGDBVersionMinor >= 0)
then esc := gdfeNone
else esc := gdfeEscSpace;
{$ELSE}
if esc = gdfeDefault then esc := gdfeNone;
{$ENDIF}
end;
end;
{$WARNINGS off}
if DirectorySeparator <> '/' then
Result := StringReplace(Result, DirectorySeparator, '/', [rfReplaceAll]);
{$WARNINGS on}
if esc = gdfeEscSpace
then Result := StringReplace(Result, ' ', '\ ', [rfReplaceAll]);
if esc = gdfeQuote
then Result := '\"' + Result + '\"';
Result := '"' + Result + '"';
end;
{ TGDBMIDebuggerCommandChangeFilename }
function TGDBMIDebuggerCommandChangeFilename.DoExecute: Boolean;
begin
Result := True;
FSuccess := DoChangeFilename;
if not FSuccess then exit;
if FFileName = '' then exit;
FSuccess := DoSetPascal;
end;
constructor TGDBMIDebuggerCommandChangeFilename.Create(AOwner: TGDBMIDebugger;
AFileName: String);
begin
FFileName := AFileName;
inherited Create(AOwner);
end;
{ TGDBMIDebuggerCommandInitDebugger }
function TGDBMIDebuggerCommandInitDebugger.DoExecute: Boolean;
function StoreGdbVersionAsNumber: Boolean;
var
i: Integer;
s: String;
begin
FTheDebugger.FGDBVersionMajor := -1;
FTheDebugger.FGDBVersionMinor := -1;
FTheDebugger.FGDBVersionRev := -1;
s := FTheDebugger.FGDBVersion;
Result := False;
// remove none leading digits
i := 1;
while (i <= Length(s)) and not (s[i] in ['0'..'9']) do inc(i);
Delete(s,1,i-1);
if s = '' then exit;
FTheDebugger.FGDBVersion := s;
// Major
i := 1;
while (i <= Length(s)) and (s[i] in ['0'..'9']) do inc(i);
if (i = 1) or (i > Length(s)) or (s[i] <> '.') then exit;
FTheDebugger.FGDBVersionMajor := StrToIntDef(copy(s,1,i-1), -1);
if i < 0 then exit;
Delete(s,1,i);
// Minor
i := 1;
while (i <= Length(s)) and (s[i] in ['0'..'9']) do inc(i);
if (i = 1) then exit;
FTheDebugger.FGDBVersionMinor := StrToIntDef(copy(s,1,i-1), -1);
Result := True;
if (i > Length(s)) or (s[i] <> '.') then exit;
Delete(s,1,i);
// Rev
i := 1;
while (i <= Length(s)) and (s[i] in ['0'..'9']) do inc(i);
if (i = 1) then exit;
FTheDebugger.FGDBVersionRev := StrToIntDef(copy(s,1,i-1), -1);
end;
function ParseGDBVersionMI: Boolean;
var
R: TGDBMIExecResult;
S: String;
List: TGDBMINameValueList;
begin
Result := ExecuteCommand('-gdb-version', R);
Result := Result and (R.Values <> '');
if (not Result) then exit;
List := TGDBMINameValueList.Create(R);
FTheDebugger.FGDBVersion := List.Values['version'];
S := List.Values['target'];
FTheDebugger.FGDBCPU := GetPart('', '-', S);
GetPart('-', '-', S); // strip vendor
FTheDebugger.FGDBOS := GetPart(['-'], ['-', ''], S);
List.Free;
if StoreGdbVersionAsNumber
then exit;
// maybe a none MI result
S := GetPart(['configured as \"'], ['\"'], R.Values, False, False);
if Pos('--target=', S) <> 0 then
S := GetPart('--target=', '', S);
FTheDebugger.FGDBCPU := GetPart('', '-', S);
GetPart('-', '-', S); // strip vendor
FTheDebugger.FGDBOS := GetPart('-', '-', S);
FTheDebugger.FGDBVersion := GetPart(['('], [')'], R.Values, False, False);
if StoreGdbVersionAsNumber then Exit;
FTheDebugger.FGDBVersion := GetPart(['gdb '], [#10, #13], R.Values, True, False);
if StoreGdbVersionAsNumber then Exit;
// Retry, but do not check for format (old behaviour)
FTheDebugger.FGDBVersion := GetPart(['('], [')'], R.Values, False, False);
StoreGdbVersionAsNumber;
if FTheDebugger.FGDBVersion <> '' then Exit;
FTheDebugger.FGDBVersion := GetPart(['gdb '], [#10, #13], R.Values, True, False);
StoreGdbVersionAsNumber;
Result := False;
end;
var
R: TGDBMIExecResult;
begin
Result := True;
FContext.ThreadContext := ccNotRequired;
FContext.StackContext := ccNotRequired;
FSuccess := ExecuteCommand('-gdb-set confirm off', R);
FSuccess := FSuccess and (r.State <> dsError);
if (not FSuccess) then exit;
// for win32, turn off a new console otherwise breaking gdb will fail
// ignore the error on other platforms
FSuccess := ExecuteCommand('-gdb-set new-console off', R);
if (not FSuccess) then exit;
// set the output width to a great value to avoid unexpected
// new lines like in large functions or procedures
ExecuteCommand('set width 50000', []);
FTheDebugger.FAsyncModeEnabled := False;
if TGDBMIDebuggerPropertiesBase(FTheDebugger.GetProperties).UseAsyncCommandMode then begin
if ExecuteCommand('set target-async on', R, []) and (R.State <> dsError) then begin
ExecuteCommand('show target-async', R, []);
FTheDebugger.FAsyncModeEnabled := (R.State <> dsError) and
(pos('mode is on', LowerCase(R.Values)) > 0);
end;
if not FTheDebugger.FAsyncModeEnabled then
ExecuteCommand('set target-async off', R, []);
end;
ParseGDBVersionMI;
end;
procedure TGDBMIDebuggerCommandStack.DoCallstackFreed(Sender: TObject);
begin
debugln(DBGMI_QUEUE_DEBUG, ['DoCallstackFreed: ', DebugText]);
FCallstack := nil;
Cancel;
end;
procedure TGDBMIDebuggerCommandStack.DoLockQueueExecute;
begin
//
end;
procedure TGDBMIDebuggerCommandStack.DoUnLockQueueExecute;
begin
//
end;
procedure TGDBMIDebuggerCommandStack.DoLockQueueExecuteForInstr;
begin
///
end;
procedure TGDBMIDebuggerCommandStack.DoUnLockQueueExecuteForInstr;
begin
//
end;
constructor TGDBMIDebuggerCommandStack.Create(AOwner: TGDBMIDebugger;
ACallstack: TCallStackBase);
begin
inherited Create(AOwner);
FCallstack := ACallstack;
FCallstack.AddFreeNotification(@DoCallstackFreed);
end;
destructor TGDBMIDebuggerCommandStack.Destroy;
begin
if FCallstack <> nil
then FCallstack.RemoveFreeNotification(@DoCallstackFreed);
inherited Destroy;
end;
{ TGDBMIBreakPoints }
function TGDBMIBreakPoints.FindById(AnId: Integer): TGDBMIBreakPoint;
var
n: Integer;
begin
for n := 0 to Count - 1 do
begin
Result := TGDBMIBreakPoint(Items[n]);
if (Result.FBreakID = AnId)
then Exit;
end;
Result := nil;
end;
{ TGDBMIDebuggerCommandKill }
function TGDBMIDebuggerCommandKill.DoExecute: Boolean;
var
R: TGDBMIExecResult;
CmdRes: Boolean;
begin
Result := True;
FContext.ThreadContext := ccNotRequired;
FContext.StackContext := ccNotRequired;
// not supported yet
// ExecuteCommand('-exec-abort');
CmdRes := ExecuteCommand('kill', [], [], 1500); // Hardcoded timeout
FTheDebugger.FCurrentStackFrameValid := False;
FTheDebugger.FCurrentThreadIdValid := False;
if CmdRes
then CmdRes := ExecuteCommand('info program', R, [cfNoThreadContext], 1500); // Hardcoded timeout
if (not CmdRes)
or (Pos('not being run', R.Values) <= 0)
then begin
FTheDebugger.TerminateGDB;
SetDebuggerState(dsError); // failed to stop
exit;
end;
SetDebuggerState(dsStop);
end;
{ TGDBMIThreads }
procedure TGDBMIThreads.DoThreadsDestroyed(Sender: TObject);
begin
if FGetThreadsCmdObj = Sender
then FGetThreadsCmdObj:= nil;
end;
procedure TGDBMIThreads.DoThreadsFinished(Sender: TObject);
var
Cmd: TGDBMIDebuggerCommandThreads;
i: Integer;
begin
if Monitor = nil then exit;
Cmd := TGDBMIDebuggerCommandThreads(Sender);
if CurrentThreads = nil then exit;
if not Cmd.Success then begin
CurrentThreads.SetValidity(ddsInvalid);
CurrentThreads.CurrentThreadId := Debugger.FCurrentThreadId;
exit;
end;
CurrentThreads.Clear;
for i := 0 to Cmd.Count - 1 do
CurrentThreads.Add(Cmd.Threads[i]);
CurrentThreads.CurrentThreadId := Cmd.CurrentThreadId;
CurrentThreads.SetValidity(ddsValid);
Debugger.FCurrentThreadId := CurrentThreads.CurrentThreadId;
Debugger.FCurrentThreadIdValid := True;
end;
function TGDBMIThreads.GetDebugger: TGDBMIDebugger;
begin
Result := TGDBMIDebugger(inherited Debugger);
end;
procedure TGDBMIThreads.ThreadsNeeded;
var
ForceQueue: Boolean;
begin
if Debugger = nil then Exit;
if (Debugger.State in [dsPause, dsInternalPause])
then begin
FGetThreadsCmdObj := TGDBMIDebuggerCommandThreads.Create(Debugger);
FGetThreadsCmdObj.OnExecuted := @DoThreadsFinished;
FGetThreadsCmdObj.OnDestroy := @DoThreadsDestroyed;
FGetThreadsCmdObj.Properties := [dcpCancelOnRun];
FGetThreadsCmdObj.Priority := GDCMD_PRIOR_THREAD;
FGetThreadsCmdObj.CurrentThreads := CurrentThreads;
// If a ExecCmd is running, then defer exec until the exec cmd is done
ForceQueue := (TGDBMIDebugger(Debugger).FCurrentCommand <> nil)
and (TGDBMIDebugger(Debugger).FCurrentCommand is TGDBMIDebuggerCommandExecute)
and (not TGDBMIDebuggerCommandExecute(TGDBMIDebugger(Debugger).FCurrentCommand).NextExecQueued)
and (Debugger.State <> dsInternalPause);
TGDBMIDebugger(Debugger).QueueCommand(FGetThreadsCmdObj, ForceQueue);
(* DoEvaluationFinished may be called immediately at this point *)
end;
end;
procedure TGDBMIThreads.CancelEvaluation;
begin
if FGetThreadsCmdObj <> nil
then begin
FGetThreadsCmdObj.OnExecuted := nil;
FGetThreadsCmdObj.OnDestroy := nil;
FGetThreadsCmdObj.Cancel;
end;
FGetThreadsCmdObj := nil;
end;
destructor TGDBMIThreads.Destroy;
begin
CancelEvaluation;
inherited Destroy;
end;
procedure TGDBMIThreads.RequestMasterData;
begin
ThreadsNeeded;
end;
procedure TGDBMIThreads.ChangeCurrentThread(ANewId: Integer);
begin
if Debugger = nil then Exit;
if not(Debugger.State in [dsPause, dsInternalPause]) then exit;
Debugger.FCurrentThreadId := ANewId;
Debugger.FCurrentThreadIdValid := True;
Debugger.DoThreadChanged;
if CurrentThreads <> nil
then CurrentThreads.CurrentThreadId := ANewId;
DebugLn(DBG_THREAD_AND_FRAME, ['TGDBMIThreads THREAD wanted ', Debugger.FCurrentThreadId]);
end;
procedure TGDBMIThreads.DoCleanAfterPause;
begin
if (Debugger.State <> dsRun) or (Monitor = nil) then begin
inherited DoCleanAfterPause;
exit;
end;
//for i := 0 to Monitor.CurrentThreads.Count - 1 do
// Monitor.CurrentThreads[i].ClearLocation; // TODO enum?
end;
{ TGDBMIDebuggerCommandThreads }
function TGDBMIDebuggerCommandThreads.GetThread(AnIndex: Integer): TThreadEntry;
begin
Result := FThreads[AnIndex];
end;
function TGDBMIDebuggerCommandThreads.DoExecute: Boolean;
var
R: TGDBMIExecResult;
List, EList, ArgList: TGDBMINameValueList;
i, j: Integer;
line, ThrId: Integer;
func, filename, fullname: String;
ThrName, ThrState: string;
addr: TDBGPtr;
Arguments: TStringList;
begin
(* TODO: none MI command
<info threads>
&"info threads\n"
~" 5 thread 4928.0x1f50 0x77755ca4 in ntdll!LdrAccessResource () from C:\\Windows\\system32\\ntdll.dll\n"
~" 4 thread 4928.0x12c8 0x77755ca4 in ntdll!LdrAccessResource () from C:\\Windows\\system32\\ntdll.dll\n"
~"* 1 thread 4928.0x1d18 TFORM1__BUTTON1CLICK (SENDER=0x209ef0, this=0x209a20) at unit1.pas:65\n"
^done
(gdb)
*)
Result := True;
FContext.ThreadContext := ccNotRequired;
FContext.StackContext := ccNotRequired;
if not ExecuteCommand('-thread-info', R)
then exit;
if r.State = dsError then exit;;
List := TGDBMINameValueList.Create(R);
EList := TGDBMINameValueList.Create;
ArgList := TGDBMINameValueList.Create;
try
FCurrentThreadId := StrToIntDef(List.Values['current-thread-id'], -1);
if FCurrentThreadId < 0 then exit;
FSuccess := True;
// update queue if needed // clear current stackframe
if FTheDebugger.FInstructionQueue.CurrentThreadId <> FCurrentThreadId then
FTheDebugger.FInstructionQueue.SetKnownThread(FCurrentThreadId);
List.SetPath('threads');
SetLength(FThreads, List.Count);
for i := 0 to List.Count - 1 do begin
EList.Init(List.Items[i]^.Name);
ThrId := StrToIntDef(EList.Values['id'], -2);
ThrName := EList.Values['target-id'];
ThrState := EList.Values['state'];
EList.SetPath('frame');
addr := StrToQWordDef(EList.Values['addr'], 0);
func := EList.Values['func'];
filename := ConvertGdbPathAndFile(EList.Values['file']);
fullname := ConvertGdbPathAndFile(EList.Values['fullname']);
line := StrToIntDef(EList.Values['line'], 0);
EList.SetPath('args');
Arguments := TStringList.Create;
for j := 0 to EList.Count - 1 do begin
ArgList.Init(EList.Items[j]^.Name);
Arguments.Add(ArgList.Values['name'] + '=' + DeleteEscapeChars(ArgList.Values['value']));
end;
FThreads[i] := CurrentThreads.CreateEntry(
addr,
Arguments,
func,
filename, fullname,
line,
ThrId,ThrName, ThrState
);
Arguments.Free;
end;
finally
FreeAndNil(ArgList);
FreeAndNil(EList);
FreeAndNil(List);
end;
end;
constructor TGDBMIDebuggerCommandThreads.Create(AOwner: TGDBMIDebugger);
begin
inherited;
FSuccess := False;
end;
destructor TGDBMIDebuggerCommandThreads.Destroy;
var
i: Integer;
begin
for i := 0 to length(FThreads) - 1 do FreeAndNil(FThreads[i]);
FThreads := nil;
inherited Destroy;
end;
function TGDBMIDebuggerCommandThreads.Count: Integer;
begin
Result := length(FThreads);
end;
{ TGDBMINameValueBasedList }
constructor TGDBMINameValueBasedList.Create;
begin
FNameValueList := TGDBMINameValueList.Create;
end;
constructor TGDBMINameValueBasedList.Create(const AResultValues: String);
begin
FNameValueList := TGDBMINameValueList.Create(AResultValues);
PreParse;
end;
constructor TGDBMINameValueBasedList.Create(AResult: TGDBMIExecResult);
begin
Create(AResult.Values);
end;
destructor TGDBMINameValueBasedList.Destroy;
begin
inherited Destroy;
FreeAndNil(FNameValueList);
end;
procedure TGDBMINameValueBasedList.Init(AResultValues: string);
begin
FNameValueList.Init(AResultValues);
PreParse;
end;
procedure TGDBMINameValueBasedList.Init(AResult: TGDBMIExecResult);
begin
Init(AResult.Values);
end;
{ TGDBMIDisassembleResultList }
procedure TGDBMIDisassembleResultList.PreParse;
const
SrcAndAsm = 'src_and_asm_line';
SrcAndAsmLen = length(SrcAndAsm);
var
Itm: PGDBMINameValue;
SrcList: TGDBMINameValueList;
i, j: Integer;
SFile, SLine: TPCharWithLen;
begin
// The "^done" is stripped already
if (FNameValueList.Count <> 1) or(FNameValueList.IndexOf('asm_insns') < 0)
then debugln(DBG_DISASSEMBLER, ['WARNING: TGDBMIDisassembleResultList: Unexpected Entries']);
HasItemPointerList := False;
FNameValueList.SetPath('asm_insns');
FCount := 0;
SetLength(FItems, FNameValueList.Count * 4);
FHasSourceInfo := False;
SrcList := nil;
for i := 0 to FNameValueList.Count - 1 do begin
Itm := FNameValueList.Items[i];
if (Itm^.Name.Len = SrcAndAsmLen)
and (strlcomp(Itm^.Name.Ptr, PChar(SrcAndAsm), SrcAndAsmLen) = 0)
then begin
// Source and asm
FHasSourceInfo := True;
if SrcList = nil
then SrcList := TGDBMINameValueList.Create(Itm^.Value)
else SrcList.Init(Itm^.Value);
SFile := SrcList.ValuesPtr['file'];
SLine := SrcList.ValuesPtr['line'];
SrcList.SetPath('line_asm_insn');
if FCount + SrcList.Count >= length(FItems)
then SetLength(FItems, FCount + SrcList.Count + 20);
for j := 0 to SrcList.Count - 1 do begin
FItems[FCount].AsmEntry := SrcList.Items[j]^.Name;
FItems[FCount].SrcFile := SFile;
FItems[FCount].SrcLine := SLine;
FItems[FCount].ParsedInfo.SrcStatementIndex := j;
FItems[FCount].ParsedInfo.SrcStatementCount := SrcList.Count;
inc(FCount);
end;
end
else
if (Itm^.Name.Len > 1)
and (Itm^.Name.Ptr[0] = '{')
and (Itm^.Value.Len = 0)
then begin
// Asm only
if FCount + 1 >= length(FItems)
then SetLength(FItems, FCount + 20);
FItems[FCount].AsmEntry := Itm^.Name;
FItems[FCount].SrcFile.Ptr := nil;
FItems[FCount].SrcFile.Len := 0;
FItems[FCount].SrcLine.Ptr := nil;
FItems[FCount].SrcLine.Len := 0;
FItems[FCount].ParsedInfo.SrcStatementIndex := 0;
FItems[FCount].ParsedInfo.SrcStatementCount := 0;
inc(FCount);
end
else
begin
// unknown
debugln(['WARNING: TGDBMIDisassembleResultList.Parse: unknown disass entry',
DbgsPCLen(Itm^.Name),': ',DbgsPCLen(Itm^.Value)]);
end;
end;
FreeAndNil(SrcList);
end;
function TGDBMIDisassembleResultList.GetLastItem: PDisassemblerEntry;
begin
if HasItemPointerList
then begin
Result := ItemPointerList[Count - 1];
exit;
end;
ParseItem(Count - 1);
Result := @FItems[Count - 1].ParsedInfo;
end;
function TGDBMIDisassembleResultList.SortByAddress: Boolean;
var
i, j: Integer;
Itm1: PDisassemblerEntry;
begin
Result := True;
SetLength(ItemPointerList, FCount);
for i := 0 to Count - 1 do begin
Itm1 := Item[i];
j := i - 1;
while j >= 0 do begin
if ItemPointerList[j]^.Addr > Itm1^.Addr
then ItemPointerList[j+1] := ItemPointerList[j]
else break;
dec(j);
end;
ItemPointerList[j+1] := Itm1;
end;
HasItemPointerList := True;
end;
constructor TGDBMIDisassembleResultList.CreateSubList(ASource: TGDBMIDisassembleResultList;
AStartIdx, ACount: Integer);
begin
Create;
InitSubList(ASource, AStartIdx, ACount);
end;
procedure TGDBMIDisassembleResultList.InitSubList(ASource: TGDBMIDisassembleResultList;
AStartIdx, ACount: Integer);
var
i: Integer;
begin
SetLength(ItemPointerList, ACount);
FCount := ACount;
for i := 0 to ACount - 1 do
ItemPointerList[i] := ASource.Item[AStartIdx + i];
HasItemPointerList := True;
end;
function TGDBMIDisassembleResultList.GetItem(Index: Integer): PDisassemblerEntry;
begin
if HasItemPointerList
then begin
Result := ItemPointerList[Index];
exit;
end;
ParseItem(Index);
Result := @FItems[Index].ParsedInfo;
end;
procedure TGDBMIDisassembleResultList.ParseItem(Index: Integer);
var
AsmList: TGDBMINameValueList;
begin
if FItems[Index].AsmEntry.Ptr = nil
then exit;
AsmList := TGDBMINameValueList.Create(FItems[Index].AsmEntry);
FItems[Index].ParsedInfo.SrcFileName := ConvertGdbPathAndFile(PCLenToString(FItems[Index].SrcFile, True));
FItems[Index].ParsedInfo.SrcFileLine := PCLenToInt(FItems[Index].SrcLine, 0);
// SrcStatementIndex, SrcStatementCount are already set
FItems[Index].ParsedInfo.Addr := PCLenToQWord(AsmList.ValuesPtr['address'], 0);
FItems[Index].ParsedInfo.Statement :=
UnEscapeBackslashed(PCLenToString(AsmList.ValuesPtr['inst'], True), [uefTab], 16);
FItems[Index].ParsedInfo.FuncName := PCLenToString(AsmList.ValuesPtr['func-name'], True);
FItems[Index].ParsedInfo.Offset := PCLenToInt(AsmList.ValuesPtr['offset'], 0);
FItems[Index].AsmEntry.Ptr := nil;
FreeAndNil(AsmList);
end;
procedure TGDBMIDisassembleResultList.SetCount(const AValue: Integer);
begin
if FCount = AValue then exit;
if FCount > length(FItems)
then raise Exception.Create('Invalid Count');
FCount := AValue;
end;
procedure TGDBMIDisassembleResultList.SetItem(Index: Integer;
const AValue: PDisassemblerEntry);
begin
if HasItemPointerList
then begin
ItemPointerList[Index]^ := AValue^;
exit;
end;
FItems[Index].ParsedInfo := AValue^;
FItems[Index].AsmEntry.Ptr := nil;
end;
procedure TGDBMIDisassembleResultList.SetLastItem(const AValue: PDisassemblerEntry);
begin
if HasItemPointerList
then begin
ItemPointerList[Count - 1]^ := AValue^;
exit;
end;
FItems[Count - 1].ParsedInfo := AValue^;
FItems[Count - 1].AsmEntry.Ptr := nil;
end;
{ TGDBMIDisassembleResultFunctionIterator }
constructor TGDBMIDisassembleResultFunctionIterator.Create(AList: TGDBMIDisassembleResultList;
AStartIdx: Integer; ALastSubListEndAddr: TDBGPtr;
AnAddressToLocate, AnAddForLineAfterCounter: TDBGPtr);
begin
FList := AList;
FStartedAtIndex := AStartIdx;
FStartIdx := AStartIdx;
FLastSubListEndAddr := ALastSubListEndAddr;
FAddressToLocate := AnAddressToLocate;
FAddForLineAfterCounter := AnAddForLineAfterCounter;
FMaxIdx := FList.Count - 1;
if FStartIdx > FMaxIdx
then raise Exception.Create('internal error');
FIndexOfLocateAddress := 1;
FOffsetOfLocateAddress := -1;
FIndexOfCounterAddress := -1;
FSublistNumber := -1;
end;
function TGDBMIDisassembleResultFunctionIterator.EOL: Boolean;
begin
Result := FStartIdx > FMaxIdx ;
end;
function TGDBMIDisassembleResultFunctionIterator.NextSubList
(var AResultList: TGDBMIDisassembleResultList): Boolean;
var
WasBeforeStart: Boolean;
HasPrcName: Boolean;
PrcBaseAddr: TDBGPtr;
Itm: PDisassemblerEntry;
NextIdx: Integer;
HasLocate: Boolean;
begin
FCurIdx := FStartIdx;
if FStartIdx > FMaxIdx
then raise Exception.Create('internal error');
inc(FSublistNumber);
(* The name may change in the middle of a function. Check for either:
- change between no-name and has-name
- change of the base-address (addr-offset), if the offset is valid (if has-name)
*)
HasPrcName := FList.Item[FStartIdx]^.FuncName <> ''; // can use offsets
{$PUSH}{$IFnDEF DBGMI_WITH_DISASS_OVERFLOW}{$Q-}{$ENDIF} // Overflow is allowed to occur
PrcBaseAddr := FList.Item[FStartIdx]^.Addr - FList.Item[FStartIdx]^.Offset;
{$POP}
WasBeforeStart := FList.Item[FStartIdx]^.Addr < FAddressToLocate;
HasLocate := False;
NextIdx := FStartIdx + 1;
while NextIdx <= FMaxIdx do
begin
Itm := FList.Item[NextIdx];
{$PUSH}{$IFnDEF DBGMI_WITH_DISASS_OVERFLOW}{$Q-}{$ENDIF} // Overflow is allowed to occur
// Also check the next statement after PrcName.
// If it has FOffsetOfLocateAddress > 0, then FAddressToLocate is in current block, but not matched
if (Itm^.Addr = FAddressToLocate)
then begin
FIndexOfLocateAddress := NextIdx;
FOffsetOfLocateAddress := 0;
WasBeforeStart := False;
HasLocate := True;
end
else if WasBeforeStart and (Itm^.Addr > FAddressToLocate)
then begin
FIndexOfLocateAddress := NextIdx - 1;
FOffsetOfLocateAddress := FAddressToLocate - FList.Item[NextIdx-1]^.Addr;
WasBeforeStart := False;
HasLocate := True;
end;
if (FAddForLineAfterCounter > 0)
and ( (Itm^.Addr = FAddForLineAfterCounter)
or ((Itm^.Addr > FAddForLineAfterCounter) and (FIndexOfCounterAddress < 0)) )
then FIndexOfCounterAddress := NextIdx;
if (HasPrcName <> (Itm^.FuncName <> ''))
or (HasPrcName and (PrcBaseAddr <> Itm^.Addr - Itm^.Offset))
then break;
{$POP}
inc(NextIdx);
end;
if AResultList = nil
then AResultList := TGDBMIDisassembleResultList.CreateSubList(FList, FStartIdx, NextIdx - FStartIdx)
else AResultList.InitSubList(FList, FStartIdx, NextIdx - FStartIdx);
FStartIdx := NextIdx;
// Does the next address look good?
// And is AStartAddrHit ok
//Result := ((NextIdx > FMaxIdx) or (FList.Item[NextIdx]^.Offset = 0))
// and
Result := ( (not HasLocate) or ((FIndexOfLocateAddress < 0) or (FOffsetOfLocateAddress = 0)) );
end;
function TGDBMIDisassembleResultFunctionIterator.IsFirstSubList: Boolean;
begin
Result := FSublistNumber = 0;
end;
function TGDBMIDisassembleResultFunctionIterator.CountLinesAfterCounterAddr: Integer;
begin
Result := -1;
if FIndexOfCounterAddress >= 0 then
Result := CurrentIndex - IndexOfCounterAddress - 1;
end;
function TGDBMIDisassembleResultFunctionIterator.CurrentFixedAddr(AOffsLimit: Integer): TDBGPtr;
begin
Result := FList.Item[CurrentIndex]^.Addr - Min(FList.Item[CurrentIndex]^.Offset, AOffsLimit);
// Offset may increase to a point BEFORE the previous address (e.g. neseted proc, maybe inline?)
if CurrentIndex > 0 then
if Result <= FList.Item[CurrentIndex-1]^.Addr then
Result := FList.Item[CurrentIndex]^.Addr;
end;
function TGDBMIDisassembleResultFunctionIterator.NextStartAddr: TDBGPtr;
begin
if NextIndex <= FMaxIdx
then begin
Result := FList.Item[NextIndex]^.Addr - FList.Item[NextIndex]^.Offset;
// Offset may increase to a point BEFORE the previous address (e.g. neseted proc, maybe inline?)
if NextIndex > 0 then
if Result <= FList.Item[NextIndex-1]^.Addr then
Result := FList.Item[NextIndex]^.Addr;
end
else
Result := FLastSubListEndAddr;
end;
function TGDBMIDisassembleResultFunctionIterator.NextStartOffs: Integer;
begin
if NextIndex <= FMaxIdx
then Result := FList.Item[NextIndex]^.Offset
else Result := 0;
end;
{ TGDBMIMemoryDumpResultList }
function TGDBMIMemoryDumpResultList.GetItemNum(Index: Integer): Integer;
begin
Result := PCLenToInt(FNameValueList.Items[Index]^.Name, 0);
end;
function TGDBMIMemoryDumpResultList.GetItem(Index: Integer): TPCharWithLen;
begin
Result := FNameValueList.Items[Index]^.Name;
end;
function TGDBMIMemoryDumpResultList.GetItemTxt(Index: Integer): string;
var
itm: PGDBMINameValue;
begin
itm := FNameValueList.Items[Index];
if itm <> nil
then Result := PCLenToString(itm^.Name, True)
else Result := '';
end;
procedure TGDBMIMemoryDumpResultList.PreParse;
begin
FNameValueList.SetPath('memory');
if FNameValueList.Count = 0 then exit;
FNameValueList.Init(FNameValueList.Items[0]^.Name);
FAddr := PCLenToQWord(FNameValueList.ValuesPtr['addr'], 0);
FNameValueList.SetPath('data');
end;
function TGDBMIMemoryDumpResultList.Count: Integer;
begin
Result := FNameValueList.Count;
end;
function TGDBMIMemoryDumpResultList.AsText(AStartOffs, ACount: Integer;
AAddrWidth: Integer): string;
var
i: LongInt;
begin
if AAddrWidth > 0
then Result := IntToHex(addr + AStartOffs, AAddrWidth) + ':'
else Result := '';
for i := AStartOffs to AStartOffs + ACount do begin
if i >= ACount then exit;
Result := Result + ' ' + PCLenPartToString(Item[i], 3, 2);
end;
end;
{ TGDBMIDisassembler }
procedure TGDBMIDisassembler.DoDisassembleDestroyed(Sender: TObject);
begin
if FDisassembleEvalCmdObj = Sender
then FDisassembleEvalCmdObj := nil;
end;
procedure TGDBMIDisassembler.DoDisassembleProgress(Sender: TObject);
begin
Changed;
end;
procedure TGDBMIDisassembler.DoDisassembleExecuted(Sender: TObject);
begin
// Results were added from inside the TGDBMIDebuggerCommandDisassemble object
FLastExecAddr := TGDBMIDebuggerCommandDisassemble(Sender).StartAddr;
if dcsCanceled in TGDBMIDebuggerCommandDisassemble(Sender).SeenStates then begin
// TODO: fill a block of data with "canceled" info
FIsCancelled := True;
FCancelledAddr := TGDBMIDebuggerCommandDisassemble(Sender).StartAddr;
end;
FDisassembleEvalCmdObj := nil;
Changed;
end;
function TGDBMIDisassembler.PrepareEntries(AnAddr: TDbgPtr; ALinesBefore,
ALinesAfter: Integer): Boolean;
var
ForceQueue: Boolean;
begin
Result := False;
if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause])
then exit;
if FIsCancelled and (FCancelledAddr = AnAddr) then
exit;
if (FDisassembleEvalCmdObj <> nil)
then begin
if FDisassembleEvalCmdObj.State <> dcsQueued
then exit; // the request will be done again, after the next "Changed" (which should be the edn of the current command)
if (AnAddr < FDisassembleEvalCmdObj.StartAddr)
and (AnAddr >= FDisassembleEvalCmdObj.StartAddr
- (ALinesAfter + FDisassembleEvalCmdObj.LinesBefore) * DAssBytesPerCommandAvg)
then begin
// merge before
debugln(DBG_DISASSEMBLER, ['INFO: TGDBMIDisassembler.PrepareEntries MERGE request at START: NewStartAddr=', AnAddr,
' NewLinesBefore=', Max(ALinesBefore, FDisassembleEvalCmdObj.LinesBefore), ' OldStartAddr=', FDisassembleEvalCmdObj.StartAddr,
' OldLinesBefore=', FDisassembleEvalCmdObj.LinesBefore ]);
FDisassembleEvalCmdObj.StartAddr := AnAddr;
FDisassembleEvalCmdObj.LinesBefore := Max(ALinesBefore, FDisassembleEvalCmdObj.LinesBefore);
exit;
end;
if (AnAddr > FDisassembleEvalCmdObj.EndAddr)
and (AnAddr <= FDisassembleEvalCmdObj.EndAddr
+ (ALinesBefore + FDisassembleEvalCmdObj.LinesAfter) * DAssBytesPerCommandAvg)
then begin
// merge after
debugln(DBG_DISASSEMBLER, ['INFO: TGDBMIDisassembler.PrepareEntries MERGE request at END: NewEndAddr=', AnAddr,
' NewLinesAfter=', Max(ALinesAfter, FDisassembleEvalCmdObj.LinesAfter), ' OldEndAddr=', FDisassembleEvalCmdObj.EndAddr,
' OldLinesAfter=', FDisassembleEvalCmdObj.LinesAfter ]);
FDisassembleEvalCmdObj.EndAddr := AnAddr;
FDisassembleEvalCmdObj.LinesAfter := Max(ALinesAfter, FDisassembleEvalCmdObj.LinesAfter);
exit;
end;
exit;
end;
FDisassembleEvalCmdObj := TGDBMIDebuggerCommandDisassemble.Create
(TGDBMIDebugger(Debugger), EntryRanges, AnAddr, AnAddr, ALinesBefore, ALinesAfter);
FDisassembleEvalCmdObj.OnExecuted := @DoDisassembleExecuted;
FDisassembleEvalCmdObj.OnProgress := @DoDisassembleProgress;
FDisassembleEvalCmdObj.OnDestroy := @DoDisassembleDestroyed;
FDisassembleEvalCmdObj.Priority := GDCMD_PRIOR_DISASS;
FDisassembleEvalCmdObj.Properties := [dcpCancelOnRun];
ForceQueue := (TGDBMIDebugger(Debugger).FCurrentCommand <> nil)
and (TGDBMIDebugger(Debugger).FCurrentCommand is TGDBMIDebuggerCommandExecute)
and (not TGDBMIDebuggerCommandExecute(TGDBMIDebugger(Debugger).FCurrentCommand).NextExecQueued)
and (Debugger.State <> dsInternalPause);
TGDBMIDebugger(Debugger).QueueCommand(FDisassembleEvalCmdObj, ForceQueue);
(* DoDepthCommandExecuted may be called immediately at this point *)
Result := FDisassembleEvalCmdObj = nil; // already executed
end;
function TGDBMIDisassembler.HandleRangeWithInvalidAddr(ARange: TDBGDisassemblerEntryRange;
AnAddr: TDbgPtr; var ALinesBefore, ALinesAfter: Integer): boolean;
var
i, c: Integer;
begin
if AnAddr = FLastExecAddr
then begin
i := 0;
c := ARange.Count;
while i < c do
begin
if ARange.EntriesPtr[i]^.Addr > AnAddr
then break;
inc(i);
end;
if i > 0
then dec(i);
ALinesBefore := i;
ALinesAfter := ARange.Count - 1 - i;
Result := True;
exit;
end;
Result := inherited HandleRangeWithInvalidAddr(ARange, AnAddr, ALinesBefore, ALinesAfter);
end;
procedure TGDBMIDisassembler.Clear;
begin
FIsCancelled := False;
inherited Clear;
if FDisassembleEvalCmdObj <> nil
then begin
FDisassembleEvalCmdObj.OnExecuted := nil;
FDisassembleEvalCmdObj.OnDestroy := nil;
FDisassembleEvalCmdObj.Cancel;
end;
FDisassembleEvalCmdObj := nil;
end;
function TGDBMIDisassembler.PrepareRange(AnAddr: TDbgPtr; ALinesBefore,
ALinesAfter: Integer): Boolean;
begin
if AnAddr <> FLastExecAddr
then FLastExecAddr := 0;
Result := inherited PrepareRange(AnAddr, ALinesBefore, ALinesAfter);
end;
{ TGDBMIDebuggerCommandDisassembe }
procedure TGDBMIDebuggerCommandDisassemble.DoProgress;
begin
if assigned(FOnProgress)
then FOnProgress(Self);
end;
{$ifdef disassemblernestedproc}
function TGDBMIDebuggerCommandDisassemble.DoExecute: Boolean;
{$endif}
const
TrustedValidity = [avFoundFunction, avFoundRange, avFoundStatement];
procedure PadAddress(var AnAddr: TDisassemblerAddress; APad: Integer);
begin
{$PUSH}{$Q-}{$R-}// APad can be negative, but will be expanded to TDbgPtr (QWord)
AnAddr.Value := AnAddr.Value + APad;
{$POP}
AnAddr.Validity := avPadded;
AnAddr.Offset := -1;
end;
function {$ifndef disassemblernestedproc}TGDBMIDebuggerCommandDisassemble.{$endif}ExecDisassmble(AStartAddr, AnEndAddr: TDbgPtr; WithSrc: Boolean;
AResultList: TGDBMIDisassembleResultList = nil;
ACutBeforeEndAddr: Boolean = False): TGDBMIDisassembleResultList;
var
WS: Integer;
R: TGDBMIExecResult;
begin
WS := 0;
if WithSrc
then WS := 1;;
Result := AResultList;
ExecuteCommand('-data-disassemble -s %u -e %u -- %d', [AStartAddr, AnEndAddr, WS], R);
if Result <> nil
then Result.Init(R)
else Result := TGDBMIDisassembleResultList.Create(R);
if ACutBeforeEndAddr and Result.HasSourceInfo
then Result.SortByAddress;
while ACutBeforeEndAddr and (Result.Count > 0) and (Result.LastItem^.Addr >= AnEndAddr)
do Result.Count := Result.Count - 1;
end;
// Set Value, based on GuessedValue
function {$ifndef disassemblernestedproc}TGDBMIDebuggerCommandDisassemble.{$endif}AdjustToKnowFunctionStart(var AStartAddr: TDisassemblerAddress): Boolean;
var
DisAssList: TGDBMIDisassembleResultList;
DisAssItm: PDisassemblerEntry;
s: TDBGPtr;
begin
Result := False;
// TODO: maybe try "info symbol <addr>
s := (AStartAddr.GuessedValue -1) div 4 * 4; // 4 byte boundary
DisAssList := ExecDisassmble(s, s+1, False);
if DisAssList.Count > 0 then begin
DisAssItm := DisAssList.Item[0];
if (DisAssItm^.FuncName <> '') and (DisAssItm^.Addr <> 0) and (DisAssItm^.Offset >= 0)
then begin
AStartAddr.Value := DisAssItm^.Addr - DisAssItm^.Offset; // This should always be good
AStartAddr.Offset := 0;
AStartAddr.Validity := avFoundFunction;
Result := True;
end;
end;
FreeAndNil(DisAssList);
end;
procedure AdjustLastEntryEndAddr(const ARange: TDBGDisassemblerEntryRange;
const ADisAssList: TGDBMIDisassembleResultList);
var
i: Integer;
TmpAddr: TDBGPtr;
begin
if ARange.Count = 0 then exit;
TmpAddr := ARange.LastAddr;
i := 0;
while (i < ADisAssList.Count) and (ADisAssList.Item[i]^.Addr <= TmpAddr) do inc(i);
if i < ADisAssList.Count
then ARange.LastEntryEndAddr := ADisAssList.Item[i]^.Addr
else if ARange.LastEntryEndAddr <= ARange.RangeEndAddr
then ARange.LastEntryEndAddr := ARange.RangeEndAddr + 1;
end;
procedure CopyToRange(const ADisAssList: TGDBMIDisassembleResultList;
const ADestRange: TDBGDisassemblerEntryRange; AFromIndex, ACount: Integer;
ASrcInfoDisAssList: TGDBMIDisassembleResultList = nil);
var
i, j, MinInSrc, MaxInSrc: Integer;
ItmPtr, ItmPtr2, LastItem: PDisassemblerEntry;
begin
if ASrcInfoDisAssList = ADisAssList
then ASrcInfoDisAssList := nil;
if ADisAssList.Count = 0 then
exit;
// Clean end of range
ItmPtr := ADisAssList.Item[AFromIndex];
i := ADestRange.Count;
while (i > 0) and (ADestRange.EntriesPtr[i-1]^.Addr >= ItmPtr^.Addr) do dec(i);
if ADestRange.Count <> i then debugln(DBG_DISASSEMBLER, ['NOTICE, CopyToRange: Removing ',i,' entries from the end of Range. AFromIndex=',AFromIndex, ' ACount=', ACount, ' Range=',dbgs(ADestRange)]);
ADestRange.Count := i;
if i > 0 then begin
ItmPtr2 := ADestRange.EntriesPtr[i-1];
if ItmPtr2^.Dump <> '' then begin
{$PUSH}{$IFnDEF DBGMI_WITH_DISASS_OVERFLOW}{$Q-}{$R-}{$ENDIF} // Overflow is allowed to occur
j := (ItmPtr^.Addr - ItmPtr2^.Addr) * 2;
{$POP}
if length(ItmPtr2^.Dump) > j then debugln(DBG_DISASSEMBLER, ['NOTICE, CopyToRange: Shortening Dump at the end of Range. AFromIndex=',AFromIndex, ' ACount=', ACount, ' Range=',dbgs(ADestRange)]);
if length(ItmPtr2^.Dump) > j then ItmPtr2^.Dump := copy(ItmPtr2^.Dump, 1, j);
end;
end;
if ADestRange.Count = 0
then ADestRange.RangeStartAddr := ADisAssList.Item[AFromIndex]^.Addr;
if ADestRange.RangeEndAddr < ADisAssList.Item[AFromIndex+ACount-1]^.Addr
then ADestRange.RangeEndAddr := ADisAssList.Item[AFromIndex+ACount-1]^.Addr;
if ADisAssList.Count > AFromIndex + ACount
then begin
if ADestRange.LastEntryEndAddr < ADisAssList.Item[AFromIndex+ACount]^.Addr
then ADestRange.LastEntryEndAddr := ADisAssList.Item[AFromIndex+ACount]^.Addr;
end
else
if ADestRange.LastEntryEndAddr <= ADestRange.RangeEndAddr
then ADestRange.LastEntryEndAddr := ADestRange.RangeEndAddr + 1;
// Append new items
LastItem := nil;
MinInSrc := 0;
if ASrcInfoDisAssList <> nil
then MaxInSrc := ASrcInfoDisAssList.Count - 1;
for i := AFromIndex to AFromIndex + ACount - 1 do begin
ItmPtr := ADisAssList.Item[i];
ItmPtr2 := nil;
if ASrcInfoDisAssList <> nil
then begin
j := MinInSrc;
while j <= MaxInSrc do begin
ItmPtr2 := ASrcInfoDisAssList.Item[j];
if ItmPtr2^.Addr = itmPtr^.Addr
then break;
inc(j);
end;
if j <= MaxInSrc
then begin
ItmPtr2^.Dump := ItmPtr^.Dump;
ItmPtr := ItmPtr2;
end
else ItmPtr2 := nil;
end;
if (LastItem <> nil) then begin
// unify strings, to keep only one instance
if (ItmPtr^.SrcFileName = LastItem^.SrcFileName)
then ItmPtr^.SrcFileName := LastItem^.SrcFileName;
if (ItmPtr^.FuncName = LastItem^.FuncName)
then ItmPtr^.FuncName:= LastItem^.FuncName;
end;
ADestRange.Append(ItmPtr);
// now we can move the data, pointed to by ItmPtr // reduce search range
if ItmPtr2 <> nil
then begin
// j is valid
if j = MaxInSrc
then dec(MaxInSrc)
else if j = MinInSrc
then inc(MinInSrc)
else begin
ASrcInfoDisAssList.Item[j] := ASrcInfoDisAssList.Item[MaxInSrc];
dec(MaxInSrc);
end;
end;;
LastItem := ItmPtr;
end;
// Src list may be reused for other addresses, so discard used entries
if ASrcInfoDisAssList <> nil
then begin
for i := 0 to Min(MinInSrc - 1, MaxInSrc - MinInSrc) do
ASrcInfoDisAssList.Item[i] := ASrcInfoDisAssList.Item[i + MinInSrc];
ASrcInfoDisAssList.Count := MaxInSrc + 1 - MinInSrc;
end;
end;
procedure AddMemDumpToRange(const ARange: TDBGDisassemblerEntryRange;
AMemDump: TGDBMIMemoryDumpResultList; AFirstAddr, ALastAddr: TDBGPtr);
var
i, Cnt, FromIndex: Integer;
Itm, NextItm: PDisassemblerEntry;
Addr, Offs, Len: TDBGPtr;
s: String;
begin
Cnt := ARange.Count;
if ARange.FirstAddr > AFirstAddr
then FromIndex := -1
else FromIndex := ARange.IndexOfAddrWithOffs(AFirstAddr)-1;
if FromIndex < -1
then exit;
NextItm := ARange.EntriesPtr[FromIndex + 1];
while NextItm <> nil do
begin
inc(FromIndex);
Itm := NextItm;
if Itm^.Addr > ALastAddr
then break;
if FromIndex < Cnt - 1
then NextItm := ARange.EntriesPtr[FromIndex + 1]
else NextItm := nil;
if (Itm^.Dump <> '')
then Continue;
Itm^.Dump := ' ';
{$PUSH}{$IFnDEF DBGMI_WITH_DISASS_OVERFLOW}{$Q-}{$R-}{$ENDIF} // Overflow is allowed to occur
Addr := Itm^.Addr;
Offs := TDBGPtr(Addr - AMemDump.Addr);
if (Offs >= AMemDump.Count)
then Continue;
if (NextItm <> nil) //and (NextItm^.Addr > Addr)
then Len := NextItm^.Addr - Addr
else Len := AMemDump.Count - 1 - Offs;
if Offs + Len >= AMemDump.Count
then Len := AMemDump.Count - 1 - Offs;
if Len = 0
then Continue;
if Len > 32
then Len := 32;
{$POP}
s := '';
for i := Offs to Offs + Len - 1 do
s := s + Copy(AMemDump.ItemTxt[i],3,2);
Itm^.Dump := s;
end;
end;
(* Known issues with GDB's disassembler results:
** "-data-disassemble -s ### -e ### -- 1" with source
* Result may not be sorted by addresses
=>
* Result may be empty, even where "-- 0" (no src info) does return data
=> Remedy: disassemble those secions without src-info
If function-offset is available, this can be done per function
* Result may be missing src-info, even if src-info is available for parts of the result
This seems to be the case, if no src info is available for the start address,
then src-info for later addresses will be ignored.
=> Remedy: if function offset is available, disassembl;e per function
* Contains address gaps, as it does not show fillbytes, between functions
** "-data-disassemble -s ### -e ### -- 0" without source (probably both (with/without src)
* "func-name" may change, while "offset" keeps increasing
This was seen after the end of a procedure, with 0x00 bytes filling up to the next proc
=> Remedy: None, can be ignored
* In contineous disassemble a function may not be started at offset=0.
This seems to happen after 0x00 fill bytes.
The func-name changes and the offset restarts at a lower value (but not 0)
=> Remedy: discard data, and re-disassemble
*)
// Returns True: If some data was added
// False: if failed to add anything
function {$ifndef disassemblernestedproc}TGDBMIDebuggerCommandDisassemble.{$endif}DoDisassembleRange(AnEntryRanges: TDBGDisassemblerEntryMap;AFirstAddr,
ALastAddr: TDisassemblerAddress; StopAfterAddress: TDBGPtr;
StopAfterNumLines: Integer): Boolean;
procedure AddRangetoMemDumpsNeeded(NewRange: TDBGDisassemblerEntryRange);
var
i: Integer;
begin
i := length(FMemDumpsNeeded);
if (i > 0)
then begin
if (NewRange.RangeStartAddr <= FMemDumpsNeeded[0].FirstAddr)
and (NewRange.LastEntryEndAddr + 1 >= FMemDumpsNeeded[0].FirstAddr)
then FMemDumpsNeeded[0].FirstAddr := NewRange.RangeStartAddr
else
if (NewRange.LastEntryEndAddr + 1 >= FMemDumpsNeeded[0].LastAddr)
and (NewRange.RangeStartAddr <= FMemDumpsNeeded[0].LastAddr)
then FMemDumpsNeeded[0].LastAddr := NewRange.LastEntryEndAddr + 1
else
if (NewRange.RangeStartAddr <= FMemDumpsNeeded[i-1].FirstAddr)
and (NewRange.LastEntryEndAddr + 1 >= FMemDumpsNeeded[i-1].FirstAddr)
then FMemDumpsNeeded[i-1].FirstAddr := NewRange.RangeStartAddr
else
if (NewRange.LastEntryEndAddr + 1 >= FMemDumpsNeeded[i-1].LastAddr)
and (NewRange.RangeStartAddr <= FMemDumpsNeeded[i-1].LastAddr)
then FMemDumpsNeeded[i-1].LastAddr := NewRange.LastEntryEndAddr + 1
else begin
SetLength(FMemDumpsNeeded, i + 1);
FMemDumpsNeeded[i].FirstAddr := NewRange.RangeStartAddr;
FMemDumpsNeeded[i].LastAddr := NewRange.LastEntryEndAddr + 1;
end;
end
else begin
SetLength(FMemDumpsNeeded, i + 1);
FMemDumpsNeeded[i].FirstAddr := NewRange.RangeStartAddr;
FMemDumpsNeeded[i].LastAddr := NewRange.LastEntryEndAddr + 1;
end;
end;
procedure DoDisassembleSourceless(ASubFirstAddr, ASubLastAddr: TDBGPtr;
ARange: TDBGDisassemblerEntryRange; SkipFirstAddresses: Boolean = False);
var
DisAssList, DisAssListCurrentSub: TGDBMIDisassembleResultList;
DisAssIterator: TGDBMIDisassembleResultFunctionIterator;
i: Integer;
begin
DisAssListCurrentSub := nil;
DisAssList := ExecDisassmble(ASubFirstAddr, ASubLastAddr, False, nil, True);
if DisAssList.Count > 0 then begin
i := 0;
if SkipFirstAddresses
then i := 1; // skip the instruction exactly at ASubFirstAddr;
DisAssIterator := TGDBMIDisassembleResultFunctionIterator.Create
(DisAssList, i, ASubLastAddr, FStartAddr, 0);
ARange.Capacity := Max(ARange.Capacity, ARange.Count + DisAssList.Count);
// add without source
while not DisAssIterator.EOL
do begin
DisAssIterator.NextSubList(DisAssListCurrentSub);
// ignore StopAfterNumLines, until we have at least the source;
if (not DisAssIterator.IsFirstSubList) and (DisAssListCurrentSub.Item[0]^.Offset <> 0)
then begin
// Current block starts with offset. Adjust and disassemble again
debugln(DBG_DISASSEMBLER, ['WARNING: Sublist not at offset 0 (filling gap in/before Src-Info): FromIdx=', DisAssIterator.CurrentIndex, ' NextIdx=', DisAssIterator.NextIndex,
' SequenceNo=', DisAssIterator.SublistNumber, ' StartIdx=', DisAssIterator.IndexOfLocateAddress, ' StartOffs=', DisAssIterator.OffsetOfLocateAddress]);
DisAssListCurrentSub := ExecDisassmble(DisAssIterator.CurrentFixedAddr(DAssMaxRangeSize),
DisAssIterator.NextStartAddr, False, DisAssListCurrentSub, True);
end;
CopyToRange(DisAssListCurrentSub, ARange, 0, DisAssListCurrentSub.Count);
end;
FreeAndNil(DisAssIterator);
end;
FreeAndNil(DisAssList);
FreeAndNil(DisAssListCurrentSub);
end;
var
DisAssIterator: TGDBMIDisassembleResultFunctionIterator;
DisAssList, DisAssListCurrentSub, DisAssListWithSrc: TGDBMIDisassembleResultList;
i, Cnt, DisAssStartIdx: Integer;
NewRange: TDBGDisassemblerEntryRange;
OrigLastAddress, OrigFirstAddress: TDisassemblerAddress;
TmpAddr: TDBGPtr;
BlockOk, SkipDisAssInFirstLoop, ContinueAfterSource: Boolean;
Itm: TDisassemblerEntry;
begin
Result := False;
DisAssList := nil;
DisAssListCurrentSub := nil;
DisAssListWithSrc := nil;
DisAssIterator := nil;
OrigFirstAddress := AFirstAddr;
OrigLastAddress := ALastAddr;
SkipDisAssInFirstLoop := False;
NewRange := TDBGDisassemblerEntryRange.Create;
// set some values, wil be adjusted later (in CopyToRange
NewRange.RangeStartAddr := AFirstAddr.Value;
NewRange.RangeEndAddr := ALastAddr.Value;
NewRange.LastEntryEndAddr := ALastAddr.Value;
// No nice startingpoint found, just start to disassemble aprox 5 instructions before it
// and hope that when we started in the middle of an instruction it get sorted out.
// If so, the 4st for lines from the result must be discarded
if not (AFirstAddr.Validity in TrustedValidity)
then PadAddress(AFirstAddr, - 5 * DAssBytesPerCommandMax);
// Adjust ALastAddr
if ALastAddr.Value <= AFirstAddr.Value
then begin
ALastAddr.Value := AFirstAddr.Value;
PadAddress(ALastAddr, 2 * DAssBytesPerCommandMax);
end
else
if not (ALastAddr.Validity in TrustedValidity)
then PadAddress(ALastAddr, 2 * DAssBytesPerCommandMax);
DebugLnEnter(DBG_DISASSEMBLER, ['INFO: DoDisassembleRange for AFirstAddr =', Dbgs(AFirstAddr),
' ALastAddr=', Dbgs(ALastAddr), ' OrigFirst=', Dbgs(OrigFirstAddress), ' OrigLastAddress=', Dbgs(OrigLastAddress),
' StopAffterAddr=', StopAfterAddress, ' StopAfterLines=', StopAfterNumLines ]);
try // only needed for debugln DBG_DISASSEMBLER,
// check if we have an overall source-info
// we can only do that, if we know the offset of firstaddr (limit to DAssRangeOverFuncTreshold avg lines, should be enough)
// TODO: limit offset ONLY, if previous range known (already have disass)
if (AFirstAddr.Offset >= 0)
then begin
TmpAddr := AFirstAddr.Value - Min(AFirstAddr.Offset, DAssRangeOverFuncTreshold * DAssBytesPerCommandAvg);
DisAssListWithSrc := ExecDisassmble(TmpAddr, ALastAddr.Value, True);
end;
if (DisAssListWithSrc <> nil) and (DisAssListWithSrc.Count > 0) and DisAssListWithSrc.HasSourceInfo
then begin
DisAssListWithSrc.SortByAddress;
// gdb may return data far out of range.
if (DisAssListWithSrc.LastItem^.Addr < TmpAddr) and
(TmpAddr - DisAssListWithSrc.LastItem^.Addr > DAssMaxRangeSize)
then FreeAndNil(DisAssListWithSrc);
end;
if (DisAssListWithSrc <> nil) and (DisAssListWithSrc.Count > 0) and DisAssListWithSrc.HasSourceInfo
then begin
(* ***
*** Add the full source info
***
*)
Result := True;
//DisAssListWithSrc.SortByAddress;
if DisAssListWithSrc.Item[0]^.Addr > AFirstAddr.Value
then begin
// fill in gap at start
DoDisassembleSourceless(AFirstAddr.Value, DisAssListWithSrc.Item[0]^.Addr, NewRange);
end;
// Find out what comes after the disassembled source (need at least one statemnet, to determine end-add of last src-stmnt)
TmpAddr := DisAssListWithSrc.LastItem^.Addr;
ContinueAfterSource := OrigLastAddress.Value > TmpAddr;
if ContinueAfterSource
then TmpAddr := ALastAddr.Value;
DisAssList := ExecDisassmble(DisAssListWithSrc.LastItem^.Addr,
TmpAddr + 2 * DAssBytesPerCommandAlign, False);
// Add the known source list
if DisAssList.Count < 2
then TmpAddr := ALastAddr.Value
else TmpAddr := DisAssList.Item[1]^.Addr;
DisAssIterator := TGDBMIDisassembleResultFunctionIterator.Create
(DisAssListWithSrc, 0, TmpAddr , FStartAddr, StopAfterAddress);
NewRange.Capacity := Max(NewRange.Capacity, NewRange.Count + DisAssListWithSrc.Count);
while not DisAssIterator.EOL
do begin
if (dcsCanceled in SeenStates) then break;
DisAssIterator.NextSubList(DisAssListCurrentSub);
CopyToRange(DisAssListCurrentSub, NewRange, 0, DisAssListCurrentSub.Count); // Do not add the Sourcelist as last param, or it will get re-sorted
// check for gap
if DisAssListCurrentSub.LastItem^.Addr < DisAssIterator.NextStartAddr - DAssBytesPerCommandAlign
then begin
debugln(DBG_DISASSEMBLER, ['Info: Filling GAP in the middle of Source: Src-FromIdx=', DisAssIterator.CurrentIndex, ' Src-NextIdx=', DisAssIterator.NextIndex,
' Src-SequenceNo=', DisAssIterator.SublistNumber, ' Last Address in Src-Block=', DisAssListCurrentSub.LastItem^.Addr ]);
DoDisassembleSourceless(DisAssListCurrentSub.LastItem^.Addr, DisAssIterator.NextStartAddr, NewRange, True);
end;
end;
i := DisAssIterator.CountLinesAfterCounterAddr;
FreeAndNil(DisAssIterator);
FreeAndNil(DisAssListWithSrc);
FreeAndNil(DisAssListCurrentSub);
// Source Completly Added
if not ContinueAfterSource
then begin
AdjustLastEntryEndAddr(NewRange, DisAssList);
AddRangetoMemDumpsNeeded(NewRange);
AnEntryRanges.AddRange(NewRange); // NewRange is now owned by AnEntryRanges
NewRange := nil;
FreeAndNil(DisAssList);
exit;
end;
// continue with the DisAsslist for the remainder
AFirstAddr.Validity := avFoundFunction; // if we got source, then start is ok (original start is kept)
DisAssStartIdx := 1;
SkipDisAssInFirstLoop := True;
if i > 0
then StopAfterNumLines := StopAfterNumLines - i;
(* ***
*** Finished adding the full source info
***
*)
end
else begin
(* ***
*** Full Source was not available
***
*)
if (DisAssListWithSrc <> nil) and (DisAssListWithSrc.Count > 0)
then begin
DisAssList := DisAssListWithSrc; // got data already
DisAssListWithSrc := nil;
end
else begin
DisAssList := ExecDisassmble(AFirstAddr.Value, ALastAddr.Value, False);
end;
if DisAssList.Count < 2
then begin
debugln('Error failed to get enough data for dsassemble');
// create a dummy range, so we will not retry
NewRange.Capacity := 1;
NewRange.RangeStartAddr := AFirstAddr.Value;
if OrigLastAddress.Value > AFirstAddr.Value+1
then NewRange.RangeEndAddr := OrigLastAddress.Value
else NewRange.RangeEndAddr := AFirstAddr.Value+1;
NewRange.LastEntryEndAddr := AFirstAddr.Value+1;
Itm.Addr := AFirstAddr.Value;
Itm.Dump := ' ';
Itm.SrcFileLine := 0;
Itm.Offset := 0;
itm.Statement := '<error>';
NewRange.Append(@Itm);
AnEntryRanges.AddRange(NewRange); // NewRange is now owned by AnEntryRanges
NewRange := nil;
FreeAndNil(DisAssList);
exit;
end;
DisAssStartIdx := 0;
end;
// we may have gotten more lines than ask, and the last line we don't know the length
Cnt := DisAssList.Count;
if (ALastAddr.Validity = avPadded) or (DisAssList.LastItem^.Addr >= ALastAddr.Value)
then begin
ALastAddr.Value := DisAssList.LastItem^.Addr;
ALastAddr.Validity := avFoundStatement;
dec(Cnt);
DisAssList.Count := Cnt;
end;
// ALastAddr.Value is now the address after the last statement;
if (AFirstAddr.Validity = avPadded) // always False, if we had source-info
then begin
// drop up to 4 entries, if possible
while (DisAssStartIdx < 4) and (DisAssStartIdx + 1 < Cnt) and (DisAssList.Item[DisAssStartIdx+1]^.Addr <= OrigFirstAddress.Value)
do inc(DisAssStartIdx);
AFirstAddr.Value := DisAssList.Item[DisAssStartIdx]^.Addr;
AFirstAddr.Validity := avFoundStatement;
end;
NewRange.Capacity := Max(NewRange.Capacity, NewRange.Count + Cnt);
DisAssIterator := TGDBMIDisassembleResultFunctionIterator.Create
(DisAssList, DisAssStartIdx, ALastAddr.Value, FStartAddr, StopAfterAddress);
while not DisAssIterator.EOL
do begin
if (dcsCanceled in SeenStates) then break;
BlockOk := DisAssIterator.NextSubList(DisAssListCurrentSub);
// Do we have enough lines (without the current block)?
if (DisAssIterator.CountLinesAfterCounterAddr > StopAfterNumLines)
then begin
DebugLn(DBG_DISASSEMBLER, ['INFO: Got enough line in Iteration: CurrentIndex=', DisAssIterator.CurrentIndex]);
NewRange.LastEntryEndAddr := DisAssIterator.NextStartAddr;
//AdjustLastEntryEndAddr(NewRange, DisAssList);
break;
end;
if (not DisAssIterator.IsFirstSubList) and (DisAssListCurrentSub.Item[0]^.Offset <> 0)
then begin
// Got List with Offset at start
debugln(DBG_DISASSEMBLER, ['WARNING: Sublist not at offset 0 (offs=',DisAssListCurrentSub.Item[0]^.Offset,'): FromIdx=', DisAssIterator.CurrentIndex, ' NextIdx=', DisAssIterator.NextIndex,
' SequenceNo=', DisAssIterator.SublistNumber, ' StartIdx=', DisAssIterator.IndexOfLocateAddress, ' StartOffs=', DisAssIterator.OffsetOfLocateAddress]);
// Current block starts with offset. Adjust and disassemble again
// Try with source first, in case it returns dat without source
DisAssListWithSrc := ExecDisassmble(DisAssIterator.CurrentFixedAddr(DAssMaxRangeSize),
DisAssIterator.NextStartAddr, True, DisAssListWithSrc, True);
if (DisAssListWithSrc.Count > 0)
then begin
if DisAssListWithSrc.HasSourceInfo
then DisAssListWithSrc.SortByAddress;
if (not DisAssListWithSrc.HasSourceInfo)
or (DisAssListWithSrc.LastItem^.Addr > DisAssIterator.NextStartAddr - DAssBytesPerCommandAlign)
then begin
// no source avail, but got data
// OR source and no gap
CopyToRange(DisAssListWithSrc, NewRange, 0, DisAssListWithSrc.Count);
Result := True;
continue;
end;
end;
//get the source-less code as reference
DisAssListCurrentSub := ExecDisassmble(DisAssIterator.CurrentFixedAddr(DAssMaxRangeSize),
DisAssIterator.NextStartAddr, False, DisAssListCurrentSub, True);
CopyToRange(DisAssListCurrentSub, NewRange, 0, DisAssListCurrentSub.Count, DisAssListWithSrc);
Result := Result or (DisAssListCurrentSub.Count > 0);
continue;
end;
// Todo: Check for wrong start stmnt offset
if BlockOk
then begin
// Got a good block
if (DisAssListCurrentSub.Item[0]^.FuncName <> '')
then begin
// Try to get source-info (up to DisAssIterator.NextStartAddr)
// Subtract offset from StartAddress, in case this is the first block
// (we may continue existing data, but src info must be retrieved in full, or may be incomplete)
if not( DisAssIterator.IsFirstSubList and SkipDisAssInFirstLoop )
then begin
DisAssListWithSrc := ExecDisassmble(DisAssIterator.CurrentFixedAddr(DAssMaxRangeSize),
DisAssIterator.NextStartAddr, True, DisAssListWithSrc, True);
// We may have less lines with source, as we stripped padding at the end
if (DisAssListWithSrc <> nil) and DisAssListWithSrc.HasSourceInfo
then begin
CopyToRange(DisAssListCurrentSub, NewRange, 0, DisAssListCurrentSub.Count, DisAssListWithSrc);
Result := Result or (DisAssListCurrentSub.Count > 0);
continue;
end;
end;
end;
CopyToRange(DisAssListCurrentSub, NewRange, 0, DisAssListCurrentSub.Count);
Result := Result or (DisAssListCurrentSub.Count > 0);
continue;
end;
// Got a problematic block
debugln(DBG_DISASSEMBLER, ['WARNING: FindProcEnd reported an issue FromIdx=', DisAssIterator.CurrentIndex,' NextIdx=',
DisAssIterator.NextIndex, ' StartIdx=', DisAssIterator.IndexOfLocateAddress, ' StartOffs=', DisAssIterator.OffsetOfLocateAddress]);
//if DisAssIterator.IsFirstSubList and (not(AFirstAddr.Validity in TrustedValidity))
//and (DisAssIterator.IndexOfLocateAddress >= DisAssIterator.CurrentIndex) // in current list
//and (DisAssIterator.OffsetOfLocateAddress <> 0)
//then begin
// // FStartAddr is in the middle of a statement. Maybe move the Range?
//end;
CopyToRange(DisAssListCurrentSub, NewRange, 0, DisAssListCurrentSub.Count);
Result := Result or (DisAssListCurrentSub.Count > 0);
end;
if NewRange.LastEntryEndAddr > NewRange.RangeEndAddr
then NewRange.RangeEndAddr := NewRange.LastEntryEndAddr;
AddRangetoMemDumpsNeeded(NewRange);
AnEntryRanges.AddRange(NewRange); // NewRange is now owned by AnEntryRanges
NewRange := nil;
FreeAndNil(DisAssIterator);
FreeAndNil(DisAssList);
FreeAndNil(DisAssListCurrentSub);
FreeAndNil(DisAssListWithSrc);
finally
DebugLnExit(DBG_DISASSEMBLER, ['INFO: DoDisassembleRange finished' ]);
end;
end;
function {$ifndef disassemblernestedproc}TGDBMIDebuggerCommandDisassemble.{$endif}OnCheckCancel: boolean;
begin
result := dcsCanceled in SeenStates;
end;
{$ifndef disassemblernestedproc}
function TGDBMIDebuggerCommandDisassemble.DoExecute: Boolean;
{$endif disassemblernestedproc}
function ExecMemDump(AStartAddr: TDbgPtr; ACount: Cardinal;
AResultList: TGDBMIMemoryDumpResultList = nil): TGDBMIMemoryDumpResultList;
var
R: TGDBMIExecResult;
begin
Result := AResultList;
ExecuteCommand('-data-read-memory %u x 1 1 %u', [AStartAddr, ACount], R);
if Result <> nil
then Result.Init(R)
else Result := TGDBMIMemoryDumpResultList.Create(R);
end;
procedure AddMemDumps;
var
i: Integer;
MemDump: TGDBMIMemoryDumpResultList;
Rng: TDBGDisassemblerEntryRange;
FirstAddr: TDBGPtr;
begin
MemDump := nil;
for i := 0 to length(FMemDumpsNeeded) - 1 do
begin
if (dcsCanceled in SeenStates) then break;
FirstAddr := FMemDumpsNeeded[i].FirstAddr;
Rng := FRangeIterator.GetRangeForAddr(FirstAddr, True);
if rng <> nil
then MemDump := ExecMemDump(FirstAddr, FMemDumpsNeeded[i].LastAddr - FirstAddr, MemDump);
if DebuggerState <> dsError
then begin
while (Rng <> nil) and (Rng.FirstAddr <= FMemDumpsNeeded[i].LastAddr) do
begin
AddMemDumpToRange(Rng, MemDump, FMemDumpsNeeded[i].FirstAddr, FMemDumpsNeeded[i].LastAddr);
Rng := FRangeIterator.NextRange;
end;
end;
end;
FreeAndNil(MemDump);
end;
var
DisassembleRangeExtender: TDBGDisassemblerRangeExtender;
begin
FContext.ThreadContext := ccNotRequired;
FContext.StackContext := ccNotRequired;
if FEndAddr < FStartAddr
then FEndAddr := FStartAddr;
DisassembleRangeExtender := TDBGDisassemblerRangeExtender.Create(FKnownRanges);
try
DisassembleRangeExtender.OnDoDisassembleRange:=@DoDisassembleRange;
DisassembleRangeExtender.OnCheckCancel:=@OnCheckCancel;
DisassembleRangeExtender.OnAdjustToKnowFunctionStart:=@AdjustToKnowFunctionStart;
result := DisassembleRangeExtender.DisassembleRange(FLinesBefore, FLinesAfter, FStartAddr, FStartAddr);
finally
DisassembleRangeExtender.Free;
end;
DoProgress;
AddMemDumps;
DoProgress;
end;
constructor TGDBMIDebuggerCommandDisassemble.Create(AOwner: TGDBMIDebugger;
AKnownRanges: TDBGDisassemblerEntryMap; AStartAddr, AEndAddr: TDbgPtr; ALinesBefore,
ALinesAfter: Integer);
begin
inherited Create(AOwner);
FKnownRanges := AKnownRanges;
FRangeIterator:= TDBGDisassemblerEntryMapIterator.Create(FKnownRanges);
FStartAddr := AStartAddr;
FEndAddr := AEndAddr;
FLinesBefore := ALinesBefore;
FLinesAfter := ALinesAfter;
end;
destructor TGDBMIDebuggerCommandDisassemble.Destroy;
begin
FreeAndNil(FRangeIterator);
inherited Destroy;
end;
function TGDBMIDebuggerCommandDisassemble.DebugText: String;
begin
Result := Format('%s: FromAddr=%u ToAddr=%u LinesBefore=%d LinesAfter=%d',
[ClassName, FStartAddr, FEndAddr, FLinesBefore, FLinesAfter]);
end;
{ TGDBMIDebuggerCommandStartDebugging }
function TGDBMIDebuggerCommandStartDebugging.DoExecute: Boolean;
{$IF defined(UNIX) or defined(DBG_ENABLE_TERMINAL)}
procedure InitConsole;
var
R: TGDBMIExecResult;
s: String;
h: THandle;
isConsole: Boolean;
begin
isConsole := False;
// Make sure consule output will ot be mixed with gbd output
{$IFDEF DBG_ENABLE_TERMINAL}
{$IFDEF UNIX}
(* DBG_ENABLE_TERMINAL and UNIX *)
s := DebuggerProperties.ConsoleTty;
if s = '' then begin
FTheDebugger.FPseudoTerminal.Open;
s := FTheDebugger.FPseudoTerminal.Devicename;
isConsole := True;
end;
{$ELSE}
(* only DBG_ENABLE_TERMINAL *)
FTheDebugger.FPseudoTerminal.Open;
s := FTheDebugger.FPseudoTerminal.Devicename;
isConsole := True;
{$ENDIF}
{$ELSE}
(* only UNIX *)
s := DebuggerProperties.ConsoleTty;
if s = '' then s := '/dev/null';
{$ENDIF}
if not isConsole then begin
h := fileopen(S, fmOpenWrite);
isConsole := IsATTY(h) = 1;
FileClose(h);
end;
if isConsole then
isConsole := ExecuteCommand('set inferior-tty %s', [s], R) and (r.State <> dsError);
if not isConsole then
ExecuteCommand('set inferior-tty /dev/null', []);
end;
{$ENDIF}
var
FndOffsFile, FndOffsLine: String;
StoppedFile, StoppedLine: String;
StoppedAddr: TDBGPtr;
StoppedAtEntryPoint: Boolean;
const
MIN_RELOC_ADDRESS = $4000;
procedure RunToMain(EntryPoint: String);
type
TRunToMainType = (mtMain, mtMainAddr, mtEntry, mtAddZero);
var
EntryPointNum: TDBGPtr;
function SetMainBrk: boolean;
procedure MaybeAddMainBrk(AType: TRunToMainType; AnSkipIfCntGreater: Integer;
ACheckEntryPoinReloc: Boolean = false);
begin
// Check if the Entrypoint looks promising (if it looks like it matches the relocated address)
if ACheckEntryPoinReloc and not(EntryPointNum > MIN_RELOC_ADDRESS) then
exit;
// Check amount of already set breakpoints
if (AnSkipIfCntGreater >= 0) and (FTheDebugger.FMainAddrBreak.BreakSetCount > AnSkipIfCntGreater) then
exit;
case AType of
mtMain: FTheDebugger.FMainAddrBreak.SetByName(Self);
mtMainAddr: FTheDebugger.FMainAddrBreak.SetByAddr(Self);
mtEntry: FTheDebugger.FMainAddrBreak.SetAtCustomAddr(Self, StrToQWordDef(EntryPoint, 0));
mtAddZero: FTheDebugger.FMainAddrBreak.SetAtLineOffs(Self, 0);
end;
if (AType = mtAddZero) and (FndOffsFile = '') then begin
FndOffsLine := FTheDebugger.FMainAddrBreak.BreakLine[iblAddOffset];
if (FndOffsLine <> '') then
FndOffsFile := FTheDebugger.FMainAddrBreak.BreakFile[iblAddOffset];
end;
end;
var
bcnt: Integer;
begin
Result := False;
bcnt := FTheDebugger.FMainAddrBreak.BreakSetCount;
case DebuggerProperties.InternalStartBreak of
gdsbEntry: begin
MaybeAddMainBrk(mtEntry, -1, true);
if not FTheDebugger.FMainAddrBreak.IsBreakSet then begin
MaybeAddMainBrk(mtEntry, -1, false);
MaybeAddMainBrk(mtAddZero, -1);
// set only, if no other is set (e.g. 2nd attempt)
MaybeAddMainBrk(mtMainAddr, 0);
MaybeAddMainBrk(mtMain, 0);
end;
end;
gdsbMainAddr: begin
MaybeAddMainBrk(mtMainAddr, -1);
// set only, if no other is set (e.g. 2nd attempt)
if not FTheDebugger.FMainAddrBreak.IsBreakSet then begin
MaybeAddMainBrk(mtEntry, 0, true);
MaybeAddMainBrk(mtAddZero, 1);
MaybeAddMainBrk(mtEntry, 0, false);
MaybeAddMainBrk(mtMain, 0);
end;
end;
gdsbMain: begin
MaybeAddMainBrk(mtMain, -1);
// set only, if no other is set (e.g. 2nd attempt)
MaybeAddMainBrk(mtAddZero, 0);
MaybeAddMainBrk(mtMainAddr, 0);
MaybeAddMainBrk(mtEntry, 0, false);
end;
gdsbAddZero: begin
MaybeAddMainBrk(mtAddZero, -1);
// set only, if no other is set (e.g. 2nd attempt)
MaybeAddMainBrk(mtEntry, 0, true);
MaybeAddMainBrk(mtMain, 0);
MaybeAddMainBrk(mtEntry, 0, false);
MaybeAddMainBrk(mtMainAddr, 0);
end;
else begin // gdsbDefault
// SetByName: "main", this is the best aproach, unless any library also exports main.
MaybeAddMainBrk(mtMain, -1);
MaybeAddMainBrk(mtEntry, -1, true); // Previous versions used "+0" as 2nd in the list
MaybeAddMainBrk(mtAddZero, -1);
MaybeAddMainBrk(mtMainAddr, 2); // set only, if less than 2 are set
// set only, if no other is set (e.g. 2nd attempt)
MaybeAddMainBrk(mtEntry, 0, false);
end;
end;
Result := bcnt < FTheDebugger.FMainAddrBreak.BreakSetCount; // added new breaks
end;
function ParseLogForPid(ALogTxt: String): Integer;
var
s: String;
begin
s := GetPart(['=thread-group-started,'], [LineEnding], ALogTxt, True, False);
if s <> '' then
s := GetPart(['pid="'], ['"'], s, True, False);
if s <> '' then begin
Result := StrToIntDef(s, 0);
if Result <> 0 then exit;
end;
s := GetPart(['process '], [' local', ']'], ALogTxt, True);
Result := StrToIntDef(s, 0);
end;
function ParseStopped(AParam: String): Integer;
var
List: TGDBMINameValueList;
Reason: String;
begin
Result := -1; // no id found
List := nil;
try
List := TGDBMINameValueList.Create(AParam);
Reason := List.Values['reason'];
if (Reason = 'exited-normally') or (Reason = 'exited') or
(Reason = 'exited-signalled')
then
Result := -2;
// if Reason = 'signal-received' // Pause ?
if Reason = 'breakpoint-hit' then begin
Result := StrToIntDef(List.Values['bkptno'], -1);
StoppedAtEntryPoint := Result = FTheDebugger.FMainAddrBreak.BreakId[iblCustomAddr];
List.SetPath('frame');
StoppedAddr := StrToInt64Def(List.Values['addr'], -1);
StoppedFile := List.Values['fullname'];
if StoppedFile = '' then
StoppedFile := List.Values['file'];
StoppedLine := List.Values['line'];
end;
except
end;
List.Free;
end;
var
R: TGDBMIExecResult;
Cmd, s, s2, rval: String;
i, j, LoopCnt: integer;
List: TGDBMINameValueList;
BrkErr: Boolean;
begin
EntryPointNum := StrToQWordDef(EntryPoint, 0);
TargetInfo^.TargetPID := 0;
FDidKillNow := False;
// TODO: async
Cmd := GdbRunCommand;// '-exec-run';
rval := '';
R.State := dsError;
FTheDebugger.FMainAddrBreak.Clear(Self);
LoopCnt := 6; // max iterations
while (LoopCnt > 0) and not(DebuggerState = dsError) do begin
dec(LoopCnt);
SetMainBrk;
if not FTheDebugger.FMainAddrBreak.IsBreakSet
then begin
(* TODO:
If no main break can be set, it may still be possible (desirable) to run
the app, without debug-capacbilities
Or maybe even try to set all breakpoints.
*)
SetDebuggerErrorState(Format(gdbmiCommandStartMainBreakError, [LineEnding]),
ErrorStateInfo);
exit; // failed to find a main breakpoint
end;
// RUN
DefaultTimeOut := 0;
if not ExecuteCommand(Cmd, R, [cfTryAsync])
then begin
SetDebuggerErrorState(Format(gdbmiCommandStartMainRunError, [LineEnding]),
ErrorStateInfo);
exit;
end;
s := r.Values + FLogWarnings;
if TargetInfo^.TargetPID = 0 then
TargetInfo^.TargetPID := ParseLogForPid(s);
s2 := '';
if R.State = dsRun
then begin
if not (rfAsyncFailed in R.Flags) then begin
FCanKillNow := True;
FTheDebugger.FCurrentCmdIsAsync := True;
end;
if (TargetInfo^.TargetPID <> 0) then
FCanKillNow := True;
ProcessRunning(s2, R);
FCanKillNow := False;
FTheDebugger.FCurrentCmdIsAsync := False;
j := ParseStopped(s2);
if (j = -2) or (pos('reason="exited-normally"', s2) > 0) or FDidKillNow then begin
// app has already run
R.State := dsStop;
break;
end;
R.State := dsRun; // restore cmd state
s := s + s2 + R.Values;
Cmd := '-exec-continue'; // until we hit one of the breakpoints
end;
rval := rval + s;
DefaultTimeOut := DebuggerProperties.TimeoutForEval; // Getting address for breakpoints may need timeout
BrkErr := ParseBreakInsertError(s, i);
if not BrkErr
then break;
j := FTheDebugger.FMainAddrBreak.BreakSetCount;
while BrkErr and not(DebuggerState = dsError) do begin
if not FTheDebugger.FMainAddrBreak.ClearAndBlockId(Self, i)
then begin
DebugLn(DBG_WARNINGS, ['TGDBMIDebugger.RunToMain: An unknown breakpoint id was reported as failing: ', i]);
if not ExecuteCommand('-break-delete %d', [i], [cfCheckError]) // wil set error state if it fails
then break;
inc(j);
end;
BrkErr := ParseBreakInsertError(s, i)
end;
// Break, if no breakpoint was removed
if j = FTheDebugger.FMainAddrBreak.BreakSetCount
then break;
end;
if DebuggerState = dsError then
exit;
if FDidKillNow then
exit;
if R.State = dsStop
then begin
debugln(DBG_WARNINGS, 'Debugger INIT failed. App has already run');
SetDebuggerErrorState(Format(gdbmiCommandStartMainRunToStopError, [LineEnding]),
ErrorStateInfo);
exit;
end;
if not(R.State = dsRun)
then begin
SetDebuggerErrorState(Format(gdbmiCommandStartMainRunError, [LineEnding]),
ErrorStateInfo);
exit;
end;
FTheDebugger.FMainAddrBreak.Clear(Self);
SetDebuggerState(dsRun); // TODO: should not be needed here
// and we should ave hit a breakpoint
//List := TGDBMINameValueList.Create(R.Values);
//Reason := List.Values['reason'];
//if Reason = 'breakpoint-hit'
(* *** Find the PID *** *)
(* Try GDB output. Some of output after the -exec-run.
Mac GDB 6.3.5
~"[Switching to process 12345 local thread 0x0123]\n"
FreeBSD 9.0 GDB 6.1 (modified ?, supplied by FreeBSD)
PID is not equal to LWP.
[New LWP 100229]
[New Thread 807407400 (LWP 100229/project1)]
[Switching to Thread 807407400 (LWP 100229/project1)]
Somme linux, GDB 7.1
Win GDB 7.0
=thread-group-created,id="2125"
=thread-created,id="1",group-id="2125"
~"[New Thread 9280.0x24e4]\n" // This line is Win only (or gdb 7.0?)
^running
*running,thread-id="all"
(gdb)
Win GDB 7.4
FreeBSD 9.0 GDB 7.3 (from ports)
=thread-group-started,id="i1",pid="8876"
=thread-created,id="1",group-id="i1"
~"[New Thread 8876.0x21c0]\n" // This line is Win only (or gdb 7.0?)
^running
*running,thread-id="all"
(gdb)
FreeBSD 9.0 GDB 7.3 (from ports) CONTINUED (LWP is not useable
=thread-created,id="1",group-id="i1"
~"[New LWP 100073]\n"
*running,thread-id="1"
=thread-created,id="2",group-id="i1"
~"[New Thread 807407400 (LWP 100073)]\n"
=thread-exited,id="1",group-id="i1"
~"[Switching to Thread 807407400 (LWP 100073)]\n"
*)
if TargetInfo^.TargetPID <> 0 then
exit;
TargetInfo^.TargetPID := ParseLogForPid(rval);
if TargetInfo^.TargetPID <> 0 then
exit;
(* PID via "info program"
Somme linux, gdb 7.1
~"\tUsing the running image of child Thread 0xb7fd8820 (LWP 2125).\n"
On FreeBSD LWP may differ from PID
FreeBSD 9.0 GDB 6.1 (modified ?, supplied by FreeBSD)
PID is not equal to LWP.
Using the running image of child Thread 807407400 (LWP 100229/project1).
Win GDB 7.4
~"\tUsing the running image of child Thread 8876.0x21c0.\n"
*)
if ExecuteCommand('info program', [], R, [cfCheckState])
then begin
s := GetPart(['child process ', 'child thread ', 'lwp '], [' ', '.', ')'],
R.Values, True);
TargetInfo^.TargetPID := StrToIntDef(s, 0);
if TargetInfo^.TargetPID <> 0 then exit;
end;
// apple
if ExecuteCommand('info pid', [], R, [cfCheckState]) and (R.State <> dsError)
then begin
List := TGDBMINameValueList.Create(R);
TargetInfo^.TargetPID := StrToIntDef(List.Values['process-id'], 0);
List.Free;
if TargetInfo^.TargetPID <> 0 then exit;
end;
// gdb server
if ExecuteCommand('info proc', [], R, [cfCheckState]) and (R.State <> dsError)
then begin
s := GetPart(['process '], [#10,#13#10], R.Values, True);
TargetInfo^.TargetPID := StrToIntDef(s, 0);
if TargetInfo^.TargetPID <> 0 then exit;
end;
// apple / MacPort 7.1 / 32 bit dwarf
if ExecuteCommand('info threads', [], R, [cfCheckState]) and (R.State <> dsError)
then begin
s := GetPart(['of process '], [' '], R.Values, True);
TargetInfo^.TargetPID := StrToIntDef(s, 0);
if TargetInfo^.TargetPID <> 0 then exit;
// returned by gdb server (maybe others)
s := GetPart(['Thread '], [' ', '.'], R.Values, True);
TargetInfo^.TargetPID := StrToIntDef(s, 0);
if TargetInfo^.TargetPID <> 0 then exit;
end;
// no PID found
SetDebuggerErrorState(Format(gdbmiCommandStartMainRunNoPIDError, [LineEnding]));
end;
var
R: TGDBMIExecResult;
FileType, EntryPoint: String;
List: TGDBMINameValueList;
CanContinue: Boolean;
StateStopped: Boolean;
begin
Result := True;
FSuccess := False;
StateStopped := False;
try
if not (DebuggerState in [dsStop])
then begin
Result := True;
Exit;
end;
if not DoChangeFilename then begin
SetDebuggerErrorState(synfFailedToLoadApplicationExecutable, FErrorMsg);
exit;
end;
if not DoSetPascal then begin
SetDebuggerErrorState(synfFailedToInitializeTheDebuggerSetPascalFailed,
FLastExecResult.Values);
exit;
end;
DebugLn(['TGDBMIDebugger.StartDebugging WorkingDir="', FTheDebugger.WorkingDir,'"']);
if FTheDebugger.WorkingDir <> ''
then begin
// to workaround a possible bug in gdb, first set the workingdir to .
// otherwise on second run within the same gdb session the workingdir
// is set to c:\windows
ExecuteCommand('-environment-cd %s', ['.'], []);
ExecuteCommand('-environment-cd %s', [FTheDebugger.ConvertToGDBPath(FTheDebugger.WorkingDir, cgptCurDir)], [cfCheckError]);
end;
TargetInfo^.TargetFlags := [tfHasSymbols]; // Set until proven otherwise
// check if the exe is compiled with FPC >= 1.9.2
// then the rtl is compiled with regcalls
RetrieveRegCall;
// also call execute -exec-arguments if there are no arguments in this run
// so the possible arguments of a previous run are cleared
ExecuteCommand('-exec-arguments %s', [UTF8ToWinCP(FTheDebugger.Arguments)], [cfCheckState]);
{$IF defined(UNIX) or defined(DBG_ENABLE_TERMINAL)}
InitConsole;
{$ENDIF}
ExecuteCommand('-gdb-set language pascal', [cfCheckError]);
CheckAvailableTypes;
CommonInit;
TargetInfo^.TargetCPU := '';
TargetInfo^.TargetOS := FTheDebugger.FGDBOS; // try to detect ??
// try to retrieve the filetype and program entry point
FileType := '';
EntryPoint := '';
if ExecuteCommand('info file', R)
then begin
if rfNoMI in R.Flags
then begin
FileType := GetPart('file type ', '.', R.Values);
EntryPoint := GetPart(['Entry point: '], [#10, #13, '\t'], R.Values);
end
else begin
// OS X gdb has mi output here
List := TGDBMINameValueList.Create(R, ['section-info']);
FileType := List.Values['filetype'];
EntryPoint := List.Values['entry-point'];
List.Free;
end;
DebugLn(DBG_VERBOSE, '[Debugger] File type: ', FileType);
DebugLn(DBG_VERBOSE, '[Debugger] Entry point: ', EntryPoint);
end;
SetTargetInfo(FileType);
DefaultTimeOut := DebuggerProperties.TimeoutForEval; // Getting address for breakpoints may need timeout
DetectForceableBreaks;
(* We need a breakpoint at entry-point or main, to continue initialization
"main" could map to more than one location, so we try entry point first
*)
RunToMain(EntryPoint);
DefaultTimeOut := DebuggerProperties.TimeoutForEval; // Getting address for breakpoints may need timeout
if DebuggerState = dsStop
then begin
Result := False;
FSuccess := False;
Exit;
end;
if DebuggerState = dsError
then begin
Result := False;
FSuccess := False;
Exit;
end;
if TargetInfo^.TargetPID = 0
then begin
Result := False;
FSuccess := False;
SetDebuggerState(dsError);
Exit;
end;
DebugLn(DBG_VERBOSE, '[Debugger] Target PID: %u', [TargetInfo^.TargetPID]);
Exclude(FTheDebugger.FDebuggerFlags, dfSetBreakFailed);
Exclude(FTheDebugger.FDebuggerFlags, dfSetBreakPending);
// they may still exist from prev run, addr will be checked
// TODO: defered setting of below beakpoint / e.g. if debugging a library
{$IFdef WITH_GDB_FORCE_EXCEPTBREAK}
FTheDebugger.FExceptionBreak.SetByAddr(Self, True);
FTheDebugger.FBreakErrorBreak.SetByAddr(Self, True);
FTheDebugger.FRunErrorBreak.SetByAddr(Self, True);
{$Else}
FTheDebugger.FExceptionBreak.SetByAddr(Self);
FTheDebugger.FBreakErrorBreak.SetByAddr(Self);
FTheDebugger.FRunErrorBreak.SetByAddr(Self);
{$ENDIF}
if (not (FTheDebugger.FExceptionBreak.IsBreakSet and
FTheDebugger.FBreakErrorBreak.IsBreakSet and
FTheDebugger.FRunErrorBreak.IsBreakSet)) and
(DebuggerProperties.WarnOnSetBreakpointError in [gdbwAll, gdbwExceptionsAndRunError])
then
Include(FTheDebugger.FDebuggerFlags, dfSetBreakFailed);
SetDebuggerState(dsInit); // triggers all breakpoints to be set.
FTheDebugger.RunQueue; // run all the breakpoints
Application.ProcessMessages; // workaround, allow source-editor to queue line info request (Async call)
if FTheDebugger.FBreakAtMain <> nil
then begin
CanContinue := False;
TGDBMIBreakPoint(FTheDebugger.FBreakAtMain).Hit(CanContinue);
end
else CanContinue := True;
//if FTheDebugger.DebuggerFlags * [dfSetBreakFailed, dfSetBreakPending] <> [] then begin
// if FTheDebugger.OnFeedback
// (self, Format(synfTheDebuggerWasUnableToSetAllBreakpointsDuringIniti,
// [LineEnding]), '', ftWarning, [frOk, frStop]) = frStop
// then begin
// StateStopped := True;
// SetDebuggerState(dsStop);
// exit;
// end;
//end;
if StoppedAtEntryPoint and CanContinue and (FContinueCommand = nil) then begin
// try to step to pascal code
if (FndOffsFile <> '') and (FndOffsLine <> '') and
( (FndOffsFile <> StoppedFile) or (FndOffsLine <> StoppedLine) )
then begin
FTheDebugger.FMainAddrBreak.SetAtFileLine(Self, FndOffsFile, FndOffsLine);
if (FTheDebugger.FMainAddrBreak.BreakAddr[iblFileLine] < MIN_RELOC_ADDRESS) or
(FTheDebugger.FMainAddrBreak.BreakAddr[iblFileLine] = StoppedAddr)
then
FTheDebugger.FMainAddrBreak.Clear(Self, iblFileLine);
end;
FTheDebugger.FMainAddrBreak.SetByName(Self);
if (FTheDebugger.FMainAddrBreak.BreakAddr[iblNamed] < MIN_RELOC_ADDRESS) or
(FTheDebugger.FMainAddrBreak.BreakAddr[iblNamed] = StoppedAddr) or
(FTheDebugger.FMainAddrBreak.BreakFile[iblNamed] = '') or
(FTheDebugger.FMainAddrBreak.BreakLine[iblNamed] = '') or
( (FTheDebugger.FMainAddrBreak.BreakFile[iblNamed] = StoppedFile) and
(FTheDebugger.FMainAddrBreak.BreakFile[iblNamed] = StoppedLine) )
then
FTheDebugger.FMainAddrBreak.Clear(Self, iblNamed);
if FTheDebugger.FMainAddrBreak.IsBreakSet then begin
FContinueCommand := TGDBMIDebuggerCommandExecute.Create(FTheDebugger, ectContinue);
end;
end;
if CanContinue and (FContinueCommand <> nil)
then begin
FTheDebugger.QueueCommand(FContinueCommand);
FContinueCommand := nil;
end
else begin
SetDebuggerState(dsPause);
end;
if DebuggerState = dsPause
then ProcessFrame;
finally
ReleaseRefAndNil(FContinueCommand);
if not(StateStopped or (DebuggerState in [dsInit, dsRun, dsPause])) then
SetDebuggerErrorState(synfFailedToInitializeDebugger);
end;
FSuccess := True;
end;
function TGDBMIDebuggerCommandStartDebugging.GdbRunCommand: String;
begin
Result := '-exec-run';
end;
constructor TGDBMIDebuggerCommandStartDebugging.Create(AOwner: TGDBMIDebugger;
AContinueCommand: TGDBMIDebuggerCommand);
begin
inherited Create(AOwner);
// AContinueCommand, takes over the current reference.
// Caller will never Release it. So TGDBMIDebuggerCommandStartDebugging must do this
FContinueCommand := AContinueCommand;
FSuccess := False;
FContext.ThreadContext := ccNotRequired;
FContext.StackContext := ccNotRequired;
end;
destructor TGDBMIDebuggerCommandStartDebugging.Destroy;
begin
ReleaseRefAndNil(FContinueCommand);
inherited Destroy;
end;
function TGDBMIDebuggerCommandStartDebugging.DebugText: String;
var
s: String;
begin
s := '<none>';
if FContinueCommand <> nil
then s := FContinueCommand.DebugText;
Result := Format('%s: ContinueCommand= %s', [ClassName, s]);
end;
{ TGDBMIDebuggerCommandAttach }
function TGDBMIDebuggerCommandAttach.DoExecute: Boolean;
var
R: TGDBMIExecResult;
StoppedParams, FileType, CmdResp, s: String;
List: TGDBMINameValueList;
NewPID: Integer;
begin
Result := True;
FSuccess := False;
if not ExecuteCommand('-file-exec-and-symbols %s',
[FTheDebugger.ConvertToGDBPath('', cgptExeName)], R)
then
R.State := dsError;
if R.State = dsError then begin
SetDebuggerErrorState('Attach failed');
exit;
end;
DefaultTimeOut := DebuggerProperties.TimeoutForEval;
// Tnit (StartDebugging)
TargetInfo^.TargetFlags := [tfHasSymbols]; // Set until proven otherwise
ExecuteCommand('-gdb-set language pascal', [cfCheckError]); // TODO: Maybe remove, must be done after attach
//{$IF defined(UNIX) or defined(DBG_ENABLE_TERMINAL)}
//InitConsole;
//{$ENDIF}
SetDebuggerState(dsInit); // triggers all breakpoints to be set.
Application.ProcessMessages; // workaround, allow source-editor to queue line info request (Async call)
// Attach
if not ExecuteCommand('attach %s', [FProcessID], R) then
R.State := dsError;
if R.State = dsError then begin
ExecuteCommand('detach', [], R);
SetDebuggerErrorState('Attach failed');
exit;
end;
CmdResp := FFullCmdReply;
if (R.State <> dsNone)
then SetDebuggerState(R.State);
if R.State = dsRun then begin
ProcessRunning(StoppedParams, R);;
if (R.State = dsError) then begin
ExecuteCommand('detach', [], R);
SetDebuggerErrorState('Attach failed');
exit;
end;
end;
CmdResp := CmdResp + StoppedParams + R.Values;
// Get PID
NewPID := 0;
s := GetPart(['Attaching to process '], [LineEnding, '.'], CmdResp, True, False);
if s <> '' then
NewPID := StrToIntDef(s, 0);
if NewPID = 0 then begin
s := GetPart(['=thread-group-started,'], [LineEnding], CmdResp, True, False);
if s <> '' then
s := GetPart(['pid="'], ['"'], s, True, False);
if s <> '' then
NewPID := StrToIntDef(s, 0);
end;
if NewPID = 0 then begin
NewPID := StrToIntDef(FProcessID, 0);
end;
// "info program" may crash after attach
if NewPID = 0 then begin
if ExecuteCommand('info pid', [], R, [cfCheckState]) and (R.State <> dsError)
then begin
List := TGDBMINameValueList.Create(R);
NewPID := StrToIntDef(List.Values['process-id'], 0);
List.Free;
end;
end;
if NewPID = 0 then begin
if ExecuteCommand('info threads', [], R, [cfCheckState]) and (R.State <> dsError)
then begin
s := GetPart(['of process '], [' '], R.Values, True);
NewPID := StrToIntDef(s, 0);
end;
end;
if NewPID = 0 then begin
ExecuteCommand('detach', [], R);
SetDebuggerErrorState(Format(gdbmiCommandStartMainRunNoPIDError, [LineEnding]));
exit;
end;
TargetInfo^.TargetPID := NewPID;
DoSetPascal;
if (FTheDebugger.FileName <> '') and (pos('READING SYMBOLS FROM', UpperCase(CmdResp)) < 1) then begin
ExecuteCommand('ptype TObject', [], R);
if pos('NO SYMBOL TABLE IS LOADED', UpperCase(FFullCmdReply)) > 0 then begin
ExecuteCommand('-file-exec-and-symbols %s',
[FTheDebugger.ConvertToGDBPath(FTheDebugger.FileName, cgptExeName)], R);
DoSetPascal;
end;
end;
// Tnit (StartDebugging)
// check if the exe is compiled with FPC >= 1.9.2
// then the rtl is compiled with regcalls
RetrieveRegCall;
CheckAvailableTypes;
CommonInit;
DetectForceableBreaks;
FileType := '';
if ExecuteCommand('info file', R)
then begin
if rfNoMI in R.Flags
then begin
FileType := GetPart('file type ', '.', R.Values);
end
else begin
// OS X gdb has mi output here
List := TGDBMINameValueList.Create(R, ['section-info']);
FileType := List.Values['filetype'];
List.Free;
end;
DebugLn(DBG_VERBOSE, '[Debugger] File type: ', FileType);
end;
SetTargetInfo(FileType);
FTheDebugger.FExceptionBreak.SetByAddr(Self);
FTheDebugger.FBreakErrorBreak.SetByAddr(Self);
FTheDebugger.FRunErrorBreak.SetByAddr(Self);
if not(DebuggerState in [dsPause]) then
SetDebuggerState(dsPause);
ProcessFrame; // Includes DoLocation
FSuccess := True;
end;
constructor TGDBMIDebuggerCommandAttach.Create(AOwner: TGDBMIDebugger;
AProcessID: String);
begin
inherited Create(AOwner);
FSuccess := False;
FProcessID := AProcessID;
FContext.ThreadContext := ccNotRequired;
FContext.StackContext := ccNotRequired;
end;
function TGDBMIDebuggerCommandAttach.DebugText: String;
begin
Result := Format('%s: ProcessID= %s', [ClassName, FProcessID]);
end;
{ TGDBMIDebuggerCommandDetach }
function TGDBMIDebuggerCommandDetach.DoExecute: Boolean;
var
R: TGDBMIExecResult;
begin
Result := True;
FContext.ThreadContext := ccNotRequired;
FContext.StackContext := ccNotRequired;
if not ExecuteCommand('detach', R) then
R.State := dsError;
if R.State = dsError then begin
SetDebuggerErrorState('Detach failed');
exit;
end;
SetDebuggerState(dsStop);
end;
{ TGDBMIDebuggerCommandExecute }
procedure TGDBMIDebuggerCommandExecute.DoLockQueueExecute;
begin
// prevent lock
end;
procedure TGDBMIDebuggerCommandExecute.DoUnLockQueueExecute;
begin
// prevent lock
end;
function TGDBMIDebuggerCommandExecute.ProcessStopped(const AParams: String;
const AIgnoreSigIntState: Boolean): Boolean;
function GetLocation: TDBGLocationRec; // update current location
var
R: TGDBMIExecResult;
S: String;
FP: TDBGPtr;
i, cnt: longint;
begin
FTheDebugger.QueueExecuteLock;
try
Result.SrcLine := -1;
Result.SrcFile := '';
Result.FuncName := '';
// Get the frame and addr info from the call-params
if tfRTLUsesRegCall in TargetInfo^.TargetFlags
then begin
Result.Address := GetPtrValue(TargetInfo^.TargetRegisters[1], []);
FP := GetPtrValue(TargetInfo^.TargetRegisters[2], []);
end else begin
Result.Address := GetData('$fp+%d', [TargetInfo^.TargetPtrSize * 3]);
FP := GetData('$fp+%d', [TargetInfo^.TargetPtrSize * 4]);
end;
if FP <> 0 then begin
// try finding the stackframe
cnt := GetStackDepth(33); // do not search more than 32 deep, takes a lot of time
i := FindStackFrame(Fp, 0, cnt);
if i >= 0 then begin
FTheDebugger.FCurrentStackFrame := i;
DebugLn(DBG_THREAD_AND_FRAME, ['ProcessStopped GetLocation found fp Stack(Internal) = ', FTheDebugger.FCurrentStackFrame]);
end;
if FTheDebugger.FCurrentStackFrame <> 0
then begin
// This frame should have all the info we need
s := GetFrame(FTheDebugger.FCurrentStackFrame);
if s <> '' then
FTheDebugger.FCurrentLocation := FrameToLocation(S);
Result.SrcFile := FTheDebugger.FCurrentLocation.SrcFile;
Result.SrcFullName := FTheDebugger.FCurrentLocation.SrcFullName;
Result.FuncName := FTheDebugger.FCurrentLocation.FuncName;
Result.SrcLine := FTheDebugger.FCurrentLocation.SrcLine;
end;
end;
if (Result.SrcLine = -1) or (Result.SrcFile = '') then begin
Str(Result.Address, S);
if ExecuteCommand('info line *%s', [S], R)
then begin
Result.SrcLine := StrToIntDef(GetPart('Line ', ' of', R.Values), -1);
Result.SrcFile := ConvertGdbPathAndFile(GetPart('\"', '\"', R.Values));
end;
end;
FTheDebugger.FCurrentLocation := Result;
finally
FTheDebugger.QueueExecuteUnlock;
end;
end;
function GetExceptionInfo: TGDBMIExceptionInfo;
begin
FTheDebugger.QueueExecuteLock;
try
if tfRTLUsesRegCall in TargetInfo^.TargetFlags
then Result.ObjAddr := TargetInfo^.TargetRegisters[0]
else begin
if dfImplicidTypes in FTheDebugger.DebuggerFlags
then Result.ObjAddr := Format('^%s($fp+%d)^', [PointerTypeCast, TargetInfo^.TargetPtrSize * 2])
else Str(GetData('$fp+%d', [TargetInfo^.TargetPtrSize * 2]), Result.ObjAddr);
end;
Result.Name := GetInstanceClassName(Result.ObjAddr, []);
if Result.Name = ''
then Result.Name := 'Unknown';
finally
FTheDebugger.QueueExecuteUnlock;
end;
end;
procedure ProcessException;
var
ExceptionMessage: String;
CanContinue: Boolean;
Location: TDBGLocationRec;
ExceptInfo: TGDBMIExceptionInfo;
ExceptItem: TBaseException;
begin
FTheDebugger.FStoppedReason := srRaiseExcept;
if (FTheDebugger.Exceptions = nil) or FTheDebugger.Exceptions.IgnoreAll
then begin
Result := True; //ExecuteCommand('-exec-continue')
exit;
end;
ExceptInfo := GetExceptionInfo;
// check if we should ignore this exception
ExceptItem := FTheDebugger.Exceptions.Find(ExceptInfo.Name);
if (ExceptItem <> nil) and (ExceptItem.Enabled)
then begin
Result := True; //ExecuteCommand('-exec-continue')
exit;
end;
FTheDebugger.QueueExecuteLock;
try
if (dfImplicidTypes in FTheDebugger.DebuggerFlags)
then begin
if (tfFlagHasTypeException in TargetInfo^.TargetFlags) then begin
if tfExceptionIsPointer in TargetInfo^.TargetFlags
then ExceptionMessage := GetText('Exception(%s).FMessage', [ExceptInfo.ObjAddr])
else ExceptionMessage := GetText('^Exception(%s)^.FMessage', [ExceptInfo.ObjAddr]);
if FLastExecResult.State = dsError then begin
if tfExceptionIsPointer in TargetInfo^.TargetFlags then begin
ExceptionMessage := GetText('^Exception(%s).FMessage', [ExceptInfo.ObjAddr]);
if FLastExecResult.State <> dsError then
Exclude(TargetInfo^.TargetFlags, tfExceptionIsPointer);
end;
if FLastExecResult.State = dsError then
ExceptionMessage := GetText('^^char(^%s(%s)+1)^', [PointerTypeCast, ExceptInfo.ObjAddr]);
end;
//ExceptionMessage := GetText('^^Exception($fp+8)^^.FMessage', []);
end else begin
// Only works if Exception class is not changed. FMessage must be first member
ExceptionMessage := GetText('^^char(^%s(%s)+1)^', [PointerTypeCast, ExceptInfo.ObjAddr]);
end;
end
else ExceptionMessage := '### Not supported on GDB < 5.3 ###';
Location := GetLocation;
finally
FTheDebugger.QueueExecuteUnlock;
end;
FTheDebugger.DoException(deInternal, ExceptInfo.Name, Location, ExceptionMessage, CanContinue);
if CanContinue
then begin
//ExecuteCommand('-exec-continue')
Result := True; // outer funciton result
exit;
end;
SetDebuggerState(dsPause); // after GetLocation => dsPause may run stack, watches etc
FTheDebugger.DoCurrent(Location);
end;
procedure ProcessBreak;
var
ErrorNo: Integer;
CanContinue: Boolean;
Location: TDBGLocationRec;
ExceptName: String;
ExceptItem: TBaseException;
begin
FTheDebugger.QueueExecuteLock;
try
if tfRTLUsesRegCall in TargetInfo^.TargetFlags
then ErrorNo := GetIntValue(TargetInfo^.TargetRegisters[0], [])
else ErrorNo := Integer(GetData('$fp+%d', [TargetInfo^.TargetPtrSize * 2]));
ErrorNo := ErrorNo and $FFFF;
Location := GetLocation;
finally
FTheDebugger.QueueExecuteUnlock;
end;
ExceptName := Format('RunError(%d)', [ErrorNo]);
ExceptItem := FTheDebugger.Exceptions.Find(ExceptName);
if (ExceptItem <> nil) and (ExceptItem.Enabled)
then begin
Result := True; //ExecuteCommand('-exec-continue')
exit;
end;
FTheDebugger.DoException(deRunError, ExceptName, Location, '', CanContinue);
if CanContinue
then begin
//ExecuteCommand('-exec-continue')
Result := True; // outer funciton result
exit;
end;
SetDebuggerState(dsPause); // after GetLocation => dsPause may run stack, watches etc
FTheDebugger.DoCurrent(Location);
end;
procedure ProcessRunError;
var
ErrorNo: Integer;
CanContinue: Boolean;
Location: TDBGLocationRec;
ExceptName: String;
ExceptItem: TBaseException;
begin
FTheDebugger.QueueExecuteLock;
try
if tfRTLUsesRegCall in TargetInfo^.TargetFlags
then ErrorNo := GetIntValue(TargetInfo^.TargetRegisters[0], [])
else ErrorNo := Integer(GetData('$fp+%d', [TargetInfo^.TargetPtrSize * 2]));
ErrorNo := ErrorNo and $FFFF;
Location := GetLocation;
finally
FTheDebugger.QueueExecuteUnlock;
end;
ExceptName := Format('RunError(%d)', [ErrorNo]);
ExceptItem := FTheDebugger.Exceptions.Find(ExceptName);
if (ExceptItem <> nil) and (ExceptItem.Enabled)
then begin
Result := True; //ExecuteCommand('-exec-continue')
exit;
end;
FTheDebugger.DoException(deRunError, ExceptName, Location, '', CanContinue);
if CanContinue
then begin
//ExecuteCommand('-exec-continue')
Result := True; // outer funciton result
exit;
end;
SetDebuggerState(dsPause); // after GetLocation => dsPause may run stack, watches etc
ProcessFrame(GetFrame(1));
end;
procedure ProcessSignalReceived(const AList: TGDBMINameValueList);
var
SigInt, CanContinue: Boolean;
S, F: String;
{$IFdef MSWindows}
fixed: Boolean;
{$ENDIF}
begin
// TODO: check to run (un)handled
S := AList.Values['signal-name'];
F := AList.Values['frame'];
{$IFdef MSWindows}
SigInt := S = 'SIGTRAP';
if FTheDebugger.FAsyncModeEnabled then
SigInt := SigInt or (S = 'SIGINT');
{$ELSE}
SigInt := S = 'SIGINT';
{$ENDIF}
{$IFdef MSWindows}
if SigInt and (FTheDebugger.PauseWaitState = pwsNone) and
(pos('DbgUiConvertStateChangeStructure', FTheDebugger.FCurrentLocation.FuncName) > 0)
then begin
Result := True;
exit;
end;
{$ENDIF}
if not AIgnoreSigIntState // not pwsInternal
or not SigInt
then begin
// user-requested pause OR other signal (not sigint)
// TODO: if SigInt, check that it was issued by IDE
{$IFdef MSWindows}
FTheDebugger.QueueExecuteLock;
try
fixed := FixThreadForSigTrap;
finally
FTheDebugger.QueueExecuteUnlock;
end;
// Before anything else goes => correct the thread
if fixed
then F := '';
{$ENDIF}
SetDebuggerState(dsPause);
end;
if not SigInt
then FTheDebugger.DoException(deExternal, 'External: ' + S, FTheDebugger.FCurrentLocation, '', CanContinue);
FTheDebugger.QueueExecuteLock;
try
if not AIgnoreSigIntState
or not SigInt
then ProcessFrame(F);
finally
FTheDebugger.QueueExecuteUnlock;
end;
end;
procedure ProcessBreakPoint(ABreakId: Integer; const List: TGDBMINameValueList;
AReason: TGDBMIBreakpointReason; AOldVal: String = ''; ANewVal: String = '');
var
BreakPoint: TGDBMIBreakPoint;
CanContinue: Boolean;
Location: TDBGLocationRec;
BrkSlave: TBaseBreakPoint;
begin
BreakPoint := nil;
if ABreakId >= 0 then
BreakPoint := TGDBMIBreakPoint(FTheDebugger.FindBreakpoint(ABreakID));
if (BreakPoint <> nil) and (BreakPoint.Kind <> bpkData) and
(AReason in [gbrWatchScope, gbrWatchTrigger])
then BreakPoint := nil;
if BreakPoint <> nil
then begin
try
(* - Breakpoint may not be destroyed, while in use
- And it may not be destroyed, before state is set (otherwhise an InterruptTarget is triggered)
*)
BreakPoint.AddReference;
BrkSlave := BreakPoint.Slave;
if BrkSlave <> nil then BrkSlave.AddReference;
CanContinue := False;
FTheDebugger.QueueExecuteLock;
try
Location := FrameToLocation(List.Values['frame']);
FTheDebugger.FCurrentLocation := Location;
finally
FTheDebugger.QueueExecuteUnlock;
end;
FTheDebugger.DoDbgBreakpointEvent(BreakPoint, Location, AReason, AOldVal, ANewVal);
// Important: The Queue must be unlocked
// BreakPoint.Hit may evaluate stack and expressions
// SetDebuggerState may evaluate data for Snapshot
BreakPoint.Hit(CanContinue);
if CanContinue
then begin
// Important trigger State => as snapshot is taken in TDebugManager.DebuggerChangeState
SetDebuggerState(dsInternalPause);
Result := True;
end
else begin
SetDebuggerState(dsPause);
ProcessFrame(Location);
// inform the user, why we stopped
// TODO: Add a dedicated callback
case AReason of
gbrWatchTrigger: FTheDebugger.OnFeedback
(self, Format('The Watchpoint for "%1:s" was triggered.%0:s%0:sOld value: %2:s%0:sNew value: %3:s',
[LineEnding, BreakPoint.WatchData, AOldVal, ANewVal]),
'', ftInformation, [frOk]);
gbrWatchScope: FTheDebugger.OnFeedback
(self, Format('The Watchpoint for "%s" went out of scope', [BreakPoint.WatchData]),
'', ftInformation, [frOk]);
end;
end;
if AReason = gbrWatchScope
then begin
BreakPoint.ReleaseBreakPoint; // gdb should have released already => ignore error
BreakPoint.Enabled := False;
BreakPoint.FBreakID := 0; // removed by debugger, ID no longer exists
end;
finally
if BrkSlave <> nil then BrkSlave.ReleaseReference;
BreakPoint.ReleaseReference;
end;
exit;
end;
if (DebuggerState = dsRun)
then begin
debugln(['********** WARNING: breakpoint hit, but nothing known about it ABreakId=', ABreakID, ' brbtno=', List.Values['bkptno'] ]);
{$IFDEF DBG_VERBOSE_BRKPOINT}
debugln(['-*- List of breakpoints Cnt=', FTheDebugger.Breakpoints.Count]);
for ABreakID := 0 to FTheDebugger.Breakpoints.Count - 1 do
debugln(['* ',Dbgs(FTheDebugger.Breakpoints[ABreakID]), ':', DbgsName(FTheDebugger.Breakpoints[ABreakID]), ' ABreakId=',TGDBMIBreakPoint(FTheDebugger.Breakpoints[ABreakID]).FBreakID, ' Source=', FTheDebugger.Breakpoints[ABreakID].Source, ' Line=', FTheDebugger.Breakpoints[ABreakID].Line ]);
debugln(['************************************************************************ ']);
debugln(['************************************************************************ ']);
debugln(['************************************************************************ ']);
{$ENDIF}
case FTheDebugger.OnFeedback
(self, Format(gdbmiWarningUnknowBreakPoint,
[LineEnding, GDBMIBreakPointReasonNames[AReason]]),
List.Text, ftWarning, [frOk, frStop]
)
of
frOk: begin
SetDebuggerState(dsPause);
ProcessFrame(List.Values['frame']); // and jump to it
end;
frStop: begin
FTheDebugger.Stop;
end;
end;
end;
end;
var
List, List2: TGDBMINameValueList;
Reason: String;
BreakID: Integer;
CanContinue: Boolean;
i: Integer;
s: String;
begin
(* The Queue is not locked / This code can be interupted
Therefore all calls to ExecuteCommand (gdb cmd) must be wrapped in QueueExecuteLock
*)
Result := False;
FTheDebugger.FInProcessStopped := True; // paused, but maybe state run
FTheDebugger.FStoppedReason := srNone;
List := TGDBMINameValueList.Create(AParams);
List2 := nil;
FTheDebugger.FCurrentStackFrame := 0;
FTheDebugger.FCurrentThreadId := StrToIntDef(List.Values['thread-id'], -1);
FTheDebugger.FCurrentThreadIdValid := True;
FTheDebugger.FCurrentStackFrameValid := True;
FTheDebugger.FInstructionQueue.SetKnownThreadAndFrame(FTheDebugger.FCurrentThreadId, 0);
FContext.ThreadContext := ccUseGlobal;
FContext.StackContext := ccUseGlobal;
FTheDebugger.FCurrentLocation.Address := 0;
FTheDebugger.FCurrentLocation.SrcFile := '';
FTheDebugger.FCurrentLocation.SrcFullName := '';
try
Reason := List.Values['reason'];
if (Reason = 'exited-normally')
then begin
DoDbgEvent(ecProcess, etProcessExit, gdbmiEventLogProcessExitNormally);
SetDebuggerState(dsStop);
Exit;
end;
if Reason = 'exited'
then begin
FTheDebugger.SetExitCode(StrToIntDef(List.Values['exit-code'], 0));
DoDbgEvent(ecProcess, etProcessExit, Format(gdbmiEventLogProcessExitCode, [List.Values['exi'
+'t-code']]));
SetDebuggerState(dsStop);
Exit;
end;
if Reason = 'exited-signalled'
then begin
SetDebuggerState(dsStop);
FTheDebugger.DoException(deExternal, 'External: ' + List.Values['signal-name'], FTheDebugger.FCurrentLocation, '', CanContinue);
// ProcessFrame(List.Values['frame']);
Exit;
end;
// not stopped? Then we should have a location
FTheDebugger.FCurrentLocation := FrameToLocation(List.Values['frame']);
if Reason = 'signal-received'
then begin
ProcessSignalReceived(List);
Exit;
end;
if (Reason = 'watchpoint-trigger') or (Reason = 'access-watchpoint-trigger') or
(Reason = 'read-watchpoint-trigger')
then begin
i := 0;
List2 := nil;
while i < List.Count do begin
s := PCLenToString(List.Items[i]^.Name);
if copy(s, Length(s) - 2, 3) = 'wpt' then
List2 := TGDBMINameValueList.Create(List.Values[s]);
inc(i);
end;
if List2 <> nil then begin
BreakID := StrToIntDef(List2.Values['number'], -1);
// Use List2.Values['exp'] ? It may contain globalized expression
List2.Init(List.Values['value']);
ProcessBreakPoint(BreakID, List, gbrWatchTrigger, List2.Values['old'], List2.Values['new']);
exit;
end;
end;
if Reason = 'watchpoint-scope'
then begin
BreakID := StrToIntDef(List.Values['wpnum'], -1);
ProcessBreakPoint(BreakID, List, gbrWatchScope);
exit;
end;
if Reason = 'breakpoint-hit'
then begin
BreakID := StrToIntDef(List.Values['bkptno'], -1);
if BreakID = -1
then begin
ProcessBreakPoint(BreakID, List, gbrBreak);
SetDebuggerState(dsError);
Exit;
end;
if FTheDebugger.FBreakErrorBreak.MatchId(BreakID)
then begin
ProcessBreak; // will set dsPause / unless CanContinue
Exit;
end;
if FTheDebugger.FRunErrorBreak.MatchId(BreakID)
then begin
ProcessRunError; // will set dsPause / unless CanCuntinue
Exit;
end;
if FTheDebugger.FExceptionBreak.MatchId(BreakID)
then begin
ProcessException; // will set dsPause / unless CanCuntinue
Exit;
end;
if FTheDebugger.FPopExceptStack.MatchId(BreakID)
then begin
FTheDebugger.FStoppedReason := srPopExceptStack;
Result := True;
Exit;
end;
if FTheDebugger.FCatchesBreak.MatchId(BreakID)
then begin
FTheDebugger.FStoppedReason := srCatches;
Result := True;
Exit;
end;
if FTheDebugger.FReRaiseBreak.MatchId(BreakID)
then begin
FTheDebugger.FStoppedReason := srReRaiseExcept;
Result := True;
Exit;
end;
if FTheDebugger.FMainAddrBreak.MatchId(BreakID)
then begin
FTheDebugger.FMainAddrBreak.Clear(Self); // done with launch
SetDebuggerState(dsPause);
ProcessFrame(FTheDebugger.FCurrentLocation );
Exit;
end;
if (FStepBreakPoint > 0) and (BreakID = FStepBreakPoint)
then begin
SetDebuggerState(dsPause);
ProcessFrame(FTheDebugger.FCurrentLocation );
exit;
end;
ProcessBreakPoint(BreakID, List, gbrBreak);
exit;
end;
if Reason = 'function-finished'
then begin
SetDebuggerState(dsPause);
ProcessFrame(List.Values['frame'], False);
Exit;
end;
if Reason = 'end-stepping-range'
then begin
SetDebuggerState(dsPause);
ProcessFrame(List.Values['frame'], False);
Exit;
end;
if Reason = 'location-reached'
then begin
SetDebuggerState(dsPause);
ProcessFrame(List.Values['frame'], False);
Exit;
end;
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: Unknown stopped reason: ', Reason);
SetDebuggerState(dsPause);
ProcessFrame(List.Values['frame']);
finally
FTheDebugger.FInProcessStopped := False;
List.Free;
list2.Free;
end;
end;
{$IFDEF MSWindows}
function TGDBMIDebuggerCommandExecute.FixThreadForSigTrap: Boolean;
var
R: TGDBMIExecResult;
List: TGDBMINameValueList;
s: string;
n, ID1, ID2: Integer;
begin
Result := False;
if not ExecuteCommand('info program', R, [cfNoThreadContext])
then exit;
S := GetPart(['.0x'], ['.'], R.Values, True, False); // From the line "using child thread"
if PtrInt(StrToQWordDef('$'+S, 0)) <> FTheDebugger.FPauseRequestInThreadID
then Exit;
if not ExecuteCommand('-thread-list-ids', R, [cfNoThreadContext])
then Exit;
List := TGDBMINameValueList.Create(R);
try
n := StrToIntDef(List.Values['number-of-threads'], 0);
if n < 2 then Exit; //nothing to switch
List.SetPath(['thread-ids']);
if List.Count < 2 then Exit; // ???
ID1 := StrToIntDef(List.Values['thread-id'], 0);
List.Delete(0);
ID2 := StrToIntDef(List.Values['thread-id'], 0);
if ID1 = ID2 then Exit;
finally
List.Free;
end;
Result := True;
FTheDebugger.FCurrentThreadId := ID2;
FTheDebugger.FCurrentThreadIdValid := True;
DebugLn(DBG_THREAD_AND_FRAME, ['FixThreadForSigTrap Thread(Internal) = ', FTheDebugger.FCurrentThreadId]);
end;
{$ENDIF}
function TGDBMIDebuggerCommandExecute.DoExecute: Boolean;
var
RunMode: (rmNormal, rmStepToFinally);
const
BreaKErrMsg = 'not insert breakpoint ';
WatchErrMsg = 'not insert hardware watchpoint ';
function HandleBreakPointError(var ARes: TGDBMIExecResult; AError: String): Boolean;
var
c, i: Integer;
bp: Array of Integer;
s, s2: string;
b: TGDBMIBreakPoint;
begin
Result := False;
s := AError;
c := 0;
while ParseBreakInsertError(s, i) do begin
if FTheDebugger.FMainAddrBreak.ClearId(Self, i) then begin
Result := True;
ARes.State := dsRun;
continue;
end;
SetLength(bp, c+1);
bp[c] := i;
if bp[c] >= 0 then inc(c);
end;
if Result and not FTheDebugger.FMainAddrBreak.IsBreakSet then
ARes.State := dsPause; // no break left
if c = 0 then exit;
Result := True;
if ARes.State = dsError
then begin
s := ARes.Values;
if FLogWarnings <> ''
then s2 := Format(gdbmiErrorOnRunCommandWithWarning, [LineEnding, FLogWarnings])
else s2 := '';
FLogWarnings := '';
end else begin
s := AError;
s2 := '';
end;
case FTheDebugger.OnFeedback(self,
Format(gdbmiBreakPointErrorOnRunCommand, [LineEnding, s]) + s2,
ARes.Values, ftError, [frOk, frStop]
) of
frOk: begin
ARes.State := dsPause;
ProcessFrame;
FTheDebugger.FInProcessStopped := True; // paused, but maybe state run
try
for i := 0 to length(bp)-1 do begin
b := TGDBMIBreakPoints(FTheDebugger.BreakPoints).FindById(bp[i]);
if b <> nil
then begin
if b.Kind = bpkData
then b.Enabled := False
else b.MakeInvalid;
end
else ExecuteCommand('-break-delete %d', [bp[i]], [cfNoThreadContext]);
end;
finally
FTheDebugger.FInProcessStopped := False; // paused, but maybe state run
end;
end;
frStop: begin
FTheDebugger.Stop;
ARes.State := dsStop;
end;
end;
end;
function HandleRunError(var ARes: TGDBMIExecResult): Boolean;
var
s, s2: String;
List: TGDBMINameValueList;
begin
Result := False; // keep the error state
// check known errors
if (Pos('program is not being run', ARes.Values) > 0) then begin // Should lead to dsStop
SetDebuggerState(dsError);
exit;
end;
if (Pos(BreaKErrMsg, ARes.Values) > 0) or
(Pos(BreaKErrMsg, FLogWarnings) > 0) or
(Pos(WatchErrMsg, ARes.Values) > 0) or
(Pos(WatchErrMsg, FLogWarnings) > 0)
then begin
Result := HandleBreakPointError(ARes, ARes.Values + FLogWarnings);
if Result then exit;
end;
if assigned(FTheDebugger.OnFeedback) then begin
List := TGDBMINameValueList.Create(ARes);
s := List.Values['msg'];
FreeAndNil(List);
if FLogWarnings <> ''
then s2 := Format(gdbmiErrorOnRunCommandWithWarning, [LineEnding, FLogWarnings])
else s2 := '';
FLogWarnings := '';
if s <> '' then begin
case FTheDebugger.OnFeedback(self,
Format(gdbmiErrorOnRunCommand, [LineEnding, s]) + s2,
ARes.Values, ftError, [frOk, frStop]
) of
frOk: begin
ARes.State := dsPause;
ProcessFrame;
Result := True;
end;
frStop: begin
FTheDebugger.Stop;
ARes.State := dsStop;
Result := True;
exit;
end;
end;
end
end;
end;
function CheckResultForError(var ARes: TGDBMIExecResult): Boolean;
begin
Result := False;
if (ARes.State = dsError) and (not HandleRunError(ARes)) then begin
DoDbgEvent(ecDebugger, etDefault, Format(gdbmiFatalErrorOccurred, [ARes.Values]));
SetDebuggerState(dsError);
Result := True;
end;
end;
function FindStackWithSymbols(StartAt,
MaxDepth: Integer): Integer;
var
R: TGDBMIExecResult;
List: TGDBMINameValueList;
begin
// Result;
// -1 : Not found
// -2 : FP is outside stack
Result := StartAt;
List := TGDBMINameValueList.Create('');
try
repeat
if not ExecuteCommand('-stack-list-frames %d %d', [Result, Result], R, [cfNoStackContext])
or (R.State = dsError)
then begin
Result := -1;
break;
end;
List.Init(R.Values);
List.SetPath('stack');
if List.Count > 0 then List.Init(List.GetString(0));
List.SetPath('frame');
if List.Values['file'] <> ''
then exit;
inc(Result);
until Result > MaxDepth;
Result := -1;
finally
List.Free;
end;
end;
var
FP: TDBGPtr;
CurThreadId: Integer;
function DoContinueStepping: Boolean;
procedure DoEndStepping;
begin
Result := True;
FCurrentExecCmd := ectNone;
FCurrentExecArg := '';
SetDebuggerState(dsPause);
FTheDebugger.DoCurrent(FTheDebugger.FCurrentLocation);
end;
const
MaxStackDepth = 99;
var
cnt, i: Integer;
R: TGDBMIExecResult;
begin
// TODO: an exception can skip the step-end breakpoint....
// TODO: the "break" breakpoint can stop on the current, instead of the next instruction
Result := False;
if RunMode = rmStepToFinally then begin
Result := FTheDebugger.FStoppedReason in [srPopExceptStack, srCatches];
if Result then
FCurrentExecCmd := ectStepOut;
exit;
end;
if FTheDebugger.FStoppedReason = srReRaiseExcept then begin
FTheDebugger.FPopExceptStack.EnableOrSetByAddr(Self, True);
FTheDebugger.FCatchesBreak.EnableOrSetByAddr(Self, True);
FCurrentExecCmd := ectContinue;
Result := True;
exit;
end;
if FTheDebugger.FStoppedReason in [srPopExceptStack, srCatches] then begin
FTheDebugger.FPopExceptStack.Disable(Self);
FTheDebugger.FCatchesBreak.Disable(Self);
i := FindStackFrame(Fp, 0, 1);
if (i in [0, 1]) or (i = -2) // -2 already stepped out of the desired frame, enter dsPause
then begin
FCurrentExecCmd := ectStepOut; // ecStepOut will not offer a change to ContinueStepping
Result := True;
exit;
end;
end;
case FExecType of
ectContinue, ectRun:
begin
FCurrentExecCmd := ectContinue;
FCurrentExecArg := '';
Result := True;
end;
ectRunTo: // check if we are at correct location
begin
Result := not(
( (FTheDebugger.FCurrentLocation.SrcFile = FRunToSrc) or
(FTheDebugger.FCurrentLocation.SrcFullName = FRunToSrc) ) and
(FTheDebugger.FCurrentLocation.SrcLine = FRunToLine)
);
if not Result
then DoEndStepping; // location reached
end;
ectStepOver, ectStepOverInstruction, ectStepOut, ectStepInto:
begin
FTheDebugger.FPopExceptStack.EnableOrSetByAddr(Self, True);
FTheDebugger.FCatchesBreak.EnableOrSetByAddr(Self, True);
Result := FStepBreakPoint > 0;
if Result then
exit;
i := -1;
if FP <> 0 then begin
cnt := GetStackDepth(MaxStackDepth);
if FExecType = ectStepInto
then i := FindStackWithSymbols(0, cnt)
else i := FindStackFrame(Fp, 0, cnt);
if (FExecType = ectStepOut) and (i >= 0)
then inc(i);
end;
if (i = 0) or (i = -2) // -2 already stepped out of the desired frame, enter dsPause
then begin
DoEndStepping;
exit;
end;
if i > 0
then begin
// TODO: move to queue
// must use none gdbmi commands
FContext.ThreadContext := ccUseGlobal;
FTheDebugger.QueueExecuteLock; // force queue
try
// This messes up the Stack context of the queue.
FTheDebugger.FInstructionQueue.InvalidateThredAndFrame;
if (not ExecuteCommand('frame %d', [i], R, [cfNoStackContext])) or (R.State = dsError)
then i := -3; // error to user
if (i < 0) or (not ExecuteCommand('break', [i], R, [cfNoStackContext])) or (R.State = dsError)
then i := -3; // error to user
finally
FTheDebugger.QueueExecuteUnlock;
end;
FStepBreakPoint := StrToIntDef(GetPart(['Breakpoint '], [' at '], R.Values), -1);
if FStepBreakPoint < 0
then i := -3;
if i > 0 then begin
Result := True;
FCurrentExecCmd := ectContinue;
FCurrentExecArg := '';
end;
end;
if i < 0
then begin
DebugLn(['CommandExecute: exStepOver, frame not found: ', i]);
DoEndStepping; // TODO: User-error feedback
end;
end;
//ectStepOut:
// begin
// end;
//ectStepInto:
// begin
// end;
//ectStepOverInstruction:
// begin
// end;
ectStepIntoInstruction:
DoEndStepping;
ectReturn:
DoEndStepping;
end;
end;
function GetCurrentFp: TDBGPtr;
begin
FContext.ThreadContext := ccUseLocal;
FContext.StackContext := ccUseLocal;
FContext.StackFrame := 0;
FContext.ThreadId := CurThreadId;
Result := GetPtrValue('$fp', []);
FContext.ThreadContext := ccNotRequired;
FContext.StackContext := ccNotRequired;
end;
function DoExecCommand(AnExecCmd: TGDBMIExecCommandType; AnExecArg: String): Boolean;
var
UseMI: Boolean;
AFlags: TGDBMICommandFlags;
s: String;
begin
Result := False;
if AnExecCmd in [ectStepOut, ectReturn {, ectRunTo}] then begin
FContext.ThreadContext := ccUseLocal;
FContext.StackContext := ccUseLocal;
FContext.StackFrame := 0;
FContext.ThreadId := CurThreadId;
end
else begin
FContext.ThreadContext := ccNotRequired;
FContext.StackContext := ccNotRequired;
end;
UseMI := not FTheDebugger.FCommandNoneMiState[AnExecCmd];
if UseMI then
s := GDBMIExecCommandMap[AnExecCmd] + AnExecArg
else
s := GDBMIExecCommandMapNoneMI[AnExecCmd] + AnExecArg;
AFlags := [];
if FTheDebugger.FAsyncModeEnabled and FTheDebugger.FCommandAsyncState[AnExecCmd] then
AFlags := [cfTryAsync];
if (UseMI) and (cfTryAsync in AFlags) and (DebuggerProperties.UseNoneMiRunCommands = gdnmFallback)
then begin
if not ExecuteCommand(s + ' &', FResult, []) then // Try MI in async
exit;
if (FResult.State = dsError) then begin
// Retry none MI
FTheDebugger.FCommandNoneMiState[AnExecCmd] := True;
s := GDBMIExecCommandMapNoneMI[AnExecCmd] + AnExecArg;
if not ExecuteCommand(s, FResult, AFlags) then
exit;
end;
end
else begin
if not ExecuteCommand(s, FResult, AFlags) then
exit;
end;
if (cfTryAsync in AFlags) and (FResult.State <> dsError) then begin
if (rfAsyncFailed in FResult.Flags) then
FTheDebugger.FCommandAsyncState[AnExecCmd] := False
else
FTheDebugger.FCurrentCmdIsAsync := True;
end;
Result := True;
end;
var
StoppedParams, RunWarnings: String;
ContinueExecution, ContinueStep: Boolean;
NextExecCmdObj: TGDBMIDebuggerCommandExecute;
R: TGDBMIExecResult;
begin
Result := True;
FCanKillNow := False;
FDidKillNow := False;
FNextExecQueued := False;
FP := 0;
CurThreadId := FTheDebugger.FCurrentThreadId;
if not FTheDebugger.FCurrentThreadIdValid then CurThreadId := 1; // TODO, but we need something
ContinueStep := False; // A step command was interupted, and is continued on breakpoint
FStepBreakPoint := -1;
RunMode := rmNormal;
if (FExecType in [ectStepOver, ectStepInto, ectStepOut]) and
(FTheDebugger.FStoppedReason = srRaiseExcept)
then begin
RunMode := rmStepToFinally;
FCurrentExecCmd := ectContinue;
FTheDebugger.FPopExceptStack.EnableOrSetByAddr(Self, True);
FTheDebugger.FCatchesBreak.EnableOrSetByAddr(Self, True);
end;
if (FExecType in [ectRunTo, ectStepOver{, ectStepInto}, ectStepOut, ectStepOverInstruction {, ectStepIntoInstruction}]) then
FTheDebugger.FReRaiseBreak.EnableOrSetByAddr(Self, True)
else
FTheDebugger.FReRaiseBreak.Disable(Self);
try
repeat
FTheDebugger.CancelBeforeRun; // TODO: see comment on top of TGDBMIDebugger.QueueCommand
FTheDebugger.QueueExecuteLock; // prevent other commands from executing
try
if (not ContinueStep) and (not (RunMode in [rmStepToFinally])) and
(FExecType in [ectStepOver, ectStepInto, ectStepOut, ectStepOverInstruction, ectStepIntoInstruction])
then
FP := GetCurrentFp;
FTheDebugger.FCurrentStackFrameValid := False;
FTheDebugger.FCurrentThreadIdValid := False;
FTheDebugger.FCurrentCmdIsAsync := False;
if not DoExecCommand(FCurrentExecCmd, FCurrentExecArg) then
exit;
if CheckResultForError(FResult)
then exit;
RunWarnings := FLogWarnings;
if (FResult.State <> dsNone)
then SetDebuggerState(FResult.State);
// if ContinueExecution will be true, the we ignore dsError..
// TODO: check for cancelled
StoppedParams := '';
FCanKillNow := True;
R.State := dsNone;
if FResult.State = dsRun
then Result := ProcessRunning(StoppedParams, R);
finally
FCanKillNow := False;
// allow other commands to execute
// e.g. source-line-info, watches.. all triggered in ProcessStopped)
//TODO: prevent the next exec-command from running (or the order of SetLocation in Process Stopped is wrong)
FTheDebugger.QueueExecuteUnlock;
end;
if FDidKillNow or CheckResultForError(R)
then exit;
ContinueExecution := False;
if HandleBreakPointError(FResult, RunWarnings + LineEnding + FLogWarnings) then begin
if FResult.State = dsStop then exit;
ContinueExecution := FResult.State = dsRun; // no user interaction => FMainAddrBreak
end;
ContinueStep := False;
if StoppedParams <> ''
then ContinueExecution := ProcessStopped(StoppedParams, FTheDebugger.PauseWaitState = pwsInternal);
if ContinueExecution
then begin
ContinueStep := DoContinueStepping; // will set dsPause, if step has finished
if (not ContinueStep) and (FCurrentExecCmd <> ectNone) then begin
// - Fall back to "old" behaviour and queue a new exec-continue
// - Queue is unlocked, so nothing should be empty
// But make info available, if anything wants to queue
FNextExecQueued := True;
debugln(DBGMI_QUEUE_DEBUG, ['CommandExecute: Internal queuing -exec-continue (ContinueExecution = True)']);
FTheDebugger.FPauseWaitState := pwsNone;
NextExecCmdObj := TGDBMIDebuggerCommandExecute.Create(FTheDebugger, ectContinue);
FTheDebugger.QueueExecuteLock; // force queue
FTheDebugger.QueueCommand(NextExecCmdObj, DebuggerState = dsInternalPause); // TODO: ForceQueue, only until better means of queue control... (allow snapshot to run)
FTheDebugger.QueueExecuteUnlock;
end;
end;
until (not ContinueStep) or (FCurrentExecCmd = ectNone);
finally
if FStepBreakPoint > 0
then ExecuteCommand('-break-delete %d', [FStepBreakPoint], [cfNoThreadContext]);
FStepBreakPoint := -1;
FTheDebugger.FPopExceptStack.Disable(Self);
FTheDebugger.FCatchesBreak.Disable(Self);
FTheDebugger.FMainAddrBreak.Clear(Self);
end;
if (not ContinueExecution) and (DebuggerState = dsRun) and
(FTheDebugger.PauseWaitState <> pwsInternal)
then begin
// Handle the unforeseen
if (StoppedParams <> '')
then debugln(['ERROR: Got stop params, but did not change FTheDebugger.state: ', StoppedParams])
else debugln(['ERROR: Got NO stop params at all, but was running']);
SetDebuggerState(dsPause);
end;
end;
constructor TGDBMIDebuggerCommandExecute.Create(AOwner: TGDBMIDebugger;
const ExecType: TGDBMIExecCommandType);
begin
Create(AOwner, ExecType, []);
end;
constructor TGDBMIDebuggerCommandExecute.Create(AOwner: TGDBMIDebugger;
const ExecType: TGDBMIExecCommandType; Args: array of const);
begin
inherited Create(AOwner);
FQueueRunLevel := 0; // Execommands are only allowed at level 0
FCanKillNow := False;
FDidKillNow := False;;
FNextExecQueued := False;
FExecType := ExecType;
FCurrentExecCmd := ExecType;
FCurrentExecArg := '';
if FCurrentExecCmd = ectRunTo then begin
FRunToSrc := AnsiString(Args[0].VAnsiString);
FRunToLine := Args[1].VInteger;
FCurrentExecArg := Format(' %s:%d', [FRunToSrc, FRunToLine]);
end;
end;
function TGDBMIDebuggerCommandExecute.DebugText: String;
begin
Result := Format('%s: %s', [ClassName, GDBMIExecCommandMap[FCurrentExecCmd]]);
end;
{ TGDBMIDebuggerCommandLineSymbolInfo }
function TGDBMIDebuggerCommandLineSymbolInfo.DoExecute: Boolean;
var
Src: String;
begin
Result := True;
FContext.ThreadContext := ccNotRequired;
FContext.StackContext := ccNotRequired;
Src := StringReplace(FSource, '\', '/', [rfReplaceAll]);
Src := StringReplace(Src, '"', '\"', [rfReplaceAll]);
ExecuteCommand('-symbol-list-lines "%s"', [Src], FResult);
if (FResult.State = dsError) and not(dcsCanceled in SeenStates)
then
ExecuteCommand('-symbol-list-lines %s', [FSource], FResult);
if (FResult.State = dsError) and not(dcsCanceled in SeenStates)
then begin
// the second trial: gdb can return info to file w/o path
Src := ExtractFileName(FSource);
if Src <> FSource
then ExecuteCommand('-symbol-list-lines %s', [Src], FResult);
end;
end;
constructor TGDBMIDebuggerCommandLineSymbolInfo.Create(AOwner: TGDBMIDebugger;
Source: string);
begin
inherited Create(AOwner);
FSource := Source;
end;
function TGDBMIDebuggerCommandLineSymbolInfo.DebugText: String;
begin
Result := Format('%s: Source=%s', [ClassName, FSource]);
end;
{ TGDBMIDebuggerCommandStackDepth }
function TGDBMIDebuggerCommandStackDepth.DoExecute: Boolean;
var
R: TGDBMIExecResult;
List: TGDBMINameValueList;
i, cnt: longint;
begin
Result := True;
if (FCallstack = nil) or (dcsCanceled in SeenStates) then exit;
FContext.StackContext := ccNotRequired;
FContext.ThreadContext := ccUseLocal;
FContext.ThreadId := FCallstack.ThreadId;
FDepth := -1;
if FLimit > 0 then
ExecuteCommand('-stack-info-depth %d', [FLimit], R)
else
ExecuteCommand('-stack-info-depth', R);
List := TGDBMINameValueList.Create(R);
cnt := StrToIntDef(List.Values['depth'], -1);
FreeAndNil(List);
if cnt = -1 then
begin
{ In case of error some stackframes still can be accessed.
Trying to find out how many...
We try maximum 40 frames, because sometimes a corrupt stack and a bug in
gdb may cooperate, so that -stack-info-depth X returns always X }
FLimit := 0; // this is a final result
i:=0;
repeat
inc(i);
ExecuteCommand('-stack-info-depth %d', [i], R);
List := TGDBMINameValueList.Create(R);
cnt := StrToIntDef(List.Values['depth'], -1);
FreeAndNil(List);
if (cnt = -1) then begin
// no valid stack-info-depth found, so the previous was the last valid one
cnt:=i - 1;
end;
until (cnt < i) or (i = 40);
end;
FDepth := cnt;
end;
constructor TGDBMIDebuggerCommandStackDepth.Create(AOwner: TGDBMIDebugger;
ACallstack: TCallStackBase);
begin
inherited Create(AOwner, ACallstack);
FLimit := 0;
end;
function TGDBMIDebuggerCommandStackDepth.DebugText: String;
begin
Result := Format('%s:', [ClassName]);
end;
{ TGDBMIDebuggerCommandStackFrames }
function TGDBMIDebuggerCommandStackFrames.DoExecute: Boolean;
var
CurStartIdx: Integer;
It: TMapIterator;
procedure FreeList(var AList: TGDBMINameValueListArray);
var
i : Integer;
begin
for i := low(AList) to high(AList) do
AList[i].Free;
end;
procedure UpdateEntry(AnEntry: TCallStackEntry; AArgInfo, AFrameInfo : TGDBMINameValueList);
var
i, j, n, e, NameEnd: Integer;
Arguments: TStringList;
List: TGDBMINameValueList;
Arg: PGDBMINameValue;
addr: TDbgPtr;
func, filename, fullname, line, cl, fn, fa, un: String;
begin
Arguments := TStringList.Create;
if (AArgInfo <> nil) and (AArgInfo.Count > 0)
then begin
List := TGDBMINameValueList.Create('');
for n := 0 to AArgInfo.Count - 1 do
begin
Arg := AArgInfo.Items[n];
List.Init(Arg^.Name);
Arguments.Add(List.Values['name'] + '=' + DeleteEscapeChars(List.Values['value']));
end;
FreeAndNil(List);
end;
addr := 0;
func := '';
filename := '';
fullname := '';
line := '';
if AFrameInfo <> nil
then begin
Val(AFrameInfo.Values['addr'], addr, e);
if e=0 then ;
func := AFrameInfo.Values['func'];
filename := ConvertGdbPathAndFile(AFrameInfo.Values['file']);
fullname := ConvertGdbPathAndFile(AFrameInfo.Values['fullname']);
line := AFrameInfo.Values['line'];
end;
(*
func="fpc_pushexceptaddr"
func="_$CODETEMPLATESDLG$_Ld98"
func="_$CODETEMPLATESDLG$_Ld98"
func="??"
*)
j := pos('$', func);
if j > 1 then begin
un := '';
cl := '';
fa := '';
i := pos('_$__', func);
if i > 1 then begin
// CLASSES$_$TREADER_$__$$_READINTEGER$$LONGINT
// SYSTEM_TOBJECT_$__DISPATCH$formal
// UNIT1_TFORM1_$__FORMCLOSE$TOBJECT$TCLOSEACTION
cl := copy(func, 1, i - 1); // unit and class
if copy(func, i + 4, 3) = '$$_' then
inc(i, 3);
NameEnd := PosEx('$', func, i + 4);
if NameEnd <= 0
then NameEnd := length(func) + 1;
fn := copy(func, i + 4, NameEnd - (i + 4)); // function
i := pos('$_$', cl);
if i > 1 then begin
un := copy(cl, 1, i - 1); // unit
delete(cl, 1, i + 2); // class
end
else begin
i := pos('_', cl);
if posex('_', cl, i + 1) < 1 then begin
// Only one _ => split unit and class
un := copy(cl, 1, i - 1); // unit
delete(cl, 1, i); // class
end;
end;
end
else begin
// SYSUTILS_COMPARETEXT$ANSISTRING$ANSISTRING$$LONGINT
NameEnd := j;
fn := copy(func, 1, NameEnd - 1);
i := pos('_', fn);
if posex('_', fn, i + 1) < 1 then begin
// Only one _ => split unit and class
un := copy(fn, 1, i - 1); // unit
delete(fn, 1, i); // class
end;
end;
inc(NameEnd, 1);
if copy(func, NameEnd, 1) = '$' then
inc(NameEnd, 1);
if (length(func) >= NameEnd) and (func[NameEnd] in ['a'..'z', 'A'..'Z']) then
fa := copy(func, NameEnd, MaxInt); // args
fa := AnsiReplaceText(fa, '$', ',');
//debugln([cl,' ## ', fn]);
AnEntry.Init(
addr,
Arguments,
func,
un, cl, fn, fa,
StrToIntDef(line, 0)
);
end
else begin
AnEntry.Init(
addr,
Arguments,
func,
filename, fullname,
StrToIntDef(line, 0)
);
end;
Arguments.Free;
end;
procedure PrepareArgs(var ADest: TGDBMINameValueListArray; AStart, AStop: Integer;
const ACmd, APath1, APath2: String);
var
R: TGDBMIExecResult;
i, lvl : Integer;
ResultList, SubList: TGDBMINameValueList;
begin
ExecuteCommand(ACmd, [AStart, AStop], R, [cfNoStackContext]);
if R.State = dsError
then begin
i := AStop - AStart;
case i of
0 : exit;
1..5: begin
while i >= 0 do
begin
PrepareArgs(ADest, AStart+i, AStart+i, ACmd, APath1, APath2);
dec(i);
end;
end;
else
i := i div 2;
PrepareArgs(ADest, AStart, AStart+i, ACmd, APath1, APath2);
PrepareArgs(ADest, AStart+i+1, AStop, ACmd, APath1, APath2);
end;
end;
ResultList := TGDBMINameValueList.Create(R, [APath1]);
for i := 0 to ResultList.Count - 1 do
begin
SubList := TGDBMINameValueList.Create(ResultList.GetString(i), ['frame']);
lvl := StrToIntDef(SubList.Values['level'], -1);
if (lvl >= AStart) and (lvl <= AStop)
then begin
if APath2 <> ''
then SubList.SetPath(APath2);
ADest[lvl-CurStartIdx] := SubList;
end
else SubList.Free;
end;
ResultList.Free;
end;
procedure ExecForRange(AStartIdx, AEndIdx: Integer);
var
Args: TGDBMINameValueListArray;
Frames: TGDBMINameValueListArray;
e: TCallStackEntry;
begin
try
CurStartIdx := AStartIdx;
SetLength(Args, AEndIdx-AStartIdx+1);
PrepareArgs(Args, AStartIdx, AEndIdx, '-stack-list-arguments 1 %d %d', 'stack-args', 'args');
if (FCallstack = nil) or (dcsCanceled in SeenStates) then exit;
SetLength(Frames, AEndIdx-AStartIdx+1);
PrepareArgs(Frames, AStartIdx, AEndIdx, '-stack-list-frames %d %d', 'stack', '');
if (FCallstack = nil) or (dcsCanceled in SeenStates) then exit;
if not It.Locate(AStartIdx)
then if not It.EOM
then IT.Next;
while it.Valid and (not It.EOM) do begin
e := TCallStackEntry(It.DataPtr^);
if e.Index > AEndIdx then break;
UpdateEntry(e, Args[e.Index-AStartIdx], Frames[e.Index-AStartIdx]);
It.Next;
end;
finally
FreeList(Args);
FreeList(Frames);
end;
end;
var
StartIdx, EndIdx: Integer;
begin
Result := True;
if (FCallstack = nil) or (dcsCanceled in SeenStates) then exit;
FContext.StackContext := ccNotRequired;
FContext.ThreadContext := ccUseLocal;
FContext.ThreadId := FCallstack.ThreadId;
It := TMapIterator.Create(FCallstack.RawEntries);
try
//if It.Locate(AIndex)
StartIdx := Max(FCallstack.LowestUnknown, 0);
EndIdx := FCallstack.HighestUnknown;
while EndIdx >= StartIdx do begin
if (FCallstack = nil) or (dcsCanceled in SeenStates) then break;
debugln(DBG_VERBOSE, ['Callstack.Frames A StartIdx=',StartIdx, ' EndIdx=',EndIdx]);
// search for existing blocks in the middle
if not It.Locate(StartIdx)
then if not It.EOM
then IT.Next;
StartIdx := TCallStackEntry(It.DataPtr^).Index;
EndIdx := StartIdx;
It.Next;
while (not It.EOM) and (TCallStackEntry(It.DataPtr^).Index = EndIdx+1) do begin
inc(EndIdx);
if EndIdx = FCallstack.HighestUnknown then
Break;
It.Next;
end;
debugln(DBG_VERBOSE, ['Callstack.Frames B StartIdx=',StartIdx, ' EndIdx=',EndIdx]);
ExecForRange(StartIdx, EndIdx);
if (FCallstack = nil) or (dcsCanceled in SeenStates) then break;
StartIdx := EndIdx + 1;
EndIdx := FCallstack.HighestUnknown;
end;
finally
IT.Free;
if FCallstack <> nil
then FCallstack.DoEntriesUpdated;
end;
end;
{ TGDBMILineInfo }
procedure TGDBMILineInfo.DoGetLineSymbolsDestroyed(Sender: TObject);
begin
if FGetLineSymbolsCmdObj = Sender
then FGetLineSymbolsCmdObj := nil;
end;
procedure TGDBMILineInfo.ClearSources;
var
n: Integer;
begin
for n := Low(FSourceMaps) to High(FSourceMaps) do
FSourceMaps[n].Map.Free;
Setlength(FSourceMaps, 0);
for n := 0 to FSourceIndex.Count - 1 do
DoChange(FSourceIndex[n]);
FSourceIndex.Clear;
//FRequestedSources.Clear;
end;
procedure TGDBMILineInfo.AddInfo(const ASource: String; const AResult: TGDBMIExecResult);
var
ID: packed record
Line, Column: Integer;
end;
Map: TMap;
n, idx: Integer;
LinesList, LineList: TGDBMINameValueList;
Item: PGDBMINameValue;
Addr: TDbgPtr;
begin
n := FSourceIndex.IndexOf(ASource);
if n = -1
then begin
idx := Length(FSourceMaps);
SetLength(FSourceMaps, idx+1);
FSourceMaps[idx].Map := nil;
FSourceMaps[idx].Source := ASource;
n := FSourceIndex.AddObject(ASource, TObject(PtrInt(idx)));
end
else idx := PtrInt(FSourceIndex.Objects[n]);
LinesList := TGDBMINameValueList.Create(AResult, ['lines']);
if LinesList = nil then Exit;
Map := FSourceMaps[idx].Map;
if Map = nil
then begin
// no map present
Map := TMap.Create(its8, SizeOf(TDBGPtr));
FSourceMaps[idx].Map := Map;
end;
ID.Column := 0;
LineList := TGDBMINameValueList.Create('');
for n := 0 to LinesList.Count - 1 do
begin
Item := LinesList.Items[n];
LineList.Init(Item^.Name);
if not TryStrToInt(Unquote(LineList.Values['line']), ID.Line) then Continue;
if not TryStrToQWord(Unquote(LineList.Values['pc']), Addr) then Continue;
// one line can have more than one address
if Map.HasId(ID) then Continue;
Map.Add(ID, Addr);
end;
LineList.Free;
LinesList.Free;
DoChange(ASource);
end;
function TGDBMILineInfo.Count: Integer;
begin
Result := FSourceIndex.Count;
end;
function TGDBMILineInfo.GetSource(const AIndex: integer): String;
begin
if AIndex < Low(FSourceMaps) then Exit('');
if AIndex > High(FSourceMaps) then Exit('');
Result := FSourceMaps[AIndex].Source;
end;
function TGDBMILineInfo.GetAddress(const AIndex: Integer; const ALine: Integer): TDbgPtr;
var
ID: packed record
Line, Column: Integer;
end;
Map: TMap;
begin
if AIndex < Low(FSourceMaps) then Exit(0);
if AIndex > High(FSourceMaps) then Exit(0);
Map := FSourceMaps[AIndex].Map;
if Map = nil then Exit(0);
ID.Line := ALine;
// since we do not have column info we map all on column 0
// ID.Column := AColumn;
ID.Column := 0;
if (Map = nil) then Exit(0);
if not Map.GetData(ID, Result) then
Result := 0;
end;
function TGDBMILineInfo.GetInfo(AAdress: TDbgPtr; out ASource, ALine, AOffset: Integer): Boolean;
begin
Result := False;
end;
procedure TGDBMILineInfo.DoStateChange(const AOldState: TDBGState);
begin
if not (Debugger.State in [dsPause, dsInternalPause, dsRun]) then
ClearSources;
end;
function TGDBMILineInfo.IndexOf(const ASource: String): integer;
begin
Result := FSourceIndex.IndexOf(ASource);
if Result <> -1
then Result := PtrInt(FSourceIndex.Objects[Result]);
end;
constructor TGDBMILineInfo.Create(const ADebugger: TDebuggerIntf);
begin
FSourceIndex := TStringList.Create;
FSourceIndex.Sorted := True;
FSourceIndex.Duplicates := dupError;
FSourceIndex.CaseSensitive := True;
FRequestedSources := TStringList.Create;
FRequestedSources.Sorted := True;
FRequestedSources.Duplicates := dupError;
FRequestedSources.CaseSensitive := True;
inherited;
end;
destructor TGDBMILineInfo.Destroy;
begin
ClearSources;
FreeAndNil(FSourceIndex);
FreeAndNil(FRequestedSources);
inherited Destroy;
end;
procedure TGDBMILineInfo.DoGetLineSymbolsFinished(Sender: TObject);
var
Cmd: TGDBMIDebuggerCommandLineSymbolInfo;
idx: LongInt;
begin
Cmd := TGDBMIDebuggerCommandLineSymbolInfo(Sender);
if Cmd.Result.State <> dsError
then
AddInfo(Cmd.Source, Cmd.Result);
idx := FRequestedSources.IndexOf(Cmd.Source);
if idx >= 0
then FRequestedSources.Delete(idx);
FGetLineSymbolsCmdObj := nil;
// DoChange is calle in AddInfo
end;
procedure TGDBMILineInfo.Request(const ASource: String);
var
idx: Integer;
begin
if (ASource = '') or (Debugger = nil) or (FRequestedSources.IndexOf(ASource) >= 0)
then Exit;
idx := IndexOf(ASource);
if (idx <> -1) and (FSourceMaps[idx].Map <> nil) then Exit; // already present
// add empty entry, to prevent further requests
FRequestedSources.Add(ASource);
// Need to interupt debugger
if Debugger.State = dsRun
then TGDBMIDebugger(Debugger).GDBPause(True);
FGetLineSymbolsCmdObj := TGDBMIDebuggerCommandLineSymbolInfo.Create(TGDBMIDebugger(Debugger), ASource);
FGetLineSymbolsCmdObj.OnExecuted := @DoGetLineSymbolsFinished;
FGetLineSymbolsCmdObj.OnDestroy := @DoGetLineSymbolsDestroyed;
FGetLineSymbolsCmdObj.Priority := GDCMD_PRIOR_LINE_INFO;
(* TGDBMIDebugger(Debugger).FCommandQueueExecLock > 0
Force queue, if locked. This will set the RunLevel
This can be called in AsyncCAll (TApplication), while in QueueExecuteLock (this does not run on unlock)
Without ForceQueue, the queue is virtually locked until the current command finishes.
But ExecCommand must be able to unlock
Reproduce: Trigger Exception in app startup (lfm loading). Stack is not searched.
*)
TGDBMIDebugger(Debugger).QueueCommand(FGetLineSymbolsCmdObj,
TGDBMIDebugger(Debugger).FCommandQueueExecLock > 0
);
(* DoEvaluationFinished may be called immediately at this point *)
end;
procedure TGDBMILineInfo.Cancel(const ASource: String);
var
i: Integer;
q: TGDBMIDebugger;
begin
q := TGDBMIDebugger(Debugger);
i := q.FCommandQueue.Count - 1;
while i >= 0 do begin
if (q.FCommandQueue[i] is TGDBMIDebuggerCommandLineSymbolInfo) and
(TGDBMIDebuggerCommandLineSymbolInfo(q.FCommandQueue[i]).Source = ASource)
then q.FCommandQueue[i].Cancel;
dec(i);
if i >= q.FCommandQueue.Count
then i := q.FCommandQueue.Count - 1;
end;
end;
{ =========================================================================== }
{ TGDBMIDebuggerPropertiesBase }
{ =========================================================================== }
procedure TGDBMIDebuggerPropertiesBase.SetTimeoutForEval(const AValue: Integer);
begin
if FTimeoutForEval = AValue then exit;
FTimeoutForEval := AValue;
if (FTimeoutForEval <> -1) and (FTimeoutForEval < 50)
then FTimeoutForEval := -1;
end;
procedure TGDBMIDebuggerPropertiesBase.SetMaxDisplayLengthForString(AValue: Integer);
begin
if FMaxDisplayLengthForString = AValue then Exit;
if AValue < 0 then
AValue := 0;
FMaxDisplayLengthForString := AValue;
end;
procedure TGDBMIDebuggerPropertiesBase.SetWarnOnTimeOut(const AValue: Boolean);
begin
if FWarnOnTimeOut = AValue then exit;
FWarnOnTimeOut := AValue;
end;
constructor TGDBMIDebuggerPropertiesBase.Create;
begin
{$IFDEF UNIX}
FConsoleTty := '';
{$ENDIF}
FMaxDisplayLengthForString := 2500;
{$IFDEF darwin}
FTimeoutForEval := 250;
{$ELSE darwin}
FTimeoutForEval := -1;
{$ENDIF}
FWarnOnTimeOut := True;
FWarnOnInternalError := True;
FEncodeCurrentDirPath := gdfeDefault;
FEncodeExeFileName := gdfeDefault;
FInternalStartBreak := gdsbDefault;
FUseAsyncCommandMode := False;
FDisableLoadSymbolsForLibraries := False;
FUseNoneMiRunCommands := gdnmFallback;
FDisableForcedBreakpoint := False;
FWarnOnSetBreakpointError := gdbwAll;
inherited;
end;
procedure TGDBMIDebuggerPropertiesBase.Assign(Source: TPersistent);
begin
inherited Assign(Source);
FGDBOptions := TGDBMIDebuggerPropertiesBase(Source).FGDBOptions;
{$IFDEF UNIX}
FConsoleTty := TGDBMIDebuggerPropertiesBase(Source).FConsoleTty;
{$ENDIF}
FMaxDisplayLengthForString := TGDBMIDebuggerPropertiesBase(Source).FMaxDisplayLengthForString;
FTimeoutForEval := TGDBMIDebuggerPropertiesBase(Source).FTimeoutForEval;
FWarnOnTimeOut := TGDBMIDebuggerPropertiesBase(Source).FWarnOnTimeOut;
FEncodeCurrentDirPath := TGDBMIDebuggerPropertiesBase(Source).FEncodeCurrentDirPath;
FEncodeExeFileName := TGDBMIDebuggerPropertiesBase(Source).FEncodeExeFileName;
FInternalStartBreak := TGDBMIDebuggerPropertiesBase(Source).FInternalStartBreak;
FUseAsyncCommandMode := TGDBMIDebuggerPropertiesBase(Source).FUseAsyncCommandMode;
FDisableLoadSymbolsForLibraries := TGDBMIDebuggerPropertiesBase(Source).FDisableLoadSymbolsForLibraries;
FUseNoneMiRunCommands := TGDBMIDebuggerPropertiesBase(Source).FUseNoneMiRunCommands;
FDisableForcedBreakpoint := TGDBMIDebuggerPropertiesBase(Source).FDisableForcedBreakpoint;
FWarnOnSetBreakpointError := TGDBMIDebuggerPropertiesBase(Source).FWarnOnSetBreakpointError;
end;
{ =========================================================================== }
{ TGDBMIDebugger }
{ =========================================================================== }
class function TGDBMIDebugger.Caption: String;
begin
Result := 'GNU debugger (gdb)';
end;
function TGDBMIDebugger.ChangeFileName: Boolean;
var
Cmd: TGDBMIDebuggerCommandChangeFilename;
begin
Result := False;
FCurrentStackFrameValid := False; // not running => not valid
FCurrentThreadIdValid := False;
if State = dsIdle then begin
// will do in start debugging
if not (inherited ChangeFileName) then Exit;
Result:=true;
exit;
end;
Cmd := TGDBMIDebuggerCommandChangeFilename.Create(Self, FileName);
Cmd.AddReference;
QueueCommand(Cmd);
// if filename = '', then command may be queued
if (FileName <> '') and (not Cmd.Success) then begin
MessageDlg('Debugger', Format('Failed to load file: %s', [Cmd.ErrorMsg]), mtError, [mbOK], 0);
Cmd.Cancel;
Cmd.ReleaseReference;
SetState(dsStop);
end
else begin
Cmd.ReleaseReference;
end;
if not (inherited ChangeFileName) then Exit;
Result:=true;
end;
constructor TGDBMIDebugger.Create(const AExternalDebugger: String);
begin
FReleaseLock := 0;
FMainAddrBreak := TGDBMIInternalBreakPoint.Create('main');
FBreakErrorBreak := TGDBMIInternalBreakPoint.Create('FPC_BREAK_ERROR');
FRunErrorBreak := TGDBMIInternalBreakPoint.Create('FPC_RUNERROR');
FExceptionBreak := TGDBMIInternalBreakPoint.Create('FPC_RAISEEXCEPTION');
FPopExceptStack := TGDBMIInternalBreakPoint.Create('FPC_POPADDRSTACK');
FCatchesBreak := TGDBMIInternalBreakPoint.Create('FPC_CATCHES');
FReRaiseBreak := TGDBMIInternalBreakPoint.Create('FPC_RERAISE');
{$IFdef WITH_GDB_FORCE_EXCEPTBREAK}
FBreakErrorBreak.UseForceFlag := True;
FRunErrorBreak.UseForceFlag := True;
FExceptionBreak.UseForceFlag := True;
{$ENDIF}
FInstructionQueue := TGDBMIDbgInstructionQueue.Create(Self);
FCommandQueue := TGDBMIDebuggerCommandList.Create;
FTargetInfo.TargetPID := 0;
FTargetInfo.TargetFlags := [];
FDebuggerFlags := [];
FSourceNames := TStringList.Create;
FSourceNames.Sorted := True;
FSourceNames.Duplicates := dupError;
FSourceNames.CaseSensitive := False;
FCommandQueueExecLock := 0;
FRunQueueOnUnlock := False;
FThreadGroups := TStringList.Create;
FTypeRequestCache := CreateTypeRequestCache;
FMaxLineForUnitCache := TStringList.Create;
FInProcessStopped := False;
FNeedStateToIdle := False;
FNeedReset := False;
{$IFdef MSWindows}
InitWin32;
{$ENDIF}
{$IFDEF DBG_ENABLE_TERMINAL}
FPseudoTerminal := TPseudoTerminal.Create;
FPseudoTerminal.OnCanRead :=@DoPseudoTerminalRead;
{$ENDIF}
inherited;
end;
function TGDBMIDebugger.CreateBreakPoints: TDBGBreakPoints;
begin
Result := TGDBMIBreakPoints.Create(Self, TGDBMIBreakPoint);
end;
function TGDBMIDebugger.CreateCallStack: TCallStackSupplier;
begin
Result := TGDBMICallStack.Create(Self);
end;
function TGDBMIDebugger.CreateDisassembler: TDBGDisassembler;
begin
Result := TGDBMIDisassembler.Create(Self);
end;
function TGDBMIDebugger.CreateLocals: TLocalsSupplier;
begin
Result := TGDBMILocals.Create(Self);
end;
function TGDBMIDebugger.CreateLineInfo: TDBGLineInfo;
begin
Result := TGDBMILineInfo.Create(Self);
end;
class function TGDBMIDebugger.CreateProperties: TDebuggerProperties;
begin
Result := TGDBMIDebuggerProperties.Create;
end;
function TGDBMIDebugger.CreateRegisters: TRegisterSupplier;
begin
Result := TGDBMIRegisterSupplier.Create(Self);
end;
function TGDBMIDebugger.CreateWatches: TWatchesSupplier;
begin
Result := TGDBMIWatches.Create(Self);
end;
function TGDBMIDebugger.CreateThreads: TThreadsSupplier;
begin
Result := TGDBMIThreads.Create(Self);
end;
function TGDBMIDebugger.CreateCommandInit: TGDBMIDebuggerCommandInitDebugger;
begin
Result := TGDBMIDebuggerCommandInitDebugger.Create(Self);
end;
function TGDBMIDebugger.CreateCommandStartDebugging
(AContinueCommand: TGDBMIDebuggerCommand): TGDBMIDebuggerCommandStartDebugging;
begin
Result:= TGDBMIDebuggerCommandStartDebugging.Create(Self, AContinueCommand);
end;
destructor TGDBMIDebugger.Destroy;
begin
LockRelease;
inherited;
ClearCommandQueue;
//RemoveRunQueueASync;
FreeAndNil(FCommandQueue);
FreeAndNil(FInstructionQueue);
ClearSourceInfo;
FreeAndNil(FSourceNames);
FreeAndNil(FThreadGroups);
{$IFDEF DBG_ENABLE_TERMINAL}
FreeAndNil(FPseudoTerminal);
{$ENDIF}
FreeAndNil(FTypeRequestCache);
FreeAndNil(FMaxLineForUnitCache);
FreeAndNil(FMainAddrBreak);
FreeAndNil(FBreakErrorBreak);
FreeAndNil(FRunErrorBreak);
FreeAndNil(FExceptionBreak);
FreeAndNil(FPopExceptStack);
FreeAndNil(FCatchesBreak);
FreeAndNil(FReRaiseBreak);
end;
procedure TGDBMIDebugger.Done;
begin
if State = dsDestroying
then begin
ClearCommandQueue;
inherited Done;
exit;
end;
LockRelease;
try
CancelAllQueued;
if (DebugProcess <> nil) and DebugProcess.Running then begin
if FCurrentCommand <> Nil then
FCurrentCommand.KillNow;
if (State = dsRun) then GDBPause(True);
// fire and forget. Donst wait on the queue.
FCurrentStackFrameValid := False;
FCurrentThreadIdValid := False;
SendCmdLn('kill');
SendCmdLn('-gdb-exit');
end;
inherited Done;
finally
UnlockRelease;
end;
end;
function TGDBMIDebugger.GetLocation: TDBGLocationRec;
begin
Result := FCurrentLocation;
end;
function TGDBMIDebugger.GetProcessList(AList: TRunningProcessInfoList): boolean;
{$ifdef darwin}
var
AResult: TGDBMIExecResult;
ARunningProcessInfo: TRunningProcessInfo;
pname,pid,aLine: string;
s: string;
i: integer;
{$endif}
begin
{$ifdef darwin}
result := State in [dsIdle, dsStop, dsInit];
if not Result then
exit;
AResult:=GDBMIExecResultDefault;
ExecuteCommand('info mach-tasks',[],[], AResult);
s := AResult.Values;
i := pos(sLineBreak,s);
while i>0 do
begin
aLine := trim(copy(s,1,i-1));
delete(s,1,i+1);
i := pos(' is ', aLine);
pid := copy(aLine,1,i-1);
pname := copy(aLine,i+4,PosEx(' ',aLine,i+4)-(i+4));
if pid <> '' then
begin
ARunningProcessInfo := TRunningProcessInfo.Create(StrToIntDef(pname,-1), pid);
AList.Add(ARunningProcessInfo);
end;
i := pos(sLineBreak,s);
end;
{$else}
result := false;
{$endif}
end;
procedure TGDBMIDebugger.LockCommandProcessing;
begin
// Keep a different counter than QueueExecuteLock
// So we can detect, if RunQueue was blocked by this
inc(FCommandProcessingLock);
end;
procedure TGDBMIDebugger.UnLockCommandProcessing;
begin
dec(FCommandProcessingLock);
if (FCommandProcessingLock = 0)
and FRunQueueOnUnlock
then begin
FRunQueueOnUnlock := False;
// if FCommandQueueExecLock, then queu will be run, by however has that lock
if (FCommandQueueExecLock = 0) and (FCommandQueue.Count > 0)
then begin
DebugLnEnter(DBGMI_QUEUE_DEBUG, ['TGDBMIDebugger.UnLockCommandProcessing: Execute RunQueue ']);
RunQueue; // ASync
DebugLnExit(DBGMI_QUEUE_DEBUG, ['TGDBMIDebugger.UnLockCommandProcessing: Finished RunQueue']);
end
end;
end;
procedure TGDBMIDebugger.DoState(const OldState: TDBGState);
begin
FTypeRequestCache.Clear;
if not (State in [dsRun, dsPause, dsInit, dsInternalPause])
then FMaxLineForUnitCache.Clear;
if not (State in [dsPause, dsInternalPause]) then
FStoppedReason := srNone;;
if State in [dsStop, dsError]
then begin
ClearSourceInfo;
FPauseWaitState := pwsNone;
// clear un-needed commands
if State = dsError
then CancelAllQueued
else CancelAfterStop;
end;
if (State = dsError) and (DebugProcessRunning) then begin
FCurrentStackFrameValid := False;
FCurrentThreadIdValid := False;
FCurrentThreadId := 0;
FCurrentStackFrame := 0;
SendCmdLn('kill'); // try to kill the debugged process. bypass all queues.
TerminateGDB;
end;
if (OldState in [dsPause, dsInternalPause]) and (State = dsRun)
then begin
FPauseWaitState := pwsNone;
{$IFDEF MSWindows}
FPauseRequestInThreadID := 0;
{$ENDIF}
end;
CallStack.CurrentCallStackList.EntriesForThreads[FCurrentThreadId].CurrentIndex := FCurrentStackFrame;
inherited DoState(OldState);
end;
procedure TGDBMIDebugger.DoBeforeState(const OldState: TDBGState);
begin
if State in [dsStop] then begin
FCurrentStackFrameValid := False;
FCurrentThreadIdValid := False;
FCurrentThreadId := 0;
FCurrentStackFrame := 0;
end;
inherited DoBeforeState(OldState);
Threads.CurrentThreads.CurrentThreadId := FCurrentThreadId; // TODO: Works only because CurrentThreadId is always valid
end;
function TGDBMIDebugger.LineEndPos(const s: string; out LineEndLen: integer): integer;
var
l: Integer;
begin
Result := 1;
LineEndLen := 0;
l := Length(s);
while (Result <= l) and not(s[Result] in [#10, #13]) do inc(Result);
if (Result <= l) then begin
LineEndLen := 1;
if (Result < l) and (s[Result + 1] in [#10, #13]) and (s[Result] <> s[Result + 1]) then
LineEndLen := 2;
end
else
Result := 0;
end;
procedure TGDBMIDebugger.DoThreadChanged;
begin
TGDBMICallstack(CallStack).DoThreadChanged;
if Registers.CurrentRegistersList <> nil then
Registers.CurrentRegistersList.Clear;
end;
procedure TGDBMIDebugger.DoRelease;
begin
SetState(dsDestroying);
if FReleaseLock > 0
then exit;
inherited DoRelease;
end;
procedure TGDBMIDebugger.DoUnknownException(Sender: TObject; AnException: Exception);
var
I: Integer;
Frames: PPointer;
Report, Report2: string;
begin
try
debugln(['ERROR: Exception occurred in ',Sender.ClassName+': ',
AnException.ClassName, ' Msg="', AnException.Message, '" Addr=', dbgs(ExceptAddr),
' Dbg.State=', dbgs(State)]);
Report := BackTraceStrFunc(ExceptAddr);
Report2 := Report;
Frames := ExceptFrames;
for I := 0 to ExceptFrameCount - 1 do begin
Report := Report + LineEnding + BackTraceStrFunc(Frames[I]);
if i < 5
then Report2 := Report;
end;
except
end;
debugln(Report);
if MessageDlg(gdbmiTheDebuggerExperiencedAnUnknownCondition,
Format(gdbmiPressIgnoreToContinueDebuggingThisMayNOTBeSafePres,
[LineEnding, AnException.ClassName, AnException.Message, Report2, Sender.ClassName, dbgs(State)]),
mtWarning, [mbIgnore, mbAbort], 0, mbAbort) = mrAbort
then begin
try
CancelAllQueued;
finally
Stop;
end;
end;
end;
procedure TGDBMIDebugger.AddThreadGroup(const S: String);
var
List: TGDBMINameValueList;
begin
List := TGDBMINameValueList.Create(S);
FThreadGroups.Values[List.Values['id']] := List.Values['pid'];
List.Free;
end;
procedure TGDBMIDebugger.RemoveThreadGroup(const S: String);
begin
// Some gdb info contains thread group which are already exited => don't remove them
end;
function TGDBMIDebugger.ParseLibraryLoaded(const S: String): String;
const
DebugInfo: array[Boolean] of String = ('No Debug Info', 'Has Debug Info');
var
List: TGDBMINameValueList;
ThreadGroup: String;
begin
// input: =library-loaded,id="C:\\Windows\\system32\\ntdll.dll",target-name="C:\\Windows\\system32\\ntdll.dll",host-name="C:\\Windows\\system32\\ntdll.dll",symbols-loaded="0",thread-group="i1"
List := TGDBMINameValueList.Create(S);
ThreadGroup := List.Values['thread-group'];
Result := Format('Module Load: "%s". %s. Thread Group: %s (%s)', [ConvertGdbPathAndFile(List.Values['id']), DebugInfo[List.Values['symbols-loaded'] = '1'], ThreadGroup, FThreadGroups.Values[ThreadGroup]]);
List.Free;
end;
function TGDBMIDebugger.ParseLibraryUnLoaded(const S: String): String;
var
List: TGDBMINameValueList;
ThreadGroup: String;
begin
// input: =library-unloaded,id="C:\\Windows\\system32\\advapi32.dll",target-name="C:\\Windows\\system32\\advapi32.dll",host-name="C:\\Windows\\system32\\advapi32.dll",thread-group="i1"
List := TGDBMINameValueList.Create(S);
ThreadGroup := List.Values['thread-group'];
Result := Format('Module Unload: "%s". Thread Group: %s (%s)', [ConvertGdbPathAndFile(List.Values['id']), ThreadGroup, FThreadGroups.Values[ThreadGroup]]);
List.Free;
end;
function TGDBMIDebugger.ParseThread(const S, EventText: String): String;
var
List: TGDBMINameValueList;
ThreadGroup: String;
begin
if EventText = 'thread-created' then
Result := 'Thread Start: '
else
Result := 'Thread Exit: ';
List := TGDBMINameValueList.Create(S);
ThreadGroup := List.Values['group-id'];
Result := Result + Format('Thread ID: %s. Thread Group: %s (%s)', [List.Values['id'], ThreadGroup, FThreadGroups.Values[ThreadGroup]]);
List.Free;
end;
function TGDBMIDebugger.CreateTypeRequestCache: TGDBPTypeRequestCache;
begin
Result := TGDBPTypeRequestCache.Create;
end;
procedure TGDBMIDebugger.DoNotifyAsync(Line: String);
var
EventText: String;
begin
EventText := GetPart(['='], [','], Line, False, False);
case StringCase(EventText, [
'shlibs-added',
'library-loaded',
'library-unloaded',
'shlibs-updated',
'thread-group-started',
'thread-group-exited',
'thread-created',
'thread-exited'], False, False) of
0: DoDbgEvent(ecModule, etModuleLoad, Line);
1: DoDbgEvent(ecModule, etModuleLoad, ParseLibraryLoaded(Line));
2: DoDbgEvent(ecModule, etModuleUnload, ParseLibraryUnloaded(Line));
3: DoDbgEvent(ecModule, etDefault, Line);
4: AddThreadGroup(Line);
5: RemoveThreadGroup(Line);
6: DoDbgEvent(ecThread, etThreadStart, ParseThread(Line, EventText));
7: DoDbgEvent(ecThread, etThreadExit, ParseThread(Line, EventText));
else
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: Unexpected async-record: ', Line);
end;
end;
procedure TGDBMIDebugger.DoDbgBreakpointEvent(ABreakpoint: TDBGBreakPoint;
Location: TDBGLocationRec; AReason: TGDBMIBreakpointReason;
AOldVal: String = ''; ANewVal: String = '');
var
SrcName, Msg: String;
SrcLine: Integer;
begin
SrcName := Location.SrcFullName;
if SrcName = '' then
SrcName := Location.SrcFile;
if (SrcName = '') and (ABreakPoint <> nil) and (ABreakPoint.Kind = bpkSource) then
SrcName := ABreakpoint.Source;
SrcLine := Location.SrcLine;
if (SrcLine < 1) and (ABreakPoint <> nil) and (ABreakPoint.Kind = bpkSource) then
SrcLine := ABreakpoint.Line;
if ABreakpoint = nil then begin
Msg := Format('Unknown %s', [GDBMIBreakPointReasonNames[AReason]]);
if AReason = gbrWatchTrigger then
Msg := Msg + Format(' changed from "%s" to "%s"', [AOldVal, ANewVal]);
end
else begin
case ABreakPoint.Kind of
bpkSource: Msg := 'Source Breakpoint';
bpkAddress: Msg := 'Address Breakpoint';
bpkData:
begin
if AReason = gbrWatchScope then
Msg := Format('Watchpoint for "%s" out of scope', [ABreakpoint.WatchData])
else
Msg := Format('Watchpoint for "%s" was triggered. Old value "%s", New Value "%s"', [ABreakpoint.WatchData, AOldVal, ANewVal]);
end;
end;
end;
if SrcName <> '' then begin
DoDbgEvent(ecBreakpoint, etBreakpointHit,
Format('%s at $%.' + IntToStr(TargetPtrSize * 2) + 'x: %s line %d',
[Msg, Location.Address, SrcName, SrcLine]));
end
else begin
DoDbgEvent(ecBreakpoint, etBreakpointHit,
Format('%s at $%.' + IntToStr(TargetPtrSize * 2) + 'x',
[Msg, Location.Address]));
end;
end;
function TGDBMIDebugger.ExecuteCommand(const ACommand: String;
const AValues: array of const; const AFlags: TGDBMICommandFlags): Boolean;
var
R: TGDBMIExecResult;
begin
R:=GDBMIExecResultDefault;
Result := ExecuteCommandFull(ACommand, AValues, AFlags, nil, 0, R);
end;
function TGDBMIDebugger.ExecuteCommand(const ACommand: String;
const AValues: array of const; const AFlags: TGDBMICommandFlags;
var AResult: TGDBMIExecResult): Boolean;
begin
Result := ExecuteCommandFull(ACommand, AValues, AFlags, nil, 0, AResult);
end;
function TGDBMIDebugger.ExecuteCommandFull(const ACommand: String;
const AValues: array of const; const AFlags: TGDBMICommandFlags;
const ACallback: TGDBMICallback; const ATag: PtrInt;
var AResult: TGDBMIExecResult): Boolean;
var
CommandObj: TGDBMIDebuggerSimpleCommand;
begin
CommandObj := TGDBMIDebuggerSimpleCommand.Create(Self, ACommand, AValues, AFlags, ACallback, ATag);
CommandObj.AddReference;
QueueCommand(CommandObj);
Result := CommandObj.State in [dcsExecuting, dcsFinished];
if Result
then
AResult := CommandObj.Result;
CommandObj.ReleaseReference;
end;
procedure TGDBMIDebugger.RunQueue;
var
R: Boolean;
Cmd, NestedCurrentCmd, NestedCurrentCmdTmp: TGDBMIDebuggerCommand;
SavedInExecuteCount: LongInt;
begin
//RemoveRunQueueASync;
if FCommandQueue.Count = 0
then exit;
if FCommandProcessingLock > 0
then begin
FRunQueueOnUnlock := True;
exit
end;
// Safeguard the NestLvl and outer CurrrentCmd
SavedInExecuteCount := FInExecuteCount;
NestedCurrentCmd := FCurrentCommand;
LockRelease;
try
try
repeat
Cmd := FCommandQueue[0];
if (Cmd.QueueRunLevel >= 0) and (Cmd.QueueRunLevel < FInExecuteCount)
then break;
Inc(FInExecuteCount);
FCommandQueue.Delete(0);
DebugLnEnter(DBGMI_QUEUE_DEBUG, ['Executing (Recurse-Count=', FInExecuteCount-1, ') queued= ', FCommandQueue.Count, ' CmdPrior=', Cmd.Priority,' CmdMinRunLvl=', Cmd.QueueRunLevel, ' : "', Cmd.DebugText,'" State=',dbgs(State),' PauseWaitState=',ord(FPauseWaitState) ]);
// cmd may be canceled while executed => don't loose it while working with it
Cmd.AddReference;
NestedCurrentCmdTmp := FCurrentCommand; // TODO: needs to be canceled, if there is a cancelation
FCurrentCommand := Cmd;
// excute, has it's own try-except block => so we don't have one here
R := Cmd.Execute;
Cmd.DoFinished;
FCurrentCommand := NestedCurrentCmdTmp;
Cmd.ReleaseReference;
DebugLnExit(DBGMI_QUEUE_DEBUG, 'Exec done');
Dec(FInExecuteCount);
// Do not add code with callbacks outside "FInExecuteCount"
// Otherwhise "LockCommandProcessing" will fail to continue the queue
// TODO: if the debugger can accept them into a separate queue, the set stae here
// TODO: For now do not allow new session, before old session is finished
// There may already be commands for the next run queued,
// which will then set a new state.
//if FNeedStateToIdle and (FInExecuteCount = 0)
//then ResetStateToIdle;
if State in [dsError, dsDestroying]
then begin
//DebugLn(DBG_WARNINGS, '[WARNING] TGDBMIDebugger: ExecuteCommand "',Cmd,'" failed.');
Break;
end;
if FCommandQueue.Count = 0
then begin
if (FInExecuteCount = 0) // not in Recursive call
and (FPauseWaitState = pwsInternal)
and (State = dsRun)
then begin
// reset state
FPauseWaitState := pwsNone;
// insert continue command
Cmd := TGDBMIDebuggerCommandExecute.Create(Self, ectContinue);
FCommandQueue.Add(Cmd);
debugln(DBGMI_QUEUE_DEBUG, ['Internal Queueing: exec-continue']);
end
else Break; // Queue empty
end;
until not R;
debugln(DBGMI_QUEUE_DEBUG, ['Leaving Queue with count: ', FCommandQueue.Count, ' Recurse-Count=', FInExecuteCount,' State=',dbgs(State)]);
finally
UnlockRelease;
FInExecuteCount := SavedInExecuteCount;
FCurrentCommand := NestedCurrentCmd;
end;
except
On E: Exception do DoUnknownException(Self, E);
else
debugln(['ERROR: Exception occurred in ',ClassName+': ',
'" Addr=', dbgs(ExceptAddr), ' Dbg.State=', dbgs(State)]);
end;
if (FCommandQueue.Count = 0) and assigned(OnIdle) and (FInExecuteCount=0) and
(not FInIdle) and not(State in [dsError, dsDestroying])
then begin
repeat
DebugLnEnter(DBGMI_QUEUE_DEBUG, ['>> Run OnIdle']);
LockCommandProcessing;
FInIdle := True;
try
OnIdle(Self);
finally
R := (FCommandQueue.Count > 0) and (FCommandProcessingLock = 1) and FRunQueueOnUnlock;
DebugLn(DBGMI_QUEUE_DEBUG, ['OnIdle: UnLock']);
UnLockCommandProcessing;
FInIdle := False;
end;
DebugLnExit(DBGMI_QUEUE_DEBUG, ['<< Run OnIdle']);
until (not R) or (not assigned(OnIdle)) or (State in [dsError, dsDestroying]);
DebugLn(DBGMI_QUEUE_DEBUG, ['OnIdle: Finished ']);
end;
if FNeedStateToIdle and (FInExecuteCount = 0)
then ResetStateToIdle;
end;
procedure TGDBMIDebugger.QueueCommand(const ACommand: TGDBMIDebuggerCommand; ForceQueue: Boolean = False);
var
i, p: Integer;
CanRunQueue: Boolean;
begin
(* TODO: if an exec-command is queued, cancel watches-commands, etc (unless required for snapshot)
This may occur if multiply exe are queued.
Currently, they will be ForcedQueue, and end up, after the exec command => cancel by state change
Also see call to CancelBeforeRun in TGDBMIDebuggerCommandExecute.DoExecute
*)
p := ACommand.Priority;
i := 0;
// CanRunQueue: The queue can be run for "ACommand"
// Either the queue is empty (so no other command will run)
// Or the first command on the queue is blocked by "QueueRunLevel"
CanRunQueue := (FCommandQueue.Count = 0)
or ( (FCommandQueue.Count > 0)
and (FCommandQueue[0].QueueRunLevel >= 0)
and (FCommandQueue[0].QueueRunLevel < FInExecuteCount)
)
or ( (p > FCommandQueue[0].Priority) and (FCommandQueueExecLock = 0) );
if (ACommand is TGDBMIDebuggerCommandExecute) then begin
// Execute-commands, must be queued at the end. They have QueueRunLevel, so they only run in the outer loop
CanRunQueue := (FCommandQueue.Count = 0);
i := FCommandQueue.Add(ACommand);
end
else
if p > 0 then begin
// Queue Pririty commands
// TODO: check for "CanRunQueue": should be at start?
while (i < FCommandQueue.Count)
and (FCommandQueue[i].Priority >= p)
and ( (ForceQueue)
or (FCommandQueue[i].QueueRunLevel < 0)
or (FCommandQueue[i].QueueRunLevel >= FInExecuteCount)
)
do inc(i);
FCommandQueue.Insert(i, ACommand);
end
else begin
// Queue normal commands
if (not ForceQueue) and (FCommandQueue.Count > 0)
and CanRunQueue // first item is deferred, so new item inserted can run
then
FCommandQueue.Insert(0, ACommand)
else
i := FCommandQueue.Add(ACommand);
end;
// if other commands do run the queue,
// make sure this command only runs after the CurrentCommand finished
if ForceQueue and
( (ACommand.QueueRunLevel < 0) or (ACommand.QueueRunLevel >= FInExecuteCount) )
then
ACommand.QueueRunLevel := FInExecuteCount - 1;
if (not CanRunQueue) or (FCommandQueueExecLock > 0)
or (FCommandProcessingLock > 0) or ForceQueue
then begin
debugln(DBGMI_QUEUE_DEBUG, ['Queueing (Recurse-Count=', FInExecuteCount, ') at pos=', i, ' cnt=',FCommandQueue.Count-1, ' State=',dbgs(State), ' Lock=',FCommandQueueExecLock, ' Forced=', dbgs(ForceQueue), ' Prior=',p, ': "', ACommand.DebugText,'"']);
ACommand.DoQueued;
// FCommandProcessingLock still must call RunQueue
if FCommandProcessingLock = 0 then
Exit;
end;
// If we are here we can process the command directly
RunQueue;
end;
procedure TGDBMIDebugger.UnQueueCommand(const ACommand: TGDBMIDebuggerCommand);
begin
FCommandQueue.Remove(ACommand);
end;
procedure TGDBMIDebugger.CancelAllQueued;
var
i: Integer;
begin
i := FCommandQueue.Count - 1;
while i >= 0 do begin
TGDBMIDebuggerCommand(FCommandQueue[i]).Cancel;
dec(i);
if i >= FCommandQueue.Count
then i := FCommandQueue.Count - 1;
end;
if FCurrentCommand <> nil
then FCurrentCommand.Cancel;
end;
procedure TGDBMIDebugger.CancelBeforeRun;
var
i: Integer;
begin
i := FCommandQueue.Count - 1;
while i >= 0 do begin
if dcpCancelOnRun in TGDBMIDebuggerCommand(FCommandQueue[i]).Properties
then TGDBMIDebuggerCommand(FCommandQueue[i]).Cancel;
dec(i);
if i >= FCommandQueue.Count
then i := FCommandQueue.Count - 1;
end;
if (FCurrentCommand <> nil) and (dcpCancelOnRun in FCurrentCommand.Properties)
then FCurrentCommand.Cancel;
end;
procedure TGDBMIDebugger.CancelAfterStop;
var
i: Integer;
begin
i := FCommandQueue.Count - 1;
while i >= 0 do begin
if TGDBMIDebuggerCommand(FCommandQueue[i]) is TGDBMIDebuggerCommandExecute
then TGDBMIDebuggerCommand(FCommandQueue[i]).Cancel;
dec(i);
if i >= FCommandQueue.Count
then i := FCommandQueue.Count - 1;
end;
// do not cancel FCurrentCommand;
end;
procedure TGDBMIDebugger.RunQueueASync;
begin
Application.QueueAsyncCall(@DoRunQueueFromASync, 0);
end;
procedure TGDBMIDebugger.RemoveRunQueueASync;
begin
Application.RemoveAsyncCalls(Self);
end;
procedure TGDBMIDebugger.DoRunQueueFromASync(Data: PtrInt);
begin
DebugLnEnter(DBGMI_QUEUE_DEBUG, ['TGDBMIDebugger.DoRunQueueFromASync: Execute RunQueue ']);
RunQueue;
DebugLnExit(DBGMI_QUEUE_DEBUG, ['TGDBMIDebugger.DoRunQueueFromASync: Finished RunQueue']);
end;
class function TGDBMIDebugger.ExePaths: String;
begin
{$IFdef MSWindows}
Result := '$(LazarusDir)\mingw\$(TargetCPU)-$(TargetOS)\bin\gdb.exe;$(LazarusDir)\mingw\bin\gdb.exe;C:\lazarus\mingw\bin\gdb.exe';
{$ELSE}
Result := 'gdb;/usr/bin/gdb;/usr/local/bin/gdb;/opt/fpc/gdb';
{$ENDIF}
end;
function TGDBMIDebugger.FindBreakpoint(
const ABreakpoint: Integer): TDBGBreakPoint;
var
n: Integer;
begin
if ABreakpoint > 0
then
for n := 0 to Breakpoints.Count - 1 do
begin
Result := Breakpoints[n];
if TGDBMIBreakPoint(Result).FBreakID = ABreakpoint
then Exit;
end;
Result := nil;
end;
function PosSetEx(const ASubStrSet, AString: string;
const Offset: integer): integer;
begin
for Result := Offset to Length(AString) do
if Pos(AString[Result], ASubStrSet) > 0 then
exit;
Result := 0;
end;
function EscapeGDBCommand(const AInput: string): string;
var
lPiece: string;
I, lPos, len: integer;
begin
lPos := 1;
Result := '';
repeat
I := PosSetEx(#9#10#13, AInput, lPos);
{ copy unmatched characters }
if I > 0 then
len := I-lPos
else
len := Length(AInput)+1-lPos;
Result := Result + Copy(AInput, lPos, len);
{ replace a matched character or be done }
if I > 0 then
begin
case AInput[I] of
#9: lPiece := '\t';
#10: lPiece := '\n';
#13: lPiece := '\r';
else
lPiece := '';
end;
Result := Result + lPiece;
lPos := I+1;
end else
exit;
until false;
end;
function TGDBMIDebugger.GDBDisassemble(AAddr: TDbgPtr; ABackward: Boolean;
out ANextAddr: TDbgPtr; out ADump, AStatement, AFile: String; out ALine: Integer): Boolean;
var
NewEntryMap: TDBGDisassemblerEntryMap;
CmdObj: TGDBMIDebuggerCommandDisassemble;
Rng: TDBGDisassemblerEntryRange;
i: Integer;
begin
NewEntryMap := TDBGDisassemblerEntryMap.Create(itu8, SizeOf(TDBGDisassemblerEntryRange));
CmdObj := TGDBMIDebuggerCommandDisassemble.Create(Self, NewEntryMap, AAddr, AAddr, -1, 2);
CmdObj.AddReference;
CmdObj.Priority := GDCMD_PRIOR_IMMEDIATE;
QueueCommand(CmdObj);
Result := CmdObj.State in [dcsExecuting, dcsFinished];
Rng := NewEntryMap.GetRangeForAddr(AAddr);
if Result and (Rng <> nil)
then begin
i := Rng.IndexOfAddr(AAddr);
if ABackward
then dec(i);
if
i >= 0
then begin
if i < Rng.Count
then ANextAddr := Rng.EntriesPtr[i]^.Addr
else ANextAddr := Rng.LastEntryEndAddr;
ADump := Rng.EntriesPtr[i]^.Dump;
AStatement := Rng.EntriesPtr[i]^.Statement;
AFile := Rng.EntriesPtr[i]^.SrcFileName;
ALine := Rng.EntriesPtr[i]^.SrcFileLine;
end;
end;
if not Result
then CmdObj.Cancel;
CmdObj.ReleaseReference;
FreeAndNil(NewEntryMap);
end;
procedure TGDBMIDebugger.DoPseudoTerminalRead(Sender: TObject);
begin
{$IFDEF DBG_ENABLE_TERMINAL}
if assigned(OnConsoleOutput)
then OnConsoleOutput(self, FPseudoTerminal.Read);
{$ENDIF}
end;
function TGDBMIDebugger.GDBEnvironment(const AVariable: String; const ASet: Boolean): Boolean;
var
S: String;
begin
Result := True;
if State = dsRun
then GDBPause(True);
if ASet then
begin
S := EscapeGDBCommand(AVariable);
ExecuteCommand('-gdb-set env %s', [S], [cfscIgnoreState, cfNoThreadContext]);
end else begin
S := AVariable;
ExecuteCommand('unset env %s', [GetPart([], ['='], S, False, False)], [cfscIgnoreState, cfNoThreadContext]);
end;
end;
function TGDBMIDebugger.GDBEvaluate(const AExpression: String; var AResult: String;
out ATypeInfo: TGDBType; EvalFlags: TDBGEvaluateFlags): Boolean;
var
CommandObj: TGDBMIDebuggerCommandEvaluate;
begin
CommandObj := TGDBMIDebuggerCommandEvaluate.Create(Self, AExpression, wdfDefault);
CommandObj.EvalFlags := EvalFlags;
CommandObj.AddReference;
CommandObj.Priority := GDCMD_PRIOR_IMMEDIATE; // try run imediately
QueueCommand(CommandObj);
Result := CommandObj.State in [dcsExecuting, dcsFinished];
AResult := CommandObj.TextValue;
ATypeInfo := CommandObj.TypeInfo;
if EvalFlags * [defNoTypeInfo, defSimpleTypeInfo, defFullTypeInfo] = [defNoTypeInfo]
then FreeAndNil(ATypeInfo);
CommandObj.ReleaseReference;
end;
function TGDBMIDebugger.GDBModify(const AExpression, ANewValue: String): Boolean;
var
R: TGDBMIExecResult;
S: String;
begin
S := Trim(ANewValue);
if (S <> '') and (S[1] in ['''', '#'])
then begin
if not ConvertPascalExpression(S) then Exit(False);
end;
R := GDBMIExecResultDefault;
Result := ExecuteCommandFull('-gdb-set var %s := %s', [UpperCaseSymbols(AExpression), S], [cfscIgnoreError], @GDBModifyDone, 0, R)
and (R.State <> dsError);
FTypeRequestCache.Clear;
end;
procedure TGDBMIDebugger.GDBModifyDone(const AResult: TGDBMIExecResult;
const ATag: PtrInt);
begin
FTypeRequestCache.Clear;
TGDBMILocals(Locals).Changed;
TGDBMIWatches(Watches).Changed;
end;
function TGDBMIDebugger.GDBJumpTo(const ASource: String; const ALine: Integer): Boolean;
begin
Result := False;
end;
function TGDBMIDebugger.GDBAttach(AProcessID: String): Boolean;
var
Cmd: TGDBMIDebuggerCommandAttach;
begin
Result := False;
if State <> dsStop then exit;
Cmd := TGDBMIDebuggerCommandAttach.Create(Self, AProcessID);
Cmd.AddReference;
QueueCommand(Cmd);
Result := Cmd.Success;
if not Result
then Cmd.Cancel;
Cmd.ReleaseReference;
end;
function TGDBMIDebugger.GDBDetach: Boolean;
begin
Result := False;
if State = dsRun
then GDBPause(True);
CancelAllQueued;
QueueCommand(TGDBMIDebuggerCommandDetach.Create(Self));
Result := True;
end;
function TGDBMIDebugger.GDBPause(const AInternal: Boolean): Boolean;
begin
if FInProcessStopped then exit;
// Check if we already issued a break
if FPauseWaitState = pwsNone
then InterruptTarget;
if AInternal
then begin
if FPauseWaitState = pwsNone
then FPauseWaitState := pwsInternal;
end
else FPauseWaitState := pwsExternal;
Result := True;
end;
function TGDBMIDebugger.GDBRun: Boolean;
begin
Result := False;
case State of
dsStop: begin
FThreadGroups.Clear;
Result := StartDebugging(ectContinue);
end;
dsPause: begin
CancelBeforeRun;
QueueCommand(TGDBMIDebuggerCommandExecute.Create(Self, ectContinue));
Result := True;
end;
dsIdle: begin
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: Unable to run in idle state');
end;
end;
end;
function TGDBMIDebugger.GDBRunTo(const ASource: String;
const ALine: Integer): Boolean;
begin
Result := False;
case State of
dsStop: begin
Result := False;
end;
dsPause: begin
CancelBeforeRun;
QueueCommand(TGDBMIDebuggerCommandExecute.Create(Self, ectRunTo, [ASource, ALine]));
Result := True;
end;
dsIdle: begin
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: Unable to runto in idle state');
end;
end;
end;
function TGDBMIDebugger.GDBSourceAdress(const ASource: String; ALine, AColumn: Integer; out AAddr: TDbgPtr): Boolean;
var
ID: packed record
Line, Column: Integer;
end;
Map: TMap;
idx, n: Integer;
R: TGDBMIExecResult;
LinesList, LineList: TGDBMINameValueList;
Item: PGDBMINameValue;
Addr: TDbgPtr;
begin
Result := False;
AAddr := 0;
if ASource = ''
then Exit;
idx := FSourceNames.IndexOf(ASource);
if (idx <> -1)
then begin
Map := TMap(FSourceNames.Objects[idx]);
ID.Line := ALine;
// since we do not have column info we map all on column 0
// ID.Column := AColumn;
ID.Column := 0;
Result := (Map <> nil);
if Result
then Map.GetData(ID, AAddr);
Exit;
end;
R := GDBMIExecResultDefault;
Result := ExecuteCommand('-symbol-list-lines %s', [ASource], [cfscIgnoreError, cfNoThreadContext], R)
and (R.State <> dsError);
// if we have an .inc file then search for filename only since there are some
// problems with locating file by full path in gdb in case only relative file
// name is stored
if not Result then
Result := ExecuteCommand('-symbol-list-lines %s', [ExtractFileName(ASource)], [cfscIgnoreError, cfNoThreadContext], R)
and (R.State <> dsError);
if not Result then Exit;
Map := TMap.Create(its8, SizeOf(AAddr));
FSourceNames.AddObject(ASource, Map);
LinesList := TGDBMINameValueList.Create(R, ['lines']);
if LinesList = nil then Exit(False);
ID.Column := 0;
LineList := TGDBMINameValueList.Create('');
for n := 0 to LinesList.Count - 1 do
begin
Item := LinesList.Items[n];
LineList.Init(Item^.Name);
if not TryStrToInt(Unquote(LineList.Values['line']), ID.Line) then Continue;
if not TryStrToQWord(Unquote(LineList.Values['pc']), Addr) then Continue;
// one line can have more than one address
if Map.HasId(ID) then Continue;
Map.Add(ID, Addr);
if ID.Line = ALine
then AAddr := Addr;
end;
LineList.Free;
LinesList.Free;
end;
procedure TGDBMIDebugger.LockRelease;
begin
inc(FReleaseLock);
end;
procedure TGDBMIDebugger.UnlockRelease;
begin
dec(FReleaseLock);
if (FReleaseLock = 0) and (State = dsDestroying)
then Release;
end;
function TGDBMIDebugger.GDBStepInto: Boolean;
begin
Result := False;
case State of
dsStop: begin
Result := StartDebugging;
end;
dsPause: begin
CancelBeforeRun;
QueueCommand(TGDBMIDebuggerCommandExecute.Create(Self, ectStepInto));
Result := True;
end;
dsIdle: begin
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: Unable to step in idle state');
end;
end;
end;
function TGDBMIDebugger.GDBStepOverInstr: Boolean;
begin
Result := False;
case State of
dsStop: begin
Result := StartDebugging;
end;
dsPause: begin
CancelBeforeRun;
QueueCommand(TGDBMIDebuggerCommandExecute.Create(Self, ectStepOverInstruction));
Result := True;
end;
dsIdle: begin
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: Unable to step over instr in idle state');
end;
end;
end;
function TGDBMIDebugger.GDBStepIntoInstr: Boolean;
begin
Result := False;
case State of
dsStop: begin
Result := StartDebugging;
end;
dsPause: begin
CancelBeforeRun;
QueueCommand(TGDBMIDebuggerCommandExecute.Create(Self, ectStepIntoInstruction));
Result := True;
end;
dsIdle: begin
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: Unable to step in instr idle state');
end;
end;
end;
function TGDBMIDebugger.GDBStepOut: Boolean;
begin
Result := False;
case State of
dsStop: begin
Result := False;
end;
dsPause: begin
CancelBeforeRun;
QueueCommand(TGDBMIDebuggerCommandExecute.Create(Self, ectStepOut));
Result := True;
end;
dsIdle: begin
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: Unable to step out in idle state');
end;
end;
end;
function TGDBMIDebugger.GDBStepOver: Boolean;
begin
Result := False;
case State of
dsStop: begin
Result := StartDebugging;
end;
dsPause: begin
CancelBeforeRun;
QueueCommand(TGDBMIDebuggerCommandExecute.Create(Self, ectStepOver));
Result := True;
end;
dsIdle: begin
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: Unable to step over in idle state');
end;
end;
end;
function TGDBMIDebugger.GDBStop: Boolean;
begin
if State = dsError
then begin
// We don't know the state of the debugger,
// force a reinit. Let's hope this works.
TerminateGDB;
Done;
Result := True;
Exit;
end;
if (FCurrentCommand <> nil) and FCurrentCommand.KillNow then begin
debugln(DBG_VERBOSE, ['KillNow did stop']);
Result := True;
exit;
end;
if State = dsRun
then GDBPause(True);
CancelAllQueued;
QueueCommand(TGDBMIDebuggerCommandKill.Create(Self));
Result := True;
end;
function TGDBMIDebugger.GetSupportedCommands: TDBGCommands;
begin
Result := [dcRun, dcPause, dcStop, dcStepOver, dcStepInto, dcStepOut,
dcStepOverInstr, dcStepIntoInstr, dcRunTo, dcAttach, dcDetach, dcJumpto,
dcBreak, dcWatch, dcLocal, dcEvaluate, dcModify, dcEnvironment,
dcSetStackFrame, dcDisassemble
{$IFDEF DBG_ENABLE_TERMINAL}, dcSendConsoleInput{$ENDIF}
];
end;
function TGDBMIDebugger.GetCommands: TDBGCommands;
begin
if FNeedStateToIdle
then Result := []
else Result := inherited GetCommands;
end;
function TGDBMIDebugger.GetTargetWidth: Byte;
begin
Result := FTargetInfo.TargetPtrSize*8;
end;
procedure TGDBMIDebugger.Init;
procedure CheckGDBVersion;
begin
if FGDBVersion < '5.3'
then begin
DebugLn(DBG_WARNINGS, '[WARNING] Debugger: Running an old (< 5.3) GDB version: ', FGDBVersion);
DebugLn(DBG_WARNINGS, ' Not all functionality will be supported.');
end
else begin
DebugLn(DBG_VERBOSE, '[Debugger] Running GDB version: ', FGDBVersion);
Include(FDebuggerFlags, dfImplicidTypes);
end;
end;
var
Options: String;
Cmd: TGDBMIDebuggerCommandInitDebugger;
env: TStringList;
begin
Exclude(FDebuggerFlags, dfForceBreakDetected);
Exclude(FDebuggerFlags, dfSetBreakFailed);
Exclude(FDebuggerFlags, dfSetBreakPending);
LockRelease;
try
FPauseWaitState := pwsNone;
FErrorHandlingFlags := [];
FInExecuteCount := 0;
FInIdle := False;
FNeedStateToIdle := False;
Options := '-silent -i mi -nx';
if Length(TGDBMIDebuggerPropertiesBase(GetProperties).Debugger_Startup_Options) > 0
then Options := Options + ' ' + TGDBMIDebuggerPropertiesBase(GetProperties).Debugger_Startup_Options;
env := EnvironmentAsStringList;
DebuggerEnvironment := env;
env.Free;
{$ifNdef MSWindows}
DebuggerEnvironment.Values['LANG'] := 'C'; // try to prevent GDB from using localized messages
{$ENDIF}
if CreateDebugProcess(Options)
then begin
if not ParseInitialization
then begin
SetState(dsError);
end
else begin
Cmd := CreateCommandInit;
Cmd.AddReference;
QueueCommand(Cmd);
if not Cmd.Success then begin
Cmd.Cancel;
Cmd.ReleaseReference;
SetState(dsError);
end
else begin
Cmd.ReleaseReference;
CheckGDBVersion;
inherited Init;
end;
end;
end
else begin
include(FErrorHandlingFlags, ehfDeferReadWriteError);
SetErrorState(gdbmiFailedToLaunchExternalDbg, ReadLine(50));
end;
FGDBPtrSize := CpuNameToPtrSize(FGDBCPU); // will be set in StartDebugging
finally
UnlockRelease;
end;
end;
procedure TGDBMIDebugger.InterruptTarget;
{$IFdef MSWindows}
function TryNT: Boolean;
var
hProcess: THandle;
hThread: THandle;
E: Integer;
Emsg: PChar;
begin
Result := False;
hProcess := OpenProcess(PROCESS_CREATE_THREAD or PROCESS_QUERY_INFORMATION or PROCESS_VM_OPERATION or PROCESS_VM_WRITE or PROCESS_VM_READ, False, TargetPID);
if hProcess = 0 then Exit;
try
hThread := _CreateRemoteThread(hProcess, nil, 0, DebugBreakAddr, nil, 0, FPauseRequestInThreadID);
if hThread = 0
then begin
E := GetLastError;
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_ALLOCATE_BUFFER, nil, E, 0, PChar(@Emsg), 0, nil);
DebugLN(DBG_WARNINGS, 'Error creating remote thread: ' + String(EMsg));
// Yuck !
// mixing handles and pointers, but it is how MS documented it
LocalFree(HLOCAL(Emsg));
Exit;
end;
Result := True;
CloseHandle(hThread);
finally
CloseHandle(hProcess);
end;
end;
{$ENDIF}
begin
debugln(DBGMI_QUEUE_DEBUG, ['TGDBMIDebugger.InterruptTarget: TargetPID=', TargetPID]);
//if FAsyncModeEnabled then begin
if FCurrentCmdIsAsync and (FCurrentCommand <> nil) then begin
FCurrentCommand.ExecuteCommand('interrupt', [cfNoThreadContext]);
FCurrentCommand.ExecuteCommand('info program', [cfNoThreadContext]); // trigger "*stopped..." msg. This may be deferred to the cmd after the "interupt"
exit;
end;
if TargetPID = 0 then Exit;
{$IFDEF UNIX}
FpKill(TargetPID, SIGINT);
{$ENDIF}
{$IFdef MSWindows}
// GenerateConsoleCtrlEvent is nice, but only works if both gdb and
// our target have a console. On win95 and family this is our only
// option, on NT4+ we have a choice. Since this is not likely that
// we have a console, we do it the hard way. On XP there exists
// DebugBreakProcess, but it does efectively the same.
if (DebugBreakAddr = nil)
or not Assigned(_CreateRemoteThread)
or not TryNT
then begin
// We have no other choice than trying this
debugln(DBGMI_QUEUE_DEBUG, ['TGDBMIDebugger.InterruptTarget: Send CTRL_BREAK_EVENT']);
GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, TargetPID);
Exit;
end;
{$ENDIF}
end;
function TGDBMIDebugger.ParseInitialization: Boolean;
var
Line, S: String;
begin
Result := True;
// Get initial debugger lines
S := '';
Line := ReadLine;
while DebugProcessRunning and (Line <> '(gdb) ') and (State <> dsError) do
begin
if Line <> ''
then
case Line[1] of
'=': begin
case StringCase(GetPart(['='], [','], Line, False, False),
['thread-group-added'])
of
0: {ignore};
else
S := S + Line + LineEnding;
end;
end;
else
S := S + Line + LineEnding;
end;
Line := ReadLine;
end;
if S <> ''
then MessageDlg('Debugger', 'Initialization output: ' + LineEnding + S,
mtInformation, [mbOK], 0);
end;
function TGDBMIDebugger.RequestCommand(const ACommand: TDBGCommand; const AParams: array of const): Boolean;
var
EvalFlags: TDBGEvaluateFlags;
begin
LockRelease;
try
case ACommand of
dcRun: Result := GDBRun;
dcPause: Result := GDBPause(False);
dcStop: Result := GDBStop;
dcStepOver: Result := GDBStepOver;
dcStepInto: Result := GDBStepInto;
dcStepOut: Result := GDBStepOut;
dcRunTo: 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;
dcEvaluate: begin
EvalFlags := [];
if high(AParams) >= 3 then
EvalFlags := TDBGEvaluateFlags(AParams[3].VInteger);
Result := GDBEvaluate(String(AParams[0].VAnsiString),
String(AParams[1].VPointer^), TGDBType(AParams[2].VPointer^),
EvalFlags);
end;
dcModify: Result := GDBModify(String(AParams[0].VAnsiString), String(AParams[1].VAnsiString));
dcEnvironment: Result := GDBEnvironment(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-};
dcStepOverInstr: Result := GDBStepOverInstr;
dcStepIntoInstr: Result := GDBStepIntoInstr;
{$IFDEF DBG_ENABLE_TERMINAL}
dcSendConsoleInput: FPseudoTerminal.Write(String(AParams[0].VAnsiString));
{$ENDIF}
end;
finally
UnlockRelease;
end;
end;
procedure TGDBMIDebugger.ClearCommandQueue;
var
i: Integer;
begin
for i:=0 to FCommandQueue.Count-1 do begin
TGDBMIDebuggerCommand(FCommandQueue[i]).ReleaseReference;
end;
FCommandQueue.Clear;
end;
function TGDBMIDebugger.GetIsIdle: Boolean;
begin
Result := (FCommandQueue.Count = 0) and (State in [dsPause, dsInternalPause]);
end;
procedure TGDBMIDebugger.ResetStateToIdle;
begin
if FInExecuteCount > 0 then begin
debugln(DBGMI_QUEUE_DEBUG, ['Defer dsIdle: Recurse-Count=', FInExecuteCount]);
FNeedStateToIdle := True;
exit;
end;
FNeedStateToIdle := False;
inherited ResetStateToIdle;
end;
procedure TGDBMIDebugger.ClearSourceInfo;
var
n: Integer;
begin
for n := 0 to FSourceNames.Count - 1 do
FSourceNames.Objects[n].Free;
FSourceNames.Clear;
end;
function TGDBMIDebugger.StartDebugging(AContinueCommand: TGDBMIExecCommandType): Boolean;
begin
Result := StartDebugging(TGDBMIDebuggerCommandExecute.Create(Self, AContinueCommand));
end;
function TGDBMIDebugger.StartDebugging(AContinueCommand: TGDBMIExecCommandType;
AValues: array of const): Boolean;
begin
Result := StartDebugging(TGDBMIDebuggerCommandExecute.Create(Self, AContinueCommand, AValues));
end;
function TGDBMIDebugger.StartDebugging(AContinueCommand: TGDBMIDebuggerCommand = nil): Boolean;
var
Cmd: TGDBMIDebuggerCommandStartDebugging;
begin
// We expect to be run immediately, no queue
FCurrentStackFrameValid := False;
FCurrentThreadIdValid := False;
Cmd := CreateCommandStartDebugging(AContinueCommand);
Cmd.AddReference;
QueueCommand(Cmd);
Result := Cmd.Success;
if not Result
then Cmd.Cancel;
Cmd.ReleaseReference;
end;
procedure TGDBMIDebugger.TerminateGDB;
begin
AbortReadLine;
FPauseWaitState := pwsNone;
if DebugProcessRunning then begin
debugln(DBG_VERBOSE, ['TGDBMIDebugger.TerminateGDB ']);
if not DebugProcess.Terminate(0) then begin
if OnFeedback = nil then
MessageDlg(gdbmiFailedToTerminateGDBTitle,
Format(gdbmiFailedToTerminateGDB, [LineEnding]), mtError, [mbOK], 0)
else
OnFeedback(Self,
Format(gdbmiFailedToTerminateGDB, [LineEnding]),
'',
ftError, [frOk]
);
SetState(dsError);
end;
end;
end;
{$IFDEF DBG_ENABLE_TERMINAL}
procedure TGDBMIDebugger.ProcessWhileWaitForHandles;
begin
inherited ProcessWhileWaitForHandles;
FPseudoTerminal.CheckCanRead;
end;
{$ENDIF}
procedure TGDBMIDebugger.QueueExecuteLock;
begin
inc(FCommandQueueExecLock);
end;
procedure TGDBMIDebugger.QueueExecuteUnlock;
begin
dec(FCommandQueueExecLock);
end;
procedure TGDBMIDebugger.TestCmd(const ACommand: String);
begin
ExecuteCommand(ACommand, [], [cfscIgnoreError]);
end;
function TGDBMIDebugger.NeedReset: Boolean;
begin
Result := FNeedReset;
end;
{%region ***** BreakPoints ***** }
{ TGDBMIDebuggerCommandBreakPointBase }
function TGDBMIDebuggerCommandBreakPointBase.ExecCheckLineInUnit(ASource: string;
ALine: Integer): Boolean;
var
R: TGDBMIExecResult;
i, m, n: Integer;
begin
Result := ALine > 0;
if not Result then exit;
m := -1;
i := FTheDebugger.FMaxLineForUnitCache.IndexOf(ASource);
if i >= 0 then
m := PtrInt(FTheDebugger.FMaxLineForUnitCache.Objects[i]);
if ALine <= m then exit;;
if ExecuteCommand('info line "' + ASource + '":' + IntToStr(ALine), R)
and (R.State <> dsError)
then begin
m := pos('"', R.Values); // find start of filename in messages
n := pos('out of range', R.Values);
Result := (n < 1) or (n >= m);
end;
if not Result then exit;
if i < 0 then
i := FTheDebugger.FMaxLineForUnitCache.Add(ASource);
FTheDebugger.FMaxLineForUnitCache.Objects[i] := TObject(PtrInt(ALine));
end;
function TGDBMIDebuggerCommandBreakPointBase.ExecBreakDelete(ABreakId: Integer): Boolean;
begin
Result := False;
if ABreakID = 0 then Exit;
Result := ExecuteCommand('-break-delete %d', [ABreakID], []);
end;
function TGDBMIDebuggerCommandBreakPointBase.ExecBreakEnabled(ABreakId: Integer;
AnEnabled: Boolean): Boolean;
const
// Use shortstring as fix for fpc 1.9.5 [2004/07/15]
CMD: array[Boolean] of ShortString = ('disable', 'enable');
begin
Result := False;
if ABreakID = 0 then Exit;
Result := ExecuteCommand('-break-%s %d', [CMD[AnEnabled], ABreakID], []);
end;
function TGDBMIDebuggerCommandBreakPointBase.ExecBreakCondition(ABreakId: Integer;
AnExpression: string): Boolean;
begin
Result := False;
if ABreakID = 0 then Exit;
Result := ExecuteCommand('-break-condition %d %s', [ABreakID, UpperCaseSymbols(AnExpression)], []);
end;
{ TGDBMIDebuggerCommandBreakInsert }
function TGDBMIDebuggerCommandBreakInsert.ExecBreakInsert(out ABreakId, AHitCnt: Integer; out
AnAddr: TDBGPtr): Boolean;
var
R: TGDBMIExecResult;
ResultList: TGDBMINameValueList;
WatchExpr, WatchDecl, WatchAddr: String;
s1, s2: String;
begin
Result := False;
ABreakId := 0;
AHitCnt := 0;
AnAddr := 0;
case FKind of
bpkSource:
begin
if (FSource = '') or (FLine < 0) then exit;
Result := ExecCheckLineInUnit(FSource, FLine);
if not Result then exit;
s1 := '';
s2 := StringReplace(FSource, '\', '/', [rfReplaceAll]);
//s2 := StringReplace(s2, '"', '\"', [rfReplaceAll]);
Result := ExecuteCommand('-break-insert %s "\"%s\":%d"', [s1, s2, FLine], R);
if dfForceBreak in FTheDebugger.FDebuggerFlags then s1 := '-f';
if (not Result) or (R.State = dsError) then
Result := ExecuteCommand('-break-insert %s %s:%d', [s1, ExtractFileName(FSource), FLine], R);
end;
bpkAddress:
begin
if (FAddress = 0) then exit;
if dfForceBreak in FTheDebugger.FDebuggerFlags
then Result := ExecuteCommand('-break-insert -f *%u', [FAddress], R)
else Result := ExecuteCommand('-break-insert *%u', [FAddress], R);
end;
bpkData:
begin
if (FWatchData = '') then exit;
WatchExpr := UpperCaseSymbols(WatchData);
if FWatchScope = wpsGlobal then begin
Result := ExecuteCommand('ptype %s', [WatchExpr], R);
Result := Result and (R.State <> dsError);
if not Result then exit;
WatchDecl := PCLenToString(ParseTypeFromGdb(R.Values).Name);
Result := ExecuteCommand('-data-evaluate-expression %s', [Quote('@'+WatchExpr)], R);
Result := Result and (R.State <> dsError);
if not Result then exit;
WatchAddr := StripLN(GetPart('value="', '"', R.Values));
WatchExpr := WatchDecl+'(' + WatchAddr + '^)';
end;
case FWatchKind of
wpkWrite: Result := ExecuteCommand('-break-watch %s', [WatchExpr], R);
wpkRead: Result := ExecuteCommand('-break-watch -r %s', [WatchExpr], R);
wpkReadWrite: Result := ExecuteCommand('-break-watch -a %s', [WatchExpr], R);
end;
Result := Result and (R.State <> dsError);
end;
end;
ResultList := TGDBMINameValueList.Create(R);
case FKind of
bpkSource, bpkAddress:
begin
ResultList.SetPath('bkpt');
if (not Result) or (r.State = dsError) and
(DebuggerProperties.WarnOnSetBreakpointError in [gdbwAll, gdbwUserBreakPoint])
then
Include(FTheDebugger.FDebuggerFlags, dfSetBreakFailed);
if ((ResultList.IndexOf('pending') >= 0) or
(pos('pend', lowercase(ResultList.Values['addr'])) > 0)) and
(DebuggerProperties.WarnOnSetBreakpointError in [gdbwAll, gdbwUserBreakPoint])
then
Include(FTheDebugger.FDebuggerFlags, dfSetBreakPending);
end;
bpkData:
case FWatchKind of
wpkWrite: begin
if ResultList.IndexOf('hw-wpt') >= 0 then ResultList.SetPath('hw-wpt')
else
if ResultList.IndexOf('wpt') >= 0 then ResultList.SetPath('wpt');
end;
wpkRead: begin
if ResultList.IndexOf('hw-rwpt') >= 0 then ResultList.SetPath('hw-rwpt')
else
if ResultList.IndexOf('rwpt') >= 0 then ResultList.SetPath('rwpt')
else
if ResultList.IndexOf('hw-wpt') >= 0 then ResultList.SetPath('hw-wpt')
else
if ResultList.IndexOf('wpt') >= 0 then ResultList.SetPath('wpt');
end;
wpkReadWrite: begin
if ResultList.IndexOf('hw-awpt') >= 0 then ResultList.SetPath('hw-awpt')
else
if ResultList.IndexOf('awpt') >= 0 then ResultList.SetPath('awpt')
else
if ResultList.IndexOf('hw-wpt') >= 0 then ResultList.SetPath('hw-wpt')
else
if ResultList.IndexOf('wpt') >= 0 then ResultList.SetPath('wpt');
end;
end;
end;
ABreakID := StrToIntDef(ResultList.Values['number'], 0);
AHitCnt := StrToIntDef(ResultList.Values['times'], 0);
AnAddr := StrToQWordDef(ResultList.Values['addr'], 0);
if ABreakID = 0
then Result := False;
ResultList.Free;
end;
function TGDBMIDebuggerCommandBreakInsert.DoExecute: Boolean;
begin
Result := True;
FContext.ThreadContext := ccNotRequired;
FContext.StackContext := ccNotRequired;
FValid := False;
DefaultTimeOut := DebuggerProperties.TimeoutForEval;
try
if FReplaceId <> 0
then ExecBreakDelete(FReplaceId);
FValid := ExecBreakInsert(FBreakID, FHitCnt, FAddr);
if not FValid then Exit;
if (FExpression <> '') and not (dcsCanceled in SeenStates)
then ExecBreakCondition(FBreakID, FExpression);
if not (dcsCanceled in SeenStates)
then ExecBreakEnabled(FBreakID, FEnabled);
if dcsCanceled in SeenStates
then begin
ExecBreakDelete(FBreakID);
FBreakID := 0;
FValid := False;
FAddr := 0;
FHitCnt := 0;
end;
finally
DefaultTimeOut := -1;
end;
end;
constructor TGDBMIDebuggerCommandBreakInsert.Create(AOwner: TGDBMIDebugger; ASource: string;
ALine: Integer; AEnabled: Boolean; AnExpression: string; AReplaceId: Integer);
begin
inherited Create(AOwner);
FKind := bpkSource;
FSource := ASource;
FLine := ALine;
FEnabled := AEnabled;
FExpression := AnExpression;
FReplaceId := AReplaceId;
end;
constructor TGDBMIDebuggerCommandBreakInsert.Create(AOwner: TGDBMIDebugger;
AAddress: TDBGPtr; AEnabled: Boolean; AnExpression: string;
AReplaceId: Integer);
begin
inherited Create(AOwner);
FKind := bpkAddress;
FAddress := AAddress;
FEnabled := AEnabled;
FExpression := AnExpression;
FReplaceId := AReplaceId;
end;
constructor TGDBMIDebuggerCommandBreakInsert.Create(AOwner: TGDBMIDebugger; AData: string;
AScope: TDBGWatchPointScope; AKind: TDBGWatchPointKind; AEnabled: Boolean;
AnExpression: string; AReplaceId: Integer);
begin
inherited Create(AOwner);
FKind := bpkData;
FWatchData := AData;
FWatchScope := AScope;
FWatchKind := AKind;
FEnabled := AEnabled;
FExpression := AnExpression;
FReplaceId := AReplaceId;
end;
function TGDBMIDebuggerCommandBreakInsert.DebugText: String;
begin
case FKind of
bpkAddress:
Result := Format('%s: Address=%x, Enabled=%s', [ClassName, FAddress, dbgs(FEnabled)]);
bpkData:
Result := Format('%s: Data=%s, Enabled=%s', [ClassName, FWatchData, dbgs(FEnabled)]);
else
Result := Format('%s: Source=%s, Line=%d, Enabled=%s', [ClassName, FSource, FLine, dbgs(FEnabled)]);
end;
end;
{ TGDBMIDebuggerCommandBreakRemove }
function TGDBMIDebuggerCommandBreakRemove.DoExecute: Boolean;
begin
Result := True;
FContext.ThreadContext := ccNotRequired;
FContext.StackContext := ccNotRequired;
DefaultTimeOut := DebuggerProperties.TimeoutForEval;
try
ExecBreakDelete(FBreakId);
finally
DefaultTimeOut := -1;
end;
end;
constructor TGDBMIDebuggerCommandBreakRemove.Create(AOwner: TGDBMIDebugger;
ABreakId: Integer);
begin
inherited Create(AOwner);
FBreakId := ABreakId;
end;
function TGDBMIDebuggerCommandBreakRemove.DebugText: String;
begin
Result := Format('%s: BreakId=%d', [ClassName, FBreakId]);
end;
{ TGDBMIDebuggerCommandBreakUpdate }
function TGDBMIDebuggerCommandBreakUpdate.DoExecute: Boolean;
begin
Result := True;
FContext.ThreadContext := ccNotRequired;
FContext.StackContext := ccNotRequired;
DefaultTimeOut := DebuggerProperties.TimeoutForEval;
try
if FUpdateExpression
then ExecBreakCondition(FBreakID, FExpression);
if FUpdateEnabled
then ExecBreakEnabled(FBreakID, FEnabled);
finally
DefaultTimeOut := -1;
end;
end;
constructor TGDBMIDebuggerCommandBreakUpdate.Create(AOwner: TGDBMIDebugger; ABreakId: Integer);
begin
inherited Create(AOwner);
FBreakID := ABreakId;
FUpdateEnabled := False;
FUpdateExpression := False;
end;
constructor TGDBMIDebuggerCommandBreakUpdate.Create(AOwner: TGDBMIDebugger;
ABreakId: Integer; AnEnabled: Boolean);
begin
inherited Create(AOwner);
FBreakID := ABreakId;
FEnabled := AnEnabled;
FUpdateEnabled := True;
FUpdateExpression := False;
end;
constructor TGDBMIDebuggerCommandBreakUpdate.Create(AOwner: TGDBMIDebugger;
ABreakId: Integer; AnExpression: string);
begin
inherited Create(AOwner);
FBreakID := ABreakId;
FExpression := AnExpression;
FUpdateExpression := True;
FUpdateEnabled := False;
end;
constructor TGDBMIDebuggerCommandBreakUpdate.Create(AOwner: TGDBMIDebugger;
ABreakId: Integer; AnEnabled: Boolean; AnExpression: string);
begin
inherited Create(AOwner);
FBreakID := ABreakId;
FEnabled := AnEnabled;
FUpdateEnabled := True;
FExpression := AnExpression;
FUpdateExpression := True;
end;
function TGDBMIDebuggerCommandBreakUpdate.DebugText: String;
begin
Result := Format('%s: BreakId=%d ChangeEnabled=%s NewEnable=%s ChangeEpression=%s NewExpression=%s',
[ClassName, FBreakId, dbgs(FUpdateEnabled), dbgs(FEnabled), dbgs(FUpdateExpression), FExpression]);
end;
{ =========================================================================== }
{ TGDBMIBreakPoint }
{ =========================================================================== }
constructor TGDBMIBreakPoint.Create(ACollection: TCollection);
begin
inherited Create(ACollection);
FCurrentCmd := nil;
FUpdateFlags := [];
FBreakID := 0;
end;
destructor TGDBMIBreakPoint.Destroy;
begin
ReleaseBreakPoint;
if FCurrentCmd <> nil
then begin
// keep the command running
FCurrentCmd.OnDestroy := nil;
FCurrentCmd.OnCancel := nil;
FCurrentCmd.OnExecuted := nil;
end;
inherited Destroy;
end;
procedure TGDBMIBreakPoint.DoEnableChange;
begin
if (FBreakID = 0) and Enabled and
(TGDBMIDebugger(Debugger).State in [dsPause, dsInternalPause, dsRun])
then
SetBreakPoint
else
UpdateProperties([bufEnabled]);
inherited;
end;
procedure TGDBMIBreakPoint.DoExpressionChange;
var
S: String;
begin
S := Expression;
if ConvertPascalExpression(S)
then FParsedExpression := S
else FParsedExpression := Expression;
if (FBreakID = 0) and Enabled and
(TGDBMIDebugger(Debugger).State in [dsPause, dsInternalPause, dsRun])
then
SetBreakPoint
else
UpdateProperties([bufCondition]);
inherited;
end;
procedure TGDBMIBreakPoint.DoStateChange(const AOldState: TDBGState);
begin
inherited DoStateChange(AOldState);
case Debugger.State of
dsInit: begin
// Disabled data breakpoints: wait until enabled
// Disabled other breakpoints: Cive to GDB to see if they are valid
if (Kind <> bpkData) or Enabled then
SetBreakpoint;
end;
dsStop: begin
if FBreakID > 0
then ReleaseBreakpoint;
end;
end;
end;
procedure TGDBMIBreakPoint.DoLogExpression(const AnExpression: String);
var
s: String;
t: TGDBType;
begin
s:='';
if TGDBMIDebugger(Debugger).GDBEvaluate(AnExpression, s, t, [defNoTypeInfo])
then begin
TGDBMIDebugger(Debugger).DoDbgEvent(ecBreakpoint, etBreakpointEvaluation, s);
end;
end;
procedure TGDBMIBreakPoint.MakeInvalid;
begin
BeginUpdate;
ReleaseBreakPoint;
SetValid(vsInvalid);
Changed;
EndUpdate;
end;
procedure TGDBMIBreakPoint.SetAddress(const AValue: TDBGPtr);
begin
if (Address = AValue) then exit;
inherited;
if (Debugger = nil) then Exit;
if TGDBMIDebugger(Debugger).State in [dsPause, dsInternalPause, dsRun]
then SetBreakpoint;
end;
procedure TGDBMIBreakPoint.SetBreakpoint;
begin
if Debugger = nil then Exit;
if IsUpdating
then begin
FUpdateFlags := [bufSetBreakPoint];
exit;
end;
if (FCurrentCmd <> nil)
then begin
// We can not be changed, while we get destroyed
if (FCurrentCmd is TGDBMIDebuggerCommandBreakRemove)
then begin
SetValid(vsInvalid);
exit;
end;
if (FCurrentCmd is TGDBMIDebuggerCommandBreakInsert) and (FCurrentCmd.State = dcsQueued)
then begin
// update the current object
TGDBMIDebuggerCommandBreakInsert(FCurrentCmd).Kind := Kind;
case Kind of
bpkSource:
begin
TGDBMIDebuggerCommandBreakInsert(FCurrentCmd).Source := Source;
TGDBMIDebuggerCommandBreakInsert(FCurrentCmd).Line := Line;
end;
bpkAddress:
begin
TGDBMIDebuggerCommandBreakInsert(FCurrentCmd).Address := Address;
end;
bpkData:
begin
TGDBMIDebuggerCommandBreakInsert(FCurrentCmd).WatchData := WatchData;
TGDBMIDebuggerCommandBreakInsert(FCurrentCmd).WatchScope := WatchScope;
end;
end;
TGDBMIDebuggerCommandBreakInsert(FCurrentCmd).Enabled := Enabled;
TGDBMIDebuggerCommandBreakInsert(FCurrentCmd).Expression := FParsedExpression;
exit;
end;
if (FCurrentCmd.State = dcsQueued)
then begin
// must be update for enabled or expression. both will be included in BreakInsert
// cancel and schedule BreakInsert
FCurrentCmd.OnDestroy := nil;
FCurrentCmd.OnCancel := nil;
FCurrentCmd.OnExecuted := nil;
FCurrentCmd.Cancel;
end
else begin
// let the command run (remove flags for enabled/condition)
FUpdateFlags := [bufSetBreakPoint];
exit;
end;
end;
FUpdateFlags := [];
case Kind of
bpkSource:
FCurrentCmd := TGDBMIDebuggerCommandBreakInsert.Create(TGDBMIDebugger(Debugger), Source, Line, Enabled, FParsedExpression, FBreakID);
bpkAddress:
FCurrentCmd := TGDBMIDebuggerCommandBreakInsert.Create(TGDBMIDebugger(Debugger), Address, Enabled, FParsedExpression, FBreakID);
bpkData:
FCurrentCmd := TGDBMIDebuggerCommandBreakInsert.Create(TGDBMIDebugger(Debugger), WatchData, WatchScope, WatchKind, Enabled, FParsedExpression, FBreakID);
end;
FBreakID := 0; // will be replaced => no longer valid
FCurrentCmd.OnDestroy := @DoCommandDestroyed;
FCurrentCmd.OnExecuted := @DoCommandExecuted;
FCurrentCmd.Priority := GDCMD_PRIOR_USER_ACT;
TGDBMIDebugger(Debugger).QueueCommand(FCurrentCmd);
if Debugger.State = dsRun
then TGDBMIDebugger(Debugger).GDBPause(True);
end;
procedure TGDBMIBreakPoint.DoCommandDestroyed(Sender: TObject);
begin
if Sender = FCurrentCmd
then FCurrentCmd := nil;
// in case of cancelation
if bufSetBreakPoint in FUpdateFlags
then SetBreakPoint;
if FUpdateFlags * [bufEnabled, bufCondition] <> []
then UpdateProperties(FUpdateFlags);
end;
procedure TGDBMIBreakPoint.DoCommandExecuted(Sender: TObject);
begin
if Sender = FCurrentCmd
then FCurrentCmd := nil;
if (Sender is TGDBMIDebuggerCommandBreakInsert)
then begin
// Check Insert Result
BeginUpdate;
if TGDBMIDebuggerCommandBreakInsert(Sender).Valid
then SetValid(vsValid)
else begin
if (TGDBMIDebuggerCommandBreakInsert(Sender).Kind = bpkData) and
(TGDBMIDebugger(Debugger).State = dsInit)
then begin
// disable data breakpoint, if unable to set (only at startup)
SetValid(vsValid);
SetEnabled(False);
end
else SetValid(vsInvalid);
end;
FBreakID := TGDBMIDebuggerCommandBreakInsert(Sender).BreakID;
SetHitCount(TGDBMIDebuggerCommandBreakInsert(Sender).HitCnt);
if Enabled
and (TGDBMIDebugger(Debugger).FBreakAtMain = nil)
then begin
// Check if this BP is at the same location as the temp break
if TGDBMIDebugger(Debugger).FMainAddrBreak.MatchAddr(TGDBMIDebuggerCommandBreakInsert(Sender).Addr)
then TGDBMIDebugger(Debugger).FBreakAtMain := Self;
end;
EndUpdate;
end;
if bufSetBreakPoint in FUpdateFlags
then SetBreakPoint;
if FUpdateFlags * [bufEnabled, bufCondition] <> []
then UpdateProperties(FUpdateFlags);
end;
procedure TGDBMIBreakPoint.DoEndUpdate;
begin
if bufSetBreakPoint in FUpdateFlags
then SetBreakPoint;
if FUpdateFlags * [bufEnabled, bufCondition] <> []
then UpdateProperties(FUpdateFlags);
inherited DoChanged;
end;
procedure TGDBMIBreakPoint.ReleaseBreakPoint;
begin
if Debugger = nil then Exit;
FUpdateFlags := [];
if (FCurrentCmd <> nil) and (FCurrentCmd is TGDBMIDebuggerCommandBreakRemove)
then exit;
// Cancel any other current command
if (FCurrentCmd <> nil)
then begin
FCurrentCmd.OnDestroy := nil;
FCurrentCmd.OnCancel := nil;
FCurrentCmd.OnExecuted := nil;
// if CurrenCmd is TGDBMIDebuggerCommandBreakInsert then it will remove itself
FCurrentCmd.Cancel;
end;
if FBreakID = 0 then Exit;
FCurrentCmd := TGDBMIDebuggerCommandBreakRemove.Create(TGDBMIDebugger(Debugger), FBreakID);
FCurrentCmd.OnDestroy := @DoCommandDestroyed;
FCurrentCmd.OnExecuted := @DoCommandExecuted;
FCurrentCmd.Priority := GDCMD_PRIOR_USER_ACT;
TGDBMIDebugger(Debugger).QueueCommand(FCurrentCmd);
FBreakID:=0;
SetHitCount(0);
if Debugger.State = dsRun
then TGDBMIDebugger(Debugger).GDBPause(True);
end;
procedure TGDBMIBreakPoint.SetLocation(const ASource: String; const ALine: Integer);
begin
if (Source = ASource) and (Line = ALine) then exit;
inherited;
if (Debugger = nil) or (Source = '') then Exit;
if TGDBMIDebugger(Debugger).State in [dsPause, dsInternalPause, dsRun]
then SetBreakpoint;
end;
procedure TGDBMIBreakPoint.SetWatch(const AData: String; const AScope: TDBGWatchPointScope;
const AKind: TDBGWatchPointKind);
begin
if (AData = WatchData) and (AScope = WatchScope) and (AKind = WatchKind) then exit;
inherited SetWatch(AData, AScope, AKind);
if (Debugger = nil) or (WatchData = '') then Exit;
if TGDBMIDebugger(Debugger).State in [dsPause, dsInternalPause, dsRun]
then SetBreakpoint;
end;
procedure TGDBMIBreakPoint.UpdateProperties(AFlags: TGDBMIBreakPointUpdateFlags);
begin
if (Debugger = nil) then Exit;
if AFlags * [bufEnabled, bufCondition] = [] then Exit;
if IsUpdating
then begin
if not(bufSetBreakPoint in FUpdateFlags)
then FUpdateFlags := FUpdateFlags + AFlags;
exit;
end;
if (FCurrentCmd <> nil)
then begin
// We can not be changed, while we get destroyed
if (FCurrentCmd is TGDBMIDebuggerCommandBreakRemove)
then begin
SetValid(vsInvalid);
exit;
end;
if (FCurrentCmd is TGDBMIDebuggerCommandBreakInsert) and (FCurrentCmd.State = dcsQueued)
then begin
if bufEnabled in AFlags
then TGDBMIDebuggerCommandBreakInsert(FCurrentCmd).Enabled := Enabled;
if bufCondition in AFlags
then TGDBMIDebuggerCommandBreakInsert(FCurrentCmd).Expression := Expression;
exit;
end;
if (FCurrentCmd is TGDBMIDebuggerCommandBreakUpdate) and (FCurrentCmd.State = dcsQueued)
then begin
// update the current object
if bufEnabled in AFlags
then begin
TGDBMIDebuggerCommandBreakUpdate(FCurrentCmd).UpdateEnabled := True;
TGDBMIDebuggerCommandBreakUpdate(FCurrentCmd).Enabled := Enabled;
end;
if bufCondition in AFlags
then begin
TGDBMIDebuggerCommandBreakUpdate(FCurrentCmd).UpdateExpression := True;
TGDBMIDebuggerCommandBreakUpdate(FCurrentCmd).Expression := FParsedExpression;
end;
exit;
end;
if bufSetBreakPoint in FUpdateFlags
then exit;
// let the command run
FUpdateFlags := FUpdateFlags + AFlags;
exit;
end;
if (FBreakID = 0) then Exit;
FUpdateFlags := FUpdateFlags - [bufEnabled, bufCondition];
FCurrentCmd:= TGDBMIDebuggerCommandBreakUpdate.Create(TGDBMIDebugger(Debugger), FBreakID);
if bufEnabled in AFlags
then begin
TGDBMIDebuggerCommandBreakUpdate(FCurrentCmd).UpdateEnabled := True;
TGDBMIDebuggerCommandBreakUpdate(FCurrentCmd).Enabled := Enabled;
end;
if bufCondition in AFlags
then begin
TGDBMIDebuggerCommandBreakUpdate(FCurrentCmd).UpdateExpression := True;
TGDBMIDebuggerCommandBreakUpdate(FCurrentCmd).Expression := FParsedExpression;
end;
FCurrentCmd.OnDestroy := @DoCommandDestroyed;
FCurrentCmd.OnExecuted := @DoCommandExecuted;
FCurrentCmd.Priority := GDCMD_PRIOR_USER_ACT;
TGDBMIDebugger(Debugger).QueueCommand(FCurrentCmd);
if Debugger.State = dsRun
then TGDBMIDebugger(Debugger).GDBPause(True);
end;
{%endregion ^^^^^ BreakPoints ^^^^^ }
{%region ***** Locals ***** }
{ TGDBMIDebuggerCommandLocals }
procedure TGDBMIDebuggerCommandLocals.DoLockQueueExecute;
begin
//
end;
procedure TGDBMIDebuggerCommandLocals.DoUnLockQueueExecute;
begin
//
end;
procedure TGDBMIDebuggerCommandLocals.DoLockQueueExecuteForInstr;
begin
//
end;
procedure TGDBMIDebuggerCommandLocals.DoUnLockQueueExecuteForInstr;
begin
//
end;
function TGDBMIDebuggerCommandLocals.DoExecute: Boolean;
procedure AddLocals(const AParams: String);
var
n: Integer;
addr: TDbgPtr;
LocList, List: TGDBMINameValueList;
Item: PGDBMINameValue;
Name, Value: String;
begin
LocList := TGDBMINameValueList.Create(AParams);
List := TGDBMINameValueList.Create('');
for n := 0 to LocList.Count - 1 do
begin
Item := LocList.Items[n];
List.Init(Item^.Name);
Name := List.Values['name'];
if Name = 'this'
then Name := 'Self';
Value := List.Values['value'];
(* GDB up to about 6.6 (stabs only) may return:
{name="ARGANSISTRING",value="(ANSISTRING) 0x43cc84"}
* newer GDB may return AnsiString/PChar prefixed with an address (shortstring have no address)
{name="ARGANSISTRING",value="0x43cc84 'Ansi'"}
*)
if (lowercase(copy(Value, 1, 8)) = '(pchar) ') then begin
delete(Value, 1, 8);
if GetLeadingAddr(Value, addr) then begin
if addr = 0
then Value := ''''''
else Value := MakePrintable(GetText(addr));
end;
end
else
if (lowercase(copy(Value, 1, 13)) = '(ansistring) ') then begin
delete(Value, 1, 13);
if GetLeadingAddr(Value, addr) then begin
if addr = 0
then Value := ''''''
else Value := MakePrintable(GetText(addr));
end;
end
else
if GetLeadingAddr(Value, addr, True) then
begin
// AnsiString
if (length(Value) > 0) and (Value[1] in ['''', '#']) then begin
Value := MakePrintable(ProcessGDBResultText(Value, [prNoLeadingTab]));
end
else
Value := ProcessGDBResultStruct(List.Values['value'], [prNoLeadingTab, prMakePrintAble, prStripAddressFromString]);
end
else
// ShortString
if (length(Value) > 0) and (Value[1] in ['''', '#']) then begin
Value := MakePrintable(ProcessGDBResultText(Value, [prNoLeadingTab]));
end
else
Value := ProcessGDBResultStruct(Value, [prNoLeadingTab, prMakePrintAble, prStripAddressFromString]);
FLocals.Add(Name, Value);
end;
FreeAndNil(List);
FreeAndNil(LocList);
end;
var
R: TGDBMIExecResult;
List: TGDBMINameValueList;
begin
Result := True;
FContext.ThreadContext := ccUseLocal;
FContext.ThreadId := FLocals.ThreadId;
FContext.StackContext := ccUseLocal;
FContext.StackFrame := FLocals.StackFrame;
FLocals.Clear;
// args
ExecuteCommand('-stack-list-arguments 1 %0:d %0:d',
[FTheDebugger.FCurrentStackFrame], R, [cfNoStackContext]);
if R.State <> dsError
then begin
List := TGDBMINameValueList.Create(R, ['stack-args', 'frame']);
AddLocals(List.Values['args']);
FreeAndNil(List);
end;
// variables
ExecuteCommand('-stack-list-locals 1', R);
if R.State <> dsError
then begin
List := TGDBMINameValueList.Create(R);
AddLocals(List.Values['locals']);
FreeAndNil(List);
end;
FLocals.SetDataValidity(ddsValid);
end;
constructor TGDBMIDebuggerCommandLocals.Create(AOwner: TGDBMIDebugger; ALocals: TLocals);
begin
inherited Create(AOwner);
FLocals := ALocals;
FLocals.AddReference;
end;
destructor TGDBMIDebuggerCommandLocals.Destroy;
begin
ReleaseRefAndNil(FLocals);
inherited Destroy;
end;
function TGDBMIDebuggerCommandLocals.DebugText: String;
begin
Result := Format('%s:', [ClassName]);
end;
{ =========================================================================== }
{ TGDBMILocals }
{ =========================================================================== }
procedure TGDBMILocals.Changed;
begin
if CurrentLocalsList <> nil
then CurrentLocalsList.Clear;
end;
constructor TGDBMILocals.Create(const ADebugger: TDebuggerIntf);
begin
FCommandList := TList.Create;
inherited;
end;
destructor TGDBMILocals.Destroy;
begin
CancelAllCommands;
inherited;
FreeAndNil(FCommandList);
end;
procedure TGDBMILocals.CancelAllCommands;
var
i: Integer;
begin
for i := 0 to FCommandList.Count-1 do
with TGDBMIDebuggerCommandStack(FCommandList[i]) do begin
OnExecuted := nil;
OnDestroy := nil;
Cancel;
end;
FCommandList.Clear;
end;
function TGDBMILocals.ForceQueuing: Boolean;
begin
Result := (TGDBMIDebugger(Debugger).FCurrentCommand <> nil)
and (TGDBMIDebugger(Debugger).FCurrentCommand is TGDBMIDebuggerCommandExecute)
and (not TGDBMIDebuggerCommandExecute(TGDBMIDebugger(Debugger).FCurrentCommand).NextExecQueued)
and (Debugger.State <> dsInternalPause);
end;
procedure TGDBMILocals.RequestData(ALocals: TLocals);
var
EvaluationCmdObj: TGDBMIDebuggerCommandLocals;
begin
if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then Exit;
EvaluationCmdObj := TGDBMIDebuggerCommandLocals.Create(TGDBMIDebugger(Debugger), ALocals);
EvaluationCmdObj.OnDestroy := @DoEvaluationDestroyed;
EvaluationCmdObj.Priority := GDCMD_PRIOR_LOCALS;
EvaluationCmdObj.Properties := [dcpCancelOnRun];
FCommandList.add(EvaluationCmdObj);
TGDBMIDebugger(Debugger).QueueCommand(EvaluationCmdObj, ForceQueuing);
(* DoEvaluationFinished may be called immediately at this point *)
end;
procedure TGDBMILocals.DoEvaluationDestroyed(Sender: TObject);
begin
FCommandList.Remove(Sender);
end;
procedure TGDBMILocals.CancelEvaluation;
begin
end;
{%endregion ^^^^^ BreakPoints ^^^^^ }
{ =========================================================================== }
{ TGDBMIWatches }
{ =========================================================================== }
procedure TGDBMIWatches.DoEvaluationDestroyed(Sender: TObject);
begin
FCommandList.Remove(Sender);
end;
function TGDBMIWatches.GetParentFPList(AThreadId: Integer): PGDBMIDebuggerParentFrameCache;
var
i: Integer;
begin
for i := 0 to high(FParentFPList) do
if FParentFPList[i].ThreadId = AThreadId
then exit(@FParentFPList[i]);
i := Length(FParentFPList);
SetLength(FParentFPList, i + 1);
FParentFPList[i].ThreadId := AThreadId;
Result := @FParentFPList[i];
end;
procedure TGDBMIWatches.DoStateChange(const AOldState: TDBGState);
begin
SetLength(FParentFPList, 0);
if FParentFPListChangeStamp = high(FParentFPListChangeStamp) then
FParentFPListChangeStamp := low(FParentFPListChangeStamp)
else
inc(FParentFPListChangeStamp);
inherited DoStateChange(AOldState);
end;
procedure TGDBMIWatches.Changed;
begin
SetLength(FParentFPList, 0);
if CurrentWatches <> nil
then CurrentWatches.ClearValues;
end;
procedure TGDBMIWatches.Clear;
var
i: Integer;
begin
for i := 0 to FCommandList.Count-1 do
with TGDBMIDebuggerCommandStack(FCommandList[i]) do begin
OnExecuted := nil;
OnDestroy := nil;
Cancel;
end;
FCommandList.Clear;
end;
function TGDBMIWatches.ForceQueuing: Boolean;
begin
Result := (TGDBMIDebugger(Debugger).FCurrentCommand <> nil)
and (TGDBMIDebugger(Debugger).FCurrentCommand is TGDBMIDebuggerCommandExecute)
and (not TGDBMIDebuggerCommandExecute(TGDBMIDebugger(Debugger).FCurrentCommand).NextExecQueued)
and (Debugger.State <> dsInternalPause);
end;
procedure TGDBMIWatches.InternalRequestData(AWatchValue: TWatchValue);
var
EvaluationCmdObj: TGDBMIDebuggerCommandEvaluate;
begin
if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then begin
AWatchValue.Validity := ddsInvalid;
Exit;
end;
EvaluationCmdObj := TGDBMIDebuggerCommandEvaluate.Create
(TGDBMIDebugger(Debugger), AWatchValue);
//EvaluationCmdObj.OnExecuted := @DoEvaluationFinished;
EvaluationCmdObj.OnDestroy := @DoEvaluationDestroyed;
EvaluationCmdObj.Properties := [dcpCancelOnRun];
// If a ExecCmd is running, then defer exec until the exec cmd is done
FCommandList.Add(EvaluationCmdObj);
TGDBMIDebugger(Debugger).QueueCommand(EvaluationCmdObj, ForceQueuing);
(* DoEvaluationFinished may be called immediately at this point *)
end;
constructor TGDBMIWatches.Create(const ADebugger: TDebuggerIntf);
begin
FCommandList := TList.Create;
inherited Create(ADebugger);
end;
destructor TGDBMIWatches.Destroy;
begin
inherited Destroy;
Clear;
FreeAndNil(FCommandList);
end;
{ =========================================================================== }
{ TGDBMICallStack }
{ =========================================================================== }
procedure TGDBMICallStack.DoDepthCommandExecuted(Sender: TObject);
var
Cmd: TGDBMIDebuggerCommandStackDepth;
begin
FCommandList.Remove(Sender);
FDepthEvalCmdObj := nil;
Cmd := TGDBMIDebuggerCommandStackDepth(Sender);
if Cmd.Callstack = nil then exit;
if Cmd.Depth < 0 then begin
Cmd.Callstack.SetCountValidity(ddsInvalid);
Cmd.Callstack.SetHasAtLeastCountInfo(ddsInvalid);
end else begin
if (Cmd.Limit > 0) and not(Cmd.Depth < Cmd.Limit) then begin
Cmd.Callstack.SetHasAtLeastCountInfo(ddsValid, Cmd.Depth);
end
else begin
Cmd.Callstack.Count := Cmd.Depth;
Cmd.Callstack.SetCountValidity(ddsValid);
end;
end;
end;
procedure TGDBMICallStack.RequestCount(ACallstack: TCallStackBase);
begin
if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause])
then begin
ACallstack.SetCountValidity(ddsInvalid);
exit;
end;
if (FDepthEvalCmdObj <> nil) and (FDepthEvalCmdObj .State = dcsQueued) then begin
FDepthEvalCmdObj.Limit := -1;
exit;
end;
FDepthEvalCmdObj := TGDBMIDebuggerCommandStackDepth.Create(TGDBMIDebugger(Debugger), ACallstack);
FDepthEvalCmdObj.OnExecuted := @DoDepthCommandExecuted;
FDepthEvalCmdObj.OnDestroy := @DoCommandDestroyed;
FDepthEvalCmdObj.Priority := GDCMD_PRIOR_STACK;
FCommandList.Add(FDepthEvalCmdObj);
TGDBMIDebugger(Debugger).QueueCommand(FDepthEvalCmdObj);
(* DoDepthCommandExecuted may be called immediately at this point *)
end;
procedure TGDBMICallStack.RequestAtLeastCount(ACallstack: TCallStackBase;
ARequiredMinCount: Integer);
begin
if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause])
then begin
ACallstack.SetCountValidity(ddsInvalid);
exit;
end;
// avoid calling with many small minimum
// FLimitSeen starts at 11;
FLimitSeen := Max(FLimitSeen, Min(ARequiredMinCount, 51)); // remember, if the user has asked for more
if ARequiredMinCount <= 11 then
ARequiredMinCount := 11
else
ARequiredMinCount := Max(ARequiredMinCount, FLimitSeen);
if (FDepthEvalCmdObj <> nil) and (FDepthEvalCmdObj .State = dcsQueued) then begin
if FDepthEvalCmdObj.Limit <= 0 then
exit;
if FDepthEvalCmdObj.Limit < ARequiredMinCount then
FDepthEvalCmdObj.Limit := ARequiredMinCount;
exit;
end;
FDepthEvalCmdObj := TGDBMIDebuggerCommandStackDepth.Create(TGDBMIDebugger(Debugger), ACallstack);
FDepthEvalCmdObj.Limit := ARequiredMinCount;
FDepthEvalCmdObj.OnExecuted := @DoDepthCommandExecuted;
FDepthEvalCmdObj.OnDestroy := @DoCommandDestroyed;
FDepthEvalCmdObj.Priority := GDCMD_PRIOR_STACK;
FCommandList.Add(FDepthEvalCmdObj);
TGDBMIDebugger(Debugger).QueueCommand(FDepthEvalCmdObj);
(* DoDepthCommandExecuted may be called immediately at this point *)
end;
procedure TGDBMICallStack.RequestCurrent(ACallstack: TCallStackBase);
begin
if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then begin
ACallstack.SetCurrentValidity(ddsInvalid);
Exit;
end;
if ACallstack.ThreadId = TGDBMIDebugger(Debugger).FCurrentThreadId
then ACallstack.CurrentIndex := TGDBMIDebugger(Debugger).FCurrentStackFrame
else ACallstack.CurrentIndex := 0; // will be used, if thread is changed
ACallstack.SetCurrentValidity(ddsValid);
end;
procedure TGDBMICallStack.RequestEntries(ACallstack: TCallStackBase);
var
FramesEvalCmdObj: TGDBMIDebuggerCommandStackFrames;
begin
if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then Exit;
FramesEvalCmdObj := TGDBMIDebuggerCommandStackFrames.Create(TGDBMIDebugger(Debugger), ACallstack);
//FramesEvalCmdObj.OnExecuted := @DoFramesCommandExecuted;
FramesEvalCmdObj.OnDestroy := @DoCommandDestroyed;
FramesEvalCmdObj.Priority := GDCMD_PRIOR_STACK;
FCommandList.Add(FramesEvalCmdObj);
TGDBMIDebugger(Debugger).QueueCommand(FramesEvalCmdObj);
(* DoFramesCommandExecuted may be called immediately at this point *)
end;
procedure TGDBMICallStack.DoCommandDestroyed(Sender: TObject);
begin
FCommandList.Remove(Sender);
if FDepthEvalCmdObj = Sender then
FDepthEvalCmdObj := nil;
end;
procedure TGDBMICallStack.Clear;
var
i: Integer;
begin
for i := 0 to FCommandList.Count-1 do
with TGDBMIDebuggerCommandStack(FCommandList[i]) do begin
OnExecuted := nil;
OnDestroy := nil;
Cancel;
end;
FCommandList.Clear;
FDepthEvalCmdObj := nil;
end;
procedure TGDBMICallStack.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;
cs := TCallStackBase(CurrentCallStackList.EntriesForThreads[tid]);
idx := cs.NewCurrentIndex; // NEW-CURRENT
if TGDBMIDebugger(Debugger).FCurrentStackFrame = idx then Exit;
TGDBMIDebugger(Debugger).FCurrentStackFrame := idx;
if cs <> nil then
cs.CurrentIndex := idx;
end;
procedure TGDBMICallStack.DoThreadChanged;
var
tid, idx: Integer;
cs: TCallStackBase;
begin
if (Debugger = nil) or not(Debugger.State in [dsPause, dsInternalPause]) then begin
exit;
end;
TGDBMIDebugger(Debugger).FCurrentStackFrame := 0;
tid := Debugger.Threads.CurrentThreads.CurrentThreadId;
cs := TCallStackBase(CurrentCallStackList.EntriesForThreads[tid]);
idx := cs.CurrentIndex; // CURRENT
if idx < 0 then idx := 0;
TGDBMIDebugger(Debugger).FCurrentStackFrame := idx;
if cs <> nil then
cs.CurrentIndex := idx;
end;
constructor TGDBMICallStack.Create(const ADebugger: TDebuggerIntf);
begin
FCommandList := TList.Create;
FLimitSeen := 11;
inherited Create(ADebugger);
end;
destructor TGDBMICallStack.Destroy;
begin
inherited Destroy;
Clear;
FreeAndNil(FCommandList);
end;
{ TGDBStringIterator }
constructor TGDBStringIterator.Create(const AParsableData: String);
begin
inherited Create;
FParsableData := AParsableData;
FReadPointer := 1;
FDataSize := Length(AParsableData);
DebugLn(AParsableData);
end;
function TGDBStringIterator.ParseNext(out ADecomposable: Boolean; out
APayload: String; out ACharStopper: Char): Boolean;
var
InStr: Boolean;
InBrackets1, InBrackets2: Integer;
c: Char;
BeginString: Integer;
EndString: Integer;
begin
ADecomposable := False;
InStr := False;
InBrackets1 := 0;
InBrackets2 := 0;
BeginString := FReadPointer;
EndString := FDataSize;
ACharStopper := #0; //none
while FReadPointer <= FDataSize do
begin
c := FParsableData[FReadPointer];
if c = '''' then InStr := not InStr;
if not InStr
then begin
case c of
'{': Inc(InBrackets1);
'}': Dec(InBrackets1);
'[': Inc(InBrackets2);
']': Dec(InBrackets2);
end;
if (InBrackets1 = 0) and (InBrackets2 = 0) and (c in [',', '='])
then begin
EndString := FReadPointer - 1;
Inc(FReadPointer); //Skip this char
ACharStopper := c;
Break;
end;
end;
Inc(FReadPointer);
end;
//Remove boundary spaces.
while BeginString<EndString do
begin
if FParsableData[BeginString] <> ' ' then break;
Inc(BeginString);
end;
while EndString >= BeginString do
begin
if FParsableData[EndString] <> ' ' then break;
Dec(EndString);
end;
Result := EndString >= BeginString;
if Result
and (FParsableData[BeginString] = '{')
then begin
Result := FParsableData[EndString] = '}';
inc(BeginString);
dec(EndString);
ADecomposable := True;
end;
if Result
then APayload := Copy(FParsableData, BeginString, EndString - BeginString + 1)
else APayload := '';
end;
{ TGDBMIDebuggerCommand }
function TGDBMIDebuggerCommand.GetDebuggerState: TDBGState;
begin
Result := FTheDebugger.State;
end;
function TGDBMIDebuggerCommand.GetDebuggerProperties: TGDBMIDebuggerPropertiesBase;
begin
Result := TGDBMIDebuggerPropertiesBase(FTheDebugger.GetProperties);
end;
function TGDBMIDebuggerCommand.GetTargetInfo: PGDBMITargetInfo;
begin
Result := @FTheDebugger.FTargetInfo;
end;
function TGDBMIDebuggerCommand.ContextThreadId: Integer;
begin
if FContext.ThreadContext = ccUseGlobal then
Result := FTheDebugger.FCurrentThreadId
else
Result := FContext.ThreadId;
end;
function TGDBMIDebuggerCommand.ContextStackFrame: Integer;
begin
if FContext.StackContext = ccUseGlobal then
Result := FTheDebugger.FCurrentStackFrame
else
Result := FContext.StackFrame;
end;
procedure TGDBMIDebuggerCommand.CopyGlobalContextToLocal;
begin
if FContext.ThreadContext = ccUseGlobal then begin
if FTheDebugger.FCurrentThreadIdValid then begin
FContext.ThreadContext := ccUseLocal;
FContext.ThreadId := FTheDebugger.FCurrentThreadId
end
else
debugln(DBG_VERBOSE, ['CopyGlobalContextToLocal: FAILED thread, global data is not valid']);
end;
if FContext.StackContext = ccUseGlobal then begin
if FTheDebugger.FCurrentStackFrameValid then begin
FContext.StackContext := ccUseLocal;
FContext.StackFrame := FTheDebugger.FCurrentStackFrame;
end
else
debugln(DBG_VERBOSE, ['CopyGlobalContextToLocal: FAILED stackframe, global data is not valid']);
end;
end;
procedure TGDBMIDebuggerCommand.SetDebuggerState(const AValue: TDBGState);
begin
FTheDebugger.SetState(AValue);
end;
procedure TGDBMIDebuggerCommand.SetDebuggerErrorState(const AMsg: String;
const AInfo: String);
begin
FTheDebugger.SetErrorState(AMsg, AInfo);
end;
function TGDBMIDebuggerCommand.ErrorStateMessage: String;
begin
Result := '';
if ehfGotWriteError in FTheDebugger.FErrorHandlingFlags
then Result := Result + Format(gdbmiErrorStateInfoFailedWrite, [LineEnding])
else
if ehfGotReadError in FTheDebugger.FErrorHandlingFlags
then Result := Result + Format(gdbmiErrorStateInfoFailedRead, [LineEnding]);
if not FTheDebugger.DebugProcessRunning
then Result := Result + Format(gdbmiErrorStateInfoGDBGone, [LineEnding]);
end;
function TGDBMIDebuggerCommand.ErrorStateInfo: String;
begin
Result := Format(gdbmiErrorStateGenericInfo, [LineEnding, DebugText]);
if FLastExecResult.Values = ''
then Result := Format(gdbmiErrorStateInfoCommandNoResult, [LineEnding, FLastExecCommand])
else Result := Format(gdbmiErrorStateInfoCommandError, [LineEnding, FLastExecCommand, FLastExecResult.Values]);
if not FTheDebugger.DebugProcessRunning
then Result := Result + Format(gdbmiErrorStateInfoGDBGone, [LineEnding]);
end;
procedure TGDBMIDebuggerCommand.SetCommandState(NewState: TGDBMIDebuggerCommandState);
var
OldState: TGDBMIDebuggerCommandState;
begin
if FState = NewState
then exit;
OldState := FState;
FState := NewState;
Include(FSeenStates, NewState);
DoStateChanged(OldState);
if (State in [dcsFinished, dcsCanceled]) and not(dcsInternalRefReleased in FSeenStates)
then begin
Include(FSeenStates, dcsInternalRefReleased);
ReleaseReference; //internal reference
end;
end;
procedure TGDBMIDebuggerCommand.DoStateChanged(OldState: TGDBMIDebuggerCommandState);
begin
// nothing
end;
procedure TGDBMIDebuggerCommand.DoLockQueueExecute;
begin
FTheDebugger.QueueExecuteLock;
end;
procedure TGDBMIDebuggerCommand.DoUnLockQueueExecute;
begin
FTheDebugger.QueueExecuteUnlock;
end;
procedure TGDBMIDebuggerCommand.DoLockQueueExecuteForInstr;
begin
FTheDebugger.QueueExecuteLock;
end;
procedure TGDBMIDebuggerCommand.DoUnLockQueueExecuteForInstr;
begin
FTheDebugger.QueueExecuteUnlock;
end;
procedure TGDBMIDebuggerCommand.DoOnExecuted;
begin
if assigned(FOnExecuted) then
FOnExecuted(self);
end;
procedure TGDBMIDebuggerCommand.DoCancel;
begin
// empty
end;
procedure TGDBMIDebuggerCommand.DoOnCanceled;
begin
if assigned(FOnCancel) then
FOnCancel(self);
end;
function TGDBMIDebuggerCommand.ExecuteCommand(const ACommand: String;
AFlags: TGDBMICommandFlags = []; ATimeOut: Integer = -1): Boolean;
var
R: TGDBMIExecResult;
begin
Result := ExecuteCommand(ACommand, R, AFlags, ATimeOut);
end;
function TGDBMIDebuggerCommand.ExecuteCommand(const ACommand: String;
out AResult: TGDBMIExecResult; AFlags: TGDBMICommandFlags = [];
ATimeOut: Integer = -1): Boolean;
var
Instr: TGDBMIDebuggerInstruction;
ASyncFailed: Boolean;
begin
ASyncFailed := False;
if cfTryAsync in AFlags then begin
if FTheDebugger.FAsyncModeEnabled then begin
Result := ExecuteCommand(ACommand + ' &', AResult, AFlags - [cfTryAsync], ATimeOut);
if (not Result) or (AResult.State <> dsError) then
exit;
end;
ASyncFailed := True;
end;
FLastExecCommand := ACommand;
FLastExecwasTimeOut := False;
if (ATimeOut = -1) and (DefaultTimeOut > 0)
then ATimeOut := DefaultTimeOut;
try
DoLockQueueExecuteForInstr;
if (cfNoThreadContext in AFlags) or (FContext.ThreadContext = ccNotRequired) or
((FContext.ThreadContext = ccUseGlobal) and (not FTheDebugger.FCurrentThreadIdValid)) or
(ContextThreadId = 0) // TODO: 0 is not valid => use current
then
Instr := TGDBMIDebuggerInstruction.Create(ACommand, [], ATimeOut)
else
if (cfNoStackContext in AFlags) or (FContext.StackContext = ccNotRequired) or
((FContext.StackContext = ccUseGlobal) and (not FTheDebugger.FCurrentStackFrameValid))
then
Instr := TGDBMIDebuggerInstruction.Create(ACommand, ContextThreadId, [], ATimeOut)
else
Instr := TGDBMIDebuggerInstruction.Create(ACommand, ContextThreadId,
ContextStackFrame, [], ATimeOut);
Instr.AddReference;
Instr.Cmd := Self;
FTheDebugger.FInstructionQueue.RunInstruction(Instr);
Result := Instr.IsSuccess and Instr.FHasResult;
AResult := Instr.ResultData;
if ASyncFailed then
AResult.Flags := [rfAsyncFailed];
FLastExecResult := AResult;
FLogWarnings := Instr.LogWarnings; // TODO: Do not clear in time-out handling
FFullCmdReply := Instr.FullCmdReply; // TODO: Do not clear in time-out handling
if (ifeTimedOut in Instr.ErrorFlags) then begin
AResult.State := dsError;
FLastExecwasTimeOut := True;
end;
if (ifeRecoveredTimedOut in Instr.ErrorFlags) then begin
// TODO: use feedback dialog
Result := True;
DoDbgEvent(ecDebugger, etDefault, Format(gdbmiTimeOutForCmd, [ACommand]));
DoTimeoutFeedback;
end;
finally
DoUnLockQueueExecuteForInstr;
Instr.ReleaseReference;
end;
if not Result
then begin
// either gdb did not return a Result Record: "^xxxx,"
// or the Result Record was not a known one: 'done', 'running', 'exit', 'error'
DebugLn(DBG_WARNINGS, '[WARNING] TGDBMIDebugger: ExecuteCommand "',ACommand,'" failed.');
SetDebuggerErrorState(ErrorStateMessage, ErrorStateInfo);
AResult.State := dsError;
end;
if (cfCheckError in AFlags) and (AResult.State = dsError)
then SetDebuggerErrorState(ErrorStateMessage, ErrorStateInfo);
if (cfCheckState in AFlags) and not (AResult.State in [dsError, dsNone])
then SetDebuggerState(AResult.State);
end;
function TGDBMIDebuggerCommand.ExecuteCommand(const ACommand: String;
const AValues: array of const; AFlags: TGDBMICommandFlags;
ATimeOut: Integer = -1): Boolean;
var
R: TGDBMIExecResult;
begin
Result := ExecuteCommand(ACommand, AValues, R, AFlags, ATimeOut);
end;
function TGDBMIDebuggerCommand.ExecuteCommand(const ACommand: String;
const AValues: array of const; out AResult: TGDBMIExecResult;
AFlags: TGDBMICommandFlags = []; ATimeOut: Integer = -1): Boolean;
begin
Result := ExecuteCommand(Format(ACommand, AValues), AResult, AFlags, ATimeOut);
end;
procedure TGDBMIDebuggerCommand.DoTimeoutFeedback;
begin
if DebuggerProperties.WarnOnTimeOut
then MessageDlg('Warning', 'A timeout occurred, the debugger will try to continue, but further error may occur later',
mtWarning, [mbOK], 0);
end;
function TGDBMIDebuggerCommand.ProcessGDBResultStruct(S: String;
Opts: TGDBMIProcessResultOpts): String;
function ProcessData(AData: String): String;
var
addr: TDBGPtr;
begin
Result := AData;
if (prStripAddressFromString in Opts) and GetLeadingAddr(Result, addr, True) then
if (Result = '') or not(Result[1] in ['''', '#']) then
Result := AData; // Restore address, not a string
if (Result <> '') and (Result[1] in ['''', '#']) and (prMakePrintAble in Opts) then
Result := MakePrintable(ProcessGDBResultText(Result, Opts + [prNoLeadingTab]));
end;
var
start, idx, len: Integer;
InQuote, InSingle, InValue: Boolean;
InStruct: Integer;
begin
Result := '';
InQuote := False; // "
InSingle := False; // '
InValue := False; // after "="
InStruct := 0;
len := Length(S);
start := 1;
idx := 1;
while idx <= len do begin
case S[idx] of
'"': begin // will be escaped if in single quotes
inc(idx);
InValue := False; // should never happen
if not InQuote then
Result := Result + copy(s, start, idx - start)
else
Result := Result + ProcessData(copy(s, start, idx - start - 1)) + '"';
InQuote := not InQuote;
start := idx;
end;
'\': begin
inc(idx,2);
end;
'''': begin
InSingle := not InSingle;
inc(idx);
end;
'=': begin
if (not (InQuote or InSingle)) and (InStruct > 0) and (idx > 1) and (idx < len) and
(S[idx-1] = ' ') and (S[idx+1] = ' ') then
begin
inc(idx, 2);
Result := Result + copy(s, start, idx - start);
start := idx;
InValue := True;
end
else
inc(idx);
end;
',': begin
if (not (InQuote or InSingle)) and InValue and (idx < len) and
(S[idx+1] = ' ')
then begin
Result := Result + ProcessData(copy(s, start, idx - start));
start := idx;
InValue := False;
end
else
inc(idx);
end;
'}': begin
if (not (InQuote or InSingle)) then begin
if InStruct > 0 then
dec(InStruct);
if InValue then begin
Result := Result + ProcessData(copy(s, start, idx - start));
start := idx;
end;
InValue := False;
end;
inc(idx);
end;
'{': begin
if (not (InQuote or InSingle)) then begin
inc(InStruct);
InValue := False;
end;
inc(idx);
end;
else begin
inc(idx);
end;
end;
end;
if idx > len then idx := len + 1;
if not InQuote then
Result := Result + copy(s, start, idx - start)
else
Result := Result + ProcessData(copy(s, start, idx - start - 1)) + '"';
end;
function TGDBMIDebuggerCommand.ProcessGDBResultText(S: String;
Opts: TGDBMIProcessResultOpts = []): String;
var
Trailor: String;
n, len, idx: Integer;
v: Integer;
begin
// don't use ' as end terminator, there might be one as part of the text
// since ' will be the last char, simply strip it.
if not (prNOLeadingTab in Opts) then begin
S := GetPart(['\t'], [], S);
if (length(S) > 0) and (S[1] = ' ') then
delete(S,1,1);
end;
// Scan the string
len := Length(S);
// Set the resultstring initially to the same size
SetLength(Result, len);
n := 0;
idx := 1;
Trailor:='';
while idx <= len do
begin
case S[idx] of
'''': begin
Inc(idx);
// scan till end
while idx <= len do
begin
case S[idx] of
'''' : begin
Inc(idx);
if idx > len then Break;
if S[idx] <> '''' then Break;
end;
'\' : if not (prKeepBackSlash in Opts) then begin
Inc(idx);
if idx > len then Break;
case S[idx] of
't': S[idx] := #9;
'n': S[idx] := #10;
'r': S[idx] := #13;
end;
end;
end;
Inc(n);
Result[n] := S[idx];
Inc(idx);
end;
end;
'#': begin
Inc(idx);
v := 0;
// scan till non number (correct input is assumed)
while (idx <= len) and (S[idx] >= '0') and (S[idx] <= '9') do
begin
v := v * 10 + Ord(S[idx]) - Ord('0');
Inc(idx)
end;
Inc(n);
Result[n] := Chr(v and $FF);
end;
',', ' ': begin
Inc(idx); //ignore them;
end;
'<': begin
// Debugger has returned something like <repeats 10 times>
v := StrToIntDef(GetPart(['<repeats '], [' times>'], S), 0);
// Since we deleted the first part of S, reset idx
idx := 8; // the char after ' times>'
len := Length(S);
if v <= 1 then Continue;
// limit the amount of repeats
if v > 1000
then begin
Trailor := Trailor + Format('###(repeat truncated: %u -> 1000)###', [v]);
v := 1000;
end;
// make sure result has some room
SetLength(Result, Length(Result) + v - 1);
while v > 1 do begin
Inc(n);
Result[n] := Result[n - 1];
Dec(v);
end;
end;
else // Should not get here
// Debugger has returned something we don't know of
// Append the remainder to our parsed result
Delete(S, 1, idx - 1);
Trailor := Trailor + '###(gdb unparsed remainder:' + S + ')###';
Break;
end;
end;
SetLength(Result, n);
Result := Result + Trailor;
end;
function TGDBMIDebuggerCommand.GetStackDepth(MaxDepth: integer): Integer;
var
R: TGDBMIExecResult;
List: TGDBMINameValueList;
begin
Result := -1;
if (MaxDepth < 0) and (not ExecuteCommand('-stack-info-depth', R, [cfNoStackContext]))
then exit;
if (MaxDepth >= 0) and (not ExecuteCommand('-stack-info-depth %d', [MaxDepth], R, [cfNoStackContext]))
then exit;
if R.State = dsError
then exit;
List := TGDBMINameValueList.Create(R);
Result := StrToIntDef(List.Values['depth'], -1);
FreeAndNil(List);
end;
function TGDBMIDebuggerCommand.FindStackFrame(FP: TDBGPtr; StartAt,
MaxDepth: Integer): Integer;
var
R: TGDBMIExecResult;
List: TGDBMINameValueList;
Cur, Prv: QWord;
CurContext: TGDBMICommandContext;
begin
// Result;
// -1 : Not found
// -2 : FP is outside stack
Result := StartAt;
Cur := 0;
List := TGDBMINameValueList.Create('');
try
CurContext := FContext;
FContext.StackContext := ccUseLocal;
repeat
FContext.StackFrame := Result;
if not ExecuteCommand('-data-evaluate-expression $fp', R)
or (R.State = dsError)
then begin
Result := -1;
break;
end;
List.Init(R.Values);
Prv := Cur;
Cur := StrToQWordDef(List.Values['value'], 0);
if Fp = Cur then begin
exit;
end;
if (Prv <> 0) and (Prv < Cur)
then begin
// FP is increasing
if FP < Prv
then begin
Result := -2;
exit;
end;
end;
if (Prv <> 0) and (Prv > Cur)
then begin
// FP is decreasing
if FP > Prv
then begin
Result := -2;
exit;
end;
end;
inc(Result);
until Result > MaxDepth;
Result := -1;
finally
List.Free;
FContext := CurContext;
end;
end;
function TGDBMIDebuggerCommand.GetFrame(const AIndex: Integer): String;
var
R: TGDBMIExecResult;
List: TGDBMINameValueList;
begin
Result := '';
if ExecuteCommand('-stack-list-frames %d %d', [AIndex, AIndex], R, [cfNoStackContext])
then begin
List := TGDBMINameValueList.Create(R, ['stack']);
Result := List.Values['frame'];
List.Free;
end;
end;
function TGDBMIDebuggerCommand.GetText(const ALocation: TDBGPtr): String;
var
S: String;
begin
Str(ALocation, S);
Result := GetText(S, []);
end;
function TGDBMIDebuggerCommand.GetText(const AExpression: String;
const AValues: array of const): String;
var
R: TGDBMIExecResult;
begin
if not ExecuteCommand('x/s ' + AExpression, AValues, R, [],
DebuggerProperties.TimeoutForEval)
then begin
FLastExecResult.State := dsError;
Result := '';
Exit;
end;
Result := ProcessGDBResultText(StripLN(R.Values));
end;
function TGDBMIDebuggerCommand.GetChar(const AExpression: String;
const AValues: array of const): String;
var
R: TGDBMIExecResult;
begin
if not ExecuteCommand('x/c ' + AExpression, AValues, R)
then begin
FLastExecResult.State := dsError;
Result := '';
Exit;
end;
Result := ProcessGDBResultText(StripLN(R.Values));
end;
function TGDBMIDebuggerCommand.GetFloat(const AExpression: String;
const AValues: array of const): String;
var
R: TGDBMIExecResult;
begin
if not ExecuteCommand('x/f ' + AExpression, AValues, R)
then begin
Result := '';
Exit;
end;
Result := ProcessGDBResultText(StripLN(R.Values));
end;
function TGDBMIDebuggerCommand.GetWideText(const ALocation: TDBGPtr): String;
function GetWideChar(const ALocation: TDBGPtr): WideChar;
var
Address, S: String;
R: TGDBMIExecResult;
begin
Str(ALocation, Address);
if not ExecuteCommand('x/uh' + Address, [], R)
then begin
Result := #0;
Exit;
end;
S := StripLN(R.Values);
S := GetPart(['\t'], [], S);
Result := WideChar(StrToIntDef(S, 0) and $FFFF);
end;
var
OneChar: WideChar;
CurLocation: TDBGPtr;
WStr: WideString;
begin
WStr := '';
CurLocation := ALocation;
repeat
OneChar := GetWideChar(CurLocation);
if OneChar <> #0 then
begin
WStr := WStr + OneChar;
CurLocation := CurLocation + 2;
end;
until (OneChar = #0) or (Length(WStr) > DebuggerProperties.MaxDisplayLengthForString);
Result := UTF16ToUTF8(WStr);
end;
function TGDBMIDebuggerCommand.GetGDBTypeInfo(const AExpression: String;
FullTypeInfo: Boolean; AFlags: TGDBTypeCreationFlags; AFormat: TWatchDisplayFormat;
ARepeatCount: Integer): TGDBType;
var
R: TGDBMIExecResult;
f: Boolean;
AReq: PGDBPTypeRequest;
CReq: TGDBPTypeRequest;
i: Integer;
begin
(* Analyze what type is in AExpression
* "whatis AExpr"
This return the declared type of the expression (as in the pascal source)
- The type may be replaced:
- type TAlias = TOriginal; // TAlias may be reported as TOriginal
type TAlias = type TOriginal; // Not guranteed, but not likely to be replaced
// This leaves room for arbitraty names for all types
- ^TFoo may be replaced by PFF, if PFF exist and is ^TFoo (seen with stabs, not dwarf)
- The type may be prefixed by "&" for var param under dwarf (an fpc workaround)
Under dwarf var param are hnadled by gdb, if casted or part of an expression,
but not if standalone or dereferred ("^") only
Under stabs "var param" have no indications, but are completely and correctly
handled by gdb
* ptype TheWhatisType
Should return the base type info
Since under dwarf classes are always pointers (again work in expression,
but not standalone); a further "whatis" on the declared-type may be needed,
to check if the type is a pointer or not.
This may be limited, if types are strongly aliased over several levels...
* tfClassIsPointer in TargetFlags
usually true for dwarf, false for stabs. Can be detected with "ptype TObject"
Dwarf:
"ptype TObject" => ~"type = ^TOBJECT = class \n"
Stabs:
"ptype TObject" => ~ ~"type = TOBJECT = class \n"
* Examples
* Type-info for objects
TFoo = Tobject; PFoo = ^TFoo;
ArgTFoo: TFoo; ArgPFoo: PFoo
Dwarf:
"whatis ArgTFoo\n" => ~"type = TFOO\n" (for var-param ~"type = &TFOO\n")
"ptype TFoo\n" => ~"type = ^TFOO = class : public TOBJECT \n"
whatis ArgPFoo\n" => ~"type = PFOO\n"
"ptype PFoo\n" => ~"type = ^TFOO = class : public TOBJECT \n"
// ptype is the same for TFoo and PFoo, so we need to find out if any is a pointer:
// they both have "^", but PFoo does not have "= class"
// (this may fial if pfoo is an alias for yet another name)
"whatis TFoo\n" => ~"type = ^TFOO = class \n"
"whatis PFoo\n" => ~"type = ^TFOO\n"
Stabs:
"whatis ArgTFoo\n" => ~"type = TFOO\n" (same vor var param)
"ptype TFoo\n" => ~"type = TFOO = class : public TOBJECT \n"
"whatis ArgPFoo\n" => ~"type = PFOO\n"
ptype PFoo\n" => ~"type = ^TFOO = class : public TOBJECT \n"
// ptype gives desired info in stabs (and whatis, does not reveal anything)
"whatis TFoo\n" => ~"type = TFOO\n"
"whatis PFoo\n" => ~"type = PFOO\n"
Limitations: Under Mac gdb 6.3.50 "whatis" does not work on types.
The info can not be obtained (with Dwarf: PFoo will be treated the same as TFoo)
*
*)
if tfClassIsPointer in TargetInfo^.TargetFlags
then AFlags := AFlags + [gtcfClassIsPointer];
if FullTypeInfo
then AFlags := AFlags + [gtcfFullTypeInfo];
Result := TGdbType.CreateForExpression(AExpression, AFlags, wdfDefault, ARepeatCount);
while not Result.ProcessExpression do begin
if Result.EvalError
then break;
AReq := Result.EvalRequest;
while AReq <> nil do begin
if (dcsCanceled in SeenStates) then begin
FreeAndNil(Result);
exit;
end;
i := FTheDebugger.FTypeRequestCache.IndexOf(ContextThreadId, ContextStackFrame, AReq^);
if i >= 0 then begin
debugln(DBGMI_QUEUE_DEBUG, ['DBG TypeRequest-Cache: Found entry for T=', ContextThreadId,
' F=', ContextStackFrame, ' R="', AReq^.Request,'"']);
CReq := FTheDebugger.FTypeRequestCache.Request[i];
AReq^.Result := CReq.Result;
AReq^.Error := CReq.Error;
end
else begin
f := ExecuteCommand(AReq^.Request, R);
if f and (R.State <> dsError) then begin
if AReq^.ReqType = gcrtPType
then AReq^.Result := ParseTypeFromGdb(R.Values)
else begin
AReq^.Result.GdbDescription := R.Values;
AReq^.Result.Kind := ptprkSimple;
end;
end
else begin
AReq^.Result.GdbDescription := R.Values;
AReq^.Error := R.Values;
end;
FTheDebugger.FTypeRequestCache.Add(ContextThreadId, ContextStackFrame, AReq^);
end;
AReq := AReq^.Next;
end;
end;
if Result.EvalError then begin
FreeAndNil(Result);
end;
end;
function TGDBMIDebuggerCommand.GetClassName(const AClass: TDBGPtr): String;
var
S: String;
begin
// format has a problem with %u, so use Str for it
Str(AClass, S);
Result := GetClassName(S, []);
end;
function TGDBMIDebuggerCommand.GetClassName(const AExpression: String;
const AValues: array of const): String;
var
OK: Boolean;
S: String;
R: TGDBMIExecResult;
ResultList: TGDBMINameValueList;
UseShortString: Boolean;
i: Integer;
begin
Result := '';
UseShortString := False;
if dfImplicidTypes in FTheDebugger.DebuggerFlags
then begin
S := Format(AExpression, AValues);
UseShortString := tfFlagHasTypeShortstring in TargetInfo^.TargetFlags;
if UseShortString
then s := Format('^^shortstring(%s+%d)^^', [S, TargetInfo^.TargetPtrSize * 3])
else s := Format('^^char(%s+%d)^', [S, TargetInfo^.TargetPtrSize * 3]);
OK := ExecuteCommand('-data-evaluate-expression %s',
[S], R);
if (not OK) or (LastExecResult.State = dsError)
or (pos('value="#0', LastExecResult.Values) > 0)
then begin
OK := ExecuteCommand('-data-evaluate-expression ^char(^pointer(%s+%d)^)',
[S, TargetInfo^.TargetPtrSize * 3], R);
UseShortString := False;
end;
end
else begin
UseShortString := True;
Str(TDbgPtr(GetData(AExpression + '+12', AValues)), S);
OK := ExecuteCommand('-data-evaluate-expression pshortstring(%s)^', [S], R);
end;
if OK
then begin
ResultList := TGDBMINameValueList.Create(R);
S := ResultList.Values['value'];
if UseShortString then begin
Result := GetPart('''', '''', S);
end
else begin
s := ParseGDBString(s);
if s <> ''
then i := ord(s[1])
else i := 1;
if i <= length(s)-1 then begin
Result := copy(s, 2, i);
end
else begin
// fall back
S := DeleteEscapeChars(S);
Result := GetPart('''', '''', S);
end;
end;
ResultList.Free;
end;
end;
function TGDBMIDebuggerCommand.GetInstanceClassName(const AInstance: TDBGPtr): String;
var
S: String;
begin
Str(AInstance, S);
Result := GetInstanceClassName(S, []);
end;
function TGDBMIDebuggerCommand.GetInstanceClassName(const AExpression: String;
const AValues: array of const): String;
begin
if dfImplicidTypes in FTheDebugger.DebuggerFlags
then begin
Result := GetClassName('^' + PointerTypeCast + '(' + AExpression + ')^', AValues);
end
else begin
Result := GetClassName(GetData(AExpression, AValues));
end;
end;
function TGDBMIDebuggerCommand.GetData(const ALocation: TDbgPtr): TDbgPtr;
var
S: String;
begin
Str(ALocation, S);
Result := GetData(S, []);
end;
function TGDBMIDebuggerCommand.GetData(const AExpression: String;
const AValues: array of const): TDbgPtr;
var
R: TGDBMIExecResult;
e: Integer;
begin
Result := 0;
if ExecuteCommand('x/d ' + AExpression, AValues, R)
then Val(StripLN(GetPart('\t', '', R.Values)), Result, e);
if e=0 then ;
end;
function TGDBMIDebuggerCommand.GetStrValue(const AExpression: String;
const AValues: array of const): String;
var
R: TGDBMIExecResult;
ResultList: TGDBMINameValueList;
begin
if ExecuteCommand('-data-evaluate-expression %s', [Format(AExpression, AValues)], R)
then begin
ResultList := TGDBMINameValueList.Create(R);
Result := DeleteEscapeChars(ResultList.Values['value']);
ResultList.Free;
end
else Result := '';
end;
function TGDBMIDebuggerCommand.GetIntValue(const AExpression: String;
const AValues: array of const): Integer;
var
e: Integer;
begin
Result := 0;
Val(GetStrValue(AExpression, AValues), Result, e);
if e=0 then ;
end;
function TGDBMIDebuggerCommand.GetPtrValue(const AExpression: String;
const AValues: array of const; ConvertNegative: Boolean = False): TDbgPtr;
var
e: Integer;
i: Int64;
s: String;
begin
Result := 0;
s := GetStrValue(AExpression, AValues);
if (s <> '') and (s[1] = '-')
then begin
Val(s, i, e);
Result := TDBGPtr(i);
end
else Val(s, Result, e);
if e=0 then ;
end;
function TGDBMIDebuggerCommand.CheckHasType(TypeName: String;
TypeFlag: TGDBMITargetFlag): TGDBMIExecResult;
begin
if not ExecuteCommand('ptype %s', [TypeName], Result, [], DebuggerProperties.TimeoutForEval) then begin
Result.State := dsError;
exit;
end;
if (LeftStr(Result.Values, 6) = 'type =') then
include(TargetInfo^.TargetFlags, TypeFlag);
end;
function TGDBMIDebuggerCommand.PointerTypeCast: string;
begin
if tfFlagHasTypePointer in TargetInfo^.TargetFlags
then Result := 'POINTER'
// TODO: check dfImplicidTypes support?
else if tfFlagHasTypeByte in TargetInfo^.TargetFlags
then Result := '^byte'
else Result := '^char';
end;
function TGDBMIDebuggerCommand.FrameToLocation(const AFrame: String): TDBGLocationRec;
var
S: String;
e: Integer;
Frame: TGDBMINameValueList;
begin
// Do we have a frame ?
if AFrame = ''
then S := GetFrame(0)
else S := AFrame;
Frame := TGDBMINameValueList.Create(S);
Result.Address := 0;
Val(Frame.Values['addr'], Result.Address, e);
if e=0 then ;
Result.FuncName := Frame.Values['func'];
Result.SrcFile := ConvertGdbPathAndFile(Frame.Values['file']);
Result.SrcFullName := ConvertGdbPathAndFile(Frame.Values['fullname']);
Result.SrcLine := StrToIntDef(Frame.Values['line'], -1);
Frame.Free;
end;
procedure TGDBMIDebuggerCommand.ProcessFrame(ALocation: TDBGLocationRec;
ASeachStackForSource: Boolean);
begin
// TODO: process stack in gdbmi debugger // currently: signal IDE
if (not ASeachStackForSource) and (ALocation.SrcLine < 0) then
ALocation.SrcLine := -2;
FTheDebugger.DoCurrent(ALocation); // TODO: only selected callers
FTheDebugger.FCurrentLocation := ALocation;
end;
procedure TGDBMIDebuggerCommand.ProcessFrame(const AFrame: String;
ASeachStackForSource: Boolean);
var
Location: TDBGLocationRec;
begin
Location := FrameToLocation(AFrame);
ProcessFrame(Location, ASeachStackForSource);
end;
procedure TGDBMIDebuggerCommand.DoDbgEvent(const ACategory: TDBGEventCategory;
const AEventType: TDBGEventType; const AText: String);
begin
FTheDebugger.DoDbgEvent(ACategory, AEventType, AText);
end;
constructor TGDBMIDebuggerCommand.Create(AOwner: TGDBMIDebugger);
begin
FQueueRunLevel := -1;
FState := dcsNone;
FTheDebugger := AOwner;
FContext.StackContext := ccUseGlobal;
FContext.ThreadContext := ccUseGlobal;
FDefaultTimeOut := -1;
FPriority := 0;
FProperties := [];
AddReference; // internal reference
end;
destructor TGDBMIDebuggerCommand.Destroy;
begin
if assigned(FOnDestroy)
then FOnDestroy(Self);
inherited Destroy;
end;
procedure TGDBMIDebuggerCommand.DoQueued;
begin
SetCommandState(dcsQueued);
end;
procedure TGDBMIDebuggerCommand.DoFinished;
begin
SetCommandState(dcsFinished);
end;
function TGDBMIDebuggerCommand.Execute: Boolean;
begin
// Set the state first, so DoExecute can set an error-state
SetCommandState(dcsExecuting);
AddReference;
DoLockQueueExecute;
try
Result := DoExecute;
DoOnExecuted;
except
On E: Exception do FTheDebugger.DoUnknownException(Self, E)
else
debugln(['ERROR: Exception occurred in ',ClassName+'.DoExecute ',
'" Addr=', dbgs(ExceptAddr), ' Dbg.State=', dbgs(FTheDebugger.State)]);
end;
// No re-raise in the except block. So no try-finally required
DoUnLockQueueExecute;
ReleaseReference;
end;
procedure TGDBMIDebuggerCommand.Cancel;
begin
debugln(DBGMI_QUEUE_DEBUG, ['Canceling: "', DebugText,'"']);
FTheDebugger.UnQueueCommand(Self);
DoCancel;
DoOnCanceled;
SetCommandState(dcsCanceled);
end;
function TGDBMIDebuggerCommand.KillNow: Boolean;
begin
Result := False;
end;
function TGDBMIDebuggerCommand.DebugText: String;
begin
Result := ClassName;
end;
{ TGDBMIDebuggerCommandList }
function TGDBMIDebuggerCommandList.Get(Index: Integer): TGDBMIDebuggerCommand;
begin
Result := TGDBMIDebuggerCommand(inherited Items[Index]);
end;
procedure TGDBMIDebuggerCommandList.Put(Index: Integer; const AValue: TGDBMIDebuggerCommand);
begin
inherited Items[Index] := AValue;
end;
{ TGDBMIInternalBreakPoint }
procedure TGDBMIInternalBreakPoint.Clear(ACmd: TGDBMIDebuggerCommand;
ALoc: TInternalBreakLocation; ABlock: TBlockOpt);
begin
if (FBreaks[ALoc].BreakGdbId = -2) and (ABlock <> boUnblock) then exit;
if (FBreaks[ALoc].BreakGdbId = -1) then exit;
if (FBreaks[ALoc].BreakGdbId >= 0) then
ACmd.ExecuteCommand('-break-delete %d', [FBreaks[ALoc].BreakGdbId], [cfCheckError]);
if ABlock = boBlock then
FBreaks[ALoc].BreakGdbId := -2
else
FBreaks[ALoc].BreakGdbId := -1;
FBreaks[ALoc].BreakAddr := 0;
FBreaks[ALoc].BreakFunction := '';
FBreaks[ALoc].BreakFile := '';
FBreaks[ALoc].BreakLine := '';
FEnabled := FEnabled and IsBreakSet;
if ALoc = iblAddrOfNamed then FMainAddrFound := 0;
end;
function TGDBMIInternalBreakPoint.BreakSet(ACmd: TGDBMIDebuggerCommand; ABreakLoc: String;
ALoc: TInternalBreakLocation; AClearIfSet: TClearOpt): Boolean;
var
R: TGDBMIExecResult;
ResultList: TGDBMINameValueList;
begin
Result := True; // true, if already set (dsError does not matter)
if ACmd.DebuggerState = dsError then exit;
if AClearIfSet = coClearIfSet then
Clear(ACmd, ALoc); // keeps blocked indicator
if FBreaks[ALoc].BreakGdbId <> -1 then exit; // not(set or blocked)
FBreaks[ALoc].BreakGdbId := -1;
FBreaks[ALoc].BreakAddr := 0;
FBreaks[ALoc].BreakFunction := '';
if UseForceFlag and (dfForceBreak in ACmd.FTheDebugger.FDebuggerFlags) then
begin
if (not ACmd.ExecuteCommand('-break-insert -f %s', [ABreakLoc], R)) or
(R.State = dsError)
then
ACmd.ExecuteCommand('-break-insert %s', [ABreakLoc], R);
end
else
ACmd.ExecuteCommand('-break-insert %s', [ABreakLoc], R);
Result := R.State <> dsError;
if not Result then exit;
FEnabled := True; // TODO: What if some bp are disabled?
ResultList := TGDBMINameValueList.Create(R, ['bkpt']);
FBreaks[ALoc].BreakGdbId := StrToIntDef(ResultList.Values['number'], -1);
FBreaks[ALoc].BreakAddr := StrToQWordDef(ResultList.Values['addr'], 0);
FBreaks[ALoc].BreakFunction := ResultList.Values['func'];
FBreaks[ALoc].BreakFile := ResultList.Values['fullname'];
if FBreaks[ALoc].BreakFile = '' then
FBreaks[ALoc].BreakFile := ResultList.Values['file'];
FBreaks[ALoc].BreakLine := ResultList.Values['line'];
ResultList.Free;
end;
function TGDBMIInternalBreakPoint.GetBreakAddr(ALoc: TInternalBreakLocation): TDBGPtr;
begin
Result := FBreaks[ALoc].BreakAddr;
end;
function TGDBMIInternalBreakPoint.GetBreakFile(ALoc: TInternalBreakLocation): String;
begin
Result := FBreaks[ALoc].BreakFile;
end;
function TGDBMIInternalBreakPoint.GetBreakId(ALoc: TInternalBreakLocation): Integer;
begin
Result := FBreaks[ALoc].BreakGdbId;
end;
function TGDBMIInternalBreakPoint.GetBreakLine(ALoc: TInternalBreakLocation): String;
begin
Result := FBreaks[ALoc].BreakLine;
end;
function TGDBMIInternalBreakPoint.GetInfoAddr(ACmd: TGDBMIDebuggerCommand): TDBGPtr;
var
R: TGDBMIExecResult;
S: String;
begin
Result := FMainAddrFound;
if Result <> 0 then
exit;
if ACmd.DebuggerState = dsError then Exit;
if (not ACmd.ExecuteCommand('info address ' + FName, R)) or
(R.State = dsError)
then exit;
S := GetPart(['at address ', ' at '], ['.', ' '], R.Values);
if S <> '' then
Result := StrToQWordDef(S, 0);
FMainAddrFound := Result;
end;
function TGDBMIInternalBreakPoint.HasBreakAtAddr(AnAddr: TDBGPtr): Boolean;
var
i: TInternalBreakLocation;
begin
Result := True;
for i := low(TInternalBreakLocation) to high(TInternalBreakLocation) do
if (FBreaks[i].BreakGdbId >= 0) and (FBreaks[i].BreakAddr = AnAddr) then
exit;
Result := False;
end;
function TGDBMIInternalBreakPoint.HasBreakWithId(AnId: Integer): Boolean;
var
i: TInternalBreakLocation;
begin
Result := True;
for i := low(TInternalBreakLocation) to high(TInternalBreakLocation) do
if (FBreaks[i].BreakGdbId = AnId) then
exit;
Result := False;
end;
procedure TGDBMIInternalBreakPoint.InternalSetAddr(ACmd: TGDBMIDebuggerCommand;
ALoc: TInternalBreakLocation; AnAddr: TDBGPtr);
begin
if (AnAddr = 0) or HasBreakAtAddr(AnAddr) then // HasBreakAddr includes this BP being allready at AnAddr.
exit;
// Always ClearIfSet since the address changed
BreakSet(ACmd, Format('*%u', [AnAddr]), ALoc, coClearIfSet);
end;
constructor TGDBMIInternalBreakPoint.Create(AName: string);
var
i: TInternalBreakLocation;
begin
FMainAddrFound := 0;
for i := low(TInternalBreakLocation) to high(TInternalBreakLocation) do begin
FBreaks[i].BreakGdbId := -1;
FBreaks[i].BreakAddr := 0;
end;
FUseForceFlag := False;
FName := AName;
FEnabled := False;
end;
(* Using -insert-break with a function name allows GDB to adjust the address
to be behind the functions initialization.
Which means values passed by register may no longer be accessible.
Therefore we determine the address and force the breakpoint to it.
This does not work for position independent executables (PIE), if the
breakpoint is set before the application is run, because the real address
is only known at run time.
Therefore during startup a named break point is used as fallback.
*)
procedure TGDBMIInternalBreakPoint.SetBoth(ACmd: TGDBMIDebuggerCommand);
begin
if not BreakSet(ACmd, FName, iblNamed, coKeepIfSet) then exit;
if FBreaks[iblAddrOfNamed].BreakGdbId = -2 then exit;
// Try to retrieve the address of the procedure
InternalSetAddr(ACmd, iblAddrOfNamed, GetInfoAddr(ACmd));
end;
procedure TGDBMIInternalBreakPoint.SetByName(ACmd: TGDBMIDebuggerCommand);
begin
BreakSet(ACmd, FName, iblNamed, coKeepIfSet);
// keep others
end;
procedure TGDBMIInternalBreakPoint.SetByAddr(ACmd: TGDBMIDebuggerCommand; SetNamedOnFail: Boolean = False);
begin
if FBreaks[iblAddrOfNamed].BreakGdbId <> -2 then
InternalSetAddr(ACmd, iblAddrOfNamed, GetInfoAddr(ACmd));
// SetNamedOnFail includes if blocked
If SetNamedOnFail and (FBreaks[iblNamed].BreakGdbId < 0) then
BreakSet(ACmd, FName, iblNamed, coKeepIfSet);
end;
procedure TGDBMIInternalBreakPoint.SetAtCustomAddr(ACmd: TGDBMIDebuggerCommand; AnAddr: TDBGPtr);
begin
InternalSetAddr(ACmd, iblCustomAddr, AnAddr);
end;
procedure TGDBMIInternalBreakPoint.SetAtLineOffs(ACmd: TGDBMIDebuggerCommand; AnOffset: integer);
begin
// always clear, and set again
if AnOffset < 0 then
BreakSet(ACmd, Format('%d', [AnOffset]), iblAddOffset, coClearIfSet)
else
BreakSet(ACmd, Format('+%d', [AnOffset]), iblAddOffset, coClearIfSet);
end;
procedure TGDBMIInternalBreakPoint.SetAtFileLine(ACmd: TGDBMIDebuggerCommand; AFile,
ALine: String);
begin
AFile := StringReplace(AFile, '\', '/', [rfReplaceAll]);
BreakSet(ACmd, Format(' "\"%s\":%s"', [AFile, ALine]), iblFileLine, coKeepIfSet);
end;
procedure TGDBMIInternalBreakPoint.Clear(ACmd: TGDBMIDebuggerCommand);
var
i: TInternalBreakLocation;
begin
if ACmd.DebuggerState = dsError then Exit;
for i := low(TInternalBreakLocation) to high(TInternalBreakLocation) do
Clear(ACmd, i, boUnblock);
FEnabled := False;
end;
function TGDBMIInternalBreakPoint.ClearId(ACmd: TGDBMIDebuggerCommand; AnId: Integer): Boolean;
var
i: TInternalBreakLocation;
begin
Result := False;
for i := low(TInternalBreakLocation) to high(TInternalBreakLocation) do
if (AnId = FBreaks[i].BreakGdbId) then begin
Clear(ACmd, i);
Result := True;
break;
end;
end;
function TGDBMIInternalBreakPoint.ClearAndBlockId(ACmd: TGDBMIDebuggerCommand;
AnId: Integer): Boolean;
var
i: TInternalBreakLocation;
begin
Result := False;
for i := low(TInternalBreakLocation) to high(TInternalBreakLocation) do
if (AnId = FBreaks[i].BreakGdbId) then begin
Clear(ACmd, i, boBlock);
Result := True;
break;
end;
end;
function TGDBMIInternalBreakPoint.MatchAddr(AnAddr: TDBGPtr): boolean;
begin
Result := (AnAddr <> 0) and HasBreakAtAddr(AnAddr);
end;
function TGDBMIInternalBreakPoint.MatchId(AnId: Integer): boolean;
begin
Result := (AnId >= 0) and HasBreakWithId(AnId);
end;
function TGDBMIInternalBreakPoint.IsBreakSet: boolean;
begin
Result := BreakSetCount > 0;
end;
function TGDBMIInternalBreakPoint.BreakSetCount: Integer;
var
i: TInternalBreakLocation;
begin
Result := 0;
for i := low(TInternalBreakLocation) to high(TInternalBreakLocation) do
if (FBreaks[i].BreakGdbId >= 0) then
inc(Result);
end;
procedure TGDBMIInternalBreakPoint.EnableOrSetByAddr(ACmd: TGDBMIDebuggerCommand;
SetNamedOnFail: Boolean);
begin
if IsBreakSet then
Enable(ACmd)
else
SetByAddr(ACmd, SetNamedOnFail);
end;
procedure TGDBMIInternalBreakPoint.Enable(ACmd: TGDBMIDebuggerCommand);
var
R: TGDBMIExecResult;
i: TInternalBreakLocation;
begin
if FEnabled then exit;
for i := low(TInternalBreakLocation) to high(TInternalBreakLocation) do
if FBreaks[i].BreakGdbId >= 0 then begin
ACmd.ExecuteCommand('-break-enable %d', [FBreaks[i].BreakGdbId], R);
FEnabled := True;
end;
end;
procedure TGDBMIInternalBreakPoint.Disable(ACmd: TGDBMIDebuggerCommand);
var
R: TGDBMIExecResult;
i: TInternalBreakLocation;
begin
if not FEnabled then exit;
FEnabled := False;
for i := low(TInternalBreakLocation) to high(TInternalBreakLocation) do
if FBreaks[i].BreakGdbId >= 0 then
ACmd.ExecuteCommand('-break-disable %d', [FBreaks[i].BreakGdbId], R);
end;
{ TGDBMIDebuggerSimpleCommand }
constructor TGDBMIDebuggerSimpleCommand.Create(AOwner: TGDBMIDebugger;
const ACommand: String; const AValues: array of const; const AFlags: TGDBMICommandFlags;
const ACallback: TGDBMICallback; const ATag: PtrInt);
begin
inherited Create(AOwner);
FCommand := Format(ACommand, AValues);
FFlags := AFlags;
FCallback := ACallback;
FTag := ATag;
FResult.Values := '';
FResult.State := dsNone;
FResult.Flags := [];
end;
function TGDBMIDebuggerSimpleCommand.DebugText: String;
begin
Result := Format('%s: %s', [ClassName, FCommand]);
end;
function TGDBMIDebuggerSimpleCommand.DoExecute: Boolean;
begin
Result := True;
if not ExecuteCommand(FCommand, FResult, FFlags)
then exit;
if (FResult.State <> dsNone)
and not (cfscIgnoreState in FFlags)
and ((FResult.State <> dsError) or not (cfscIgnoreError in FFlags))
then SetDebuggerState(FResult.State);
if Assigned(FCallback)
then FCallback(FResult, FTag);
end;
{ TGDBMIDebuggerCommandEvaluate }
function TGDBMIDebuggerCommandEvaluate.GetTypeInfo: TGDBType;
begin
Result := FTypeInfo;
// if the command wasn't executed, typeinfo may still get set, and need auto-destroy
FTypeInfoAutoDestroy := FTypeInfo = nil;
end;
procedure TGDBMIDebuggerCommandEvaluate.DoWatchFreed(Sender: TObject);
begin
debugln(DBGMI_QUEUE_DEBUG, ['DoWatchFreed: ', DebugText]);
FWatchValue := nil;
Cancel;
end;
procedure TGDBMIDebuggerCommandEvaluate.DoLockQueueExecute;
begin
FLockFlag := FWatchValue = nil;
//if FLockFlag then
// inherited DoLockQueueExecute;
end;
procedure TGDBMIDebuggerCommandEvaluate.DoUnLockQueueExecute;
begin
//if FLockFlag then
// inherited DoUnLockQueueExecute;
end;
procedure TGDBMIDebuggerCommandEvaluate.DoLockQueueExecuteForInstr;
begin
//
end;
procedure TGDBMIDebuggerCommandEvaluate.DoUnLockQueueExecuteForInstr;
begin
//
end;
function TGDBMIDebuggerCommandEvaluate.DoExecute: Boolean;
var
TypeInfoFlags: TGDBTypeCreationFlags;
function FormatResult(const AInput: String; IsArray: Boolean = False): String;
const
INDENTSTRING = ' ';
var
Indent: String;
i: Integer;
InStr: Boolean;
InBrackets, InRounds: Integer;
Limit: Integer;
Skip: Integer;
begin
Indent := '';
Skip := 0;
InStr := False;
InBrackets := 0;
InRounds := 0;
Limit := Length(AInput);
Result := '';
for i := 1 to Limit do
begin
if Skip>0
then begin
Dec(SKip);
Continue;
end;
if AInput[i] in [#10, #13]
then begin
//Removes unneeded LineEnding.
Continue;
end;
Result := Result + AInput[i];
if InStr
then begin
InStr := AInput[i] <> '''';
Continue;
end;
if InBrackets > 0
then begin
if AInput[i] = ']' then
dec(InBrackets);
Continue;
end;
case AInput[i] of
'[': begin
inc(InBrackets);
end;
'(': begin
inc(InRounds);
end;
')': begin
if InRounds > 0 then
dec(InRounds);
end;
'''': begin
InStr:=true;
end;
'{': begin
if (i < Limit) and (AInput[i+1] <> '}')
then begin
Indent := Indent + INDENTSTRING;
if (not IsArray) or (InRounds = 0) then
Result := Result + LineEnding + Indent;
end;
end;
'}': begin
if (i > 1) and (AInput[i-1] <> '{') and
((not IsArray) or (InRounds = 0))
then Delete(Indent, 1, Length(INDENTSTRING));
end;
' ': begin
if ((i > 1) and (AInput[i-1] = ',')) and
( (not IsArray) or
((Indent = '') and (InRounds <= 1)) or
((Indent = INDENTSTRING) and (InRounds = 0))
)
then Result := Result + LineEnding + Indent;
end;
'0': begin
if (i > 4) and (i < Limit - 2)
then begin
//Pascalize pointers "Var = 0x12345 => Var = $12345"
if (AInput[i-3] = ' ')
and (AInput[i-2] = '=')
and (AInput[i-1] = ' ')
and (AInput[i+1] = 'x')
then begin
Skip := 1;
Result[Length(Result)] := '$';
end;
end;
end;
end;
end;
end;
function WhichIsFirst(const ASource: String; const ASearchable: array of Char): Integer;
var
j, k: Integer;
InString: Boolean;
begin
InString := False;
for j := 1 to Length(ASource) do
begin
if ASource[j] = '''' then InString := not InString;
if InString then Continue;
for k := Low(ASearchable) to High(ASearchable) do
begin
if ASource[j] = ASearchable[k] then Exit(j);
end;
end;
Result := -1;
end;
function SkipPairs(var ASource: String; const ABeginChar: Char; const AEndChar: Char): String;
var
Deep,j: SizeInt;
InString: Boolean;
begin
DebugLn(DBG_VERBOSE, '->->', ASource);
Deep := 0;
InString := False;
for j := 1 to Length(ASource) do
begin
if ASource[j]='''' then InString := not InString;
if InString then Continue;
if ASource[j] = ABeginChar
then begin
Inc(Deep)
end
else begin
if ASource[j] = AEndChar
then Dec(Deep);
end;
if Deep=0
then begin
Result := Copy(ASource, 1, j);
ASource := Copy(ASource, j + 1, Length(ASource) - j);
Exit;
end;
end;
end;
function IsHexC(const ASource: String): Boolean;
begin
if Length(ASource) <= 2 then Exit(False);
if ASource[1] <> '0' then Exit(False);
Result := ASource[2] = 'x';
end;
function HexCToHexPascal(const ASource: String; MinChars: Byte = 0): String;
var
Zeros: String;
begin
if IsHexC(Asource)
then begin
Result := Copy(ASource, 3, Length(ASource) - 2);
if Length(Result) < MinChars then
begin
SetLength(Zeros, MinChars - Length(Result));
FillChar(Zeros[1], Length(Zeros), '0');
Result := Zeros + Result;
end;
Result := '$' + Result;
end
else Result := ASource;
end;
procedure PutValuesInTypeRecord(const AType: TDBGType; const ATextInfo: String);
var
GDBParser: TGDBStringIterator;
Payload: String;
Composite: Boolean;
StopChar: Char;
j: Integer;
begin
GDBParser := TGDBStringIterator.Create(ATextInfo);
GDBParser.ParseNext(Composite, Payload, StopChar);
GDBParser.Free;
if not Composite
then begin
//It is not a record
debugln(DBGMI_STRUCT_PARSER, 'Expected record, but found: "', ATextInfo, '"');
exit;
end;
//Parse information between brackets...
GDBParser := TGDBStringIterator.Create(Payload);
for j := 0 to AType.Fields.Count-1 do
begin
if not GDBParser.ParseNext(Composite, Payload, StopChar)
then begin
debugln(DBGMI_STRUCT_PARSER, 'Premature end of parsing');
Break;
end;
if Payload <> AType.Fields[j].Name
then begin
debugln(DBGMI_STRUCT_PARSER, 'Field name does not match, expected "', AType.Fields[j].Name, '" but found "', Payload,'"');
Break;
end;
if StopChar <> '='
then begin
debugln(DBGMI_STRUCT_PARSER, 'Expected assignment, but other found.');
Break;
end;
//Field name verified...
if not GDBParser.ParseNext(Composite, Payload, StopChar)
then begin
debugln(DBGMI_STRUCT_PARSER, 'Premature end of parsing');
Break;
end;
if Composite
then THackDBGType(AType.Fields[j].DBGType).FKind := skRecord;
AType.Fields[j].DBGType.Value.AsString := HexCToHexPascal(Payload);
end;
GDBParser.Free;
end;
procedure PutValuesInClass(const AType: TGDBType; ATextInfo: String);
var
//GDBParser: TGDBStringIterator;
//Payload: String;
//Composite: Boolean;
//StopChar: Char;
//j: Integer;
AWarnText: string;
StartPtr, EndPtr: PChar;
Procedure SkipSpaces;
begin
while (StartPtr <= EndPtr) and (StartPtr^ = ' ') do inc(StartPtr);
end;
Procedure SkipToEndOfField(EndAtComma: Boolean = False);
var
i, j: Integer;
begin
// skip forward, past the next ",", but do NOT skip the closing "}"
i := 1;
j := 0;
while (StartPtr <= EndPtr) and (i > 0) do begin
case StartPtr^ of
'{': inc(i);
'}': if i = 1
then break // do not skip }
else dec(i);
'[': inc(j);
']': dec(j);
'''': begin
inc(StartPtr);
while (StartPtr <= EndPtr) and (StartPtr^ <> '''') do inc(StartPtr);
end;
',': if (i = 1) and (j < 1) then begin
if EndAtComma then break; // Do not increase StartPtr;
i := 0;
end;
end;
inc(StartPtr);
end;
SkipSpaces;
end;
procedure ProcessAncestor(ATypeName: String);
var
HelpPtr, HelpPtr2: PChar;
NewName, NewVal: String;
i: Integer;
NewField: TDBGField;
begin
inc(StartPtr); // skip '{'
SkipSpaces;
if StartPtr^ = '<' Then begin
inc(StartPtr);
HelpPtr := StartPtr;
while (HelpPtr <= EndPtr) and (HelpPtr^ <> '>') do inc(HelpPtr);
NewName := copy(StartPtr, 1, HelpPtr - StartPtr);
StartPtr := HelpPtr + 1;
SkipSpaces;
if StartPtr^ <> '=' then begin
debugln(DBGMI_STRUCT_PARSER, 'WARNING: PutValuesInClass: Expected "=" for ancestor "' + NewName + '" in: ' + AWarnText);
AWarnText := '';
SkipToEndOfField;
// continue fields, or end
end
else begin
inc(StartPtr);
SkipSpaces;
if StartPtr^ <> '{'
then begin
//It is not a class
debugln(DBGMI_STRUCT_PARSER, 'WARNING: PutValuesInClass: Expected "{" for ancestor "' + NewName + '" in: ' + AWarnText);
AWarnText := '';
SkipToEndOfField;
end
else
ProcessAncestor(NewName);
if StartPtr^ = ',' then inc(StartPtr);
SkipSpaces;
end;
end;
// process fields in this ancestor
while (StartPtr <= EndPtr) and (StartPtr^ <> '}') do begin
HelpPtr := StartPtr;
while (HelpPtr < EndPtr) and not (HelpPtr^ in [' ', '=', ',']) do inc(HelpPtr);
NewName := uppercase(copy(StartPtr, 1, HelpPtr - StartPtr)); // name of field
StartPtr := HelpPtr;
SkipSpaces;
if StartPtr^ <> '=' then begin
debugln(DBGMI_STRUCT_PARSER, 'WARNING: PutValuesInClass: Expected "=" for field"' + NewName + '" in: ' + AWarnText);
AWarnText := '';
SkipToEndOfField;
continue;
end;
inc(StartPtr);
SkipSpaces;
HelpPtr := StartPtr;
SkipToEndOfField(True);
HelpPtr2 := StartPtr; // "," or "}"
dec(HelpPtr2);
while HelpPtr2^ = ' ' do dec(HelpPtr2);
NewVal := copy(HelpPtr, 1, HelpPtr2 + 1 - HelpPtr); // name of field
i := AType.Fields.Count - 1;
while (i >= 0)
and ( (uppercase(AType.Fields[i].Name) <> NewName)
or (uppercase(AType.Fields[i].ClassName) <> ATypeName) )
do dec(i);
if i < 0 then begin
if (uppercase(ATypeName) <> 'TOBJECT') or (pos('VPTR', uppercase(NewName)) < 1) then begin
if not(defFullTypeInfo in FEvalFlags) then begin
NewField := TDBGField.Create(NewName, TGDBType.Create(skSimple, ''), flPublic, [], '');
AType.Fields.Add(NewField);
NewField.DBGType.Value.AsString := HexCToHexPascal(NewVal);
end
else
debugln(DBGMI_STRUCT_PARSER, 'WARNING: PutValuesInClass: No field for "' + ATypeName + '"."' + NewName + '"');
end;
end
else
AType.Fields[i].DBGType.Value.AsString := HexCToHexPascal(NewVal);
if (StartPtr^ <> '}') then inc(StartPtr);
SkipSpaces;
end;
inc(StartPtr); // skip the }
end;
begin
if ATextInfo = '' then exit;
AWarnText := ATextInfo;
StartPtr := @ATextInfo[1];
EndPtr := @ATextInfo[length(ATextInfo)];
while EndPtr^ = ' ' do dec(EndPtr);
SkipSpaces;
if StartPtr^ <> '{'
then begin
//It is not a class
debugln(DBGMI_STRUCT_PARSER, 'ERROR: PutValuesInClass: Expected class, but found: "', ATextInfo, '"');
exit;
end;
ProcessAncestor(AType.TypeName);
end;
procedure PutValuesInTree();
var
ValData: string;
begin
if not Assigned(FTypeInfo) then exit;
ValData := FTextValue;
case FTypeInfo.Kind of
skClass: begin
GetPart('','{',ValData);
PutValuesInClass(FTypeInfo,ValData);
end;
skRecord: begin
GetPart('','{',ValData);
PutValuesInTypeRecord(FTypeInfo,ValData);
end;
skVariant: begin
FTypeInfo.Value.AsString:=ValData;
end;
skEnum: begin
FTypeInfo.Value.AsString:=ValData;
end;
skSet: begin
FTypeInfo.Value.AsString:=ValData;
end;
skSimple: begin
FTypeInfo.Value.AsString:=ValData;
end;
// skPointer: ;
end;
end;
function SelectParentFrame(var aFrameIdx: Integer): Boolean;
var
CurPFPListChangeStamp: Integer;
function ParentSearchCanContinue: Boolean;
begin
Result :=
(not (dcsCanceled in SeenStates)) and
(CurPFPListChangeStamp = TGDBMIWatches(FTheDebugger.Watches).ParentFPListChangeStamp) and // State changed: FrameCache is no longer valid
(FTheDebugger.State <> dsError);
end;
var
R: TGDBMIExecResult;
List: TGDBMINameValueList;
ParentFp, Fp, LastFp: String;
i, j: Integer;
FrameCache: PGDBMIDebuggerParentFrameCache;
ParentFpNum, FpNum, FpDiff, LastFpDiff: QWord;
FpDir: Integer;
begin
Result := False;
CurPFPListChangeStamp := TGDBMIWatches(FTheDebugger.Watches).ParentFPListChangeStamp;
FrameCache := TGDBMIWatches(FTheDebugger.Watches).GetParentFPList(ContextThreadId);
List := nil;
try
i := length(FrameCache^.ParentFPList);
j := Max(i, aFrameIdx+1);
if j >= i
then SetLength(FrameCache^.ParentFPList, j + 3);
// Did a previous check for parentfp fail?
ParentFP := FrameCache^.ParentFPList[aFrameIdx].parentfp;
if ParentFp = '-'
then Exit(False);
if ParentFp = '' then begin
// not yet evaluated
if ExecuteCommand('-data-evaluate-expression parentfp', R)
and (R.State <> dsError)
then begin
List := TGDBMINameValueList.Create(R);
ParentFP := List.Values['value'];
end;
if not ParentSearchCanContinue then
exit;
if ParentFp = '' then begin
FrameCache^.ParentFPList[aFrameIdx].parentfp := '-'; // mark as no parentfp
Exit(False);
end;
FrameCache^.ParentFPList[aFrameIdx].parentfp := ParentFp;
end;
ParentFpNum := StrToQWordDef(ParentFp, 0);
if ParentFpNum = 0 then begin
FrameCache^.ParentFPList[aFrameIdx].parentfp := '-'; // mark as no parentfp
Exit(False);
end;
if List = nil
then List := TGDBMINameValueList.Create('');
LastFp := '';
LastFpDiff := 0;
FpDir := 0;
repeat
Inc(aFrameIdx);
i := length(FrameCache^.ParentFPList);
j := Max(i, aFrameIdx+1);
if j >= i
then SetLength(FrameCache^.ParentFPList, j + 5);
Fp := FrameCache^.ParentFPList[aFrameIdx].Fp;
if Fp = '-'
then begin
Exit(False);
end;
if (Fp = '') or (Fp = ParentFP) then begin
FContext.StackContext := ccUseLocal;
FContext.StackFrame := aFrameIdx;
if (Fp = '') then begin
if not ExecuteCommand('-data-evaluate-expression $fp', R)
or (R.State = dsError)
then begin
FrameCache^.ParentFPList[aFrameIdx].Fp := '-'; // mark as no Fp (not accesible)
Exit(False);
end;
if not ParentSearchCanContinue then
exit;
List.Init(R.Values);
Fp := List.Values['value'];
if Fp = ''
then Fp := '-';
FrameCache^.ParentFPList[aFrameIdx].Fp := Fp;
end;
end;
if FP = LastFp then // Propably top of stack, FP no longer changes
Exit(False);
LastFp := Fp;
// check that FP gets closer to ParentFp
FpNum := StrToQWordDef(Fp, 0);
if FpNum > ParentFpNum then begin
if FpDir = 1 then exit; // went to far
FpDir := -1;
FpDiff := FpNum - ParentFpNum;
end else begin
if FpDir = -1 then exit; // went to far
FpDir := 1;
FpDiff := ParentFpNum - FpNum;
end;
if (LastFpDiff <> 0) and (FpDiff >= LastFpDiff) then
Exit(False);
LastFpDiff := FpDiff;
until ParentFP = Fp;
Result := True;
finally
List.Free;
end;
end;
function PascalizePointer(AString: String; const TypeCast: String = ''): String;
var
s: String;
begin
Result := AString;
if not IsHexC(AString)
then exit;
// there may be data after the pointer
s := GetPart([], [' '], AString, False, True);
if s = '0x0'
then begin
Result := 'nil';
end
else begin
// 0xabc0 => $0000ABC0
Result := UpperCase(HexCToHexPascal(s, FTheDebugger.TargetWidth div 4));
end;
if TypeCast <> '' then
Result := TypeCast + '(' + Result + ')';
if AString <> '' then
Result := Result + ' ' + AString;
end;
function FormatCurrency(const AString: String): String;
var
i, e: Integer;
c: Currency;
begin
Result := AString;
Val(Result, i, e);
// debugger outputs 12345 for 1,2345 values
if e=0 then
begin
c := i / 10000;
Result := CurrToStr(c);
end;
end;
function GetVariantValue(AString: String): String;
function FormatVarError(const AString: String): String; inline;
begin
Result := 'Error('+AString+')';
end;
var
VarList: TGDBMINameValueList;
VType: Integer;
Addr: TDbgPtr;
dt: TDateTime;
e: Integer;
begin
VarList := TGDBMINameValueList.Create('');
try
VarList.UseTrim := True;
VarList.Init(AString);
VType := StrToIntDef(VarList.Values['VTYPE'], -1);
if VType = -1 then // can never happen if no error since varType is word
Exit('variant: unknown type');
case VType and not varTypeMask of
0:
begin
case VType of
varEmpty: Result := 'UnAssigned';
varNull: Result := 'Null';
varsmallint: Result := VarList.Values['VSMALLINT'];
varinteger: Result := VarList.Values['VINTEGER'];
varsingle: Result := VarList.Values['VSINGLE'];
vardouble: Result := VarList.Values['VDOUBLE'];
vardate:
begin
// float number
Result := VarList.Values['VDATE'];
val(Result, dt, e);
if e = 0 then
Result := DateTimeToStr(dt);
end;
varcurrency: Result := FormatCurrency(VarList.Values['VCURRENCY']);
varolestr: Result := VarList.Values['VOLESTR'];
vardispatch: Result := PascalizePointer(VarList.Values['VDISPATCH'], 'IDispatch');
varerror: Result := FormatVarError(VarList.Values['VERROR']);
varboolean: Result := VarList.Values['VBOOLEAN'];
varunknown: Result := PascalizePointer(VarList.Values['VUNKNOWN'], 'IUnknown');
varshortint: Result := VarList.Values['VSHORTINT'];
varbyte: Result := VarList.Values['VBYTE'];
varword: Result := VarList.Values['VWORD'];
varlongword: Result := VarList.Values['VLONGWORD'];
varint64: Result := VarList.Values['VINT64'];
varqword: Result := VarList.Values['VQWORD'];
varstring:
begin
// address of string
Result := VarList.Values['VSTRING'];
Val(Result, Addr, e);
if e = 0 then
begin
if Addr = 0 then
Result := ''''''
else
Result := MakePrintable(GetText(Addr));
end;
end;
varany: Result := VarList.Values['VANY'];
else
Result := 'unsupported variant type: ' + VarTypeAsText(VType);
end;
end;
varArray:
begin
Result := VarTypeAsText(VType);
// TODO: show variant array data?
// Result := VarList.Values['VARRAY'];
end;
varByRef:
begin
Result := VarList.Values['VPOINTER'];
Val(Result, Addr, e);
if e = 0 then
begin
if Addr = 0 then
Result := '???'
else
begin
// Result contains a valid address
case VType xor varByRef of
varEmpty: Result := 'UnAssigned';
varNull: Result := 'Null';
varsmallint: Result := GetStrValue('psmallint(%s)^', [Result]);
varinteger: Result := GetStrValue('pinteger(%s)^', [Result]);
varsingle: Result := GetStrValue('psingle(%s)^', [Result]);
vardouble: Result := GetStrValue('pdouble(%s)^', [Result]);
vardate:
begin
// float number
Result := GetStrValue('pdatetime(%s)^', [Result]);
val(Result, dt, e);
if e = 0 then
Result := DateTimeToStr(dt);
end;
varcurrency: Result := FormatCurrency(GetStrValue('pcurrency(%s)^', [Result]));
varolestr:
begin
Result := GetStrValue('^pointer(%s)^', [Result]);
val(Result, Addr, e);
if e = 0 then
Result := MakePrintable(GetWideText(Addr));
end;
vardispatch: Result := PascalizePointer(GetStrValue('ppointer(%s)^', [Result]), 'IDispatch');
varerror: Result := FormatVarError(GetStrValue('phresult(%s)^', [Result]));
varboolean: Result := GetStrValue('pwordbool(%s)^', [Result]);
varunknown: Result := PascalizePointer(GetStrValue('ppointer(%s)^', [Result]), 'IUnknown');
varshortint: Result := GetStrValue('pshortint(%s)^', [Result]);
varbyte: Result := GetStrValue('pbyte(%s)^', [Result]);
varword: Result := GetStrValue('pword(%s)^', [Result]);
varlongword: Result := GetStrValue('plongword(%s)^', [Result]);
varint64: Result := GetStrValue('pint64(%s)^', [Result]);
varqword: Result := GetStrValue('pqword(%s)^', [Result]);
varstring: Result := MakePrintable(GetText('pansistring(%s)^', [Result]));
else
Result := 'unsupported variant type: ' + VarTypeAsText(VType);
end;
end;
end;
end;
else
Result := 'unsupported variant type: ' + VarTypeAsText(VType);
end;
finally
VarList.Free;
end;
end;
function StripExprNewlines(const ASource: String): String;
var
len: Integer;
srcPtr, dstPtr: PChar;
begin
len := Length(ASource);
SetLength(Result, len);
if len = 0 then Exit;
srcPtr := @ASource[1];
dstPtr := @Result[1];
while len > 0 do
begin
case srcPtr^ of
#0:;
#10, #13: dstPtr^ := ' ';
else
dstPtr^ := srcPtr^;
end;
Dec(len);
Inc(srcPtr);
Inc(dstPtr);
end;
end;
procedure FixUpResult(AnExpression: string; ResultInfo: TGDBType = nil);
var
addr: TDbgPtr;
e: Integer;
PrintableString: String;
i: Integer;
addrtxt: string;
begin
// Check for strings
if ResultInfo = nil then
ResultInfo := GetGDBTypeInfo(AnExpression, defFullTypeInfo in FEvalFlags, TypeInfoFlags);
if (ResultInfo = nil) then Exit;
FTypeInfo := ResultInfo;
case ResultInfo.Kind of
skPointer: begin
addrtxt := GetPart([], [' '], FTextValue, False, False);
Val(addrtxt, addr, e);
if e <> 0 then
Exit;
AnExpression := Lowercase(ResultInfo.TypeName);
case StringCase(AnExpression, ['char', 'character', 'ansistring', '__vtbl_ptr_type',
'wchar', 'widechar', 'widestring', 'unicodestring',
'pointer'])
of
0, 1, 2: begin // 'char', 'character', 'ansistring'
// check for addr 'text' / 0x1234 'abc'
i := length(addrtxt)+1;
if (i <= length(FTextValue)) and (FTextValue[i] = ' ') then inc(i); // skip 1 or 2 spaces after addr
if (i <= length(FTextValue)) and (FTextValue[i] = ' ') then inc(i);
if (i <= length(FTextValue)) and (FTextValue[i] in ['''', '#'])
then
FTextValue := MakePrintable(ProcessGDBResultText(
copy(FTextValue, i, length(FTextValue) - i + 1), [prNoLeadingTab]))
else
if Addr = 0
then
FTextValue := ''''''
else
FTextValue := MakePrintable(GetText(Addr));
PrintableString := FTextValue;
end;
3: begin // '__vtbl_ptr_type'
if Addr = 0
then FTextValue := 'nil'
else begin
AnExpression := GetClassName(Addr);
if AnExpression = '' then AnExpression := '???';
FTextValue := 'class of ' + AnExpression + ' ' + UnEscapeBackslashed(FTextValue);
end;
end;
4,5,6,7: begin // 'wchar', 'widechar'
// widestring handling
if Addr = 0
then FTextValue := ''''''
else FTextValue := MakePrintable(GetWideText(Addr));
PrintableString := FTextValue;
end;
8: begin // pointer
if Addr = 0
then FTextValue := 'nil';
FTextValue := PascalizePointer(UnEscapeBackslashed(FTextValue));
end;
else
if Addr = 0
then FTextValue := 'nil';
if (Length(AnExpression) > 0)
then begin
if AnExpression[1] = 't'
then begin
AnExpression[1] := 'T';
if Length(AnExpression) > 1 then AnExpression[2] := UpperCase(AnExpression[2])[1];
end;
FTextValue := PascalizePointer(UnEscapeBackslashed(FTextValue), AnExpression);
end;
end;
ResultInfo.Value.AsPointer := {%H-}Pointer(PtrUint(Addr));
AnExpression := Format('$%x', [Addr]);
if PrintableString <> ''
then AnExpression := AnExpression + ' ' + PrintableString;
ResultInfo.Value.AsString := AnExpression;
end;
skClass: begin
Val(FTextValue, addr, e); //Get the class mem address
if (e = 0) and (addr = 0)
then FTextValue := 'nil';
if (FTextValue <> '') and (FTypeInfo <> nil)
then begin
FTextValue := '<' + FTypeInfo.TypeName + '> = ' +
ProcessGDBResultStruct(FTextValue, [prNoLeadingTab, prMakePrintAble, prStripAddressFromString]);
end
else
if (e = 0) and (addr <> 0)
then begin //No error ?
AnExpression := GetInstanceClassName(Addr);
if AnExpression = '' then AnExpression := '???'; //No instanced class found
FTextValue := 'instance of ' + AnExpression + ' ' +
ProcessGDBResultStruct(FTextValue, [prNoLeadingTab, prMakePrintAble, prStripAddressFromString]);
end;
end;
skVariant: begin
FTextValue := UnEscapeBackslashed(GetVariantValue(FTextValue));
end;
skRecord: begin
FTextValue := 'record ' + ResultInfo.TypeName + ' '+
ProcessGDBResultStruct(FTextValue, [prNoLeadingTab, prMakePrintAble, prStripAddressFromString]);
end;
skSimple: begin
if ResultInfo.TypeName = 'CURRENCY' then
FTextValue := FormatCurrency(UnEscapeBackslashed(FTextValue))
else
if ResultInfo.TypeName = 'ShortString' then
FTextValue := MakePrintable(ProcessGDBResultText(FTextValue, [prNoLeadingTab]))
else
if (ResultInfo.TypeName = '&ShortString') then // should no longer happen
FTextValue := GetStrValue('ShortString(%s)', [AnExpression]) // we have an address here, so we need to typecast
else
if saDynArray in ResultInfo.Attributes then // may also be a string
FTextValue := PascalizePointer(UnEscapeBackslashed(FTextValue))
else
FTextValue := UnEscapeBackslashed(FTextValue); // TODO: Check for string
end;
end;
PutValuesInTree;
FTextValue := FormatResult(FTextValue, (ResultInfo.Kind = skSimple) and (ResultInfo.Attributes*[saArray,saDynArray] <> []));
end;
function AddAddressOfToExpression(const AnExpression: string; TypeInfo: TGDBType): String;
var
UseAt: Boolean;
begin
UseAt := True;
case TypeInfo.Kind of // (skClass, skRecord, skEnum, skSet, skProcedure, skFunction, skSimple, skPointer, skVariant)
skPointer: begin
case StringCase(Lowercase(TypeInfo.TypeName),
['char', 'character', 'ansistring', '__vtbl_ptr_type', 'wchar', 'widechar', 'pointer']
)
of
2: UseAt := False;
3: UseAt := False;
end;
end;
end;
if UseAt
then Result := '@(' + AnExpression + ')'
else Result := AnExpression;
end;
function QuoteExpr(const AnExpression: string): string;
var
i, j, Cnt: integer;
begin
if pos(' ', AnExpression) < 1
then exit(AnExpression);
Cnt := length(AnExpression);
SetLength(Result, 2 * Cnt + 2);
Result[1] := '"';
i := 1;
j := 2;
while i <= Cnt do begin
if AnExpression[i] in ['"', '\']
then begin
Result[j] := '\';
inc(j);
end;
Result[j] := AnExpression[i];
inc(i);
inc(j);
end;
Result[j] := '"';
SetLength(Result, j + 1);
end;
procedure ParseLastError;
var
ResultList: TGDBMINameValueList;
begin
if (dcsCanceled in SeenStates)
then begin
FTextValue := '<Canceled>';
FValidity := ddsInvalid;
exit;
end;
ResultList := TGDBMINameValueList.Create(LastExecResult.Values);
FTextValue := ResultList.Values['msg'];
if FTextValue = ''
then FTextValue := '<Error>';
FreeAndNil(ResultList);
FValidity := ddsError;
end;
function TryExecute(AnExpression: string): Boolean;
function PrepareExpr(var expr: string; NoAddressOp: Boolean = False): boolean;
begin
Assert(FTypeInfo = nil, 'Type info must be nil');
FTypeInfo := GetGDBTypeInfo(expr, defFullTypeInfo in FEvalFlags, TypeInfoFlags);
Result := FTypeInfo <> nil;
if (not Result) then begin
ParseLastError;
exit;
end;
if NoAddressOp
then expr := QuoteExpr(expr)
else expr := QuoteExpr(AddAddressOfToExpression(expr, FTypeInfo));
end;
var
ResultList: TGDBMINameValueList;
R: TGDBMIExecResult;
MemDump: TGDBMIMemoryDumpResultList;
i, Size: integer;
s: String;
begin
Result := False;
case FDisplayFormat of
wdfStructure:
begin
Result := ExecuteCommand('-data-evaluate-expression %s', [Quote(AnExpression)], R);
Result := Result and (R.State <> dsError);
if (not Result) then begin
ParseLastError;
exit;
end;
ResultList := TGDBMINameValueList.Create(R.Values);
if Result
then FTextValue := ResultList.Values['value']
else FTextValue := ResultList.Values['msg'];
FTextValue := DeleteEscapeChars(FTextValue);
ResultList.Free;
if Result
then begin
FixUpResult(AnExpression);
FValidity := ddsValid;
end;
end;
wdfChar:
begin
Result := PrepareExpr(AnExpression);
if not Result
then exit;
FValidity := ddsValid;
FTextValue := GetChar(AnExpression, []);
if LastExecResult.State = dsError
then ParseLastError;
end;
wdfString:
begin
Result := PrepareExpr(AnExpression);
if not Result
then exit;
FValidity := ddsValid;
FTextValue := GetText(AnExpression, []); // GetText takes Addr
if LastExecResult.State = dsError
then ParseLastError;
end;
wdfDecimal:
begin
Result := PrepareExpr(AnExpression, True);
if not Result
then exit;
FValidity := ddsValid;
FTextValue := IntToStr(Int64(GetPtrValue(AnExpression, [], True)));
if LastExecResult.State = dsError
then ParseLastError;
end;
wdfUnsigned:
begin
Result := PrepareExpr(AnExpression, True);
if not Result
then exit;
FValidity := ddsValid;
FTextValue := IntToStr(GetPtrValue(AnExpression, [], True));
if LastExecResult.State = dsError
then ParseLastError;
end;
//wdfFloat:
// begin
// Result := PrepareExpr(AnExpression);
// if not Result
// then exit;
// FTextValue := GetFloat(AnExpression, []); // GetFloat takes address
// if LastExecResult.State = dsError
// then FTextValue := '<error>';
// end;
wdfHex:
begin
Result := PrepareExpr(AnExpression, True);
if not Result
then exit;
FTextValue := IntToHex(GetPtrValue(AnExpression, [], True), 2);
FValidity := ddsValid;
if length(FTextValue) mod 2 = 1
then FTextValue := '0'+FTextValue; // make it an even number of digets
if LastExecResult.State = dsError
then ParseLastError;
end;
wdfPointer:
begin
Result := PrepareExpr(AnExpression, True);
if not Result
then exit;
FTextValue := PascalizePointer('0x' + IntToHex(GetPtrValue(AnExpression, [], True), TargetInfo^.TargetPtrSize*2));
FValidity := ddsValid;
if LastExecResult.State = dsError
then FTextValue := '<error>';
end;
wdfMemDump:
begin
Result := PrepareExpr(AnExpression);
if not Result
then exit;
Result := False;
Size := 256;
if (FTypeInfo <> nil) and (saInternalPointer in FTypeInfo.Attributes) then begin
Result := ExecuteCommand('-data-read-memory %s^ x 1 1 %u', [AnExpression, Size], R);
Result := Result and (R.State <> dsError);
// nil ?
if (R.State = dsError) and (pos('Unable to read memory', R.Values) > 0) then
Size := TargetInfo^.TargetPtrSize;
end;
if (not Result) then begin
Result := ExecuteCommand('-data-read-memory %s x 1 1 %u', [AnExpression, Size], R);
Result := Result and (R.State <> dsError);
end;
if (not Result) then begin
ParseLastError;
exit;
end;
MemDump := TGDBMIMemoryDumpResultList.Create(R);
FValidity := ddsValid;
FTextValue := MemDump.AsText(0, MemDump.Count, TargetInfo^.TargetPtrSize*2);
MemDump.Free;
end;
else // wdfDefault
begin
Result := False;
Assert(FTypeInfo = nil, 'Type info must be nil');
i := 0;
if FWatchValue <> nil then i := FWatchValue.RepeatCount;
FTypeInfo := GetGDBTypeInfo(AnExpression, defFullTypeInfo in FEvalFlags,
TypeInfoFlags + [gtcfExprEvaluate, gtcfExprEvalStrFixed], FDisplayFormat, i);
if (FTypeInfo = nil) or (dcsCanceled in SeenStates)
then begin
ParseLastError;
exit;
end;
if FTypeInfo.HasExprEvaluatedAsText then begin
FTextValue := FTypeInfo.ExprEvaluatedAsText;
//FTextValue := DeleteEscapeChars(FTextValue); // TODO: move to FixUpResult / only if really needed
FValidity := ddsValid;
Result := True;
FixUpResult(AnExpression, FTypeInfo);
if FTypeInfo.HasStringExprEvaluatedAsText then begin
s := FTextValue;
FTextValue := FTypeInfo.StringExprEvaluatedAsText;
//FTextValue := DeleteEscapeChars(FTextValue); // TODO: move to FixUpResult / only if really needed
FixUpResult(AnExpression, FTypeInfo);
FTextValue := 'PCHAR: ' + s + LineEnding + 'STRING: ' + FTextValue;
end;
exit;
end;
debugln(DBG_WARNINGS, '############# Not expected to be here');
FTextValue := '<ERROR>';
end;
end;
end;
var
S: String;
ResultList: TGDBMINameValueList;
frameidx: Integer;
{$IFDEF DBG_WITH_GDB_WATCHES} R: TGDBMIExecResult; {$ENDIF}
begin
SelectContext;
try
FTextValue:='';
FTypeInfo:=nil;
TypeInfoFlags := [];
if defClassAutoCast in FEvalFlags
then include(TypeInfoFlags, gtcfAutoCastClass);
S := StripExprNewlines(FExpression);
if S = '' then Exit(false);
{$IFDEF DBG_WITH_GDB_WATCHES}
(* This code is experimental. No support will be provided.
It is intended for people extending the GDBMI classes of the IDE, and requires deep knowledge on how the IDE works.
WARNING:
- This bypasses some of the internals of the debugger.
- It does intentionally no check or validation
- Using this feature without full knowledge of all internals of the debugger, can *HANG* or *CRASH* the debugger or the entire IDE.
*)
if S[1]='>' then begin // raw cli commands
delete(S,1,1);
Result := ExecuteCommand('%s', [S], R);
Result := Result and (R.State <> dsError);
if (not Result) then begin
ParseLastError;
exit(True);
end;
FValidity := ddsValid;
FTextValue := UnEscapeBackslashed(R.Values, [uefNewLine, uefTab], 3);
exit;
end;
{$ENDIF}
ResultList := TGDBMINameValueList.Create('');
// keep the internal stackframe => same as requested by watch
frameidx := ContextStackFrame;
DefaultTimeOut := DebuggerProperties.TimeoutForEval;
try
repeat
if TryExecute(S)
then Break;
FreeAndNil(FTypeInfo);
if (dcsCanceled in SeenStates)
then break;
until not SelectParentFrame(frameidx); // may set FStackFrameChanged to force UnSelectContext()
finally
DefaultTimeOut := -1;
FreeAndNil(ResultList);
end;
Result := True;
finally
UnSelectContext;
if FWatchValue <> nil then begin
FWatchValue.Value := FTextValue;
FWatchValue.TypeInfo := TypeInfo;
FWatchValue.Validity := FValidity;
end;
end;
end;
function TGDBMIDebuggerCommandEvaluate.SelectContext: Boolean;
begin
Result := True;
if FWatchValue = nil then begin
CopyGlobalContextToLocal;
exit;
end;
FContext.ThreadContext := ccUseLocal;
FContext.ThreadId := FWatchValue.ThreadId;
FContext.StackContext := ccUseLocal;
FContext.StackFrame := FWatchValue.StackFrame;
end;
procedure TGDBMIDebuggerCommandEvaluate.UnSelectContext;
begin
FContext.ThreadContext := ccUseGlobal;
FContext.StackContext := ccUseGlobal;
end;
constructor TGDBMIDebuggerCommandEvaluate.Create(AOwner: TGDBMIDebugger; AExpression: String;
ADisplayFormat: TWatchDisplayFormat);
begin
inherited Create(AOwner);
FWatchValue := nil;
FExpression := AExpression;
FDisplayFormat := ADisplayFormat;
FTextValue := '';
FTypeInfo:=nil;
FEvalFlags := [];
FTypeInfoAutoDestroy := True;
FValidity := ddsValid;
FLockFlag := False;
end;
constructor TGDBMIDebuggerCommandEvaluate.Create(AOwner: TGDBMIDebugger;
AWatchValue: TWatchValue);
begin
Create(AOwner, AWatchValue.Watch.Expression, AWatchValue.DisplayFormat);
EvalFlags := AWatchValue.EvaluateFlags;
FWatchValue := AWatchValue;
FWatchValue.AddFreeNotification(@DoWatchFreed);
end;
destructor TGDBMIDebuggerCommandEvaluate.Destroy;
begin
if FWatchValue <> nil
then FWatchValue.RemoveFreeNotification(@DoWatchFreed);
if FTypeInfoAutoDestroy
then FreeAndNil(FTypeInfo);
inherited Destroy;
end;
function TGDBMIDebuggerCommandEvaluate.DebugText: String;
begin
if FWatchValue <> nil
then Result := Format('%s: %s Thread=%d, Frame=%d', [ClassName, FExpression, FWatchValue.ThreadId, FWatchValue.StackFrame])
else Result := Format('%s: %s', [ClassName, FExpression]);
end;
procedure Register;
begin
RegisterDebugger(TGDBMIDebugger);
end;
initialization
DBGMI_QUEUE_DEBUG := DebugLogger.RegisterLogGroup('DBGMI_QUEUE_DEBUG' {$IFDEF DBGMI_QUEUE_DEBUG} , True {$ENDIF} );
DBGMI_STRUCT_PARSER := DebugLogger.RegisterLogGroup('DBGMI_STRUCT_PARSER' {$IFDEF DBGMI_STRUCT_PARSER} , True {$ENDIF} );
DBG_VERBOSE := DebugLogger.FindOrRegisterLogGroup('DBG_VERBOSE' {$IFDEF DBG_VERBOSE} , True {$ENDIF} );
DBG_WARNINGS := DebugLogger.FindOrRegisterLogGroup('DBG_WARNINGS' {$IFDEF DBG_WARNINGS} , True {$ENDIF} );
DBG_DISASSEMBLER := DebugLogger.FindOrRegisterLogGroup('DBG_DISASSEMBLER' {$IFDEF DBG_DISASSEMBLER} , True {$ENDIF} );
DBG_THREAD_AND_FRAME := DebugLogger.FindOrRegisterLogGroup('DBG_THREAD_AND_FRAME' {$IFDEF DBG_THREAD_AND_FRAME} , True {$ENDIF} );
end.