gdbmiint.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. {
  2. Copyright (c) 2015 by Nikolay Nikolov
  3. Copyright (c) 1998 by Peter Vreman
  4. This is a replacement for GDBInt, implemented on top of GDB/MI,
  5. instead of LibGDB. This allows integration of GDB/MI support in the
  6. text mode IDE.
  7. See the file COPYING.FPC, included in this distribution,
  8. for details about the copyright.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  12. **********************************************************************}
  13. unit gdbmiint;
  14. {$MODE fpc}{$H-}
  15. {$I globdir.inc}
  16. interface
  17. uses
  18. gdbmiwrap;
  19. type
  20. CORE_ADDR = gdbmiwrap.CORE_ADDR;
  21. PPFrameEntry = ^PFrameEntry;
  22. PFrameEntry = ^TFrameEntry;
  23. TFrameEntry = object
  24. private
  25. procedure Reset;
  26. procedure Clear;
  27. public
  28. file_name: PChar;
  29. function_name: PChar;
  30. args: PChar;
  31. line_number: LongInt;
  32. address: CORE_ADDR;
  33. level : longint;
  34. constructor Init;
  35. destructor Done;
  36. end;
  37. TGDBBuffer = object
  38. private
  39. buf: PChar;
  40. size, idx: LongInt;
  41. procedure Resize(nsize: LongInt);
  42. procedure Append(p: PChar);
  43. procedure LAppend(p: PChar; len: LongInt);
  44. public
  45. constructor Init;
  46. destructor Done;
  47. procedure Reset;
  48. end;
  49. TGDBInterface = object
  50. private
  51. user_screen_shown: Boolean;
  52. {$ifdef GDB_RAW_OUTPUT}
  53. output_raw : boolean;
  54. {$endif GDB_RAW_OUTPUT}
  55. protected
  56. GDB: TGDBWrapper;
  57. procedure i_gdb_command(const S: string);
  58. procedure WaitForProgramStop;
  59. procedure ProcessResponse;
  60. public
  61. GDBErrorBuf: TGDBBuffer;
  62. GDBOutputBuf: TGDBBuffer;
  63. {$ifdef GDB_RAW_OUTPUT}
  64. { Separate Raw buffer to display everything inside GDB Window
  65. but without parsing it twice }
  66. GDBRawBuf: TGDBBuffer;
  67. {$endif GDB_RAW_OUTPUT}
  68. got_error: Boolean;
  69. reset_command: Boolean;
  70. Debuggee_started: Boolean;
  71. init_count : longint;
  72. { frames and frame info while recording a frame }
  73. frames: PPFrameEntry;
  74. frame_count: LongInt;
  75. command_level: LongInt;
  76. signal_name: PChar;
  77. signal_string: PChar;
  78. current_pc: CORE_ADDR;
  79. switch_to_user: Boolean;
  80. { init }
  81. constructor Init;
  82. destructor Done;
  83. { from gdbcon }
  84. function GetOutput: PChar;
  85. function GetError: PChar;
  86. {$ifdef DEBUG}
  87. function GetRaw: PChar;
  88. {$endif DEBUG}
  89. { Lowlevel }
  90. procedure Set_debuggee_started;
  91. function error: Boolean;
  92. function error_num: LongInt;
  93. function get_current_frame: PtrInt;
  94. function set_current_frame(level: LongInt): Boolean;
  95. procedure clear_frames;
  96. { Highlevel }
  97. procedure DebuggerScreen;
  98. procedure UserScreen;
  99. procedure FlushAll; virtual;
  100. function Query(question: PChar; args: PChar): LongInt; virtual;
  101. { Hooks }
  102. function DoSelectSourceline(const fn: string; line, BreakIndex: longint): Boolean;virtual;
  103. procedure DoStartSession; virtual;
  104. procedure DoBreakSession; virtual;
  105. procedure DoEndSession(code: LongInt); virtual;
  106. procedure DoUserSignal; virtual;
  107. procedure DoDebuggerScreen; virtual;
  108. procedure DoUserScreen; virtual;
  109. function AllowQuit: Boolean; virtual;
  110. end;
  111. const
  112. use_gdb_file: Boolean = False;
  113. var
  114. gdb_file: Text;
  115. function GDBVersion: string;
  116. function GDBVersionOK: boolean;
  117. function inferior_pid : longint;
  118. {$ifdef windows}
  119. { We need to do some path conversions if we are using Cygwin GDB }
  120. var
  121. using_cygwin_gdb : boolean;
  122. {$endif windows}
  123. implementation
  124. uses
  125. strings;
  126. constructor TFrameEntry.Init;
  127. begin
  128. Reset;
  129. end;
  130. destructor TFrameEntry.Done;
  131. begin
  132. Clear;
  133. end;
  134. procedure TFrameEntry.Reset;
  135. begin
  136. file_name := nil;
  137. function_name := nil;
  138. args := nil;
  139. line_number := 0;
  140. address := 0;
  141. level := 0;
  142. end;
  143. procedure TFrameEntry.Clear;
  144. begin
  145. if Assigned(file_name) then
  146. StrDispose(file_name);
  147. if Assigned(function_name) then
  148. StrDispose(function_name);
  149. if Assigned(args) then
  150. StrDispose(args);
  151. Reset;
  152. end;
  153. const
  154. BlockSize = 2048;
  155. constructor TGDBBuffer.Init;
  156. begin
  157. buf := nil;
  158. size := 0;
  159. Resize(BlockSize);
  160. Reset;
  161. end;
  162. destructor TGDBBuffer.Done;
  163. begin
  164. if Assigned(buf) then
  165. FreeMem(buf, size);
  166. end;
  167. procedure TGDBBuffer.Reset;
  168. begin
  169. idx := 0;
  170. buf[0] := #0;
  171. end;
  172. procedure TGDBBuffer.Resize(nsize: LongInt);
  173. var
  174. np: PChar;
  175. begin
  176. nsize := ((nsize + BlockSize - 1) div BlockSize) * BlockSize;
  177. GetMem(np, nsize);
  178. if Assigned(buf) then
  179. begin
  180. Move(buf^, np^, size);
  181. FreeMem(buf, size);
  182. end;
  183. buf := np;
  184. size := nsize;
  185. end;
  186. procedure TGDBBuffer.Append(p: PChar);
  187. var
  188. len: LongInt;
  189. begin
  190. if not Assigned(p) then
  191. exit;
  192. len := StrLen(p);
  193. LAppend(p, len);
  194. end;
  195. procedure TGDBBuffer.LAppend(p: PChar; len: LongInt);
  196. begin
  197. if not Assigned(p) then
  198. exit;
  199. if (len + idx + 1) > size then
  200. Resize(len + idx + 1);
  201. Move(p^, buf[idx], len);
  202. Inc(idx, len);
  203. buf[idx] := #0;
  204. end;
  205. constructor TGDBInterface.Init;
  206. begin
  207. GDBErrorBuf.Init;
  208. GDBOutputBuf.Init;
  209. GDB := TGDBWrapper.Create;
  210. command_level := 0;
  211. Debuggee_started:=false;
  212. init_count:=0;
  213. {$ifdef GDB_RAW_OUTPUT}
  214. output_raw:=true;
  215. GDBRawBuf.Init;
  216. {$endif GDB_RAW_OUTPUT}
  217. { other standard commands used for fpc debugging }
  218. i_gdb_command('-gdb-set print demangle off');
  219. i_gdb_command('-gdb-set gnutarget auto');
  220. i_gdb_command('-gdb-set language auto');
  221. i_gdb_command('-gdb-set print vtbl on');
  222. i_gdb_command('-gdb-set print object on');
  223. i_gdb_command('-gdb-set print null-stop');
  224. end;
  225. destructor TGDBInterface.Done;
  226. begin
  227. clear_frames;
  228. GDB.Free;
  229. GDBErrorBuf.Done;
  230. GDBOutputBuf.Done;
  231. {$ifdef GDB_RAW_OUTPUT}
  232. GDBRawBuf.Done;
  233. {$endif GDB_RAW_OUTPUT}
  234. end;
  235. function TGDBInterface.GetOutput: PChar;
  236. begin
  237. GetOutput := GDBOutputBuf.buf;
  238. end;
  239. {$ifdef GDB_RAW_OUTPUT}
  240. function TGDBInterface.GetRaw: PChar;
  241. begin
  242. GetRaw := GDBRawBuf.buf;
  243. end;
  244. {$endif GDB_RAW_OUTPUT}
  245. function TGDBInterface.GetError: PChar;
  246. var
  247. p: PChar;
  248. begin
  249. p := GDBErrorBuf.buf;
  250. if (p^=#0) and got_error then
  251. GetError := PChar(PtrInt(GDBOutputBuf.buf) + GDBOutputBuf.idx)
  252. else
  253. GetError := p;
  254. end;
  255. procedure TGDBInterface.Set_debuggee_started;
  256. begin
  257. if not Debuggee_started then
  258. begin
  259. inc(init_count);
  260. Debuggee_started:=true;
  261. end;
  262. end;
  263. procedure TGDBInterface.i_gdb_command(const S: string);
  264. var
  265. I: LongInt;
  266. begin
  267. Inc(command_level);
  268. got_error := False;
  269. GDB.Command(S);
  270. {$ifdef GDB_RAW_OUTPUT}
  271. if output_raw then
  272. for I := 0 to GDB.RawResponse.Count - 1 do
  273. GDBRawBuf.Append(PChar(GDB.RawResponse[I]));
  274. {$endif GDB_RAW_OUTPUT}
  275. for I := 0 to GDB.ConsoleStream.Count - 1 do
  276. GDBOutputBuf.Append(PChar(GDB.ConsoleStream[I]));
  277. if GDB.ResultRecord.AsyncClass='error' then
  278. begin
  279. got_error := True;
  280. if Assigned(GDB.ResultRecord.Parameters['msg']) then
  281. GDBErrorBuf.Append(PChar(GDB.ResultRecord.Parameters['msg'].AsString));
  282. end;
  283. ProcessResponse;
  284. Dec(command_level);
  285. end;
  286. procedure TGDBInterface.WaitForProgramStop;
  287. label
  288. Ignore;
  289. var
  290. StopReason: string;
  291. LocalSignalString,LocalSignalName: String;
  292. FileName: string = '';
  293. LineNumber: LongInt = 0;
  294. Addr: CORE_ADDR;
  295. BreakpointNo: LongInt;
  296. ExitCode: LongInt;
  297. begin
  298. Ignore:
  299. GDB.WaitForProgramStop;
  300. if not GDB.Alive then
  301. begin
  302. DebuggerScreen;
  303. current_pc := 0;
  304. Debuggee_started := False;
  305. exit;
  306. end;
  307. ProcessResponse;
  308. StopReason := GDB.ExecAsyncOutput.Parameters['reason'].AsString;
  309. case StopReason of
  310. 'watchpoint-scope':
  311. begin
  312. { A watchpoint has gone out of scope (e.g. if it was a local variable). TODO: should we stop
  313. the program and notify the user or maybe silently disable it in the breakpoint list and
  314. continue execution? The libgdb.a version of the debugger just silently ignores this case.
  315. We have: GDB.ExecAsyncOutput.Parameters['wpnum'].AsLongInt }
  316. i_gdb_command('-exec-continue');
  317. if not GDB.ResultRecord.Success then
  318. begin
  319. DebuggerScreen;
  320. got_error := True;
  321. exit;
  322. end;
  323. goto Ignore;
  324. end;
  325. 'signal-received':
  326. begin
  327. { TODO: maybe show information to the user about the signal
  328. we have:
  329. GDB.ExecAsyncOutput.Parameters['signal-name'].AsString (e.g. 'SIGTERM')
  330. GDB.ExecAsyncOutput.PArameters['signal-meaning'].AsString (e.g. 'Terminated')
  331. }
  332. LocalSignalName:=GDB.ExecAsyncOutput.Parameters['signal-name'].AsString;
  333. LocalSignalString:=GDB.ExecAsyncOutput.PArameters['signal-meaning'].AsString;
  334. signal_name:=@LocalSignalName;
  335. signal_string:=@LocalSignalString;
  336. if (user_screen_shown) then
  337. begin
  338. DebuggerScreen;
  339. DoUserSignal;
  340. UserScreen;
  341. end
  342. else
  343. DoUserSignal;
  344. i_gdb_command('-exec-continue');
  345. if not GDB.ResultRecord.Success then
  346. begin
  347. DebuggerScreen;
  348. got_error := True;
  349. exit;
  350. end;
  351. goto Ignore;
  352. end;
  353. 'breakpoint-hit',
  354. 'watchpoint-trigger',
  355. 'access-watchpoint-trigger',
  356. 'read-watchpoint-trigger',
  357. 'end-stepping-range',
  358. 'function-finished':
  359. begin
  360. if StopReason = 'breakpoint-hit' then
  361. BreakpointNo := GDB.ExecAsyncOutput.Parameters['bkptno'].AsLongInt
  362. else if StopReason = 'watchpoint-trigger' then
  363. BreakpointNo := GDB.ExecAsyncOutput.Parameters['wpt'].AsTuple['number'].AsLongInt
  364. else if StopReason = 'access-watchpoint-trigger' then
  365. BreakpointNo := GDB.ExecAsyncOutput.Parameters['hw-awpt'].AsTuple['number'].AsLongInt
  366. else if StopReason = 'read-watchpoint-trigger' then
  367. BreakpointNo := GDB.ExecAsyncOutput.Parameters['hw-rwpt'].AsTuple['number'].AsLongInt
  368. else
  369. BreakpointNo := 0;
  370. if Assigned(GDB.ExecAsyncOutput.Parameters['frame']) then
  371. begin
  372. if Assigned(GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['addr']) then
  373. Addr := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['addr'].AsCoreAddr
  374. else
  375. Addr := 0;
  376. if Assigned(GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['fullname']) then
  377. FileName := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['fullname'].AsString;
  378. if Assigned(GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['line']) then
  379. LineNumber := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['line'].AsLongInt;
  380. end
  381. else
  382. begin
  383. if Assigned(GDB.ExecAsyncOutput.Parameters['fullname']) then
  384. FileName := GDB.ExecAsyncOutput.Parameters['fullname'].AsString;
  385. if Assigned(GDB.ExecAsyncOutput.Parameters['line']) then
  386. LineNumber := GDB.ExecAsyncOutput.Parameters['line'].AsLongInt;
  387. end;
  388. { this kills GDB.ExecAsyncOutput, because it may execute other gdb commands, so
  389. make sure we have read all parameters that we need to local variables before that }
  390. DebuggerScreen;
  391. set_debuggee_started;
  392. current_pc := Addr;
  393. if not DoSelectSourceLine(FileName, LineNumber, BreakpointNo) then
  394. begin
  395. UserScreen;
  396. i_gdb_command('-exec-continue');
  397. if not GDB.ResultRecord.Success then
  398. begin
  399. DebuggerScreen;
  400. got_error := True;
  401. exit;
  402. end;
  403. goto Ignore;
  404. end;
  405. end;
  406. 'exited-signalled':
  407. begin
  408. DebuggerScreen;
  409. current_pc := 0;
  410. Debuggee_started := False;
  411. { TODO: maybe show information to the user about the signal
  412. we have:
  413. GDB.ExecAsyncOutput.Parameters['signal-name'].AsString (e.g. 'SIGTERM')
  414. GDB.ExecAsyncOutput.PArameters['signal-meaning'].AsString (e.g. 'Terminated')
  415. }
  416. DoEndSession(1);
  417. end;
  418. 'exited':
  419. begin
  420. ExitCode := LongInt(GDB.ExecAsyncOutput.Parameters['exit-code'].AsLongWord);
  421. DebuggerScreen;
  422. current_pc := 0;
  423. Debuggee_started := False;
  424. DoEndSession(ExitCode);
  425. end;
  426. 'exited-normally':
  427. begin
  428. DebuggerScreen;
  429. current_pc := 0;
  430. Debuggee_started := False;
  431. DoEndSession(0);
  432. end;
  433. end;
  434. end;
  435. procedure TGDBInterface.ProcessResponse;
  436. //var
  437. // NAO: TGDBMI_AsyncOutput;
  438. // Code: LongInt;
  439. begin
  440. // for NAO in GDB.NotifyAsyncOutput do
  441. // begin
  442. // if NAO.AsyncClass = 'breakpoint-created' then
  443. // begin
  444. // Writeln('BREAKPOINT created!');
  445. // Val(NAO.Parameters['bkpt'].AsTuple['number'].AsString, last_breakpoint_number, Code);
  446. // Writeln('last_breakpoint_number=', last_breakpoint_number);
  447. // end;
  448. // end;
  449. end;
  450. function TGDBInterface.error: Boolean;
  451. begin
  452. error := got_error or not GDB.Alive;
  453. end;
  454. function TGDBInterface.error_num: LongInt;
  455. begin
  456. error_num := 0; { TODO }
  457. end;
  458. function TGDBInterface.get_current_frame: PtrInt;
  459. begin
  460. i_gdb_command('-stack-info-frame');
  461. if GDB.ResultRecord.Success then
  462. get_current_frame := GDB.ResultRecord.Parameters['frame'].AsTuple['level'].AsLongInt
  463. else
  464. get_current_frame := 0;
  465. end;
  466. function TGDBInterface.set_current_frame(level: LongInt): Boolean;
  467. var
  468. s: string;
  469. begin
  470. str(level,s);
  471. { Note: according to the gdb docs, '-stack-select-frame' is deprecated in favor of passing the '--frame' option to every command }
  472. i_gdb_command('-stack-select-frame '+s);
  473. set_current_frame := GDB.ResultRecord.Success;
  474. end;
  475. procedure TGDBInterface.clear_frames;
  476. var
  477. I: LongInt;
  478. begin
  479. for I := 0 to frame_count - 1 do
  480. Dispose(frames[I], Done);
  481. if Assigned(frames) then
  482. begin
  483. FreeMem(frames, SizeOf(Pointer) * frame_count);
  484. frames := nil;
  485. end;
  486. frame_count := 0;
  487. end;
  488. procedure TGDBInterface.DebuggerScreen;
  489. begin
  490. if user_screen_shown then
  491. DoDebuggerScreen;
  492. user_screen_shown := False;
  493. end;
  494. procedure TGDBInterface.UserScreen;
  495. begin
  496. if switch_to_user then
  497. begin
  498. if not user_screen_shown then
  499. DoUserScreen;
  500. user_screen_shown := True;
  501. end;
  502. end;
  503. procedure TGDBInterface.FlushAll;
  504. begin
  505. end;
  506. function TGDBInterface.Query(question: PChar; args: PChar): LongInt;
  507. begin
  508. Query := 0;
  509. end;
  510. function TGDBInterface.DoSelectSourceline(const fn: string; line, BreakIndex: LongInt): Boolean;
  511. begin
  512. end;
  513. procedure TGDBInterface.DoStartSession;
  514. begin
  515. end;
  516. procedure TGDBInterface.DoBreakSession;
  517. begin
  518. end;
  519. procedure TGDBInterface.DoEndSession(code: LongInt);
  520. begin
  521. end;
  522. procedure TGDBInterface.DoUserSignal;
  523. begin
  524. end;
  525. procedure TGDBInterface.DoDebuggerScreen;
  526. begin
  527. end;
  528. procedure TGDBInterface.DoUserScreen;
  529. begin
  530. end;
  531. function TGDBInterface.AllowQuit: Boolean;
  532. begin
  533. AllowQuit := True;
  534. end;
  535. function inferior_pid : longint;
  536. begin
  537. inferior_pid:=0; {inferior_ptid.pid; }
  538. end;
  539. var
  540. CachedGDBVersion: string;
  541. CachedGDBVersionOK : boolean;
  542. function GDBVersion: string;
  543. var
  544. GDB: TGDBWrapper;
  545. {$ifdef windows}
  546. i : longint;
  547. line :string;
  548. {$endif windows}
  549. begin
  550. if CachedGDBVersion <> '' then
  551. begin
  552. GDBVersion := CachedGDBVersion;
  553. exit;
  554. end;
  555. GDBVersion := '';
  556. GDB := TGDBWrapper.Create;
  557. GDB.Command('-gdb-version');
  558. if GDB.ConsoleStream.Count > 0 then
  559. GDBVersion := GDB.ConsoleStream[0];
  560. if (GDBVersion <> '') and (GDBVersion[Length(GDBVersion)]=#10) then
  561. Delete(GDBVersion, Length(GDBVersion), 1);
  562. {$ifdef windows}
  563. i:=0;
  564. using_cygwin_gdb:=false;
  565. while i < GDB.ConsoleStream.Count do
  566. begin
  567. line:=GDB.ConsoleStream[i];
  568. if pos('This GDB was configured',line) > 0 then
  569. using_cygwin_gdb:=pos('cygwin',line) > 0;
  570. inc(i);
  571. end;
  572. {$endif windows}
  573. GDB.Free;
  574. CachedGDBVersion := GDBVersion;
  575. if GDBVersion = '' then
  576. begin
  577. GDBVersion := 'GDB missing or does not work'#13
  578. +#3'Consider using -G command line option'#13
  579. +#3'or set FPIDE_GDBPROC environment variable'#13
  580. +#3'to specify full path to GDB';
  581. CachedGDBVersionOK := false;
  582. end;
  583. end;
  584. function GDBVersionOK: boolean;
  585. var
  586. S : string;
  587. begin
  588. { Be sure GDBVersion is called }
  589. S:=GDBVersion;
  590. GDBVersionOK := CachedGDBVersionOK;
  591. end;
  592. begin
  593. CachedGDBVersion := '';
  594. CachedGDBVersionOK := true;
  595. end.