mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-09 14:08:05 +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 -
560 lines
14 KiB
ObjectPascal
560 lines
14 KiB
ObjectPascal
{
|
|
Copyright (c) 2015 by Nikolay Nikolov
|
|
|
|
This unit provides a wrapper around GDB and implements parsing of
|
|
the GDB/MI command result records.
|
|
|
|
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 gdbmiwrap;
|
|
|
|
{$MODE objfpc}{$H+}
|
|
{$ASSERTIONS on}
|
|
|
|
{$I globdir.inc}
|
|
|
|
interface
|
|
|
|
uses
|
|
SysUtils, Classes, GDBMIProc;
|
|
|
|
type
|
|
{$ifdef TARGET_IS_64BIT}
|
|
{ force 64bit if target compilation CPU is 64-bit address CPU }
|
|
CORE_ADDR = Qword;
|
|
{$else}
|
|
CORE_ADDR = PtrUInt;
|
|
{$endif}
|
|
|
|
TGDBMI_TupleValue = class;
|
|
TGDBMI_ListValue = class;
|
|
TGDBMI_Value = class
|
|
function AsString: string;
|
|
function AsInt64: Int64;
|
|
function AsQWord: QWord;
|
|
function AsLongInt: LongInt;
|
|
function AsLongWord: LongWord;
|
|
function AsCoreAddr: CORE_ADDR;
|
|
function AsTuple: TGDBMI_TupleValue;
|
|
function AsList: TGDBMI_ListValue;
|
|
end;
|
|
|
|
{ "C string\n" }
|
|
TGDBMI_StringValue = class(TGDBMI_Value)
|
|
FStringValue: string;
|
|
public
|
|
constructor Create(const S: string);
|
|
property StringValue: string read FStringValue;
|
|
end;
|
|
|
|
(* {...} or [...] *)
|
|
TGDBMI_TupleOrListValue = class(TGDBMI_Value)
|
|
private
|
|
FNames: array of string;
|
|
FValues: array of TGDBMI_Value;
|
|
function GetValue(const AName: string): TGDBMI_Value;
|
|
public
|
|
destructor Destroy; override;
|
|
procedure Clear;
|
|
procedure Add(AName: string; AValue: TGDBMI_Value);
|
|
function HasNames: Boolean;
|
|
function IsEmpty: Boolean;
|
|
property Values [const AName: string]: TGDBMI_Value read GetValue; default;
|
|
end;
|
|
|
|
(* {} or {variable=value,variable=value,variable=value} *)
|
|
TGDBMI_TupleValue = class(TGDBMI_TupleOrListValue)
|
|
end;
|
|
|
|
{ [] or [value,value,value] or [variable=value,variable=value,variable=value] }
|
|
TGDBMI_ListValue = class(TGDBMI_TupleOrListValue)
|
|
private
|
|
function GetCount: LongInt;
|
|
function GetValueAt(AIndex: LongInt): TGDBMI_Value;
|
|
public
|
|
property Count: LongInt read GetCount;
|
|
property ValueAt [AIndex: LongInt]: TGDBMI_Value read GetValueAt;
|
|
end;
|
|
|
|
TGDBMI_AsyncOutput = class
|
|
FAsyncClass: string;
|
|
FParameters: TGDBMI_TupleValue;
|
|
public
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Clear;
|
|
property AsyncClass: string read FAsyncClass write FAsyncClass;
|
|
property Parameters: TGDBMI_TupleValue read FParameters;
|
|
end;
|
|
|
|
TGDBMI_ResultRecord = class(TGDBMI_AsyncOutput)
|
|
public
|
|
function Success: Boolean;
|
|
end;
|
|
|
|
TGDBMI_AsyncOutput_List = array of TGDBMI_AsyncOutput;
|
|
|
|
TGDBWrapper = class
|
|
private
|
|
FProcess: TGDBProcess;
|
|
FRawResponse: TStringList;
|
|
FConsoleStream: TStringList;
|
|
FExecAsyncOutput: TGDBMI_AsyncOutput;
|
|
FResultRecord: TGDBMI_ResultRecord;
|
|
|
|
function IsAlive: Boolean;
|
|
procedure ReadResponse;
|
|
public
|
|
NotifyAsyncOutput: TGDBMI_AsyncOutput_List;
|
|
|
|
constructor Create;
|
|
destructor Destroy; override;
|
|
procedure Command(S: string);
|
|
procedure WaitForProgramStop;
|
|
property RawResponse: TStringList read FRawResponse;
|
|
property ConsoleStream: TStringList read FConsoleStream;
|
|
property ExecAsyncOutput: TGDBMI_AsyncOutput read FExecAsyncOutput;
|
|
property ResultRecord: TGDBMI_ResultRecord read FResultRecord write FResultRecord;
|
|
property Alive: Boolean read IsAlive;
|
|
end;
|
|
|
|
function QuoteString(S: string): string;
|
|
function C2PascalNumberPrefix(const S: string): string;
|
|
|
|
implementation
|
|
|
|
function QuoteString(S: string): string;
|
|
var
|
|
I: LongInt;
|
|
begin
|
|
I := 1;
|
|
Result := '';
|
|
while I <= Length(S) do
|
|
begin
|
|
case S[I] of
|
|
'''': Result := Result + '\''';
|
|
'"': Result := Result + '\"';
|
|
#10: Result := Result + '\n';
|
|
#13: Result := Result + '\r';
|
|
#9: Result := Result + '\t';
|
|
#11: Result := Result + '\v';
|
|
#8: Result := Result + '\b';
|
|
#12: Result := Result + '\f';
|
|
#7: Result := Result + '\a';
|
|
'\': Result := Result + '\\';
|
|
'?': Result := Result + '\?';
|
|
else
|
|
Result := Result + S[I];
|
|
end;
|
|
Inc(I);
|
|
end;
|
|
Result := '"' + Result + '"';
|
|
end;
|
|
|
|
function C2PascalNumberPrefix(const S: string): string;
|
|
begin
|
|
{ hex: 0x -> $ }
|
|
if (Length(S) >= 3) and (s[1] = '0') and ((s[2] = 'x') or (s[2] = 'X')) then
|
|
exit('$' + Copy(S, 3, Length(S) - 2));
|
|
|
|
{ oct: 0 -> & }
|
|
if (Length(S) >= 2) and (s[1] = '0') and ((s[2] >= '0') and (s[2] <= '7')) then
|
|
exit('&' + Copy(S, 2, Length(S) - 1));
|
|
|
|
Result := S;
|
|
end;
|
|
|
|
function TGDBMI_Value.AsString: string;
|
|
begin
|
|
Result := (self as TGDBMI_StringValue).StringValue;
|
|
end;
|
|
|
|
function TGDBMI_Value.AsInt64: Int64;
|
|
begin
|
|
Result := StrToInt64(C2PascalNumberPrefix(AsString));
|
|
end;
|
|
|
|
function TGDBMI_Value.AsQWord: QWord;
|
|
begin
|
|
Result := StrToQWord(C2PascalNumberPrefix(AsString));
|
|
end;
|
|
|
|
function TGDBMI_Value.AsLongInt: LongInt;
|
|
begin
|
|
Result := StrToInt(C2PascalNumberPrefix(AsString));
|
|
end;
|
|
|
|
function TGDBMI_Value.AsLongWord: LongWord;
|
|
const
|
|
SInvalidInteger = '"%s" is an invalid integer';
|
|
var
|
|
S: string;
|
|
Error: LongInt;
|
|
begin
|
|
S := C2PascalNumberPrefix(AsString);
|
|
Val(S, Result, Error);
|
|
if Error <> 0 then
|
|
raise EConvertError.CreateFmt(SInvalidInteger,[S]);
|
|
end;
|
|
|
|
function TGDBMI_Value.AsCoreAddr: CORE_ADDR;
|
|
begin
|
|
{$if defined(TARGET_IS_64BIT)}
|
|
Result := AsQWord;
|
|
{$elseif defined(CPU64)}
|
|
Result := AsQWord;
|
|
{$else}
|
|
Result := AsLongWord;
|
|
{$endif}
|
|
end;
|
|
|
|
function TGDBMI_Value.AsTuple: TGDBMI_TupleValue;
|
|
begin
|
|
Result := self as TGDBMI_TupleValue;
|
|
end;
|
|
|
|
function TGDBMI_Value.AsList: TGDBMI_ListValue;
|
|
begin
|
|
Result := self as TGDBMI_ListValue;
|
|
end;
|
|
|
|
constructor TGDBMI_StringValue.Create(const S: string);
|
|
begin
|
|
FStringValue := S;
|
|
end;
|
|
|
|
destructor TGDBMI_TupleOrListValue.Destroy;
|
|
begin
|
|
Clear;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TGDBMI_TupleOrListValue.Clear;
|
|
var
|
|
I: LongInt;
|
|
begin
|
|
SetLength(FNames, 0);
|
|
for I := Low(FValues) to High(FValues) do
|
|
FreeAndNil(FValues[I]);
|
|
SetLength(FValues, 0);
|
|
end;
|
|
|
|
procedure TGDBMI_TupleOrListValue.Add(AName: string; AValue: TGDBMI_Value);
|
|
begin
|
|
Assert(AValue <> nil);
|
|
Assert(IsEmpty or (HasNames = (AName <> '')));
|
|
if AName <> '' then
|
|
begin
|
|
SetLength(FNames, Length(FNames) + 1);
|
|
FNames[Length(FNames) - 1] := AName;
|
|
end;
|
|
SetLength(FValues, Length(FValues) + 1);
|
|
FValues[Length(FValues) - 1] := AValue;
|
|
end;
|
|
|
|
function TGDBMI_TupleOrListValue.HasNames: Boolean;
|
|
begin
|
|
Result := Length(FNames) > 0;
|
|
end;
|
|
|
|
function TGDBMI_TupleOrListValue.IsEmpty: Boolean;
|
|
begin
|
|
Result := Length(FValues) = 0;
|
|
end;
|
|
|
|
function TGDBMI_TupleOrListValue.GetValue(const AName: string): TGDBMI_Value;
|
|
var
|
|
I: LongInt;
|
|
begin
|
|
for I := Low(FNames) to High(FNames) do
|
|
if FNames[I] = AName then
|
|
begin
|
|
Result := FValues[I];
|
|
exit;
|
|
end;
|
|
Result := nil;
|
|
end;
|
|
|
|
function TGDBMI_ListValue.GetCount: LongInt;
|
|
begin
|
|
Result := Length(FValues);
|
|
end;
|
|
|
|
function TGDBMI_ListValue.GetValueAt(AIndex: LongInt): TGDBMI_Value;
|
|
begin
|
|
Assert((AIndex >= Low(FValues)) and (AIndex <= High(FValues)));
|
|
Result := FValues[AIndex];
|
|
end;
|
|
|
|
constructor TGDBMI_AsyncOutput.Create;
|
|
begin
|
|
FParameters := TGDBMI_TupleValue.Create;
|
|
end;
|
|
|
|
destructor TGDBMI_AsyncOutput.Destroy;
|
|
begin
|
|
FParameters.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TGDBMI_AsyncOutput.Clear;
|
|
begin
|
|
AsyncClass := '';
|
|
Parameters.Clear;
|
|
end;
|
|
|
|
function TGDBMI_ResultRecord.Success: Boolean;
|
|
begin
|
|
{ according to the GDB docs, 'done' and 'running' should be treated identically by clients }
|
|
Result := (AsyncClass='done') or (AsyncClass='running');
|
|
end;
|
|
|
|
function ParseCString(const CStr: string; var NextCharPos: LongInt): string;
|
|
begin
|
|
if (NextCharPos <= Length(CStr)) and (CStr[NextCharPos] = '"') then
|
|
Inc(NextCharPos);
|
|
Result := '';
|
|
while NextCharPos <= Length(CStr) do
|
|
begin
|
|
if CStr[NextCharPos] = '"' then
|
|
begin
|
|
Inc(NextCharPos);
|
|
exit;
|
|
end
|
|
else if CStr[NextCharPos] = '\' then
|
|
begin
|
|
Inc(NextCharPos);
|
|
if NextCharPos <= Length(CStr) then
|
|
case CStr[NextCharPos] of
|
|
'''': Result := Result + '''';
|
|
'"': Result := Result + '"';
|
|
'n': Result := Result + #10;
|
|
'r': Result := Result + #13;
|
|
't': Result := Result + #9;
|
|
'v': Result := Result + #11;
|
|
'b': Result := Result + #8;
|
|
'f': Result := Result + #12;
|
|
'a': Result := Result + #7;
|
|
'\': Result := Result + '\';
|
|
'?': Result := Result + '?';
|
|
{\0, \000, \xhhh}
|
|
end;
|
|
end
|
|
else
|
|
Result := Result + CStr[NextCharPos];
|
|
Inc(NextCharPos);
|
|
end;
|
|
end;
|
|
|
|
function ParseIdentifier(const S: string; var NextCharPos: LongInt): string;
|
|
begin
|
|
Result := '';
|
|
while (NextCharPos <= Length(S)) and (S[NextCharPos] in ['A'..'Z', 'a'..'z', '0'..'9', '-']) do
|
|
begin
|
|
Result := Result + S[NextCharPos];
|
|
Inc(NextCharPos);
|
|
end;
|
|
end;
|
|
|
|
function ParseValue(const S: string; var NextCharPos: LongInt): TGDBMI_Value;
|
|
var
|
|
CStr: string;
|
|
Tuple: TGDBMI_TupleValue;
|
|
List: TGDBMI_ListValue;
|
|
|
|
Name: string;
|
|
Value: TGDBMI_Value;
|
|
begin
|
|
Assert(NextCharPos <= Length(S));
|
|
case S[NextCharPos] of
|
|
'"':
|
|
begin
|
|
CStr := ParseCString(S, NextCharPos);
|
|
Result := TGDBMI_StringValue.Create(CStr);
|
|
end;
|
|
'{':
|
|
begin
|
|
Inc(NextCharPos);
|
|
Assert(NextCharPos <= Length(S));
|
|
Tuple := TGDBMI_TupleValue.Create;
|
|
Result := Tuple;
|
|
while (NextCharPos <= Length(S)) and (S[NextCharPos] <> '}') do
|
|
begin
|
|
Name := ParseIdentifier(S, NextCharPos);
|
|
Assert(NextCharPos <= Length(S));
|
|
Assert(S[NextCharPos] = '=');
|
|
Inc(NextCharPos);
|
|
Value := ParseValue(S, NextCharPos);
|
|
Tuple.Add(Name, Value);
|
|
Assert(NextCharPos <= Length(S));
|
|
Assert(S[NextCharPos] in [',', '}']);
|
|
if S[NextCharPos] = ',' then
|
|
Inc(NextCharPos);
|
|
end;
|
|
if (NextCharPos <= Length(S)) and (S[NextCharPos] = '}') then
|
|
Inc(NextCharPos);
|
|
end;
|
|
'[':
|
|
begin
|
|
Inc(NextCharPos);
|
|
Assert(NextCharPos <= Length(S));
|
|
List := TGDBMI_ListValue.Create;
|
|
Result := List;
|
|
if S[NextCharPos] in ['"', '{', '['] then
|
|
begin
|
|
{ list of values, no names }
|
|
while (NextCharPos <= Length(S)) and (S[NextCharPos] <> ']') do
|
|
begin
|
|
Value := ParseValue(S, NextCharPos);
|
|
List.Add('', Value);
|
|
Assert(NextCharPos <= Length(S));
|
|
Assert(S[NextCharPos] in [',', ']']);
|
|
if S[NextCharPos] = ',' then
|
|
Inc(NextCharPos);
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
{ list of name=value pairs (like a tuple) }
|
|
while (NextCharPos <= Length(S)) and (S[NextCharPos] <> ']') do
|
|
begin
|
|
Name := ParseIdentifier(S, NextCharPos);
|
|
Assert(NextCharPos <= Length(S));
|
|
Assert(S[NextCharPos] = '=');
|
|
Inc(NextCharPos);
|
|
Value := ParseValue(S, NextCharPos);
|
|
List.Add(Name, Value);
|
|
Assert(NextCharPos <= Length(S));
|
|
Assert(S[NextCharPos] in [',', ']']);
|
|
if S[NextCharPos] = ',' then
|
|
Inc(NextCharPos);
|
|
end;
|
|
end;
|
|
if (NextCharPos <= Length(S)) and (S[NextCharPos] = ']') then
|
|
Inc(NextCharPos);
|
|
end;
|
|
else
|
|
Assert(False);
|
|
end;
|
|
end;
|
|
|
|
procedure ParseAsyncOutput(const S: string; AsyncOutput: TGDBMI_AsyncOutput; var NextCharPos: LongInt);
|
|
var
|
|
Name: string;
|
|
Value: TGDBMI_Value;
|
|
begin
|
|
AsyncOutput.Clear;
|
|
AsyncOutput.AsyncClass := ParseIdentifier(S, NextCharPos);
|
|
while NextCharPos <= Length(S) do
|
|
begin
|
|
Assert(S[NextCharPos] = ',');
|
|
Inc(NextCharPos);
|
|
Name := ParseIdentifier(S, NextCharPos);
|
|
Assert(NextCharPos <= Length(S));
|
|
Assert(S[NextCharPos] = '=');
|
|
Inc(NextCharPos);
|
|
Value := ParseValue(S, NextCharPos);
|
|
AsyncOutput.Parameters.Add(Name, Value);
|
|
end;
|
|
end;
|
|
|
|
function TGDBWrapper.IsAlive: Boolean;
|
|
begin
|
|
Result := Assigned(FProcess) and FProcess.Alive;
|
|
end;
|
|
|
|
procedure TGDBWrapper.ReadResponse;
|
|
var
|
|
S: string;
|
|
I: LongInt;
|
|
NextCharPos: LongInt;
|
|
NAO: TGDBMI_AsyncOutput;
|
|
begin
|
|
FRawResponse.Clear;
|
|
FConsoleStream.Clear;
|
|
ExecAsyncOutput.Clear;
|
|
for I := Low(NotifyAsyncOutput) to High(NotifyAsyncOutput) do
|
|
FreeAndNil(NotifyAsyncOutput[I]);
|
|
SetLength(NotifyAsyncOutput, 0);
|
|
if not FProcess.Alive then
|
|
exit;
|
|
repeat
|
|
S := FProcess.GDBReadLn;
|
|
FRawResponse.Add(S);
|
|
if Length(S) >= 1 then
|
|
case S[1] of
|
|
'~':
|
|
begin
|
|
NextCharPos := 2;
|
|
FConsoleStream.Add(ParseCString(S, NextCharPos));
|
|
end;
|
|
'*':
|
|
begin
|
|
NextCharPos := 2;
|
|
ParseAsyncOutput(S, ExecAsyncOutput, NextCharPos);
|
|
end;
|
|
'^':
|
|
begin
|
|
NextCharPos := 2;
|
|
ParseAsyncOutput(S, ResultRecord, NextCharPos);
|
|
end;
|
|
'=':
|
|
begin
|
|
NextCharPos := 2;
|
|
NAO := TGDBMI_AsyncOutput.Create;
|
|
try
|
|
ParseAsyncOutput(S, NAO, NextCharPos);
|
|
SetLength(NotifyAsyncOutput, Length(NotifyAsyncOutput) + 1);
|
|
NotifyAsyncOutput[Length(NotifyAsyncOutput) - 1] := NAO;
|
|
NAO := nil;
|
|
finally
|
|
NAO.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
until (S = '(gdb) ') or (S = '(gdb)') or not FProcess.Alive;
|
|
end;
|
|
|
|
constructor TGDBWrapper.Create;
|
|
begin
|
|
FRawResponse := TStringList.Create;
|
|
FConsoleStream := TStringList.Create;
|
|
FProcess := TGDBProcess.Create;
|
|
FExecAsyncOutput := TGDBMI_AsyncOutput.Create;
|
|
FResultRecord := TGDBMI_ResultRecord.Create;
|
|
ReadResponse;
|
|
end;
|
|
|
|
destructor TGDBWrapper.Destroy;
|
|
begin
|
|
if Alive then
|
|
Command('-gdb-exit');
|
|
FProcess.Free;
|
|
FResultRecord.Free;
|
|
FExecAsyncOutput.Free;
|
|
FConsoleStream.Free;
|
|
FRawResponse.Free;
|
|
end;
|
|
|
|
procedure TGDBWrapper.Command(S: string);
|
|
begin
|
|
FProcess.GDBWriteLn(S);
|
|
ReadResponse;
|
|
end;
|
|
|
|
procedure TGDBWrapper.WaitForProgramStop;
|
|
begin
|
|
repeat
|
|
ReadResponse;
|
|
until (ExecAsyncOutput.AsyncClass = 'stopped') or not FProcess.Alive;
|
|
end;
|
|
|
|
end.
|