mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-05 08:38:14 +02:00
663 lines
16 KiB
ObjectPascal
663 lines
16 KiB
ObjectPascal
{
|
|
Copyright (c) 2015 by Nikolay Nikolov
|
|
Copyright (c) 1998 by Peter Vreman
|
|
|
|
This is a replacement for GDBInt, 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 gdbmiint;
|
|
|
|
{$MODE fpc}{$H-}
|
|
|
|
{$I globdir.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
gdbmiwrap;
|
|
|
|
type
|
|
CORE_ADDR = gdbmiwrap.CORE_ADDR;
|
|
|
|
PPFrameEntry = ^PFrameEntry;
|
|
PFrameEntry = ^TFrameEntry;
|
|
TFrameEntry = object
|
|
private
|
|
procedure Reset;
|
|
procedure Clear;
|
|
public
|
|
file_name: PAnsiChar;
|
|
function_name: PAnsiChar;
|
|
args: PAnsiChar;
|
|
line_number: LongInt;
|
|
address: CORE_ADDR;
|
|
level : longint;
|
|
constructor Init;
|
|
destructor Done;
|
|
end;
|
|
|
|
TGDBBuffer = object
|
|
private
|
|
buf: PAnsiChar;
|
|
size, idx: LongInt;
|
|
procedure Resize(nsize: LongInt);
|
|
procedure Append(p: PAnsiChar);
|
|
procedure LAppend(p: PAnsiChar; len: LongInt);
|
|
public
|
|
constructor Init;
|
|
destructor Done;
|
|
procedure Reset;
|
|
end;
|
|
|
|
TGDBInterface = object
|
|
private
|
|
user_screen_shown: Boolean;
|
|
{$ifdef GDB_RAW_OUTPUT}
|
|
output_raw : boolean;
|
|
{$endif GDB_RAW_OUTPUT}
|
|
protected
|
|
GDB: TGDBWrapper;
|
|
|
|
procedure i_gdb_command(const S: string);
|
|
procedure WaitForProgramStop;
|
|
procedure ProcessResponse;
|
|
public
|
|
GDBErrorBuf: TGDBBuffer;
|
|
GDBOutputBuf: TGDBBuffer;
|
|
{$ifdef GDB_RAW_OUTPUT}
|
|
{ Separate Raw buffer to display everything inside GDB Window
|
|
but without parsing it twice }
|
|
GDBRawBuf: TGDBBuffer;
|
|
{$endif GDB_RAW_OUTPUT}
|
|
got_error: Boolean;
|
|
reset_command: Boolean;
|
|
Debuggee_started: Boolean;
|
|
init_count : longint;
|
|
{ frames and frame info while recording a frame }
|
|
frames: PPFrameEntry;
|
|
frame_count: LongInt;
|
|
command_level: LongInt;
|
|
signal_name: PAnsiChar;
|
|
signal_string: PAnsiChar;
|
|
current_pc: CORE_ADDR;
|
|
switch_to_user: Boolean;
|
|
|
|
{ init }
|
|
constructor Init;
|
|
destructor Done;
|
|
{ from gdbcon }
|
|
function GetOutput: PAnsiChar;
|
|
function GetError: PAnsiChar;
|
|
{$ifdef DEBUG}
|
|
function GetRaw: PAnsiChar;
|
|
{$endif DEBUG}
|
|
{ Lowlevel }
|
|
procedure Set_debuggee_started;
|
|
function error: Boolean;
|
|
function error_num: LongInt;
|
|
function get_current_frame: PtrInt;
|
|
function set_current_frame(level: LongInt): Boolean;
|
|
procedure clear_frames;
|
|
{ Highlevel }
|
|
procedure DebuggerScreen;
|
|
procedure UserScreen;
|
|
procedure FlushAll; virtual;
|
|
function Query(question: PAnsiChar; args: PAnsiChar): LongInt; virtual;
|
|
{ Hooks }
|
|
function DoSelectSourceline(const fn: string; line, BreakIndex: longint): Boolean;virtual;
|
|
procedure DoStartSession; virtual;
|
|
procedure DoBreakSession; virtual;
|
|
procedure DoEndSession(code: LongInt); virtual;
|
|
procedure DoUserSignal; virtual;
|
|
procedure DoDebuggerScreen; virtual;
|
|
procedure DoUserScreen; virtual;
|
|
function AllowQuit: Boolean; virtual;
|
|
end;
|
|
|
|
const
|
|
use_gdb_file: Boolean = False;
|
|
|
|
var
|
|
gdb_file: Text;
|
|
|
|
function GDBVersion: string;
|
|
function GDBVersionOK: boolean;
|
|
function inferior_pid : longint;
|
|
|
|
{$ifdef windows}
|
|
{ We need to do some path conversions if we are using Cygwin GDB }
|
|
var
|
|
using_cygwin_gdb : boolean;
|
|
{$endif windows}
|
|
implementation
|
|
|
|
uses
|
|
strings;
|
|
|
|
constructor TFrameEntry.Init;
|
|
begin
|
|
Reset;
|
|
end;
|
|
|
|
destructor TFrameEntry.Done;
|
|
begin
|
|
Clear;
|
|
end;
|
|
|
|
procedure TFrameEntry.Reset;
|
|
begin
|
|
file_name := nil;
|
|
function_name := nil;
|
|
args := nil;
|
|
line_number := 0;
|
|
address := 0;
|
|
level := 0;
|
|
end;
|
|
|
|
procedure TFrameEntry.Clear;
|
|
begin
|
|
if Assigned(file_name) then
|
|
StrDispose(file_name);
|
|
if Assigned(function_name) then
|
|
StrDispose(function_name);
|
|
if Assigned(args) then
|
|
StrDispose(args);
|
|
Reset;
|
|
end;
|
|
|
|
const
|
|
BlockSize = 2048;
|
|
|
|
constructor TGDBBuffer.Init;
|
|
begin
|
|
buf := nil;
|
|
size := 0;
|
|
Resize(BlockSize);
|
|
Reset;
|
|
end;
|
|
|
|
destructor TGDBBuffer.Done;
|
|
begin
|
|
if Assigned(buf) then
|
|
FreeMem(buf, size);
|
|
end;
|
|
|
|
procedure TGDBBuffer.Reset;
|
|
begin
|
|
idx := 0;
|
|
buf[0] := #0;
|
|
end;
|
|
|
|
procedure TGDBBuffer.Resize(nsize: LongInt);
|
|
var
|
|
np: PAnsiChar;
|
|
begin
|
|
nsize := ((nsize + BlockSize - 1) div BlockSize) * BlockSize;
|
|
GetMem(np, nsize);
|
|
if Assigned(buf) then
|
|
begin
|
|
Move(buf^, np^, size);
|
|
FreeMem(buf, size);
|
|
end;
|
|
buf := np;
|
|
size := nsize;
|
|
end;
|
|
|
|
procedure TGDBBuffer.Append(p: PAnsiChar);
|
|
var
|
|
len: LongInt;
|
|
begin
|
|
if not Assigned(p) then
|
|
exit;
|
|
len := StrLen(p);
|
|
LAppend(p, len);
|
|
end;
|
|
|
|
procedure TGDBBuffer.LAppend(p: PAnsiChar; len: LongInt);
|
|
begin
|
|
if not Assigned(p) then
|
|
exit;
|
|
if (len + idx + 1) > size then
|
|
Resize(len + idx + 1);
|
|
Move(p^, buf[idx], len);
|
|
Inc(idx, len);
|
|
buf[idx] := #0;
|
|
end;
|
|
|
|
constructor TGDBInterface.Init;
|
|
begin
|
|
GDBErrorBuf.Init;
|
|
GDBOutputBuf.Init;
|
|
GDB := TGDBWrapper.Create;
|
|
command_level := 0;
|
|
Debuggee_started:=false;
|
|
init_count:=0;
|
|
{$ifdef GDB_RAW_OUTPUT}
|
|
output_raw:=true;
|
|
GDBRawBuf.Init;
|
|
{$endif GDB_RAW_OUTPUT}
|
|
{ other standard commands used for fpc debugging }
|
|
i_gdb_command('-gdb-set print demangle off');
|
|
i_gdb_command('-gdb-set gnutarget auto');
|
|
i_gdb_command('-gdb-set language auto');
|
|
i_gdb_command('-gdb-set print vtbl on');
|
|
i_gdb_command('-gdb-set print object on');
|
|
i_gdb_command('-gdb-set print null-stop');
|
|
end;
|
|
|
|
destructor TGDBInterface.Done;
|
|
begin
|
|
clear_frames;
|
|
GDB.Free;
|
|
GDBErrorBuf.Done;
|
|
GDBOutputBuf.Done;
|
|
{$ifdef GDB_RAW_OUTPUT}
|
|
GDBRawBuf.Done;
|
|
{$endif GDB_RAW_OUTPUT}
|
|
end;
|
|
|
|
function TGDBInterface.GetOutput: PAnsiChar;
|
|
begin
|
|
GetOutput := GDBOutputBuf.buf;
|
|
end;
|
|
|
|
{$ifdef GDB_RAW_OUTPUT}
|
|
function TGDBInterface.GetRaw: PAnsiChar;
|
|
begin
|
|
GetRaw := GDBRawBuf.buf;
|
|
end;
|
|
{$endif GDB_RAW_OUTPUT}
|
|
|
|
function TGDBInterface.GetError: PAnsiChar;
|
|
var
|
|
p: PAnsiChar;
|
|
begin
|
|
p := GDBErrorBuf.buf;
|
|
if (p^=#0) and got_error then
|
|
GetError := PAnsiChar(PtrInt(GDBOutputBuf.buf) + GDBOutputBuf.idx)
|
|
else
|
|
GetError := p;
|
|
end;
|
|
|
|
procedure TGDBInterface.Set_debuggee_started;
|
|
begin
|
|
if not Debuggee_started then
|
|
begin
|
|
inc(init_count);
|
|
Debuggee_started:=true;
|
|
end;
|
|
end;
|
|
|
|
procedure TGDBInterface.i_gdb_command(const S: string);
|
|
var
|
|
I: LongInt;
|
|
begin
|
|
Inc(command_level);
|
|
got_error := False;
|
|
GDB.Command(S);
|
|
{$ifdef GDB_RAW_OUTPUT}
|
|
if output_raw then
|
|
for I := 0 to GDB.RawResponse.Count - 1 do
|
|
GDBRawBuf.Append(PAnsiChar(GDB.RawResponse[I]));
|
|
{$endif GDB_RAW_OUTPUT}
|
|
|
|
for I := 0 to GDB.ConsoleStream.Count - 1 do
|
|
GDBOutputBuf.Append(PAnsiChar(GDB.ConsoleStream[I]));
|
|
if GDB.ResultRecord.AsyncClass='error' then
|
|
begin
|
|
got_error := True;
|
|
if Assigned(GDB.ResultRecord.Parameters['msg']) then
|
|
GDBErrorBuf.Append(PAnsiChar(GDB.ResultRecord.Parameters['msg'].AsString));
|
|
end;
|
|
ProcessResponse;
|
|
Dec(command_level);
|
|
end;
|
|
|
|
procedure TGDBInterface.WaitForProgramStop;
|
|
label
|
|
Ignore;
|
|
var
|
|
StopReason: string;
|
|
LocalSignalString,LocalSignalName: String;
|
|
FileName: string = '';
|
|
LineNumber: LongInt = 0;
|
|
Addr: CORE_ADDR;
|
|
BreakpointNo: LongInt;
|
|
ExitCode: LongInt;
|
|
begin
|
|
Ignore:
|
|
GDB.WaitForProgramStop;
|
|
if not GDB.Alive then
|
|
begin
|
|
DebuggerScreen;
|
|
current_pc := 0;
|
|
Debuggee_started := False;
|
|
exit;
|
|
end;
|
|
ProcessResponse;
|
|
StopReason := GDB.ExecAsyncOutput.Parameters['reason'].AsString;
|
|
case StopReason of
|
|
'watchpoint-scope':
|
|
begin
|
|
{ A watchpoint has gone out of scope (e.g. if it was a local variable). TODO: should we stop
|
|
the program and notify the user or maybe silently disable it in the breakpoint list and
|
|
continue execution? The libgdb.a version of the debugger just silently ignores this case.
|
|
|
|
We have: GDB.ExecAsyncOutput.Parameters['wpnum'].AsLongInt }
|
|
i_gdb_command('-exec-continue');
|
|
if not GDB.ResultRecord.Success then
|
|
begin
|
|
DebuggerScreen;
|
|
got_error := True;
|
|
exit;
|
|
end;
|
|
goto Ignore;
|
|
end;
|
|
'signal-received':
|
|
begin
|
|
{ TODO: maybe show information to the user about the signal
|
|
we have:
|
|
GDB.ExecAsyncOutput.Parameters['signal-name'].AsString (e.g. 'SIGTERM')
|
|
GDB.ExecAsyncOutput.PArameters['signal-meaning'].AsString (e.g. 'Terminated')
|
|
}
|
|
LocalSignalName:=GDB.ExecAsyncOutput.Parameters['signal-name'].AsString;
|
|
LocalSignalString:=GDB.ExecAsyncOutput.PArameters['signal-meaning'].AsString;
|
|
signal_name:=@LocalSignalName;
|
|
signal_string:=@LocalSignalString;
|
|
if (user_screen_shown) then
|
|
begin
|
|
DebuggerScreen;
|
|
DoUserSignal;
|
|
UserScreen;
|
|
end
|
|
else
|
|
DoUserSignal;
|
|
i_gdb_command('-exec-continue');
|
|
if not GDB.ResultRecord.Success then
|
|
begin
|
|
DebuggerScreen;
|
|
got_error := True;
|
|
exit;
|
|
end;
|
|
goto Ignore;
|
|
end;
|
|
'breakpoint-hit',
|
|
'watchpoint-trigger',
|
|
'access-watchpoint-trigger',
|
|
'read-watchpoint-trigger',
|
|
'end-stepping-range',
|
|
'function-finished':
|
|
begin
|
|
if StopReason = 'breakpoint-hit' then
|
|
BreakpointNo := GDB.ExecAsyncOutput.Parameters['bkptno'].AsLongInt
|
|
else if StopReason = 'watchpoint-trigger' then
|
|
BreakpointNo := GDB.ExecAsyncOutput.Parameters['wpt'].AsTuple['number'].AsLongInt
|
|
else if StopReason = 'access-watchpoint-trigger' then
|
|
BreakpointNo := GDB.ExecAsyncOutput.Parameters['hw-awpt'].AsTuple['number'].AsLongInt
|
|
else if StopReason = 'read-watchpoint-trigger' then
|
|
BreakpointNo := GDB.ExecAsyncOutput.Parameters['hw-rwpt'].AsTuple['number'].AsLongInt
|
|
else
|
|
BreakpointNo := 0;
|
|
|
|
if Assigned(GDB.ExecAsyncOutput.Parameters['frame']) then
|
|
begin
|
|
if Assigned(GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['addr']) then
|
|
Addr := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['addr'].AsCoreAddr
|
|
else
|
|
Addr := 0;
|
|
if Assigned(GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['fullname']) then
|
|
FileName := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['fullname'].AsString;
|
|
if Assigned(GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['line']) then
|
|
LineNumber := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['line'].AsLongInt;
|
|
end
|
|
else
|
|
begin
|
|
if Assigned(GDB.ExecAsyncOutput.Parameters['fullname']) then
|
|
FileName := GDB.ExecAsyncOutput.Parameters['fullname'].AsString;
|
|
if Assigned(GDB.ExecAsyncOutput.Parameters['line']) then
|
|
LineNumber := GDB.ExecAsyncOutput.Parameters['line'].AsLongInt;
|
|
end;
|
|
{ this kills GDB.ExecAsyncOutput, because it may execute other gdb commands, so
|
|
make sure we have read all parameters that we need to local variables before that }
|
|
DebuggerScreen;
|
|
|
|
set_debuggee_started;
|
|
current_pc := Addr;
|
|
if not DoSelectSourceLine(FileName, LineNumber, BreakpointNo) then
|
|
begin
|
|
UserScreen;
|
|
i_gdb_command('-exec-continue');
|
|
if not GDB.ResultRecord.Success then
|
|
begin
|
|
DebuggerScreen;
|
|
got_error := True;
|
|
exit;
|
|
end;
|
|
goto Ignore;
|
|
end;
|
|
end;
|
|
'exited-signalled':
|
|
begin
|
|
DebuggerScreen;
|
|
current_pc := 0;
|
|
Debuggee_started := False;
|
|
{ TODO: maybe show information to the user about the signal
|
|
we have:
|
|
GDB.ExecAsyncOutput.Parameters['signal-name'].AsString (e.g. 'SIGTERM')
|
|
GDB.ExecAsyncOutput.PArameters['signal-meaning'].AsString (e.g. 'Terminated')
|
|
}
|
|
DoEndSession(1);
|
|
end;
|
|
'exited':
|
|
begin
|
|
ExitCode := LongInt(GDB.ExecAsyncOutput.Parameters['exit-code'].AsLongWord);
|
|
DebuggerScreen;
|
|
current_pc := 0;
|
|
Debuggee_started := False;
|
|
DoEndSession(ExitCode);
|
|
end;
|
|
'exited-normally':
|
|
begin
|
|
DebuggerScreen;
|
|
current_pc := 0;
|
|
Debuggee_started := False;
|
|
DoEndSession(0);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TGDBInterface.ProcessResponse;
|
|
//var
|
|
// NAO: TGDBMI_AsyncOutput;
|
|
// Code: LongInt;
|
|
begin
|
|
// for NAO in GDB.NotifyAsyncOutput do
|
|
// begin
|
|
// if NAO.AsyncClass = 'breakpoint-created' then
|
|
// begin
|
|
// Writeln('BREAKPOINT created!');
|
|
// Val(NAO.Parameters['bkpt'].AsTuple['number'].AsString, last_breakpoint_number, Code);
|
|
// Writeln('last_breakpoint_number=', last_breakpoint_number);
|
|
// end;
|
|
// end;
|
|
end;
|
|
|
|
function TGDBInterface.error: Boolean;
|
|
begin
|
|
error := got_error or not GDB.Alive;
|
|
end;
|
|
|
|
function TGDBInterface.error_num: LongInt;
|
|
begin
|
|
error_num := 0; { TODO }
|
|
end;
|
|
|
|
function TGDBInterface.get_current_frame: PtrInt;
|
|
begin
|
|
i_gdb_command('-stack-info-frame');
|
|
if GDB.ResultRecord.Success then
|
|
get_current_frame := GDB.ResultRecord.Parameters['frame'].AsTuple['level'].AsLongInt
|
|
else
|
|
get_current_frame := 0;
|
|
end;
|
|
|
|
function TGDBInterface.set_current_frame(level: LongInt): Boolean;
|
|
var
|
|
s: string;
|
|
begin
|
|
str(level,s);
|
|
{ Note: according to the gdb docs, '-stack-select-frame' is deprecated in favor of passing the '--frame' option to every command }
|
|
i_gdb_command('-stack-select-frame '+s);
|
|
set_current_frame := GDB.ResultRecord.Success;
|
|
end;
|
|
|
|
procedure TGDBInterface.clear_frames;
|
|
var
|
|
I: LongInt;
|
|
begin
|
|
for I := 0 to frame_count - 1 do
|
|
Dispose(frames[I], Done);
|
|
if Assigned(frames) then
|
|
begin
|
|
FreeMem(frames, SizeOf(Pointer) * frame_count);
|
|
frames := nil;
|
|
end;
|
|
frame_count := 0;
|
|
end;
|
|
|
|
procedure TGDBInterface.DebuggerScreen;
|
|
begin
|
|
if user_screen_shown then
|
|
DoDebuggerScreen;
|
|
user_screen_shown := False;
|
|
end;
|
|
|
|
procedure TGDBInterface.UserScreen;
|
|
begin
|
|
if switch_to_user then
|
|
begin
|
|
if not user_screen_shown then
|
|
DoUserScreen;
|
|
user_screen_shown := True;
|
|
end;
|
|
end;
|
|
|
|
procedure TGDBInterface.FlushAll;
|
|
begin
|
|
end;
|
|
|
|
function TGDBInterface.Query(question: PAnsiChar; args: PAnsiChar): LongInt;
|
|
begin
|
|
Query := 0;
|
|
end;
|
|
|
|
function TGDBInterface.DoSelectSourceline(const fn: string; line, BreakIndex: LongInt): Boolean;
|
|
begin
|
|
end;
|
|
|
|
procedure TGDBInterface.DoStartSession;
|
|
begin
|
|
end;
|
|
|
|
procedure TGDBInterface.DoBreakSession;
|
|
begin
|
|
end;
|
|
|
|
procedure TGDBInterface.DoEndSession(code: LongInt);
|
|
begin
|
|
end;
|
|
|
|
procedure TGDBInterface.DoUserSignal;
|
|
begin
|
|
end;
|
|
|
|
procedure TGDBInterface.DoDebuggerScreen;
|
|
begin
|
|
end;
|
|
|
|
procedure TGDBInterface.DoUserScreen;
|
|
begin
|
|
end;
|
|
|
|
function TGDBInterface.AllowQuit: Boolean;
|
|
begin
|
|
AllowQuit := True;
|
|
end;
|
|
|
|
function inferior_pid : longint;
|
|
begin
|
|
inferior_pid:=0; {inferior_ptid.pid; }
|
|
end;
|
|
|
|
|
|
var
|
|
CachedGDBVersion: string;
|
|
CachedGDBVersionOK : boolean;
|
|
|
|
function GDBVersion: string;
|
|
var
|
|
GDB: TGDBWrapper;
|
|
{$ifdef windows}
|
|
i : longint;
|
|
line :string;
|
|
{$endif windows}
|
|
begin
|
|
if CachedGDBVersion <> '' then
|
|
begin
|
|
GDBVersion := CachedGDBVersion;
|
|
exit;
|
|
end;
|
|
GDBVersion := '';
|
|
GDB := TGDBWrapper.Create;
|
|
GDB.Command('-gdb-version');
|
|
if GDB.ConsoleStream.Count > 0 then
|
|
GDBVersion := GDB.ConsoleStream[0];
|
|
if (GDBVersion <> '') and (GDBVersion[Length(GDBVersion)]=#10) then
|
|
Delete(GDBVersion, Length(GDBVersion), 1);
|
|
{$ifdef windows}
|
|
i:=0;
|
|
using_cygwin_gdb:=false;
|
|
while i < GDB.ConsoleStream.Count do
|
|
begin
|
|
line:=GDB.ConsoleStream[i];
|
|
if pos('This GDB was configured',line) > 0 then
|
|
using_cygwin_gdb:=pos('cygwin',line) > 0;
|
|
inc(i);
|
|
end;
|
|
{$endif windows}
|
|
GDB.Free;
|
|
CachedGDBVersion := GDBVersion;
|
|
if GDBVersion = '' then
|
|
begin
|
|
GDBVersion := 'GDB missing or does not work'#13
|
|
+#3'Consider using -G command line option'#13
|
|
+#3'or set FPIDE_GDBPROC environment variable'#13
|
|
+#3'to specify full path to GDB';
|
|
CachedGDBVersionOK := false;
|
|
end;
|
|
end;
|
|
|
|
function GDBVersionOK: boolean;
|
|
var
|
|
S : string;
|
|
begin
|
|
{ Be sure GDBVersion is called }
|
|
S:=GDBVersion;
|
|
GDBVersionOK := CachedGDBVersionOK;
|
|
end;
|
|
|
|
begin
|
|
CachedGDBVersion := '';
|
|
CachedGDBVersionOK := true;
|
|
end.
|