mirror of https://gitlab.com/basile.b/dexed.git
735 lines
20 KiB
Plaintext
735 lines
20 KiB
Plaintext
unit ce_gdb;
|
|
|
|
{$I ce_defines.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, FileUtil, ListFilterEdit, Forms, Controls, Graphics, RegExpr,
|
|
ComCtrls, PropEdits, GraphPropEdits, RTTIGrids, Dialogs, ExtCtrls, Menus, strutils,
|
|
Buttons, StdCtrls, process ,ce_common, ce_interfaces, ce_widget, ce_processes,
|
|
ce_observer, ce_synmemo, ce_sharedres, ce_stringrange, fpjson;
|
|
|
|
type
|
|
|
|
//TODO-cDebugging: write a parser for the DBG/MI output messages
|
|
|
|
{$IFDEF CPU64}
|
|
TCpuRegister = (rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, r8, r9, r10, r11, r12, r13,
|
|
r14, r15, rip);
|
|
{$ENDIF}
|
|
|
|
{$IFDEF CPU32}
|
|
TCpuRegister = (eax, ebx, ecx, edx, esi, edi, ebp, esp, eip);
|
|
{$ENDIF}
|
|
|
|
TFLAG = (CS, PF, AF, ZF, SF, TF, IF_, DF, OF_, NT, RF, VM,
|
|
AC, VIF, VIP, ID);
|
|
TEFLAG = set of TFLAG;
|
|
|
|
TSegmentRegister = (S_CS, S_SS, S_DS, S_ES, S_FS, S_GS);
|
|
|
|
// aliased to get hex display in object inspector.
|
|
TCpuRegValue = type PtrInt;
|
|
|
|
// Interprets a GDBMI message as a JSON
|
|
TGdbMessage = class
|
|
private
|
|
fCli: string;
|
|
fOut: string;
|
|
fComponents: TJSONObject;
|
|
public
|
|
constructor create;
|
|
destructor destroy; override;
|
|
// called by the widget when the process receives
|
|
procedure parse(str: string);
|
|
// after parse, contains the standard CLI messages
|
|
property cli: string read fCli;
|
|
// after parse, contains the debuggee output
|
|
property stdout: string read fOut;
|
|
// after parse, contains the message components
|
|
property components: TJSONObject read fComponents;
|
|
end;
|
|
|
|
// Makes a category for the GP registers in a project inspector
|
|
TInspectableGPR = class(TPersistent)
|
|
end;
|
|
|
|
// Makes a category for the FP registers in a project inspector
|
|
TInspectableFPR = class(TPersistent)
|
|
end;
|
|
|
|
// Makes a category for the SSE registers in a project inspector
|
|
TInspectableSSER = class(TPersistent)
|
|
end;
|
|
|
|
// Makes a category for the call stack in a project inspector
|
|
TInspectableStack = class(Tpersistent)
|
|
end;
|
|
|
|
|
|
// Stores the stack and the registers content, to be displayable in
|
|
// an object inspector.
|
|
TInspectableState = class(TPersistent)
|
|
private
|
|
fWordSpliter: TRegExpr;
|
|
fFlags: TEFLAG;
|
|
fSegment: array[TSegmentRegister] of byte;
|
|
fLastCalls: array[0..9] of string;
|
|
fCallStack: TStringList;
|
|
fRegisters: array[TCpuRegister] of TCpuRegValue;
|
|
published
|
|
property EFLAGS: TEFLAG read fFlags;
|
|
{$IFDEF CPU64}
|
|
property RAX: TCpuRegValue read fRegisters[TCpuRegister.rax];
|
|
property RBX: TCpuRegValue read fRegisters[TCpuRegister.rbx];
|
|
property RCX: TCpuRegValue read fRegisters[TCpuRegister.rcx];
|
|
property RDX: TCpuRegValue read fRegisters[TCpuRegister.rdx];
|
|
property RSI: TCpuRegValue read fRegisters[TCpuRegister.rsi];
|
|
property RDI: TCpuRegValue read fRegisters[TCpuRegister.rdi];
|
|
property RBP: TCpuRegValue read fRegisters[TCpuRegister.rbp];
|
|
property RSP: TCpuRegValue read fRegisters[TCpuRegister.rsp];
|
|
property R8: TCpuRegValue read fRegisters[TCpuRegister.r8];
|
|
property R9: TCpuRegValue read fRegisters[TCpuRegister.r9];
|
|
property R10: TCpuRegValue read fRegisters[TCpuRegister.r10];
|
|
property R11: TCpuRegValue read fRegisters[TCpuRegister.r11];
|
|
property R12: TCpuRegValue read fRegisters[TCpuRegister.r12];
|
|
property R13: TCpuRegValue read fRegisters[TCpuRegister.r13];
|
|
property R14: TCpuRegValue read fRegisters[TCpuRegister.r14];
|
|
property R15: TCpuRegValue read fRegisters[TCpuRegister.r15];
|
|
property RIP: TCpuRegValue read fRegisters[TCpuRegister.rip];
|
|
{$ELSE}
|
|
property EAX: TCpuRegValue read fRegisters[TCpuRegister.eax];
|
|
property EBX: TCpuRegValue read fRegisters[TCpuRegister.ebx];
|
|
property ECX: TCpuRegValue read fRegisters[TCpuRegister.ecx];
|
|
property EDX: TCpuRegValue read fRegisters[TCpuRegister.edx];
|
|
property ESI: TCpuRegValue read fRegisters[TCpuRegister.esi];
|
|
property EDI: TCpuRegValue read fRegisters[TCpuRegister.edi];
|
|
property EBP: TCpuRegValue read fRegisters[TCpuRegister.ebp];
|
|
property ESP: TCpuRegValue read fRegisters[TCpuRegister.esp];
|
|
property EIP: TCpuRegValue read fRegisters[TCpuRegister.eip];
|
|
{$ENDIF}
|
|
property CallStack_M0: string read fLastCalls[0];
|
|
property CallStack_M1: string read fLastCalls[1];
|
|
property CallStack_M2: string read fLastCalls[2];
|
|
property CallStack_M3: string read fLastCalls[3];
|
|
property CallStack_M4: string read fLastCalls[4];
|
|
property CallStack_M5: string read fLastCalls[5];
|
|
property CallStack_M6: string read fLastCalls[6];
|
|
property CallStack_M7: string read fLastCalls[7];
|
|
property CallStack_M8: string read fLastCalls[8];
|
|
property CallStack_M9: string read fLastCalls[9];
|
|
property CallStack: TStringList read fCallStack;
|
|
//
|
|
property CS: byte read fSegment[TSegmentRegister.S_CS];
|
|
property DS: byte read fSegment[TSegmentRegister.S_DS];
|
|
property ES: byte read fSegment[TSegmentRegister.S_ES];
|
|
property FS: byte read fSegment[TSegmentRegister.S_FS];
|
|
property GS: byte read fSegment[TSegmentRegister.S_GS];
|
|
property SS: byte read fSegment[TSegmentRegister.S_SS];
|
|
public
|
|
constructor create;
|
|
destructor destroy; override;
|
|
end;
|
|
|
|
TCpuRegValueEditor = class(TIntegerProperty)
|
|
public
|
|
function GetValue: ansistring; override;
|
|
end;
|
|
|
|
TGDBMI_Frame = record
|
|
level: integer;
|
|
func: string;
|
|
adrress: ptruint;
|
|
fname: string; // named "file"
|
|
line: integer;
|
|
from: string;
|
|
end;
|
|
|
|
{
|
|
breakpoint:
|
|
|
|
(gdb)
|
|
=breakpoint-modified,bkpt={number="2",type="breakpoint",disp="keep",enabled="y",addr="0x000000000049dc7a",func="D main",file="/home/basile/Dev/dproj/Resource.d/src/resource.d",fullname="/home/basile/Dev/dproj/Resource.d/src/resource.d",line="39",thread-groups=["i1"],times="1",original-location="/home/basile/Dev/dproj/Resource.d/src/resource.d:39"}
|
|
~"\nBreakpoint "
|
|
~"2, D main (args=...) at /home/basile/Dev/dproj/Resource.d/src/resource.d:39\n"
|
|
~"39\t getopt(args, config.passThrough, \"h|help\", &wantHelp);\n"
|
|
*stopped,reason="breakpoint-hit",disp="keep",bkptno="2",frame={addr="0x000000000049dc7a", func="D main",args=[{name="args",value="..."}],file="/home/basile/Dev/dproj/Resource.d/src/resource.d",fullname="/home/basile/Dev/dproj/Resource.d/src/resource.d",line="39"},thread-id="1",stopped-threads="all",core="3"
|
|
(gdb)
|
|
|
|
. line starting with = is to parse as TGDBMI_Breakpoint, thorically its [opt token]=, no token for breakpoint reached since it's not a result
|
|
. lines starting with "~" can be ignored, they represent the output stream displayed in the CLI
|
|
|
|
}
|
|
TGDBMI_Breakpoint = record
|
|
number: integer;
|
|
tpe: string; // named "type"
|
|
catchtype: string; // named "catch-type"
|
|
disp: string; // "del" | "keep"
|
|
enabled: boolean; // "y" | "n"
|
|
addr: ptrUint; // hex | <PENDING> | <MULTIPLE>
|
|
func: string;
|
|
filename: string;
|
|
fullname: string;
|
|
line: integer;
|
|
at: string;
|
|
pending: string; // value is the command passed to set the BP
|
|
evaluateby: string; // named "evaluate-by" , host | target
|
|
thread: ptrUint;
|
|
task: string;
|
|
cond: string;
|
|
ignore: integer;
|
|
enable: integer;
|
|
traceframeusage: string;// named "traceframe-usage"
|
|
statictraceid: string; // named "static-tracepoint-marker-string-id"
|
|
mask: string;
|
|
pass: integer;
|
|
originloc: string; // named "original-location"
|
|
times: integer;
|
|
installed: boolean; // "y" | "n" , only for trace points
|
|
what: string;
|
|
end;
|
|
|
|
TGDBMI_Thread = record
|
|
id: ptrUint;
|
|
targetid: string; // named "target-id"
|
|
details: string;
|
|
state: string; // running | stopped
|
|
core: integer;
|
|
end;
|
|
|
|
|
|
{ TCEGdbWidget }
|
|
TCEGdbWidget = class(TCEWidget, ICEProjectObserver, ICEDocumentObserver)
|
|
btnReg: TBitBtn;
|
|
btnStack: TBitBtn;
|
|
btnSendCom: TBitBtn;
|
|
btnStop: TBitBtn;
|
|
btnStart: TBitBtn;
|
|
btnCont: TBitBtn;
|
|
Edit1: TEdit;
|
|
lstfilter: TListFilterEdit;
|
|
Panel1: TPanel;
|
|
Panel2: TPanel;
|
|
Panel3: TPanel;
|
|
stateViewer: TTIPropertyGrid;
|
|
procedure btnContClick(Sender: TObject);
|
|
procedure btnRegClick(Sender: TObject);
|
|
procedure btnSendComClick(Sender: TObject);
|
|
procedure btnStackClick(Sender: TObject);
|
|
procedure btnStartClick(Sender: TObject);
|
|
procedure btnStopClick(Sender: TObject);
|
|
procedure Edit1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
private
|
|
fDoc: TCESynMemo;
|
|
fProj: ICECommonProject;
|
|
fLog: TStringList;
|
|
fFileLineBrks: TStringList;
|
|
fDocHandler: ICEMultiDocHandler;
|
|
fMsg: ICEMessagesDisplay;
|
|
fGdb: TCEAutoBufferedProcess;
|
|
fInspState: TInspectableState;
|
|
fGdbMessage: TGdbMessage;
|
|
//
|
|
procedure startDebugging;
|
|
procedure killGdb;
|
|
procedure updateFileLineBrks;
|
|
procedure editorModBrk(sender: TCESynMemo; line: integer; modification: TBreakPointModification);
|
|
// GDB output processors
|
|
procedure gdboutQuiet(sender: TObject);
|
|
procedure gdboutJsonize(sender: TObject);
|
|
procedure interpretJson;
|
|
// GDB commands & actions
|
|
procedure gdbCommand(aCommand: string; gdboutProcessor: TNotifyEvent = nil);
|
|
procedure infoRegs;
|
|
procedure infoStack;
|
|
//
|
|
procedure projNew(project: ICECommonProject);
|
|
procedure projChanged(project: ICECommonProject);
|
|
procedure projClosing(project: ICECommonProject);
|
|
procedure projFocused(project: ICECommonProject);
|
|
procedure projCompiling(project: ICECommonProject);
|
|
procedure projCompiled(project: ICECommonProject; success: boolean);
|
|
//
|
|
procedure docNew(document: TCESynMemo);
|
|
procedure docFocused(document: TCESynMemo);
|
|
procedure docChanged(document: TCESynMemo);
|
|
procedure docClosing(document: TCESynMemo);
|
|
public
|
|
constructor create(aOwner: TComponent); override;
|
|
destructor destroy; override;
|
|
end;
|
|
|
|
|
|
implementation
|
|
{$R *.lfm}
|
|
|
|
|
|
|
|
{$REGION TGdbMessage -----------------------------------------------------------}
|
|
constructor TGdbMessage.create;
|
|
begin
|
|
fComponents := TJSONObject.Create;
|
|
end;
|
|
|
|
destructor TGdbMessage.destroy;
|
|
begin
|
|
fComponents.Free;
|
|
inherited;
|
|
end;
|
|
|
|
procedure TGdbMessage.parse(str: string);
|
|
|
|
procedure parseProperty(node: TJSONObject; r: PStringRange); forward;
|
|
procedure parseProperty(node: TJSONArray; r: PStringRange); forward;
|
|
|
|
procedure parseProperty(node: TJSONArray; r: PStringRange);
|
|
var
|
|
c: char;
|
|
begin
|
|
while true do
|
|
begin
|
|
if r^.empty then
|
|
exit;
|
|
c := r^.front;
|
|
case c of
|
|
'{':
|
|
begin
|
|
r^.popFront;
|
|
node.Objects[node.Count] := TJSONObject.Create;
|
|
parseProperty(node.Objects[node.Count-1], r);
|
|
end;
|
|
']':
|
|
begin
|
|
r^.popFront;
|
|
exit;
|
|
end;
|
|
',': r^.popFront;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure parseProperty(node: TJSONObject; r: PStringRange);
|
|
var
|
|
idt: string;
|
|
c: char;
|
|
begin
|
|
while true do
|
|
begin
|
|
if r^.empty then
|
|
exit;
|
|
c := r^.front;
|
|
case c of
|
|
',':
|
|
begin
|
|
r^.popFront;
|
|
end;
|
|
'a'..'z':
|
|
begin
|
|
idt := r^.takeUntil('=').yield;
|
|
r^.popFront;
|
|
end;
|
|
'"':
|
|
begin
|
|
node.Strings[idt] := r^.popFront^.takeUntil('"').yield;
|
|
r^.popFront;
|
|
end;
|
|
'{':
|
|
begin
|
|
r^.popFront;
|
|
node.Objects[idt] := TJSONObject.Create;
|
|
parseProperty(node.Objects[idt], r);
|
|
end;
|
|
'[':
|
|
begin
|
|
r^.popFront;
|
|
node.Arrays[idt] := TJSONArray.Create;
|
|
parseProperty(node.Arrays[idt], r);
|
|
end;
|
|
'}', ']':
|
|
begin
|
|
r^.popFront;
|
|
exit;
|
|
end;
|
|
' ', #9:
|
|
r^.popFront;
|
|
#10:
|
|
begin
|
|
r^.popFront;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
var
|
|
rng: TStringRange = (ptr: nil; pos: 0; len: 0);
|
|
begin
|
|
fComponents.Clear;
|
|
if str.length = 0 then
|
|
exit;
|
|
rng.init(str);
|
|
while true do
|
|
begin
|
|
if rng.empty then
|
|
exit;
|
|
if rng.front = '*' then
|
|
parseProperty(fComponents, rng.popUntil(',')^.popFront);
|
|
rng.popUntil(#10);
|
|
if not rng.empty then
|
|
rng.popFront;
|
|
end;
|
|
end;
|
|
{$ENDREGION}
|
|
|
|
|
|
{$REGION TInspectableState -----------------------------------------------------}
|
|
function TCpuRegValueEditor.GetValue: ansistring;
|
|
begin
|
|
{$IFDEF CPU64}
|
|
result := '0x' + IntToHex(GetInt64Value, 16);
|
|
{$ELSE}
|
|
result := '0x' + IntToHex(GetOrdValue, 8);
|
|
{$ENDIF}
|
|
end;
|
|
|
|
constructor TInspectableState.create;
|
|
begin
|
|
fCallStack := TStringList.Create;
|
|
fWordSpliter := TRegExpr.Create('[A-Za-z0-9_#]+');
|
|
fWordSpliter.Compile;
|
|
end;
|
|
|
|
destructor TInspectableState.destroy;
|
|
begin
|
|
fCallStack.free;
|
|
fWordSpliter.Free;
|
|
inherited;
|
|
end;
|
|
{$ENDREGION}
|
|
|
|
{$REGION Common/standard comp --------------------------------------------------}
|
|
constructor TCEGdbWidget.create(aOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
EntitiesConnector.addObserver(self);
|
|
fDocHandler:= getMultiDocHandler;
|
|
fMsg:= getMessageDisplay;
|
|
fFileLineBrks:= TStringList.Create;
|
|
fLog := TStringList.Create;
|
|
fInspState := TInspectableState.Create;
|
|
stateViewer.TIObject := fInspState;
|
|
fGdbMessage:= TGdbMessage.create;
|
|
//
|
|
AssignPng(btnSendCom, 'ACCEPT');
|
|
end;
|
|
|
|
destructor TCEGdbWidget.destroy;
|
|
begin
|
|
fFileLineBrks.Free;
|
|
fLog.Free;
|
|
killGdb;
|
|
fInspState.Free;
|
|
fGdbMessage.Free;
|
|
EntitiesConnector.removeObserver(self);
|
|
inherited;
|
|
end;
|
|
{$ENDREGION}
|
|
|
|
{$REGION ICEProjectObserver ----------------------------------------------------}
|
|
procedure TCEGdbWidget.projNew(project: ICECommonProject);
|
|
begin
|
|
fProj := project;
|
|
end;
|
|
|
|
procedure TCEGdbWidget.projChanged(project: ICECommonProject);
|
|
begin
|
|
if fProj <> project then
|
|
exit;
|
|
end;
|
|
|
|
procedure TCEGdbWidget.projClosing(project: ICECommonProject);
|
|
begin
|
|
if fProj <> project then
|
|
exit;
|
|
fProj := nil;
|
|
end;
|
|
|
|
procedure TCEGdbWidget.projFocused(project: ICECommonProject);
|
|
begin
|
|
fProj := project;
|
|
end;
|
|
|
|
procedure TCEGdbWidget.projCompiling(project: ICECommonProject);
|
|
begin
|
|
end;
|
|
|
|
procedure TCEGdbWidget.projCompiled(project: ICECommonProject; success: boolean);
|
|
begin
|
|
end;
|
|
{$ENDREGION}
|
|
|
|
{$REGION ICEDocumentObserver ---------------------------------------------------}
|
|
procedure TCEGdbWidget.docNew(document: TCESynMemo);
|
|
begin
|
|
if document.isDSource then
|
|
document.onBreakpointModify := @editorModBrk;
|
|
end;
|
|
|
|
procedure TCEGdbWidget.docFocused(document: TCESynMemo);
|
|
begin
|
|
if document.isDSource then
|
|
document.onBreakpointModify := @editorModBrk;
|
|
fDoc := document;
|
|
end;
|
|
|
|
procedure TCEGdbWidget.docChanged(document: TCESynMemo);
|
|
begin
|
|
end;
|
|
|
|
procedure TCEGdbWidget.docClosing(document: TCESynMemo);
|
|
begin
|
|
if fDoc = document then
|
|
fDoc := nil;
|
|
end;
|
|
{$ENDREGION}
|
|
|
|
{$REGION Unsorted Debugging things ---------------------------------------------}
|
|
procedure TCEGdbWidget.killGdb;
|
|
begin
|
|
if not assigned(fGdb) then
|
|
exit;
|
|
if fGdb.Running then
|
|
fGdb.Terminate(0);
|
|
FreeAndNil(fGdb);
|
|
end;
|
|
|
|
procedure TCEGdbWidget.updateFileLineBrks;
|
|
var
|
|
i,j: integer;
|
|
doc: TCESynMemo;
|
|
nme: string;
|
|
begin
|
|
fFileLineBrks.Clear;
|
|
if fDocHandler = nil then exit;
|
|
//
|
|
for i:= 0 to fDocHandler.documentCount-1 do
|
|
begin
|
|
doc := fDocHandler.document[i];
|
|
if not doc.isDSource then
|
|
continue;
|
|
nme := doc.fileName;
|
|
if not nme.fileExists then
|
|
continue;
|
|
{$PUSH}{$WARNINGS OFF}{$HINTS OFF}
|
|
for j := 0 to doc.breakPointsCount-1 do
|
|
fFileLineBrks.AddObject(nme, TObject(pointer(doc.BreakPointLine(j))));
|
|
{$POP}
|
|
end;
|
|
end;
|
|
|
|
procedure TCEGdbWidget.editorModBrk(sender: TCESynMemo; line: integer; modification: TBreakPointModification);
|
|
var
|
|
str: string;
|
|
nme: string;
|
|
const
|
|
cmd: array[TBreakPointModification] of string = ('break ', 'clear ');
|
|
begin
|
|
// set only breakpoint in live, while debugging
|
|
// note: only works if execution is paused (breakpoint)
|
|
// and not inside a loop (for ex. with sleep).
|
|
if fGdb = nil then exit;
|
|
if not fGdb.Running then exit;
|
|
nme := sender.fileName;
|
|
if not nme.fileExists then exit;
|
|
//
|
|
str := cmd[modification] + nme + ':' + intToStr(line);
|
|
fGdb.Suspend;
|
|
gdbCommand(str);
|
|
fGdb.Resume;
|
|
end;
|
|
|
|
procedure TCEGdbWidget.startDebugging;
|
|
var
|
|
str: string;
|
|
i: integer;
|
|
begin
|
|
// protect
|
|
if fProj = nil then exit;
|
|
if fProj.binaryKind <> executable then exit;
|
|
str := fProj.outputFilename;
|
|
if not str.fileExists then exit;
|
|
// gdb process
|
|
killGdb;
|
|
fGdb := TCEAutoBufferedProcess.create(nil);
|
|
fGdb.Executable:= 'gdb' + exeExt;
|
|
fgdb.Options:= [poUsePipes, poStderrToOutPut];
|
|
fgdb.Parameters.Add(str);
|
|
fgdb.Parameters.Add('--interpreter=mi');
|
|
fGdb.OnReadData:= @gdboutJsonize;
|
|
fGdb.OnTerminate:= @gdboutJsonize;
|
|
fgdb.execute;
|
|
// file:line breakpoints
|
|
updateFileLineBrks;
|
|
for i:= 0 to fFileLineBrks.Count-1 do
|
|
begin
|
|
str := 'break ' + fFileLineBrks.Strings[i] + ':' + intToStr(PtrUInt(fFileLineBrks.Objects[i])) + #10;
|
|
fGdb.Input.Write(str[1], str.length);
|
|
end;
|
|
// break on druntime exceptions heper + throw'
|
|
fGdb.OnReadData := @gdboutQuiet;
|
|
gdbCommand('break onAssertError');
|
|
gdbCommand('break onAssertErrorMsg');
|
|
gdbCommand('break onUnittestErrorMsg');
|
|
gdbCommand('break onRangeError');
|
|
gdbCommand('break onFinalizeError');
|
|
gdbCommand('break onHiddenFuncError');
|
|
gdbCommand('break onOutOfMemoryError');
|
|
gdbCommand('break onInvalidMemoryOperationError');
|
|
gdbCommand('break onSwitchError');
|
|
gdbCommand('break onUnicodeError');
|
|
gdbCommand('break _d_throwc');
|
|
fGdb.OnReadData := @gdboutJsonize;
|
|
// launch
|
|
gdbCommand('run');
|
|
end;
|
|
{$ENDREGION}
|
|
|
|
{$REGIOn GDB output processors -------------------------------------------------}
|
|
procedure TCEGdbWidget.interpretJson;
|
|
var
|
|
jsn: TJSONObject;
|
|
val: TJSONData;
|
|
obj: TJSONObject;
|
|
// brkp data
|
|
fne: string = '';
|
|
lne: integer = -1;
|
|
doc: TCESynMemo;
|
|
begin
|
|
jsn := fGdbMessage.components;
|
|
val := jsn.Find('reason');
|
|
if val.isNotNil and (val.AsString = 'breakpoint-hit') then
|
|
begin
|
|
obj := TJSONObject(jsn.Find('frame'));
|
|
if obj.isNotNil and (obj.JSONType = jtObject) then
|
|
begin
|
|
val := obj.Find('fullname');
|
|
if val.isNotNil then
|
|
fne := val.AsString;
|
|
val := obj.Find('line');
|
|
if val.isNotNil then
|
|
lne := strToInt(val.AsString);
|
|
if (lne <> -1) and fne.fileExists then
|
|
begin
|
|
getMultiDocHandler.openDocument(fne);
|
|
fDoc.setFocus;
|
|
fDoc.CaretY:= lne;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TCEGdbWidget.gdboutJsonize(sender: TObject);
|
|
var
|
|
str: string;
|
|
lst: TStringList;
|
|
begin
|
|
if fMsg = nil then
|
|
exit;
|
|
fLog.Clear;
|
|
fGdb.getFullLines(fLog);
|
|
//fGdb.OutputStack.Clear;
|
|
for str in fLog do
|
|
fMsg.message(str, nil, amcMisc, amkAuto);
|
|
|
|
if flog.Text.isEmpty then
|
|
exit;
|
|
|
|
fGdbMessage.parse(fLog.Text);
|
|
interpretJson;
|
|
|
|
lst := TStringList.Create;
|
|
try
|
|
str := fGdbMessage.components.FormatJSON(DefaultFormat,2);
|
|
lst.Text:= str;
|
|
lst.SaveToFile('/home/basile/gdbmessage.json');
|
|
finally
|
|
lst.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TCEGdbWidget.gdboutQuiet(sender: TObject);
|
|
begin
|
|
fGdb.OutputStack.Clear;
|
|
fGdb.OnReadData:=@gdboutJsonize;
|
|
end;
|
|
{$ENDREGION}
|
|
|
|
{$REGIOn GDB commands & actions ------------------------------------------------}
|
|
procedure TCEGdbWidget.gdbCommand(aCommand: string; gdboutProcessor: TNotifyEvent = nil);
|
|
begin
|
|
if fGdb = nil then exit;
|
|
if not fGdb.Running then exit;
|
|
//
|
|
aCommand += #10;
|
|
if assigned(gdboutProcessor) then
|
|
fGdb.OnReadData := gdboutProcessor;
|
|
fGdb.Input.Write(aCommand[1], aCommand.length);
|
|
end;
|
|
|
|
procedure TCEGdbWidget.infoRegs;
|
|
begin
|
|
// GDBMI output format, "info registers" is for CLI output
|
|
gdbCommand('-data-list-register-values d', @gdboutJsonize);
|
|
end;
|
|
|
|
procedure TCEGdbWidget.infoStack;
|
|
begin
|
|
// GDBMI output format, "info frame" is for CLI output
|
|
gdbCommand('-stack-info-frame', @gdboutJsonize);
|
|
end;
|
|
|
|
procedure TCEGdbWidget.btnStartClick(Sender: TObject);
|
|
begin
|
|
startDebugging;
|
|
end;
|
|
|
|
procedure TCEGdbWidget.btnContClick(Sender: TObject);
|
|
begin
|
|
gdbCommand('continue', @gdboutJsonize);
|
|
end;
|
|
|
|
procedure TCEGdbWidget.btnRegClick(Sender: TObject);
|
|
begin
|
|
infoRegs;
|
|
end;
|
|
|
|
procedure TCEGdbWidget.btnStopClick(Sender: TObject);
|
|
begin
|
|
gdbCommand('kill', @gdboutQuiet);
|
|
killGdb;
|
|
end;
|
|
|
|
procedure TCEGdbWidget.btnSendComClick(Sender: TObject);
|
|
begin
|
|
gdbCommand(edit1.Text, @gdboutJsonize);
|
|
edit1.Text := '';
|
|
end;
|
|
|
|
procedure TCEGdbWidget.btnStackClick(Sender: TObject);
|
|
begin
|
|
infoStack;
|
|
end;
|
|
|
|
procedure TCEGdbWidget.Edit1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
begin
|
|
if Key <> byte(#13) then exit;
|
|
gdbCommand(edit1.Text, @gdboutJsonize);
|
|
edit1.Text := '';
|
|
end;
|
|
{$ENDREGION}
|
|
initialization
|
|
RegisterPropertyEditor(TypeInfo(TCpuRegValue), nil, '', TCpuRegValueEditor);
|
|
end.
|
|
|