gdbmiint.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  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. Addr := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['addr'].AsCoreAddr;
  371. if Assigned(GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['fullname']) then
  372. FileName := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['fullname'].AsString;
  373. if Assigned(GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['line']) then
  374. LineNumber := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['line'].AsLongInt;
  375. { this kills GDB.ExecAsyncOutput, because it may execute other gdb commands, so
  376. make sure we have read all parameters that we need to local variables before that }
  377. DebuggerScreen;
  378. set_debuggee_started;
  379. current_pc := Addr;
  380. if not DoSelectSourceLine(FileName, LineNumber, BreakpointNo) then
  381. begin
  382. UserScreen;
  383. i_gdb_command('-exec-continue');
  384. if not GDB.ResultRecord.Success then
  385. begin
  386. DebuggerScreen;
  387. got_error := True;
  388. exit;
  389. end;
  390. goto Ignore;
  391. end;
  392. end;
  393. 'exited-signalled':
  394. begin
  395. DebuggerScreen;
  396. current_pc := 0;
  397. Debuggee_started := False;
  398. { TODO: maybe show information to the user about the signal
  399. we have:
  400. GDB.ExecAsyncOutput.Parameters['signal-name'].AsString (e.g. 'SIGTERM')
  401. GDB.ExecAsyncOutput.PArameters['signal-meaning'].AsString (e.g. 'Terminated')
  402. }
  403. DoEndSession(1);
  404. end;
  405. 'exited':
  406. begin
  407. ExitCode := LongInt(GDB.ExecAsyncOutput.Parameters['exit-code'].AsLongWord);
  408. DebuggerScreen;
  409. current_pc := 0;
  410. Debuggee_started := False;
  411. DoEndSession(ExitCode);
  412. end;
  413. 'exited-normally':
  414. begin
  415. DebuggerScreen;
  416. current_pc := 0;
  417. Debuggee_started := False;
  418. DoEndSession(0);
  419. end;
  420. end;
  421. end;
  422. procedure TGDBInterface.ProcessResponse;
  423. //var
  424. // NAO: TGDBMI_AsyncOutput;
  425. // Code: LongInt;
  426. begin
  427. // for NAO in GDB.NotifyAsyncOutput do
  428. // begin
  429. // if NAO.AsyncClass = 'breakpoint-created' then
  430. // begin
  431. // Writeln('BREAKPOINT created!');
  432. // Val(NAO.Parameters['bkpt'].AsTuple['number'].AsString, last_breakpoint_number, Code);
  433. // Writeln('last_breakpoint_number=', last_breakpoint_number);
  434. // end;
  435. // end;
  436. end;
  437. function TGDBInterface.error: Boolean;
  438. begin
  439. error := got_error or not GDB.Alive;
  440. end;
  441. function TGDBInterface.error_num: LongInt;
  442. begin
  443. error_num := 0; { TODO }
  444. end;
  445. function TGDBInterface.get_current_frame: PtrInt;
  446. begin
  447. i_gdb_command('-stack-info-frame');
  448. if GDB.ResultRecord.Success then
  449. get_current_frame := GDB.ResultRecord.Parameters['frame'].AsTuple['level'].AsLongInt
  450. else
  451. get_current_frame := 0;
  452. end;
  453. function TGDBInterface.set_current_frame(level: LongInt): Boolean;
  454. var
  455. s: string;
  456. begin
  457. str(level,s);
  458. { Note: according to the gdb docs, '-stack-select-frame' is deprecated in favor of passing the '--frame' option to every command }
  459. i_gdb_command('-stack-select-frame '+s);
  460. set_current_frame := GDB.ResultRecord.Success;
  461. end;
  462. procedure TGDBInterface.clear_frames;
  463. var
  464. I: LongInt;
  465. begin
  466. for I := 0 to frame_count - 1 do
  467. Dispose(frames[I], Done);
  468. if Assigned(frames) then
  469. begin
  470. FreeMem(frames, SizeOf(Pointer) * frame_count);
  471. frames := nil;
  472. end;
  473. frame_count := 0;
  474. end;
  475. procedure TGDBInterface.DebuggerScreen;
  476. begin
  477. if user_screen_shown then
  478. DoDebuggerScreen;
  479. user_screen_shown := False;
  480. end;
  481. procedure TGDBInterface.UserScreen;
  482. begin
  483. if switch_to_user then
  484. begin
  485. if not user_screen_shown then
  486. DoUserScreen;
  487. user_screen_shown := True;
  488. end;
  489. end;
  490. procedure TGDBInterface.FlushAll;
  491. begin
  492. end;
  493. function TGDBInterface.Query(question: PChar; args: PChar): LongInt;
  494. begin
  495. Query := 0;
  496. end;
  497. function TGDBInterface.DoSelectSourceline(const fn: string; line, BreakIndex: LongInt): Boolean;
  498. begin
  499. end;
  500. procedure TGDBInterface.DoStartSession;
  501. begin
  502. end;
  503. procedure TGDBInterface.DoBreakSession;
  504. begin
  505. end;
  506. procedure TGDBInterface.DoEndSession(code: LongInt);
  507. begin
  508. end;
  509. procedure TGDBInterface.DoUserSignal;
  510. begin
  511. end;
  512. procedure TGDBInterface.DoDebuggerScreen;
  513. begin
  514. end;
  515. procedure TGDBInterface.DoUserScreen;
  516. begin
  517. end;
  518. function TGDBInterface.AllowQuit: Boolean;
  519. begin
  520. AllowQuit := True;
  521. end;
  522. function inferior_pid : longint;
  523. begin
  524. inferior_pid:=0; {inferior_ptid.pid; }
  525. end;
  526. var
  527. CachedGDBVersion: string;
  528. CachedGDBVersionOK : boolean;
  529. function GDBVersion: string;
  530. var
  531. GDB: TGDBWrapper;
  532. {$ifdef windows}
  533. i : longint;
  534. line :string;
  535. {$endif windows}
  536. begin
  537. if CachedGDBVersion <> '' then
  538. begin
  539. GDBVersion := CachedGDBVersion;
  540. exit;
  541. end;
  542. GDBVersion := '';
  543. GDB := TGDBWrapper.Create;
  544. GDB.Command('-gdb-version');
  545. if GDB.ConsoleStream.Count > 0 then
  546. GDBVersion := GDB.ConsoleStream[0];
  547. if (GDBVersion <> '') and (GDBVersion[Length(GDBVersion)]=#10) then
  548. Delete(GDBVersion, Length(GDBVersion), 1);
  549. {$ifdef windows}
  550. i:=0;
  551. using_cygwin_gdb:=false;
  552. while i < GDB.ConsoleStream.Count do
  553. begin
  554. line:=GDB.ConsoleStream[i];
  555. if pos('This GDB was configured',line) > 0 then
  556. using_cygwin_gdb:=pos('cygwin',line) > 0;
  557. inc(i);
  558. end;
  559. {$endif windows}
  560. GDB.Free;
  561. CachedGDBVersion := GDBVersion;
  562. if GDBVersion = '' then
  563. begin
  564. GDBVersion := 'GDB missing or does not work'#13
  565. +#3'Consider using -G command line option'#13
  566. +#3'or set FPIDE_GDBPROC environment variable'#13
  567. +#3'to specify full path to GDB';
  568. CachedGDBVersionOK := false;
  569. end;
  570. end;
  571. function GDBVersionOK: boolean;
  572. var
  573. S : string;
  574. begin
  575. { Be sure GDBVersion is called }
  576. S:=GDBVersion;
  577. GDBVersionOK := CachedGDBVersionOK;
  578. end;
  579. begin
  580. CachedGDBVersion := '';
  581. CachedGDBVersionOK := true;
  582. end.