gdbmiwrap.pas 15 KB

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