gdbmiint.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  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. interface
  16. uses
  17. gdbmiwrap;
  18. type
  19. CORE_ADDR = PtrInt;
  20. PPFrameEntry = ^PFrameEntry;
  21. PFrameEntry = ^TFrameEntry;
  22. TFrameEntry = object
  23. private
  24. procedure Reset;
  25. procedure Clear;
  26. public
  27. file_name: PChar;
  28. function_name: PChar;
  29. args: PChar;
  30. line_number: LongInt;
  31. address: PtrInt;
  32. level : longint;
  33. constructor Init;
  34. destructor Done;
  35. end;
  36. TGDBBuffer = object
  37. private
  38. buf: PChar;
  39. size, idx: LongInt;
  40. procedure Resize(nsize: LongInt);
  41. procedure Append(p: PChar);
  42. procedure LAppend(p: PChar; len: LongInt);
  43. public
  44. constructor Init;
  45. destructor Done;
  46. procedure Reset;
  47. end;
  48. TGDBInterface = object
  49. private
  50. user_screen_shown: Boolean;
  51. output_raw : boolean;
  52. protected
  53. GDB: TGDBWrapper;
  54. procedure i_gdb_command(const S: string);
  55. procedure WaitForProgramStop;
  56. procedure ProcessResponse;
  57. public
  58. GDBErrorBuf: TGDBBuffer;
  59. GDBOutputBuf: TGDBBuffer;
  60. got_error: Boolean;
  61. reset_command: Boolean;
  62. Debuggee_started: Boolean;
  63. { frames and frame info while recording a frame }
  64. frames: PPFrameEntry;
  65. frame_count: LongInt;
  66. command_level,
  67. stop_breakpoint_number: LongInt;
  68. signal_name: PChar;
  69. signal_string: PChar;
  70. current_pc: CORE_ADDR;
  71. switch_to_user: Boolean;
  72. { init }
  73. constructor Init;
  74. destructor Done;
  75. { from gdbcon }
  76. function GetOutput: PChar;
  77. function GetError: PChar;
  78. { Lowlevel }
  79. function error: Boolean;
  80. function error_num: LongInt;
  81. function get_current_frame: PtrInt;
  82. function set_current_frame(level: LongInt): Boolean;
  83. procedure clear_frames;
  84. { Highlevel }
  85. procedure DebuggerScreen;
  86. procedure UserScreen;
  87. procedure FlushAll; virtual;
  88. function Query(question: PChar; args: PChar): LongInt; virtual;
  89. { Hooks }
  90. procedure DoSelectSourceline(const fn: string; line: LongInt); virtual;
  91. procedure DoStartSession; virtual;
  92. procedure DoBreakSession; virtual;
  93. procedure DoEndSession(code: LongInt); virtual;
  94. procedure DoUserSignal; virtual;
  95. procedure DoDebuggerScreen; virtual;
  96. procedure DoUserScreen; virtual;
  97. function AllowQuit: Boolean; virtual;
  98. end;
  99. const
  100. use_gdb_file: Boolean = False;
  101. var
  102. gdb_file: Text;
  103. function GDBVersion: string;
  104. function inferior_pid : longint;
  105. {$ifdef windows}
  106. { We need to do some path conversions if we are using Cygwin GDB }
  107. var
  108. using_cygwin_gdb : boolean;
  109. {$endif windows}
  110. implementation
  111. uses
  112. strings;
  113. constructor TFrameEntry.Init;
  114. begin
  115. Reset;
  116. end;
  117. destructor TFrameEntry.Done;
  118. begin
  119. Clear;
  120. end;
  121. procedure TFrameEntry.Reset;
  122. begin
  123. file_name := nil;
  124. function_name := nil;
  125. args := nil;
  126. line_number := 0;
  127. address := 0;
  128. level := 0;
  129. end;
  130. procedure TFrameEntry.Clear;
  131. begin
  132. if Assigned(file_name) then
  133. StrDispose(file_name);
  134. if Assigned(function_name) then
  135. StrDispose(function_name);
  136. if Assigned(args) then
  137. StrDispose(args);
  138. Reset;
  139. end;
  140. const
  141. BlockSize = 2048;
  142. constructor TGDBBuffer.Init;
  143. begin
  144. buf := nil;
  145. size := 0;
  146. Resize(BlockSize);
  147. Reset;
  148. end;
  149. destructor TGDBBuffer.Done;
  150. begin
  151. if Assigned(buf) then
  152. FreeMem(buf, size);
  153. end;
  154. procedure TGDBBuffer.Reset;
  155. begin
  156. idx := 0;
  157. buf[0] := #0;
  158. end;
  159. procedure TGDBBuffer.Resize(nsize: LongInt);
  160. var
  161. np: PChar;
  162. begin
  163. nsize := ((nsize + BlockSize - 1) div BlockSize) * BlockSize;
  164. GetMem(np, nsize);
  165. if Assigned(buf) then
  166. begin
  167. Move(buf^, np^, size);
  168. FreeMem(buf, size);
  169. end;
  170. buf := np;
  171. size := nsize;
  172. end;
  173. procedure TGDBBuffer.Append(p: PChar);
  174. var
  175. len: LongInt;
  176. begin
  177. if not Assigned(p) then
  178. exit;
  179. len := StrLen(p);
  180. LAppend(p, len);
  181. end;
  182. procedure TGDBBuffer.LAppend(p: PChar; len: LongInt);
  183. begin
  184. if not Assigned(p) then
  185. exit;
  186. if (len + idx + 1) > size then
  187. Resize(len + idx + 1);
  188. Move(p^, buf[idx], len);
  189. Inc(idx, len);
  190. buf[idx] := #0;
  191. end;
  192. constructor TGDBInterface.Init;
  193. begin
  194. GDBErrorBuf.Init;
  195. GDBOutputBuf.Init;
  196. GDB := TGDBWrapper.Create;
  197. command_level := 0;
  198. {$ifdef DEBUG}
  199. output_raw:=true;
  200. {$else}
  201. output_raw:=false;
  202. {$endif}
  203. { other standard commands used for fpc debugging }
  204. i_gdb_command('-gdb-set print demangle off');
  205. i_gdb_command('-gdb-set gnutarget auto');
  206. i_gdb_command('-gdb-set language auto');
  207. i_gdb_command('-gdb-set print vtbl on');
  208. i_gdb_command('-gdb-set print object on');
  209. i_gdb_command('-gdb-set print null-stop');
  210. end;
  211. destructor TGDBInterface.Done;
  212. begin
  213. clear_frames;
  214. GDB.Free;
  215. GDBErrorBuf.Done;
  216. GDBOutputBuf.Done;
  217. end;
  218. function TGDBInterface.GetOutput: PChar;
  219. begin
  220. GetOutput := GDBOutputBuf.buf;
  221. end;
  222. function TGDBInterface.GetError: PChar;
  223. var
  224. p: PChar;
  225. begin
  226. p := GDBErrorBuf.buf;
  227. if (p^=#0) and got_error then
  228. GetError := PChar(PtrInt(GDBOutputBuf.buf) + GDBOutputBuf.idx)
  229. else
  230. GetError := p;
  231. end;
  232. procedure TGDBInterface.i_gdb_command(const S: string);
  233. var
  234. prev_stop_breakpoint_number: LongInt;
  235. I: LongInt;
  236. begin
  237. Inc(command_level);
  238. got_error := False;
  239. if command_level = 1 then
  240. prev_stop_breakpoint_number := 0
  241. else
  242. prev_stop_breakpoint_number := stop_breakpoint_number;
  243. GDB.Command(S);
  244. if output_raw then
  245. for I := 0 to GDB.RawResponse.Count - 1 do
  246. GDBOutputBuf.Append(PChar(GDB.RawResponse[I]));
  247. for I := 0 to GDB.ConsoleStream.Count - 1 do
  248. GDBOutputBuf.Append(PChar(GDB.ConsoleStream[I]));
  249. if GDB.ResultRecord.AsyncClass='error' then
  250. begin
  251. got_error := True;
  252. if Assigned(GDB.ResultRecord.Parameters['msg']) then
  253. GDBErrorBuf.Append(PChar(GDB.ResultRecord.Parameters['msg'].AsString));
  254. end;
  255. ProcessResponse;
  256. Dec(command_level);
  257. stop_breakpoint_number := prev_stop_breakpoint_number;
  258. end;
  259. procedure TGDBInterface.WaitForProgramStop;
  260. label
  261. Ignore;
  262. var
  263. StopReason: string;
  264. Line: LongInt;
  265. FileName: string = '';
  266. LineNumber: LongInt = 0;
  267. begin
  268. Ignore:
  269. GDB.WaitForProgramStop;
  270. if not GDB.Alive then
  271. begin
  272. DebuggerScreen;
  273. current_pc := 0;
  274. Debuggee_started := False;
  275. exit;
  276. end;
  277. ProcessResponse;
  278. StopReason := GDB.ExecAsyncOutput.Parameters['reason'].AsString;
  279. case StopReason of
  280. 'watchpoint-scope':
  281. begin
  282. { A watchpoint has gone out of scope (e.g. if it was a local variable). TODO: should we stop
  283. the program and notify the user or maybe silently disable it in the breakpoint list and
  284. continue execution? The libgdb.a version of the debugger just silently ignores this case.
  285. We have: GDB.ExecAsyncOutput.Parameters['wpnum'].AsLongInt }
  286. i_gdb_command('-exec-continue');
  287. goto Ignore;
  288. end;
  289. 'signal-received':
  290. begin
  291. { TODO: maybe show information to the user about the signal
  292. we have:
  293. GDB.ExecAsyncOutput.Parameters['signal-name'].AsString (e.g. 'SIGTERM')
  294. GDB.ExecAsyncOutput.PArameters['signal-meaning'].AsString (e.g. 'Terminated')
  295. }
  296. i_gdb_command('-exec-continue');
  297. goto Ignore;
  298. end;
  299. 'breakpoint-hit',
  300. 'watchpoint-trigger',
  301. 'access-watchpoint-trigger',
  302. 'read-watchpoint-trigger',
  303. 'end-stepping-range',
  304. 'function-finished':
  305. begin
  306. { this resets stop_breakpoint_number to zero, so it's important to set it *afterwards* }
  307. DebuggerScreen;
  308. { now, set stop_breakpoint_number (if applicable) }
  309. if StopReason = 'breakpoint-hit' then
  310. stop_breakpoint_number := GDB.ExecAsyncOutput.Parameters['bkptno'].AsLongInt;
  311. if StopReason = 'watchpoint-trigger' then
  312. stop_breakpoint_number := GDB.ExecAsyncOutput.Parameters['wpt'].AsTuple['number'].AsLongInt;
  313. if StopReason = 'access-watchpoint-trigger' then
  314. stop_breakpoint_number := GDB.ExecAsyncOutput.Parameters['hw-awpt'].AsTuple['number'].AsLongInt;
  315. if StopReason = 'read-watchpoint-trigger' then
  316. stop_breakpoint_number := GDB.ExecAsyncOutput.Parameters['hw-rwpt'].AsTuple['number'].AsLongInt;
  317. Debuggee_started := True;
  318. current_pc := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['addr'].AsPtrInt;
  319. if Assigned(GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['fullname']) then
  320. FileName := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['fullname'].AsString;
  321. if Assigned(GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['line']) then
  322. LineNumber := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['line'].AsLongInt;
  323. DoSelectSourceLine(FileName, LineNumber);
  324. end;
  325. 'exited-signalled':
  326. begin
  327. DebuggerScreen;
  328. current_pc := 0;
  329. Debuggee_started := False;
  330. { TODO: maybe show information to the user about the signal
  331. we have:
  332. GDB.ExecAsyncOutput.Parameters['signal-name'].AsString (e.g. 'SIGTERM')
  333. GDB.ExecAsyncOutput.PArameters['signal-meaning'].AsString (e.g. 'Terminated')
  334. }
  335. DoEndSession(1);
  336. end;
  337. 'exited':
  338. begin
  339. DebuggerScreen;
  340. current_pc := 0;
  341. Debuggee_started := False;
  342. DoEndSession(GDB.ExecAsyncOutput.Parameters['exit-code'].AsLongInt);
  343. end;
  344. 'exited-normally':
  345. begin
  346. DebuggerScreen;
  347. current_pc := 0;
  348. Debuggee_started := False;
  349. DoEndSession(0);
  350. end;
  351. end;
  352. end;
  353. procedure TGDBInterface.ProcessResponse;
  354. //var
  355. // NAO: TGDBMI_AsyncOutput;
  356. // Code: LongInt;
  357. begin
  358. // for NAO in GDB.NotifyAsyncOutput do
  359. // begin
  360. // if NAO.AsyncClass = 'breakpoint-created' then
  361. // begin
  362. // Writeln('BREAKPOINT created!');
  363. // Val(NAO.Parameters['bkpt'].AsTuple['number'].AsString, last_breakpoint_number, Code);
  364. // Writeln('last_breakpoint_number=', last_breakpoint_number);
  365. // end;
  366. // end;
  367. end;
  368. function TGDBInterface.error: Boolean;
  369. begin
  370. error := got_error or not GDB.Alive;
  371. end;
  372. function TGDBInterface.error_num: LongInt;
  373. begin
  374. error_num := 0; { TODO }
  375. end;
  376. function TGDBInterface.get_current_frame: PtrInt;
  377. begin
  378. i_gdb_command('-stack-info-frame');
  379. if GDB.ResultRecord.Success then
  380. get_current_frame := GDB.ResultRecord.Parameters['frame'].AsTuple['level'].AsLongInt
  381. else
  382. get_current_frame := 0;
  383. end;
  384. function TGDBInterface.set_current_frame(level: LongInt): Boolean;
  385. var
  386. s: string;
  387. begin
  388. str(level,s);
  389. { Note: according to the gdb docs, '-stack-select-frame' is deprecated in favor of passing the '--frame' option to every command }
  390. i_gdb_command('-stack-select-frame '+s);
  391. set_current_frame := GDB.ResultRecord.Success;
  392. end;
  393. procedure TGDBInterface.clear_frames;
  394. var
  395. I: LongInt;
  396. begin
  397. for I := 0 to frame_count - 1 do
  398. Dispose(frames[I], Done);
  399. if Assigned(frames) then
  400. begin
  401. FreeMem(frames, SizeOf(Pointer) * frame_count);
  402. frames := nil;
  403. end;
  404. frame_count := 0;
  405. end;
  406. procedure TGDBInterface.DebuggerScreen;
  407. begin
  408. if user_screen_shown then
  409. DoDebuggerScreen;
  410. user_screen_shown := False;
  411. end;
  412. procedure TGDBInterface.UserScreen;
  413. begin
  414. if switch_to_user then
  415. begin
  416. if not user_screen_shown then
  417. DoUserScreen;
  418. user_screen_shown := True;
  419. end;
  420. end;
  421. procedure TGDBInterface.FlushAll;
  422. begin
  423. end;
  424. function TGDBInterface.Query(question: PChar; args: PChar): LongInt;
  425. begin
  426. Query := 0;
  427. end;
  428. procedure TGDBInterface.DoSelectSourceline(const fn: string; line: LongInt);
  429. begin
  430. end;
  431. procedure TGDBInterface.DoStartSession;
  432. begin
  433. end;
  434. procedure TGDBInterface.DoBreakSession;
  435. begin
  436. end;
  437. procedure TGDBInterface.DoEndSession(code: LongInt);
  438. begin
  439. end;
  440. procedure TGDBInterface.DoUserSignal;
  441. begin
  442. end;
  443. procedure TGDBInterface.DoDebuggerScreen;
  444. begin
  445. end;
  446. procedure TGDBInterface.DoUserScreen;
  447. begin
  448. end;
  449. function TGDBInterface.AllowQuit: Boolean;
  450. begin
  451. AllowQuit := True;
  452. end;
  453. function inferior_pid : longint;
  454. begin
  455. inferior_pid:=0; {inferior_ptid.pid; }
  456. end;
  457. var
  458. CachedGDBVersion: string;
  459. function GDBVersion: string;
  460. var
  461. GDB: TGDBWrapper;
  462. {$ifdef windows}
  463. i : longint;
  464. line :string;
  465. {$endif windows}
  466. begin
  467. if CachedGDBVersion <> '' then
  468. begin
  469. GDBVersion := CachedGDBVersion;
  470. exit;
  471. end;
  472. GDBVersion := '';
  473. GDB := TGDBWrapper.Create;
  474. GDB.Command('-gdb-version');
  475. if GDB.ConsoleStream.Count > 0 then
  476. GDBVersion := GDB.ConsoleStream[0];
  477. if (GDBVersion <> '') and (GDBVersion[Length(GDBVersion)]=#10) then
  478. Delete(GDBVersion, Length(GDBVersion), 1);
  479. {$ifdef windows}
  480. i:=0;
  481. using_cygwin_gdb:=false;
  482. while i < GDB.ConsoleStream.Count do
  483. begin
  484. line:=GDB.ConsoleStream[i];
  485. if pos('This GDB was configured',line) > 0 then
  486. using_cygwin_gdb:=pos('cygwin',line) > 0;
  487. inc(i);
  488. end;
  489. {$endif windows}
  490. GDB.Free;
  491. CachedGDBVersion := GDBVersion;
  492. if GDBVersion = '' then
  493. GDBVersion := 'GDB missing or does not work';
  494. end;
  495. begin
  496. CachedGDBVersion := '';
  497. end.