gdbmiint.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  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. protected
  52. GDB: TGDBWrapper;
  53. procedure i_gdb_command(const S: string);
  54. procedure WaitForProgramStop;
  55. procedure ProcessResponse;
  56. public
  57. GDBErrorBuf: TGDBBuffer;
  58. GDBOutputBuf: TGDBBuffer;
  59. got_error: Boolean;
  60. reset_command: Boolean;
  61. Debuggee_started: Boolean;
  62. { frames and frame info while recording a frame }
  63. frames: PPFrameEntry;
  64. frame_count: LongInt;
  65. command_level,
  66. stop_breakpoint_number: LongInt;
  67. signal_name: PChar;
  68. signal_string: PChar;
  69. current_pc: CORE_ADDR;
  70. last_breakpoint_number: LongInt;
  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. { other standard commands used for fpc debugging }
  199. i_gdb_command('-gdb-set print demangle off');
  200. i_gdb_command('-gdb-set gnutarget auto');
  201. i_gdb_command('-gdb-set language auto');
  202. i_gdb_command('-gdb-set print vtbl on');
  203. i_gdb_command('-gdb-set print object on');
  204. i_gdb_command('-gdb-set print null-stop');
  205. end;
  206. destructor TGDBInterface.Done;
  207. begin
  208. clear_frames;
  209. GDB.Free;
  210. GDBErrorBuf.Done;
  211. GDBOutputBuf.Done;
  212. end;
  213. function TGDBInterface.GetOutput: PChar;
  214. begin
  215. GetOutput := GDBOutputBuf.buf;
  216. end;
  217. function TGDBInterface.GetError: PChar;
  218. var
  219. p: PChar;
  220. begin
  221. p := GDBErrorBuf.buf;
  222. if (p^=#0) and got_error then
  223. GetError := PChar(PtrInt(GDBOutputBuf.buf) + GDBOutputBuf.idx)
  224. else
  225. GetError := p;
  226. end;
  227. procedure TGDBInterface.i_gdb_command(const S: string);
  228. var
  229. prev_stop_breakpoint_number: LongInt;
  230. I: LongInt;
  231. begin
  232. Inc(command_level);
  233. got_error := False;
  234. if command_level = 1 then
  235. prev_stop_breakpoint_number := 0
  236. else
  237. prev_stop_breakpoint_number := stop_breakpoint_number;
  238. GDB.Command(S);
  239. for I := 0 to GDB.ConsoleStream.Count - 1 do
  240. GDBOutputBuf.Append(PChar(GDB.ConsoleStream[I]));
  241. ProcessResponse;
  242. Dec(command_level);
  243. stop_breakpoint_number := prev_stop_breakpoint_number;
  244. end;
  245. procedure TGDBInterface.WaitForProgramStop;
  246. var
  247. StopReason: string;
  248. Line: LongInt;
  249. FileName: string = '';
  250. LineNumber: LongInt = 0;
  251. begin
  252. GDB.WaitForProgramStop;
  253. if not GDB.Alive then
  254. begin
  255. DebuggerScreen;
  256. current_pc := 0;
  257. Debuggee_started := False;
  258. exit;
  259. end;
  260. ProcessResponse;
  261. StopReason := GDB.ExecAsyncOutput.Parameters['reason'].AsString;
  262. case StopReason of
  263. 'breakpoint-hit',
  264. 'watchpoint-trigger',
  265. 'end-stepping-range',
  266. 'function-finished':
  267. begin
  268. { this resets stop_breakpoint_number to zero, so it's important to set it *afterwards* }
  269. DebuggerScreen;
  270. { now, set stop_breakpoint_number (if applicable) }
  271. if StopReason = 'breakpoint-hit' then
  272. stop_breakpoint_number := GDB.ExecAsyncOutput.Parameters['bkptno'].AsLongInt;
  273. if StopReason = 'watchpoint-trigger' then
  274. stop_breakpoint_number := GDB.ExecAsyncOutput.Parameters['wpt'].AsTuple['number'].AsLongInt;
  275. Debuggee_started := True;
  276. current_pc := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['addr'].AsPtrInt;
  277. if Assigned(GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['fullname']) then
  278. FileName := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['fullname'].AsString;
  279. if Assigned(GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['line']) then
  280. LineNumber := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['line'].AsLongInt;
  281. DoSelectSourceLine(FileName, LineNumber);
  282. end;
  283. 'exited':
  284. begin
  285. DebuggerScreen;
  286. current_pc := 0;
  287. Debuggee_started := False;
  288. DoEndSession(GDB.ExecAsyncOutput.Parameters['exit-code'].AsLongInt);
  289. end;
  290. 'exited-normally':
  291. begin
  292. DebuggerScreen;
  293. current_pc := 0;
  294. Debuggee_started := False;
  295. DoEndSession(0);
  296. end;
  297. end;
  298. end;
  299. procedure TGDBInterface.ProcessResponse;
  300. var
  301. NAO: TGDBMI_AsyncOutput;
  302. Code: LongInt;
  303. begin
  304. for NAO in GDB.NotifyAsyncOutput do
  305. begin
  306. if NAO.AsyncClass = 'breakpoint-created' then
  307. begin
  308. // Writeln('BREAKPOINT created!');
  309. Val(NAO.Parameters['bkpt'].AsTuple['number'].AsString, last_breakpoint_number, Code);
  310. // Writeln('last_breakpoint_number=', last_breakpoint_number);
  311. // if Assigned(NAO.Parameters['bkpt'].AsTuple['file']) then
  312. // Writeln('file = ', NAO.Parameters['bkpt'].AsTuple['file'].AsString);
  313. // Readln;
  314. end;
  315. end;
  316. end;
  317. function TGDBInterface.error: Boolean;
  318. begin
  319. error := got_error or not GDB.Alive;
  320. end;
  321. function TGDBInterface.error_num: LongInt;
  322. begin
  323. error_num := 0; { TODO }
  324. end;
  325. function TGDBInterface.get_current_frame: PtrInt;
  326. begin
  327. i_gdb_command('-stack-info-frame');
  328. if GDB.ResultRecord.Success then
  329. get_current_frame := GDB.ResultRecord.Parameters['frame'].AsTuple['level'].AsLongInt
  330. else
  331. get_current_frame := 0;
  332. end;
  333. function TGDBInterface.set_current_frame(level: LongInt): Boolean;
  334. var
  335. s: string;
  336. begin
  337. str(level,s);
  338. { Note: according to the gdb docs, '-stack-select-frame' is deprecated in favor of passing the '--frame' option to every command }
  339. i_gdb_command('-stack-select-frame '+s);
  340. set_current_frame := GDB.ResultRecord.Success;
  341. end;
  342. procedure TGDBInterface.clear_frames;
  343. var
  344. I: LongInt;
  345. begin
  346. for I := 0 to frame_count - 1 do
  347. Dispose(frames[I], Done);
  348. if Assigned(frames) then
  349. begin
  350. FreeMem(frames, SizeOf(Pointer) * frame_count);
  351. frames := nil;
  352. end;
  353. frame_count := 0;
  354. end;
  355. procedure TGDBInterface.DebuggerScreen;
  356. begin
  357. if user_screen_shown then
  358. DoDebuggerScreen;
  359. user_screen_shown := False;
  360. end;
  361. procedure TGDBInterface.UserScreen;
  362. begin
  363. if switch_to_user then
  364. begin
  365. if not user_screen_shown then
  366. DoUserScreen;
  367. user_screen_shown := True;
  368. end;
  369. end;
  370. procedure TGDBInterface.FlushAll;
  371. begin
  372. end;
  373. function TGDBInterface.Query(question: PChar; args: PChar): LongInt;
  374. begin
  375. Query := 0;
  376. end;
  377. procedure TGDBInterface.DoSelectSourceline(const fn: string; line: LongInt);
  378. begin
  379. end;
  380. procedure TGDBInterface.DoStartSession;
  381. begin
  382. end;
  383. procedure TGDBInterface.DoBreakSession;
  384. begin
  385. end;
  386. procedure TGDBInterface.DoEndSession(code: LongInt);
  387. begin
  388. end;
  389. procedure TGDBInterface.DoUserSignal;
  390. begin
  391. end;
  392. procedure TGDBInterface.DoDebuggerScreen;
  393. begin
  394. end;
  395. procedure TGDBInterface.DoUserScreen;
  396. begin
  397. end;
  398. function TGDBInterface.AllowQuit: Boolean;
  399. begin
  400. AllowQuit := True;
  401. end;
  402. function inferior_pid : longint;
  403. begin
  404. inferior_pid:=0; {inferior_ptid.pid; }
  405. end;
  406. var
  407. CachedGDBVersion: string;
  408. function GDBVersion: string;
  409. var
  410. GDB: TGDBWrapper;
  411. {$ifdef windows}
  412. i : longint;
  413. line :string;
  414. {$endif windows}
  415. begin
  416. if CachedGDBVersion <> '' then
  417. begin
  418. GDBVersion := CachedGDBVersion;
  419. exit;
  420. end;
  421. GDBVersion := '';
  422. GDB := TGDBWrapper.Create;
  423. GDB.Command('-gdb-version');
  424. if GDB.ConsoleStream.Count > 0 then
  425. GDBVersion := GDB.ConsoleStream[0];
  426. if (GDBVersion <> '') and (GDBVersion[Length(GDBVersion)]=#10) then
  427. Delete(GDBVersion, Length(GDBVersion), 1);
  428. {$ifdef windows}
  429. i:=0;
  430. using_cygwin_gdb:=false;
  431. while i < GDB.ConsoleStream.Count do
  432. begin
  433. line:=GDB.ConsoleStream[i];
  434. if pos('This GDB was configured',line) > 0 then
  435. using_cygwin_gdb:=pos('cygwin',line) > 0;
  436. inc(i);
  437. end;
  438. {$endif windows}
  439. GDB.Free;
  440. CachedGDBVersion := GDBVersion;
  441. if GDBVersion = '' then
  442. GDBVersion := 'GDB missing or does not work';
  443. end;
  444. begin
  445. CachedGDBVersion := '';
  446. end.