gdbmiint.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  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. if StopReason = 'breakpoint-hit' then
  269. stop_breakpoint_number := GDB.ExecAsyncOutput.Parameters['bkptno'].AsLongInt;
  270. if StopReason = 'watchpoint-trigger' then
  271. stop_breakpoint_number := GDB.ExecAsyncOutput.Parameters['wpt'].AsTuple['number'].AsLongInt;
  272. DebuggerScreen;
  273. Debuggee_started := True;
  274. current_pc := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['addr'].AsPtrInt;
  275. if Assigned(GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['fullname']) then
  276. FileName := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['fullname'].AsString;
  277. if Assigned(GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['line']) then
  278. LineNumber := GDB.ExecAsyncOutput.Parameters['frame'].AsTuple['line'].AsLongInt;
  279. DoSelectSourceLine(FileName, LineNumber);
  280. end;
  281. 'exited':
  282. begin
  283. DebuggerScreen;
  284. current_pc := 0;
  285. Debuggee_started := False;
  286. DoEndSession(GDB.ExecAsyncOutput.Parameters['exit-code'].AsLongInt);
  287. end;
  288. 'exited-normally':
  289. begin
  290. DebuggerScreen;
  291. current_pc := 0;
  292. Debuggee_started := False;
  293. DoEndSession(0);
  294. end;
  295. end;
  296. end;
  297. procedure TGDBInterface.ProcessResponse;
  298. var
  299. NAO: TGDBMI_AsyncOutput;
  300. Code: LongInt;
  301. begin
  302. for NAO in GDB.NotifyAsyncOutput do
  303. begin
  304. if NAO.AsyncClass = 'breakpoint-created' then
  305. begin
  306. // Writeln('BREAKPOINT created!');
  307. Val(NAO.Parameters['bkpt'].AsTuple['number'].AsString, last_breakpoint_number, Code);
  308. // Writeln('last_breakpoint_number=', last_breakpoint_number);
  309. // if Assigned(NAO.Parameters['bkpt'].AsTuple['file']) then
  310. // Writeln('file = ', NAO.Parameters['bkpt'].AsTuple['file'].AsString);
  311. // Readln;
  312. end;
  313. end;
  314. end;
  315. function TGDBInterface.error: Boolean;
  316. begin
  317. error := got_error or not GDB.Alive;
  318. end;
  319. function TGDBInterface.error_num: LongInt;
  320. begin
  321. error_num := 0; { TODO }
  322. end;
  323. function TGDBInterface.get_current_frame: PtrInt;
  324. begin
  325. i_gdb_command('-stack-info-frame');
  326. if GDB.ResultRecord.Success then
  327. get_current_frame := GDB.ResultRecord.Parameters['frame'].AsTuple['level'].AsLongInt
  328. else
  329. get_current_frame := 0;
  330. end;
  331. function TGDBInterface.set_current_frame(level: LongInt): Boolean;
  332. var
  333. s: string;
  334. begin
  335. str(level,s);
  336. { Note: according to the gdb docs, '-stack-select-frame' is deprecated in favor of passing the '--frame' option to every command }
  337. i_gdb_command('-stack-select-frame '+s);
  338. set_current_frame := GDB.ResultRecord.Success;
  339. end;
  340. procedure TGDBInterface.clear_frames;
  341. var
  342. I: LongInt;
  343. begin
  344. for I := 0 to frame_count - 1 do
  345. Dispose(frames[I], Done);
  346. if Assigned(frames) then
  347. begin
  348. FreeMem(frames, SizeOf(Pointer) * frame_count);
  349. frames := nil;
  350. end;
  351. frame_count := 0;
  352. end;
  353. procedure TGDBInterface.DebuggerScreen;
  354. begin
  355. if user_screen_shown then
  356. DoDebuggerScreen;
  357. user_screen_shown := False;
  358. end;
  359. procedure TGDBInterface.UserScreen;
  360. begin
  361. if switch_to_user then
  362. begin
  363. if not user_screen_shown then
  364. DoUserScreen;
  365. user_screen_shown := True;
  366. end;
  367. end;
  368. procedure TGDBInterface.FlushAll;
  369. begin
  370. end;
  371. function TGDBInterface.Query(question: PChar; args: PChar): LongInt;
  372. begin
  373. Query := 0;
  374. end;
  375. procedure TGDBInterface.DoSelectSourceline(const fn: string; line: LongInt);
  376. begin
  377. end;
  378. procedure TGDBInterface.DoStartSession;
  379. begin
  380. end;
  381. procedure TGDBInterface.DoBreakSession;
  382. begin
  383. end;
  384. procedure TGDBInterface.DoEndSession(code: LongInt);
  385. begin
  386. end;
  387. procedure TGDBInterface.DoUserSignal;
  388. begin
  389. end;
  390. procedure TGDBInterface.DoDebuggerScreen;
  391. begin
  392. end;
  393. procedure TGDBInterface.DoUserScreen;
  394. begin
  395. end;
  396. function TGDBInterface.AllowQuit: Boolean;
  397. begin
  398. AllowQuit := True;
  399. end;
  400. function inferior_pid : longint;
  401. begin
  402. inferior_pid:=0; {inferior_ptid.pid; }
  403. end;
  404. var
  405. CachedGDBVersion: string;
  406. function GDBVersion: string;
  407. var
  408. GDB: TGDBWrapper;
  409. {$ifdef windows}
  410. i : longint;
  411. line :string;
  412. {$endif windows}
  413. begin
  414. if CachedGDBVersion <> '' then
  415. begin
  416. GDBVersion := CachedGDBVersion;
  417. exit;
  418. end;
  419. GDBVersion := '';
  420. GDB := TGDBWrapper.Create;
  421. GDB.Command('-gdb-version');
  422. if GDB.ConsoleStream.Count > 0 then
  423. GDBVersion := GDB.ConsoleStream[0];
  424. if (GDBVersion <> '') and (GDBVersion[Length(GDBVersion)]=#10) then
  425. Delete(GDBVersion, Length(GDBVersion), 1);
  426. {$ifdef windows}
  427. i:=0;
  428. using_cygwin_gdb:=false;
  429. while i < GDB.ConsoleStream.Count do
  430. begin
  431. line:=GDB.ConsoleStream[i];
  432. if pos('This GDB was configured',line) > 0 then
  433. using_cygwin_gdb:=pos('cygwin',line) > 0;
  434. inc(i);
  435. end;
  436. {$endif windows}
  437. GDB.Free;
  438. CachedGDBVersion := GDBVersion;
  439. if GDBVersion = '' then
  440. GDBVersion := 'GDB missing or does not work';
  441. end;
  442. begin
  443. CachedGDBVersion := '';
  444. end.