mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-16 16:59:35 +02:00

- lowers build times - ide is not that important anymore than years before - other utils like pastojs are also located in the packages tree git-svn-id: trunk@37926 -
546 lines
15 KiB
ObjectPascal
546 lines
15 KiB
ObjectPascal
{
|
|
Copyright (c) 2015 by Nikolay Nikolov
|
|
Copyright (c) 1998 by Peter Vreman
|
|
|
|
This is a replacement for GDBCon, implemented on top of GDB/MI,
|
|
instead of LibGDB. This allows integration of GDB/MI support in the
|
|
text mode IDE.
|
|
|
|
See the file COPYING.FPC, included in this distribution,
|
|
for details about the copyright.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
|
|
**********************************************************************}
|
|
|
|
unit gdbmicon;
|
|
|
|
{$MODE fpc}{$H-}
|
|
|
|
{$I globdir.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
gdbmiint, gdbmiwrap;
|
|
|
|
type
|
|
TBreakpointFlags = set of (bfTemporary, bfHardware);
|
|
TWatchpointType = (wtWrite, wtReadWrite, wtRead);
|
|
TPrintFormatType = (pfbinary, pfdecimal, pfhexadecimal, pfoctal, pfnatural);
|
|
|
|
TGDBController = object(TGDBInterface)
|
|
private
|
|
FRegisterNames: array of AnsiString;
|
|
procedure UpdateRegisterNames;
|
|
function GetGdbRegisterNo(const RegName: string): LongInt;
|
|
function GetRegisterAsString(const RegName, Format: string; var Value: string): Boolean;
|
|
procedure RunExecCommand(const Cmd: string);
|
|
protected
|
|
TBreakNumber,
|
|
start_break_number: LongInt;
|
|
in_command: LongInt;
|
|
|
|
procedure CommandBegin(const s: string); virtual;
|
|
procedure CommandEnd(const s: string); virtual;
|
|
|
|
public
|
|
constructor Init;
|
|
destructor Done;
|
|
|
|
procedure Command(const s: string);
|
|
procedure Reset; virtual;
|
|
{ tracing }
|
|
procedure StartTrace;
|
|
procedure Run; virtual;
|
|
procedure TraceStep;
|
|
procedure TraceNext;
|
|
procedure TraceStepI;
|
|
procedure TraceNextI;
|
|
procedure Continue; virtual;
|
|
procedure UntilReturn; virtual;
|
|
{ registers }
|
|
function GetIntRegister(const RegName: string; var Value: UInt64): Boolean;
|
|
function GetIntRegister(const RegName: string; var Value: Int64): Boolean;
|
|
function GetIntRegister(const RegName: string; var Value: UInt32): Boolean;
|
|
function GetIntRegister(const RegName: string; var Value: Int32): Boolean;
|
|
function GetIntRegister(const RegName: string; var Value: UInt16): Boolean;
|
|
function GetIntRegister(const RegName: string; var Value: Int16): Boolean;
|
|
{ set command }
|
|
function SetCommand(Const SetExpr : string) : boolean;
|
|
{ print }
|
|
function PrintCommand(const expr : string): AnsiString;
|
|
function PrintFormattedCommand(const expr : string; Format : TPrintFormatType): AnsiString;
|
|
{ breakpoints }
|
|
function BreakpointInsert(const location: string; BreakpointFlags: TBreakpointFlags): LongInt;
|
|
function WatchpointInsert(const location: string; WatchpointType: TWatchpointType): LongInt;
|
|
function BreakpointDelete(BkptNo: LongInt): Boolean;
|
|
function BreakpointEnable(BkptNo: LongInt): Boolean;
|
|
function BreakpointDisable(BkptNo: LongInt): Boolean;
|
|
function BreakpointCondition(BkptNo: LongInt; const ConditionExpr: string): Boolean;
|
|
function BreakpointSetIgnoreCount(BkptNo: LongInt; const IgnoreCount: LongInt): Boolean;
|
|
procedure SetTBreak(tbreakstring : string);
|
|
{ frame commands }
|
|
procedure Backtrace;
|
|
function SelectFrameCommand(level :longint) : boolean;
|
|
function LoadFile(var fn: string): Boolean;
|
|
procedure SetDir(const s: string);
|
|
procedure SetArgs(const s: string);
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
{$ifdef Windows}
|
|
Windebug,
|
|
{$endif Windows}
|
|
strings;
|
|
|
|
procedure UnixDir(var s : string);
|
|
var i : longint;
|
|
begin
|
|
for i:=1 to length(s) do
|
|
if s[i]='\' then
|
|
{$ifdef windows}
|
|
{ Don't touch at '\ ' used to escapes spaces in windows file names PM }
|
|
if (i=length(s)) or (s[i+1]<>' ') then
|
|
{$endif windows}
|
|
s[i]:='/';
|
|
{$ifdef windows}
|
|
{ if we are using cygwin, we need to convert e:\ into /cygdriveprefix/e/ PM }
|
|
if using_cygwin_gdb and (length(s)>2) and (s[2]=':') and (s[3]='/') then
|
|
s:=CygDrivePrefix+'/'+s[1]+copy(s,3,length(s));
|
|
{$endif windows}
|
|
end;
|
|
|
|
constructor TGDBController.Init;
|
|
begin
|
|
inherited Init;
|
|
end;
|
|
|
|
destructor TGDBController.Done;
|
|
begin
|
|
inherited Done;
|
|
end;
|
|
|
|
procedure TGDBController.CommandBegin(const s: string);
|
|
begin
|
|
end;
|
|
|
|
procedure TGDBController.Command(const s: string);
|
|
begin
|
|
Inc(in_command);
|
|
CommandBegin(s);
|
|
GDBOutputBuf.Reset;
|
|
GDBErrorBuf.Reset;
|
|
{$ifdef GDB_RAW_OUTPUT}
|
|
GDBRawBuf.reset;
|
|
{$endif GDB_RAW_OUTPUT}
|
|
i_gdb_command(s);
|
|
CommandEnd(s);
|
|
Dec(in_command);
|
|
end;
|
|
|
|
procedure TGDBController.CommandEnd(const s: string);
|
|
begin
|
|
end;
|
|
|
|
procedure TGDBController.UpdateRegisterNames;
|
|
var
|
|
I: LongInt;
|
|
ResultList: TGDBMI_ListValue;
|
|
begin
|
|
SetLength(FRegisterNames, 0);
|
|
Command('-data-list-register-names');
|
|
if not GDB.ResultRecord.Success then
|
|
exit;
|
|
ResultList := GDB.ResultRecord.Parameters['register-names'].AsList;
|
|
SetLength(FRegisterNames, ResultList.Count);
|
|
for I := 0 to ResultList.Count - 1 do
|
|
FRegisterNames[I] := ResultList.ValueAt[I].AsString;
|
|
end;
|
|
|
|
function TGDBController.GetGdbRegisterNo(const RegName: string): LongInt;
|
|
var
|
|
I: LongInt;
|
|
begin
|
|
for I := Low(FRegisterNames) to High(FRegisterNames) do
|
|
if FRegisterNames[I] = RegName then
|
|
begin
|
|
GetGdbRegisterNo := I;
|
|
exit;
|
|
end;
|
|
GetGdbRegisterNo := -1;
|
|
end;
|
|
|
|
procedure TGDBController.Reset;
|
|
begin
|
|
end;
|
|
|
|
procedure TGDBController.StartTrace;
|
|
begin
|
|
Command('-break-insert -t PASCALMAIN');
|
|
if not GDB.ResultRecord.Success then
|
|
exit;
|
|
start_break_number := GDB.ResultRecord.Parameters['bkpt'].AsTuple['number'].AsLongInt;
|
|
Run;
|
|
end;
|
|
|
|
procedure TGDBController.RunExecCommand(const Cmd: string);
|
|
begin
|
|
UserScreen;
|
|
Command(Cmd);
|
|
if not GDB.ResultRecord.Success then
|
|
begin
|
|
DebuggerScreen;
|
|
got_error := True;
|
|
exit;
|
|
end;
|
|
WaitForProgramStop;
|
|
end;
|
|
|
|
procedure TGDBController.Run;
|
|
begin
|
|
RunExecCommand('-exec-run');
|
|
end;
|
|
|
|
procedure TGDBController.TraceStep;
|
|
begin
|
|
RunExecCommand('-exec-step');
|
|
end;
|
|
|
|
procedure TGDBController.TraceNext;
|
|
begin
|
|
RunExecCommand('-exec-next');
|
|
end;
|
|
|
|
procedure TGDBController.TraceStepI;
|
|
begin
|
|
RunExecCommand('-exec-step-instruction');
|
|
end;
|
|
|
|
procedure TGDBController.TraceNextI;
|
|
begin
|
|
RunExecCommand('-exec-next-instruction');
|
|
end;
|
|
|
|
procedure TGDBController.Continue;
|
|
begin
|
|
RunExecCommand('-exec-continue');
|
|
end;
|
|
|
|
procedure TGDBController.UntilReturn;
|
|
begin
|
|
RunExecCommand('-exec-finish');
|
|
end;
|
|
|
|
function TGDBController.GetRegisterAsString(const RegName, Format: string; var Value: string): Boolean;
|
|
var
|
|
RegNo: LongInt;
|
|
RegNoStr: string;
|
|
begin
|
|
GetRegisterAsString := False;
|
|
Value := '';
|
|
|
|
RegNo := GetGdbRegisterNo(RegName);
|
|
if RegNo = -1 then
|
|
exit;
|
|
Str(RegNo, RegNoStr);
|
|
Command('-data-list-register-values ' + Format + ' ' + RegNoStr);
|
|
if not GDB.ResultRecord.Success then
|
|
exit;
|
|
Value := GDB.ResultRecord.Parameters['register-values'].AsList.ValueAt[0].AsTuple['value'].AsString;
|
|
GetRegisterAsString := True;
|
|
end;
|
|
|
|
function TGDBController.GetIntRegister(const RegName: string; var Value: UInt64): Boolean;
|
|
var
|
|
RegValueStr: string;
|
|
Code: LongInt;
|
|
begin
|
|
GetIntRegister := False;
|
|
Value := 0;
|
|
if not GetRegisterAsString(RegName, 'x', RegValueStr) then
|
|
exit;
|
|
Val(RegValueStr, Value, Code);
|
|
if Code <> 0 then
|
|
exit;
|
|
GetIntRegister := True;
|
|
end;
|
|
|
|
function TGDBController.GetIntRegister(const RegName: string; var Value: Int64): Boolean;
|
|
var
|
|
U64Value: UInt64;
|
|
begin
|
|
GetIntRegister := GetIntRegister(RegName, U64Value);
|
|
Value := Int64(U64Value);
|
|
end;
|
|
|
|
function TGDBController.GetIntRegister(const RegName: string; var Value: UInt32): Boolean;
|
|
var
|
|
U64Value: UInt64;
|
|
begin
|
|
GetIntRegister := GetIntRegister(RegName, U64Value);
|
|
Value := UInt32(U64Value);
|
|
if (U64Value shr 32) <> 0 then
|
|
GetIntRegister := False;
|
|
end;
|
|
|
|
function TGDBController.GetIntRegister(const RegName: string; var Value: Int32): Boolean;
|
|
var
|
|
U32Value: UInt32;
|
|
begin
|
|
GetIntRegister := GetIntRegister(RegName, U32Value);
|
|
Value := Int32(U32Value);
|
|
end;
|
|
|
|
function TGDBController.GetIntRegister(const RegName: string; var Value: UInt16): Boolean;
|
|
var
|
|
U64Value: UInt64;
|
|
begin
|
|
GetIntRegister := GetIntRegister(RegName, U64Value);
|
|
Value := UInt16(U64Value);
|
|
if (U64Value shr 16) <> 0 then
|
|
GetIntRegister := False;
|
|
end;
|
|
|
|
function TGDBController.GetIntRegister(const RegName: string; var Value: Int16): Boolean;
|
|
var
|
|
U16Value: UInt16;
|
|
begin
|
|
GetIntRegister := GetIntRegister(RegName, U16Value);
|
|
Value := Int16(U16Value);
|
|
end;
|
|
|
|
|
|
{ set command }
|
|
function TGDBController.SetCommand(Const SetExpr : string) : boolean;
|
|
begin
|
|
SetCommand:=false;
|
|
Command('-gdb-set '+SetExpr);
|
|
if error then
|
|
exit;
|
|
SetCommand:=true;
|
|
end;
|
|
|
|
|
|
{ print }
|
|
function TGDBController.PrintCommand(const expr : string): AnsiString;
|
|
begin
|
|
Command('-data-evaluate-expression '+QuoteString(expr));
|
|
if GDB.ResultRecord.Success then
|
|
PrintCommand:=GDB.ResultRecord.Parameters['value'].AsString
|
|
else
|
|
PrintCommand:=AnsiString(GetError);
|
|
end;
|
|
|
|
const
|
|
PrintFormatName : Array[TPrintFormatType] of string[11] =
|
|
('binary', 'decimal', 'hexadecimal', 'octal', 'natural');
|
|
|
|
function TGDBController.PrintFormattedCommand(const expr : string; Format : TPrintFormatType): ansistring;
|
|
begin
|
|
Command('-var-evaluate-expression -f '+PrintFormatName[Format]+' '+QuoteString(expr));
|
|
if GDB.ResultRecord.Success then
|
|
PrintFormattedCommand:=GDB.ResultRecord.Parameters['value'].AsString
|
|
else
|
|
PrintFormattedCommand:=AnsiString(GetError);
|
|
end;
|
|
|
|
function TGDBController.BreakpointInsert(const location: string; BreakpointFlags: TBreakpointFlags): LongInt;
|
|
var
|
|
Options: string = '';
|
|
begin
|
|
if bfTemporary in BreakpointFlags then
|
|
Options := Options + '-t ';
|
|
if bfHardware in BreakpointFlags then
|
|
Options := Options + '-h ';
|
|
Command('-break-insert ' + Options + location);
|
|
if GDB.ResultRecord.Success then
|
|
BreakpointInsert := GDB.ResultRecord.Parameters['bkpt'].AsTuple['number'].AsLongInt
|
|
else
|
|
BreakpointInsert := 0;
|
|
end;
|
|
|
|
function TGDBController.WatchpointInsert(const location: string; WatchpointType: TWatchpointType): LongInt;
|
|
begin
|
|
case WatchpointType of
|
|
wtWrite:
|
|
Command('-break-watch ' + location);
|
|
wtReadWrite:
|
|
Command('-break-watch -a ' + location);
|
|
wtRead:
|
|
Command('-break-watch -r ' + location);
|
|
end;
|
|
if GDB.ResultRecord.Success then
|
|
case WatchpointType of
|
|
wtWrite:
|
|
WatchpointInsert := GDB.ResultRecord.Parameters['wpt'].AsTuple['number'].AsLongInt;
|
|
wtReadWrite:
|
|
WatchpointInsert := GDB.ResultRecord.Parameters['hw-awpt'].AsTuple['number'].AsLongInt;
|
|
wtRead:
|
|
WatchpointInsert := GDB.ResultRecord.Parameters['hw-rwpt'].AsTuple['number'].AsLongInt;
|
|
end
|
|
else
|
|
WatchpointInsert := 0;
|
|
end;
|
|
|
|
function TGDBController.BreakpointDelete(BkptNo: LongInt): Boolean;
|
|
var
|
|
BkptNoStr: string;
|
|
begin
|
|
Str(BkptNo, BkptNoStr);
|
|
Command('-break-delete ' + BkptNoStr);
|
|
BreakpointDelete := GDB.ResultRecord.Success;
|
|
end;
|
|
|
|
function TGDBController.BreakpointEnable(BkptNo: LongInt): Boolean;
|
|
var
|
|
BkptNoStr: string;
|
|
begin
|
|
Str(BkptNo, BkptNoStr);
|
|
Command('-break-enable ' + BkptNoStr);
|
|
BreakpointEnable := GDB.ResultRecord.Success;
|
|
end;
|
|
|
|
function TGDBController.BreakpointDisable(BkptNo: LongInt): Boolean;
|
|
var
|
|
BkptNoStr: string;
|
|
begin
|
|
Str(BkptNo, BkptNoStr);
|
|
Command('-break-disable ' + BkptNoStr);
|
|
BreakpointDisable := GDB.ResultRecord.Success;
|
|
end;
|
|
|
|
function TGDBController.BreakpointCondition(BkptNo: LongInt; const ConditionExpr: string): Boolean;
|
|
var
|
|
BkptNoStr: string;
|
|
begin
|
|
Str(BkptNo, BkptNoStr);
|
|
Command('-break-condition ' + BkptNoStr + ' ' + ConditionExpr);
|
|
BreakpointCondition := GDB.ResultRecord.Success;
|
|
end;
|
|
|
|
function TGDBController.BreakpointSetIgnoreCount(BkptNo: LongInt; const IgnoreCount: LongInt): Boolean;
|
|
var
|
|
BkptNoStr, IgnoreCountStr: string;
|
|
begin
|
|
Str(BkptNo, BkptNoStr);
|
|
Str(IgnoreCount, IgnoreCountStr);
|
|
Command('-break-after ' + BkptNoStr + ' ' + IgnoreCountStr);
|
|
BreakpointSetIgnoreCount := GDB.ResultRecord.Success;
|
|
end;
|
|
|
|
procedure TGDBController.SetTBreak(tbreakstring : string);
|
|
begin
|
|
Command('-break-insert -t ' + tbreakstring);
|
|
TBreakNumber := GDB.ResultRecord.Parameters['bkpt'].AsTuple['number'].AsLongInt;
|
|
end;
|
|
|
|
procedure TGDBController.Backtrace;
|
|
var
|
|
FrameList,FrameArgList,ArgList: TGDBMI_ListValue;
|
|
I,J,arg_count: LongInt;
|
|
s : ansistring;
|
|
begin
|
|
{ forget all old frames }
|
|
clear_frames;
|
|
|
|
Command('-stack-list-frames');
|
|
if not GDB.ResultRecord.Success then
|
|
exit;
|
|
|
|
FrameList := GDB.ResultRecord.Parameters['stack'].AsList;
|
|
frame_count := FrameList.Count;
|
|
frames := AllocMem(SizeOf(PFrameEntry) * frame_count);
|
|
for I := 0 to frame_count - 1 do
|
|
frames[I] := New(PFrameEntry, Init);
|
|
for I := 0 to FrameList.Count - 1 do
|
|
begin
|
|
frames[I]^.address := FrameList.ValueAt[I].AsTuple['addr'].AsCoreAddr;
|
|
frames[I]^.level := FrameList.ValueAt[I].AsTuple['level'].AsLongInt;
|
|
if Assigned(FrameList.ValueAt[I].AsTuple['line']) then
|
|
frames[I]^.line_number := FrameList.ValueAt[I].AsTuple['line'].AsLongInt;
|
|
if Assigned(FrameList.ValueAt[I].AsTuple['func']) then
|
|
frames[I]^.function_name := StrNew(PChar(FrameList.ValueAt[I].AsTuple['func'].AsString));
|
|
if Assigned(FrameList.ValueAt[I].AsTuple['fullname']) then
|
|
frames[I]^.file_name := StrNew(PChar(FrameList.ValueAt[I].AsTuple['fullname'].AsString));
|
|
end;
|
|
Command('-stack-list-arguments 1');
|
|
if not GDB.ResultRecord.Success then
|
|
exit;
|
|
|
|
FrameArgList := GDB.ResultRecord.Parameters['stack-args'].AsList;
|
|
arg_count:=FrameArgList.Count;
|
|
if arg_count>frame_count then
|
|
arg_count:=frame_count;
|
|
for I := 0 to arg_count - 1 do
|
|
begin
|
|
ArgList:=FrameArgList.ValueAt[I].AsTuple['args'].AsList;
|
|
s:='(';
|
|
for J:=0 to ArgList.Count-1 do
|
|
begin
|
|
if J>0 then s:=s+', ';
|
|
s:=s+ArgList.ValueAt[J].AsTuple['name'].AsString;
|
|
if Assigned(ArgList.ValueAt[J].AsTuple['value']) then
|
|
s:=s+':='+ArgList.ValueAt[J].AsTuple['value'].ASString;
|
|
end;
|
|
s:=s+')';
|
|
frames[I]^.args:=StrNew(pchar(s));
|
|
end;
|
|
end;
|
|
|
|
function TGDBController.SelectFrameCommand(level :longint) : boolean;
|
|
var
|
|
LevelStr : String;
|
|
begin
|
|
Str(Level, LevelStr);
|
|
Command('-stack-select-frame '+LevelStr);
|
|
SelectFrameCommand:=not error;
|
|
end;
|
|
|
|
function TGDBController.LoadFile(var fn: string): Boolean;
|
|
var
|
|
cmd: string;
|
|
begin
|
|
getdir(0,cmd);
|
|
UnixDir(cmd);
|
|
Command('-environment-cd ' + cmd);
|
|
GDBOutputBuf.Reset;
|
|
GDBErrorBuf.Reset;
|
|
{$ifdef GDB_RAW_OUTPUT}
|
|
GDBRawBuf.reset;
|
|
{$endif GDB_RAW_OUTPUT}
|
|
UnixDir(fn);
|
|
Command('-file-exec-and-symbols ' + fn);
|
|
if not GDB.ResultRecord.Success then
|
|
begin
|
|
LoadFile:=false;
|
|
exit;
|
|
end;
|
|
{ the register list may change *after* loading a file, because there }
|
|
{ are gdb versions that support multiple archs, e.g. i386 and x86_64 }
|
|
UpdateRegisterNames; { so that's why we update it here }
|
|
LoadFile := True;
|
|
end;
|
|
|
|
procedure TGDBController.SetDir(const s: string);
|
|
var
|
|
hs: string;
|
|
begin
|
|
hs:=s;
|
|
UnixDir(hs);
|
|
{ Avoid error message if s is empty }
|
|
if hs<>'' then
|
|
Command('-environment-cd ' + hs);
|
|
end;
|
|
|
|
procedure TGDBController.SetArgs(const s: string);
|
|
begin
|
|
Command('-exec-arguments ' + s);
|
|
end;
|
|
|
|
end.
|