gdb commander, mioutput to json + test with brkpts

This commit is contained in:
Basile Burg 2016-09-18 02:44:04 +02:00
parent 337cf1bbbb
commit 39b4ff9dc8
No known key found for this signature in database
GPG Key ID: 1868039F415CB8CF
2 changed files with 239 additions and 72 deletions

View File

@ -8,7 +8,7 @@ uses
Classes, SysUtils, FileUtil, ListFilterEdit, Forms, Controls, Graphics, RegExpr, Classes, SysUtils, FileUtil, ListFilterEdit, Forms, Controls, Graphics, RegExpr,
ComCtrls, PropEdits, GraphPropEdits, RTTIGrids, Dialogs, ExtCtrls, Menus, strutils, ComCtrls, PropEdits, GraphPropEdits, RTTIGrids, Dialogs, ExtCtrls, Menus, strutils,
Buttons, StdCtrls, process ,ce_common, ce_interfaces, ce_widget, ce_processes, Buttons, StdCtrls, process ,ce_common, ce_interfaces, ce_widget, ce_processes,
ce_observer, ce_synmemo, ce_sharedres, ce_stringrange; ce_observer, ce_synmemo, ce_sharedres, ce_stringrange, fpjson;
type type
@ -32,6 +32,42 @@ type
// aliased to get hex display in object inspector. // aliased to get hex display in object inspector.
TCpuRegValue = type PtrInt; 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 // Stores the stack and the registers content, to be displayable in
// an object inspector. // an object inspector.
TInspectableState = class(TPersistent) TInspectableState = class(TPersistent)
@ -94,10 +130,6 @@ type
public public
constructor create; constructor create;
destructor destroy; override; destructor destroy; override;
// called on the result of "info stack"
procedure parseCallStack(stream: TStream);
// called on the result of "info register"
procedure parseRegisters(stream: TStream);
end; end;
TCpuRegValueEditor = class(TIntegerProperty) TCpuRegValueEditor = class(TIntegerProperty)
@ -122,7 +154,7 @@ type
~"\nBreakpoint " ~"\nBreakpoint "
~"2, D main (args=...) at /home/basile/Dev/dproj/Resource.d/src/resource.d:39\n" ~"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" ~"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" *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) (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 . line starting with = is to parse as TGDBMI_Breakpoint, thorically its [opt token]=, no token for breakpoint reached since it's not a result
@ -189,6 +221,7 @@ type
procedure btnStopClick(Sender: TObject); procedure btnStopClick(Sender: TObject);
procedure Edit1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); procedure Edit1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
private private
fDoc: TCESynMemo;
fProj: ICECommonProject; fProj: ICECommonProject;
fLog: TStringList; fLog: TStringList;
fFileLineBrks: TStringList; fFileLineBrks: TStringList;
@ -196,18 +229,18 @@ type
fMsg: ICEMessagesDisplay; fMsg: ICEMessagesDisplay;
fGdb: TCEAutoBufferedProcess; fGdb: TCEAutoBufferedProcess;
fInspState: TInspectableState; fInspState: TInspectableState;
fGdbMessage: TGdbMessage;
// //
procedure startDebugging; procedure startDebugging;
procedure killGdb; procedure killGdb;
procedure updateFileLineBrks; procedure updateFileLineBrks;
procedure editorModBrk(sender: TCESynMemo; line: integer; modification: TBreakPointModification); procedure editorModBrk(sender: TCESynMemo; line: integer; modification: TBreakPointModification);
// GDB output processors // GDB output processors
procedure processInfoRegs(sender: TObject); procedure gdboutQuiet(sender: TObject);
procedure processInfoStack(sender: TObject); procedure gdboutJsonize(sender: TObject);
procedure processSilently(sender: TObject); procedure interpretJson;
procedure gdbOutput(sender: TObject);
// GDB commands & actions // GDB commands & actions
procedure gdbCommand(aCommand: string; outputCatcher: TNotifyEvent = nil); procedure gdbCommand(aCommand: string; gdboutProcessor: TNotifyEvent = nil);
procedure infoRegs; procedure infoRegs;
procedure infoStack; procedure infoStack;
// //
@ -231,13 +264,132 @@ type
implementation implementation
{$R *.lfm} {$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 -----------------------------------------------------} {$REGION TInspectableState -----------------------------------------------------}
function TCpuRegValueEditor.GetValue: ansistring; function TCpuRegValueEditor.GetValue: ansistring;
begin begin
{$IFDEF CPU64} {$IFDEF CPU64}
result := '0x' + IntToHex(GetInt64Value, 16); result := '0x' + IntToHex(GetInt64Value, 16);
{$ELSE} {$ELSE}
result := '0x' + IntToHex(GetInt64Value, 8); result := '0x' + IntToHex(GetOrdValue, 8);
{$ENDIF} {$ENDIF}
end; end;
@ -253,26 +405,6 @@ begin
fCallStack.free; fCallStack.free;
fWordSpliter.Free; fWordSpliter.Free;
inherited; inherited;
end;
procedure TInspectableState.parseCallStack(stream: TStream);
var
rng: TStringRange = (ptr: nil; pos: 0; len: 0);
str: string;
i,j: integer;
begin
end;
procedure TInspectableState.parseRegisters(stream: TStream);
var
reg: string;
val: string;
rng: TStringRange = (ptr: nil; pos: 0; len: 0);
begin
end; end;
{$ENDREGION} {$ENDREGION}
@ -287,6 +419,7 @@ begin
fLog := TStringList.Create; fLog := TStringList.Create;
fInspState := TInspectableState.Create; fInspState := TInspectableState.Create;
stateViewer.TIObject := fInspState; stateViewer.TIObject := fInspState;
fGdbMessage:= TGdbMessage.create;
// //
AssignPng(btnSendCom, 'ACCEPT'); AssignPng(btnSendCom, 'ACCEPT');
end; end;
@ -297,6 +430,7 @@ begin
fLog.Free; fLog.Free;
killGdb; killGdb;
fInspState.Free; fInspState.Free;
fGdbMessage.Free;
EntitiesConnector.removeObserver(self); EntitiesConnector.removeObserver(self);
inherited; inherited;
end; end;
@ -346,6 +480,7 @@ procedure TCEGdbWidget.docFocused(document: TCESynMemo);
begin begin
if document.isDSource then if document.isDSource then
document.onBreakpointModify := @editorModBrk; document.onBreakpointModify := @editorModBrk;
fDoc := document;
end; end;
procedure TCEGdbWidget.docChanged(document: TCESynMemo); procedure TCEGdbWidget.docChanged(document: TCESynMemo);
@ -354,6 +489,8 @@ end;
procedure TCEGdbWidget.docClosing(document: TCESynMemo); procedure TCEGdbWidget.docClosing(document: TCESynMemo);
begin begin
if fDoc = document then
fDoc := nil;
end; end;
{$ENDREGION} {$ENDREGION}
@ -429,8 +566,8 @@ begin
fgdb.Options:= [poUsePipes, poStderrToOutPut]; fgdb.Options:= [poUsePipes, poStderrToOutPut];
fgdb.Parameters.Add(str); fgdb.Parameters.Add(str);
fgdb.Parameters.Add('--interpreter=mi'); fgdb.Parameters.Add('--interpreter=mi');
fGdb.OnReadData:= @gdbOutput; fGdb.OnReadData:= @gdboutJsonize;
fGdb.OnTerminate:= @gdbOutput; fGdb.OnTerminate:= @gdboutJsonize;
fgdb.execute; fgdb.execute;
// file:line breakpoints // file:line breakpoints
updateFileLineBrks; updateFileLineBrks;
@ -440,7 +577,7 @@ begin
fGdb.Input.Write(str[1], str.length); fGdb.Input.Write(str[1], str.length);
end; end;
// break on druntime exceptions heper + throw' // break on druntime exceptions heper + throw'
fGdb.OnReadData := @processSilently; fGdb.OnReadData := @gdboutQuiet;
gdbCommand('break onAssertError'); gdbCommand('break onAssertError');
gdbCommand('break onAssertErrorMsg'); gdbCommand('break onAssertErrorMsg');
gdbCommand('break onUnittestErrorMsg'); gdbCommand('break onUnittestErrorMsg');
@ -452,74 +589,104 @@ begin
gdbCommand('break onSwitchError'); gdbCommand('break onSwitchError');
gdbCommand('break onUnicodeError'); gdbCommand('break onUnicodeError');
gdbCommand('break _d_throwc'); gdbCommand('break _d_throwc');
fGdb.OnReadData := @gdbOutput; fGdb.OnReadData := @gdboutJsonize;
// launch // launch
gdbCommand('run'); gdbCommand('run');
end; end;
{$ENDREGION} {$ENDREGION}
{$REGIOn GDB output processors -------------------------------------------------} {$REGIOn GDB output processors -------------------------------------------------}
procedure TCEGdbWidget.gdbOutput(sender: TObject); 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 var
str: string; str: string;
lst: TStringList;
begin begin
if fMsg = nil then if fMsg = nil then
exit; exit;
fLog.Clear; fLog.Clear;
fGdb.getFullLines(fLog); fGdb.getFullLines(fLog);
//fGdb.OutputStack.Clear;
for str in fLog do for str in fLog do
fMsg.message(str, nil, amcMisc, amkAuto); 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; end;
procedure TCEGdbWidget.processSilently(sender: TObject); procedure TCEGdbWidget.gdboutQuiet(sender: TObject);
begin begin
fGdb.OutputStack.Clear; fGdb.OutputStack.Clear;
fGdb.OnReadData:=@gdbOutput; fGdb.OnReadData:=@gdboutJsonize;
end;
procedure TCEGdbWidget.processInfoRegs(sender: TObject);
begin
try
fInspState.parseRegisters(fgdb.OutputStack);
fgdb.OutputStack.Clear;
finally
fGdb.OnReadData:=@gdbOutput;
end;
end;
procedure TCEGdbWidget.processInfoStack(sender: TObject);
begin
try
fInspState.parseCallStack(fgdb.OutputStack);
fgdb.OutputStack.Clear;
finally
fGdb.OnReadData:=@gdbOutput;
end;
end; end;
{$ENDREGION} {$ENDREGION}
{$REGIOn GDB commands & actions ------------------------------------------------} {$REGIOn GDB commands & actions ------------------------------------------------}
procedure TCEGdbWidget.gdbCommand(aCommand: string; outputCatcher: TNotifyEvent = nil); procedure TCEGdbWidget.gdbCommand(aCommand: string; gdboutProcessor: TNotifyEvent = nil);
begin begin
if fGdb = nil then exit; if fGdb = nil then exit;
if not fGdb.Running then exit; if not fGdb.Running then exit;
// //
aCommand += #10; aCommand += #10;
if assigned(outputCatcher) then if assigned(gdboutProcessor) then
fGdb.OnReadData := outputCatcher; fGdb.OnReadData := gdboutProcessor;
fGdb.Input.Write(aCommand[1], aCommand.length); fGdb.Input.Write(aCommand[1], aCommand.length);
end; end;
procedure TCEGdbWidget.infoRegs; procedure TCEGdbWidget.infoRegs;
begin begin
// GDBMI output format, "info registers" is for CLI output // GDBMI output format, "info registers" is for CLI output
gdbCommand('-data-list-register-values d', @processInfoRegs); gdbCommand('-data-list-register-values d', @gdboutJsonize);
end; end;
procedure TCEGdbWidget.infoStack; procedure TCEGdbWidget.infoStack;
begin begin
// GDBMI output format, "info frame" is for CLI output // GDBMI output format, "info frame" is for CLI output
gdbCommand('-stack-info-frame', @processInfoStack); gdbCommand('-stack-info-frame', @gdboutJsonize);
end; end;
procedure TCEGdbWidget.btnStartClick(Sender: TObject); procedure TCEGdbWidget.btnStartClick(Sender: TObject);
@ -529,7 +696,7 @@ end;
procedure TCEGdbWidget.btnContClick(Sender: TObject); procedure TCEGdbWidget.btnContClick(Sender: TObject);
begin begin
gdbCommand('continue'); gdbCommand('continue', @gdboutJsonize);
end; end;
procedure TCEGdbWidget.btnRegClick(Sender: TObject); procedure TCEGdbWidget.btnRegClick(Sender: TObject);
@ -539,13 +706,13 @@ end;
procedure TCEGdbWidget.btnStopClick(Sender: TObject); procedure TCEGdbWidget.btnStopClick(Sender: TObject);
begin begin
gdbCommand('kill'); gdbCommand('kill', @gdboutQuiet);
killGdb; killGdb;
end; end;
procedure TCEGdbWidget.btnSendComClick(Sender: TObject); procedure TCEGdbWidget.btnSendComClick(Sender: TObject);
begin begin
gdbCommand(edit1.Text); gdbCommand(edit1.Text, @gdboutJsonize);
edit1.Text := ''; edit1.Text := '';
end; end;
@ -557,7 +724,7 @@ end;
procedure TCEGdbWidget.Edit1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); procedure TCEGdbWidget.Edit1KeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin begin
if Key <> byte(#13) then exit; if Key <> byte(#13) then exit;
gdbCommand(edit1.Text); gdbCommand(edit1.Text, @gdboutJsonize);
edit1.Text := ''; edit1.Text := '';
end; end;
{$ENDREGION} {$ENDREGION}

View File

@ -14,7 +14,7 @@ uses
ce_widget, ce_messages, ce_interfaces, ce_editor, ce_projinspect, ce_projconf, ce_widget, ce_messages, ce_interfaces, ce_editor, ce_projinspect, ce_projconf,
ce_search, ce_miniexplorer, ce_libman, ce_libmaneditor, ce_todolist, ce_observer, ce_search, ce_miniexplorer, ce_libman, ce_libmaneditor, ce_todolist, ce_observer,
ce_toolseditor, ce_procinput, ce_optionseditor, ce_symlist, ce_mru, ce_processes, ce_toolseditor, ce_procinput, ce_optionseditor, ce_symlist, ce_mru, ce_processes,
ce_infos, ce_dubproject, ce_dialogs, ce_dubprojeditor, (*ce_gdb,*) ce_dfmt, ce_infos, ce_dubproject, ce_dialogs, ce_dubprojeditor, ce_gdb, ce_dfmt,
ce_lcldragdrop, ce_projgroup, ce_projutils, ce_stringrange; ce_lcldragdrop, ce_projgroup, ce_projutils, ce_stringrange;
type type
@ -362,7 +362,7 @@ type
fInfoWidg: TCEInfoWidget; fInfoWidg: TCEInfoWidget;
fDubProjWidg: TCEDubProjectEditorWidget; fDubProjWidg: TCEDubProjectEditorWidget;
fPrjGrpWidg: TCEProjectGroupWidget; fPrjGrpWidg: TCEProjectGroupWidget;
//fGdbWidg: TCEGdbWidget; fGdbWidg: TCEGdbWidget;
fDfmtWidg: TCEDfmtWidget; fDfmtWidg: TCEDfmtWidget;
fCompStart: TDateTime; fCompStart: TDateTime;
@ -1277,7 +1277,7 @@ begin
fDfmtWidg := TCEDfmtWidget.create(self); fDfmtWidg := TCEDfmtWidget.create(self);
fPrjGrpWidg := TCEProjectGroupWidget.create(self); fPrjGrpWidg := TCEProjectGroupWidget.create(self);
//fGdbWidg := TCEGdbWidget.create(self); fGdbWidg := TCEGdbWidget.create(self);
getMessageDisplay(fMsgs); getMessageDisplay(fMsgs);
@ -1298,7 +1298,7 @@ begin
fWidgList.addWidget(@fDfmtWidg); fWidgList.addWidget(@fDfmtWidg);
fWidgList.addWidget(@fPrjGrpWidg); fWidgList.addWidget(@fPrjGrpWidg);
//fWidgList.addWidget(@fGdbWidg); fWidgList.addWidget(@fGdbWidg);
fWidgList.sort(@CompareWidgCaption); fWidgList.sort(@CompareWidgCaption);