gdbmiwrap.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  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_TupleOrListValue;
  320. end_c : char;
  321. Name: string;
  322. Value: TGDBMI_Value;
  323. begin
  324. Assert(NextCharPos <= Length(S));
  325. case S[NextCharPos] of
  326. '"':
  327. begin
  328. CStr := ParseCString(S, NextCharPos);
  329. Result := TGDBMI_StringValue.Create(CStr);
  330. end;
  331. (* '{':
  332. begin
  333. Inc(NextCharPos);
  334. Assert(NextCharPos <= Length(S));
  335. Tuple := TGDBMI_TupleValue.Create;
  336. Result := Tuple;
  337. while (NextCharPos <= Length(S)) and (S[NextCharPos] <> '}') do
  338. begin
  339. Name := ParseIdentifier(S, NextCharPos);
  340. if (NextCharPos <= Length(S)) and (S[NextCharPos] = '=') then
  341. begin
  342. Inc(NextCharPos);
  343. Value := ParseValue(S, NextCharPos);
  344. Tuple.Add(Name, Value);
  345. end
  346. else
  347. begin
  348. Value:=TGDBMI_StringValue.Create(Name);
  349. Tuple.Add(Name, Value);
  350. end;
  351. Assert(NextCharPos <= Length(S));
  352. Assert(S[NextCharPos] in [',', '}']);
  353. if S[NextCharPos] = ',' then
  354. Inc(NextCharPos);
  355. end;
  356. if (NextCharPos <= Length(S)) and (S[NextCharPos] = '}') then
  357. Inc(NextCharPos);
  358. end;*)
  359. '[','{':
  360. begin
  361. if S[NextCharPos]='[' then
  362. begin
  363. end_c:=']';
  364. end
  365. else
  366. begin
  367. end_c:='}';
  368. end;
  369. Inc(NextCharPos);
  370. Assert(NextCharPos <= Length(S));
  371. if S[NextCharPos] in ['"', '{', '['] then
  372. begin
  373. List := TGDBMI_ListValue.Create;
  374. { list of values, no names }
  375. while (NextCharPos <= Length(S)) and (S[NextCharPos] <> end_c) do
  376. begin
  377. Value := ParseValue(S, NextCharPos);
  378. List.Add('', Value);
  379. Assert(NextCharPos <= Length(S));
  380. Assert(S[NextCharPos] in [',', end_c]);
  381. if S[NextCharPos] = ',' then
  382. Inc(NextCharPos);
  383. end;
  384. end
  385. else
  386. begin
  387. List := TGDBMI_TupleValue.Create;
  388. { list of name=value pairs (like a tuple) }
  389. while (NextCharPos <= Length(S)) and (S[NextCharPos] <> end_c) do
  390. begin
  391. Name := ParseIdentifier(S, NextCharPos);
  392. Assert(NextCharPos <= Length(S));
  393. Assert(S[NextCharPos] = '=');
  394. Inc(NextCharPos);
  395. Value := ParseValue(S, NextCharPos);
  396. List.Add(Name, Value);
  397. Assert(NextCharPos <= Length(S));
  398. Assert(S[NextCharPos] in [',', end_c]);
  399. if S[NextCharPos] = ',' then
  400. Inc(NextCharPos);
  401. end;
  402. end;
  403. if (NextCharPos <= Length(S)) and (S[NextCharPos] = end_c) then
  404. Inc(NextCharPos);
  405. Result := List;
  406. end;
  407. else
  408. Assert(False);
  409. end;
  410. end;
  411. procedure ParseAsyncOutput(const S: string; AsyncOutput: TGDBMI_AsyncOutput; var NextCharPos: LongInt);
  412. var
  413. Name: string;
  414. Value: TGDBMI_Value;
  415. begin
  416. AsyncOutput.Clear;
  417. AsyncOutput.AsyncClass := ParseIdentifier(S, NextCharPos);
  418. while NextCharPos <= Length(S) do
  419. begin
  420. Assert(S[NextCharPos] = ',');
  421. Inc(NextCharPos);
  422. Name := ParseIdentifier(S, NextCharPos);
  423. Assert(NextCharPos <= Length(S));
  424. Assert(S[NextCharPos] = '=');
  425. Inc(NextCharPos);
  426. Value := ParseValue(S, NextCharPos);
  427. AsyncOutput.Parameters.Add(Name, Value);
  428. end;
  429. end;
  430. function TGDBWrapper.IsAlive: Boolean;
  431. begin
  432. Result := Assigned(FProcess) and FProcess.Alive;
  433. end;
  434. procedure TGDBWrapper.ReadResponse;
  435. var
  436. S: string;
  437. I: LongInt;
  438. NextCharPos: LongInt;
  439. NAO: TGDBMI_AsyncOutput;
  440. begin
  441. FRawResponse.Clear;
  442. FConsoleStream.Clear;
  443. ExecAsyncOutput.Clear;
  444. for I := Low(NotifyAsyncOutput) to High(NotifyAsyncOutput) do
  445. FreeAndNil(NotifyAsyncOutput[I]);
  446. SetLength(NotifyAsyncOutput, 0);
  447. if not FProcess.Alive then
  448. exit;
  449. repeat
  450. S := FProcess.GDBReadLn;
  451. FRawResponse.Add(S);
  452. if Length(S) >= 1 then
  453. case S[1] of
  454. '~':
  455. begin
  456. NextCharPos := 2;
  457. FConsoleStream.Add(ParseCString(S, NextCharPos));
  458. end;
  459. '*':
  460. begin
  461. NextCharPos := 2;
  462. ParseAsyncOutput(S, ExecAsyncOutput, NextCharPos);
  463. end;
  464. '^':
  465. begin
  466. NextCharPos := 2;
  467. ParseAsyncOutput(S, ResultRecord, NextCharPos);
  468. end;
  469. '=':
  470. begin
  471. NextCharPos := 2;
  472. NAO := TGDBMI_AsyncOutput.Create;
  473. try
  474. ParseAsyncOutput(S, NAO, NextCharPos);
  475. SetLength(NotifyAsyncOutput, Length(NotifyAsyncOutput) + 1);
  476. NotifyAsyncOutput[Length(NotifyAsyncOutput) - 1] := NAO;
  477. NAO := nil;
  478. finally
  479. NAO.Free;
  480. end;
  481. end;
  482. end;
  483. until (S = '(gdb) ') or (S = '(gdb)') or not FProcess.Alive;
  484. end;
  485. constructor TGDBWrapper.Create;
  486. begin
  487. FRawResponse := TStringList.Create;
  488. FConsoleStream := TStringList.Create;
  489. FProcess := TGDBProcess.Create;
  490. FExecAsyncOutput := TGDBMI_AsyncOutput.Create;
  491. FResultRecord := TGDBMI_ResultRecord.Create;
  492. ReadResponse;
  493. end;
  494. destructor TGDBWrapper.Destroy;
  495. begin
  496. if Alive then
  497. Command('-gdb-exit');
  498. FProcess.Free;
  499. FResultRecord.Free;
  500. FExecAsyncOutput.Free;
  501. FConsoleStream.Free;
  502. FRawResponse.Free;
  503. end;
  504. procedure TGDBWrapper.Command(S: string);
  505. begin
  506. FProcess.GDBWriteLn(S);
  507. ReadResponse;
  508. end;
  509. procedure TGDBWrapper.WaitForProgramStop;
  510. begin
  511. repeat
  512. ReadResponse;
  513. until (ExecAsyncOutput.AsyncClass = 'stopped') or not FProcess.Alive;
  514. end;
  515. end.