gdbmiint.pas 14 KB

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