2
0

gdbmiwrap.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. {
  2. Copyright (c) 2015 by Nikolay Nikolov
  3. This unit provides a wrapper around GDB and implements parsing of
  4. the GDB/MI command result records.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. unit gdbmiwrap;
  12. {$MODE objfpc}{$H+}
  13. {$ASSERTIONS on}
  14. {$I globdir.inc}
  15. interface
  16. uses
  17. SysUtils, Classes, GDBMIProc;
  18. type
  19. {$ifdef TARGET_IS_64BIT}
  20. { force 64bit if target compilation CPU is 64-bit address CPU }
  21. CORE_ADDR = Qword;
  22. {$else}
  23. CORE_ADDR = PtrUInt;
  24. {$endif}
  25. TGDBMI_TupleValue = class;
  26. TGDBMI_ListValue = class;
  27. TGDBMI_Value = class
  28. function AsString: string;
  29. function AsInt64: Int64;
  30. function AsQWord: QWord;
  31. function AsLongInt: LongInt;
  32. function AsLongWord: LongWord;
  33. function AsCoreAddr: CORE_ADDR;
  34. function AsTuple: TGDBMI_TupleValue;
  35. function AsList: TGDBMI_ListValue;
  36. end;
  37. { "C string\n" }
  38. TGDBMI_StringValue = class(TGDBMI_Value)
  39. FStringValue: string;
  40. public
  41. constructor Create(const S: string);
  42. property StringValue: string read FStringValue;
  43. end;
  44. (* {...} or [...] *)
  45. TGDBMI_TupleOrListValue = class(TGDBMI_Value)
  46. private
  47. FNames: array of string;
  48. FValues: array of TGDBMI_Value;
  49. function GetValue(const AName: string): TGDBMI_Value;
  50. public
  51. destructor Destroy; override;
  52. procedure Clear;
  53. procedure Add(AName: string; AValue: TGDBMI_Value);
  54. function HasNames: Boolean;
  55. function IsEmpty: Boolean;
  56. property Values [const AName: string]: TGDBMI_Value read GetValue; default;
  57. end;
  58. (* {} or {variable=value,variable=value,variable=value} *)
  59. TGDBMI_TupleValue = class(TGDBMI_TupleOrListValue)
  60. end;
  61. { [] or [value,value,value] or [variable=value,variable=value,variable=value] }
  62. TGDBMI_ListValue = class(TGDBMI_TupleOrListValue)
  63. private
  64. function GetCount: LongInt;
  65. function GetValueAt(AIndex: LongInt): TGDBMI_Value;
  66. public
  67. property Count: LongInt read GetCount;
  68. property ValueAt [AIndex: LongInt]: TGDBMI_Value read GetValueAt;
  69. end;
  70. TGDBMI_AsyncOutput = class
  71. FAsyncClass: string;
  72. FParameters: TGDBMI_TupleValue;
  73. public
  74. constructor Create;
  75. destructor Destroy; override;
  76. procedure Clear;
  77. property AsyncClass: string read FAsyncClass write FAsyncClass;
  78. property Parameters: TGDBMI_TupleValue read FParameters;
  79. end;
  80. TGDBMI_ResultRecord = class(TGDBMI_AsyncOutput)
  81. public
  82. function Success: Boolean;
  83. end;
  84. TGDBMI_AsyncOutput_List = array of TGDBMI_AsyncOutput;
  85. TGDBWrapper = class
  86. private
  87. FProcess: TGDBProcess;
  88. FRawResponse: TStringList;
  89. FConsoleStream: TStringList;
  90. FExecAsyncOutput: TGDBMI_AsyncOutput;
  91. FResultRecord: TGDBMI_ResultRecord;
  92. function IsAlive: Boolean;
  93. procedure ReadResponse;
  94. public
  95. NotifyAsyncOutput: TGDBMI_AsyncOutput_List;
  96. constructor Create;
  97. destructor Destroy; override;
  98. procedure Command(S: string);
  99. procedure WaitForProgramStop;
  100. property RawResponse: TStringList read FRawResponse;
  101. property ConsoleStream: TStringList read FConsoleStream;
  102. property ExecAsyncOutput: TGDBMI_AsyncOutput read FExecAsyncOutput;
  103. property ResultRecord: TGDBMI_ResultRecord read FResultRecord write FResultRecord;
  104. property Alive: Boolean read IsAlive;
  105. end;
  106. function QuoteString(S: string): string;
  107. function C2PascalNumberPrefix(const S: string): string;
  108. implementation
  109. function QuoteString(S: string): string;
  110. var
  111. I: LongInt;
  112. begin
  113. I := 1;
  114. Result := '';
  115. while I <= Length(S) do
  116. begin
  117. case S[I] of
  118. '''': Result := Result + '\''';
  119. '"': Result := Result + '\"';
  120. #10: Result := Result + '\n';
  121. #13: Result := Result + '\r';
  122. #9: Result := Result + '\t';
  123. #11: Result := Result + '\v';
  124. #8: Result := Result + '\b';
  125. #12: Result := Result + '\f';
  126. #7: Result := Result + '\a';
  127. '\': Result := Result + '\\';
  128. '?': Result := Result + '\?';
  129. else
  130. Result := Result + S[I];
  131. end;
  132. Inc(I);
  133. end;
  134. Result := '"' + Result + '"';
  135. end;
  136. function C2PascalNumberPrefix(const S: string): string;
  137. begin
  138. { hex: 0x -> $ }
  139. if (Length(S) >= 3) and (s[1] = '0') and ((s[2] = 'x') or (s[2] = 'X')) then
  140. exit('$' + Copy(S, 3, Length(S) - 2));
  141. { oct: 0 -> & }
  142. if (Length(S) >= 2) and (s[1] = '0') and ((s[2] >= '0') and (s[2] <= '7')) then
  143. exit('&' + Copy(S, 2, Length(S) - 1));
  144. Result := S;
  145. end;
  146. function TGDBMI_Value.AsString: string;
  147. begin
  148. Result := (self as TGDBMI_StringValue).StringValue;
  149. end;
  150. function TGDBMI_Value.AsInt64: Int64;
  151. begin
  152. Result := StrToInt64(C2PascalNumberPrefix(AsString));
  153. end;
  154. function TGDBMI_Value.AsQWord: QWord;
  155. begin
  156. Result := StrToQWord(C2PascalNumberPrefix(AsString));
  157. end;
  158. function TGDBMI_Value.AsLongInt: LongInt;
  159. begin
  160. Result := StrToInt(C2PascalNumberPrefix(AsString));
  161. end;
  162. function TGDBMI_Value.AsLongWord: LongWord;
  163. const
  164. SInvalidInteger = '"%s" is an invalid integer';
  165. var
  166. S: string;
  167. Error: LongInt;
  168. begin
  169. S := C2PascalNumberPrefix(AsString);
  170. Val(S, Result, Error);
  171. if Error <> 0 then
  172. raise EConvertError.CreateFmt(SInvalidInteger,[S]);
  173. end;
  174. function TGDBMI_Value.AsCoreAddr: CORE_ADDR;
  175. begin
  176. {$if defined(TARGET_IS_64BIT)}
  177. Result := AsQWord;
  178. {$elseif defined(CPU64)}
  179. Result := AsQWord;
  180. {$else}
  181. Result := AsLongWord;
  182. {$endif}
  183. end;
  184. function TGDBMI_Value.AsTuple: TGDBMI_TupleValue;
  185. begin
  186. Result := self as TGDBMI_TupleValue;
  187. end;
  188. function TGDBMI_Value.AsList: TGDBMI_ListValue;
  189. begin
  190. Result := self as TGDBMI_ListValue;
  191. end;
  192. constructor TGDBMI_StringValue.Create(const S: string);
  193. begin
  194. FStringValue := S;
  195. end;
  196. destructor TGDBMI_TupleOrListValue.Destroy;
  197. begin
  198. Clear;
  199. inherited Destroy;
  200. end;
  201. procedure TGDBMI_TupleOrListValue.Clear;
  202. var
  203. I: LongInt;
  204. begin
  205. SetLength(FNames, 0);
  206. for I := Low(FValues) to High(FValues) do
  207. FreeAndNil(FValues[I]);
  208. SetLength(FValues, 0);
  209. end;
  210. procedure TGDBMI_TupleOrListValue.Add(AName: string; AValue: TGDBMI_Value);
  211. begin
  212. Assert(AValue <> nil);
  213. Assert(IsEmpty or (HasNames = (AName <> '')));
  214. if AName <> '' then
  215. begin
  216. SetLength(FNames, Length(FNames) + 1);
  217. FNames[Length(FNames) - 1] := AName;
  218. end;
  219. SetLength(FValues, Length(FValues) + 1);
  220. FValues[Length(FValues) - 1] := AValue;
  221. end;
  222. function TGDBMI_TupleOrListValue.HasNames: Boolean;
  223. begin
  224. Result := Length(FNames) > 0;
  225. end;
  226. function TGDBMI_TupleOrListValue.IsEmpty: Boolean;
  227. begin
  228. Result := Length(FValues) = 0;
  229. end;
  230. function TGDBMI_TupleOrListValue.GetValue(const AName: string): TGDBMI_Value;
  231. var
  232. I: LongInt;
  233. begin
  234. for I := Low(FNames) to High(FNames) do
  235. if FNames[I] = AName then
  236. begin
  237. Result := FValues[I];
  238. exit;
  239. end;
  240. Result := nil;
  241. end;
  242. function TGDBMI_ListValue.GetCount: LongInt;
  243. begin
  244. Result := Length(FValues);
  245. end;
  246. function TGDBMI_ListValue.GetValueAt(AIndex: LongInt): TGDBMI_Value;
  247. begin
  248. Assert((AIndex >= Low(FValues)) and (AIndex <= High(FValues)));
  249. Result := FValues[AIndex];
  250. end;
  251. constructor TGDBMI_AsyncOutput.Create;
  252. begin
  253. FParameters := TGDBMI_TupleValue.Create;
  254. end;
  255. destructor TGDBMI_AsyncOutput.Destroy;
  256. begin
  257. FParameters.Free;
  258. inherited Destroy;
  259. end;
  260. procedure TGDBMI_AsyncOutput.Clear;
  261. begin
  262. AsyncClass := '';
  263. Parameters.Clear;
  264. end;
  265. function TGDBMI_ResultRecord.Success: Boolean;
  266. begin
  267. { according to the GDB docs, 'done' and 'running' should be treated identically by clients }
  268. Result := (AsyncClass='done') or (AsyncClass='running');
  269. end;
  270. function ParseCString(const CStr: string; var NextCharPos: LongInt): string;
  271. begin
  272. if (NextCharPos <= Length(CStr)) and (CStr[NextCharPos] = '"') then
  273. Inc(NextCharPos);
  274. Result := '';
  275. while NextCharPos <= Length(CStr) do
  276. begin
  277. if CStr[NextCharPos] = '"' then
  278. begin
  279. Inc(NextCharPos);
  280. exit;
  281. end
  282. else if CStr[NextCharPos] = '\' then
  283. begin
  284. Inc(NextCharPos);
  285. if NextCharPos <= Length(CStr) then
  286. case CStr[NextCharPos] of
  287. '''': Result := Result + '''';
  288. '"': Result := Result + '"';
  289. 'n': Result := Result + #10;
  290. 'r': Result := Result + #13;
  291. 't': Result := Result + #9;
  292. 'v': Result := Result + #11;
  293. 'b': Result := Result + #8;
  294. 'f': Result := Result + #12;
  295. 'a': Result := Result + #7;
  296. '\': Result := Result + '\';
  297. '?': Result := Result + '?';
  298. {\0, \000, \xhhh}
  299. end;
  300. end
  301. else
  302. Result := Result + CStr[NextCharPos];
  303. Inc(NextCharPos);
  304. end;
  305. end;
  306. function ParseIdentifier(const S: string; var NextCharPos: LongInt): string;
  307. begin
  308. Result := '';
  309. while (NextCharPos <= Length(S)) and (S[NextCharPos] in ['A'..'Z', 'a'..'z', '0'..'9', '-']) do
  310. begin
  311. Result := Result + S[NextCharPos];
  312. Inc(NextCharPos);
  313. end;
  314. end;
  315. function ParseValue(const S: string; var NextCharPos: LongInt): TGDBMI_Value;
  316. var
  317. CStr: string;
  318. Tuple: TGDBMI_TupleValue;
  319. List: TGDBMI_ListValue;
  320. Name: string;
  321. Value: TGDBMI_Value;
  322. begin
  323. Assert(NextCharPos <= Length(S));
  324. case S[NextCharPos] of
  325. '"':
  326. begin
  327. CStr := ParseCString(S, NextCharPos);
  328. Result := TGDBMI_StringValue.Create(CStr);
  329. end;
  330. '{':
  331. begin
  332. Inc(NextCharPos);
  333. Assert(NextCharPos <= Length(S));
  334. Tuple := TGDBMI_TupleValue.Create;
  335. Result := Tuple;
  336. while (NextCharPos <= Length(S)) and (S[NextCharPos] <> '}') do
  337. begin
  338. Name := ParseIdentifier(S, NextCharPos);
  339. Assert(NextCharPos <= Length(S));
  340. Assert(S[NextCharPos] = '=');
  341. Inc(NextCharPos);
  342. Value := ParseValue(S, NextCharPos);
  343. Tuple.Add(Name, Value);
  344. Assert(NextCharPos <= Length(S));
  345. Assert(S[NextCharPos] in [',', '}']);
  346. if S[NextCharPos] = ',' then
  347. Inc(NextCharPos);
  348. end;
  349. if (NextCharPos <= Length(S)) and (S[NextCharPos] = '}') then
  350. Inc(NextCharPos);
  351. end;
  352. '[':
  353. begin
  354. Inc(NextCharPos);
  355. Assert(NextCharPos <= Length(S));
  356. List := TGDBMI_ListValue.Create;
  357. Result := List;
  358. if S[NextCharPos] in ['"', '{', '['] then
  359. begin
  360. { list of values, no names }
  361. while (NextCharPos <= Length(S)) and (S[NextCharPos] <> ']') do
  362. begin
  363. Value := ParseValue(S, NextCharPos);
  364. List.Add('', Value);
  365. Assert(NextCharPos <= Length(S));
  366. Assert(S[NextCharPos] in [',', ']']);
  367. if S[NextCharPos] = ',' then
  368. Inc(NextCharPos);
  369. end;
  370. end
  371. else
  372. begin
  373. { list of name=value pairs (like a tuple) }
  374. while (NextCharPos <= Length(S)) and (S[NextCharPos] <> ']') do
  375. begin
  376. Name := ParseIdentifier(S, NextCharPos);
  377. Assert(NextCharPos <= Length(S));
  378. Assert(S[NextCharPos] = '=');
  379. Inc(NextCharPos);
  380. Value := ParseValue(S, NextCharPos);
  381. List.Add(Name, Value);
  382. Assert(NextCharPos <= Length(S));
  383. Assert(S[NextCharPos] in [',', ']']);
  384. if S[NextCharPos] = ',' then
  385. Inc(NextCharPos);
  386. end;
  387. end;
  388. if (NextCharPos <= Length(S)) and (S[NextCharPos] = ']') then
  389. Inc(NextCharPos);
  390. end;
  391. else
  392. Assert(False);
  393. end;
  394. end;
  395. procedure ParseAsyncOutput(const S: string; AsyncOutput: TGDBMI_AsyncOutput; var NextCharPos: LongInt);
  396. var
  397. Name: string;
  398. Value: TGDBMI_Value;
  399. begin
  400. AsyncOutput.Clear;
  401. AsyncOutput.AsyncClass := ParseIdentifier(S, NextCharPos);
  402. while NextCharPos <= Length(S) do
  403. begin
  404. Assert(S[NextCharPos] = ',');
  405. Inc(NextCharPos);
  406. Name := ParseIdentifier(S, NextCharPos);
  407. Assert(NextCharPos <= Length(S));
  408. Assert(S[NextCharPos] = '=');
  409. Inc(NextCharPos);
  410. Value := ParseValue(S, NextCharPos);
  411. AsyncOutput.Parameters.Add(Name, Value);
  412. end;
  413. end;
  414. function TGDBWrapper.IsAlive: Boolean;
  415. begin
  416. Result := Assigned(FProcess) and FProcess.Alive;
  417. end;
  418. procedure TGDBWrapper.ReadResponse;
  419. var
  420. S: string;
  421. I: LongInt;
  422. NextCharPos: LongInt;
  423. NAO: TGDBMI_AsyncOutput;
  424. begin
  425. FRawResponse.Clear;
  426. FConsoleStream.Clear;
  427. ExecAsyncOutput.Clear;
  428. for I := Low(NotifyAsyncOutput) to High(NotifyAsyncOutput) do
  429. FreeAndNil(NotifyAsyncOutput[I]);
  430. SetLength(NotifyAsyncOutput, 0);
  431. if not FProcess.Alive then
  432. exit;
  433. repeat
  434. S := FProcess.GDBReadLn;
  435. FRawResponse.Add(S);
  436. if Length(S) >= 1 then
  437. case S[1] of
  438. '~':
  439. begin
  440. NextCharPos := 2;
  441. FConsoleStream.Add(ParseCString(S, NextCharPos));
  442. end;
  443. '*':
  444. begin
  445. NextCharPos := 2;
  446. ParseAsyncOutput(S, ExecAsyncOutput, NextCharPos);
  447. end;
  448. '^':
  449. begin
  450. NextCharPos := 2;
  451. ParseAsyncOutput(S, ResultRecord, NextCharPos);
  452. end;
  453. '=':
  454. begin
  455. NextCharPos := 2;
  456. NAO := TGDBMI_AsyncOutput.Create;
  457. try
  458. ParseAsyncOutput(S, NAO, NextCharPos);
  459. SetLength(NotifyAsyncOutput, Length(NotifyAsyncOutput) + 1);
  460. NotifyAsyncOutput[Length(NotifyAsyncOutput) - 1] := NAO;
  461. NAO := nil;
  462. finally
  463. NAO.Free;
  464. end;
  465. end;
  466. end;
  467. until (S = '(gdb) ') or (S = '(gdb)') or not FProcess.Alive;
  468. end;
  469. constructor TGDBWrapper.Create;
  470. begin
  471. FRawResponse := TStringList.Create;
  472. FConsoleStream := TStringList.Create;
  473. FProcess := TGDBProcess.Create;
  474. FExecAsyncOutput := TGDBMI_AsyncOutput.Create;
  475. FResultRecord := TGDBMI_ResultRecord.Create;
  476. ReadResponse;
  477. end;
  478. destructor TGDBWrapper.Destroy;
  479. begin
  480. if Alive then
  481. Command('-gdb-exit');
  482. FProcess.Free;
  483. FResultRecord.Free;
  484. FExecAsyncOutput.Free;
  485. FConsoleStream.Free;
  486. FRawResponse.Free;
  487. end;
  488. procedure TGDBWrapper.Command(S: string);
  489. begin
  490. FProcess.GDBWriteLn(S);
  491. ReadResponse;
  492. end;
  493. procedure TGDBWrapper.WaitForProgramStop;
  494. begin
  495. repeat
  496. ReadResponse;
  497. until (ExecAsyncOutput.AsyncClass = 'stopped') or not FProcess.Alive;
  498. end;
  499. end.