lineinfo.pp 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2000 by Peter Vreman
  4. Stabs Line Info Retriever
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. {
  12. This unit should not be compiled in objfpc mode, since this would make it
  13. dependent on objpas unit.
  14. }
  15. {$IFNDEF FPC_DOTTEDUNITS}
  16. unit lineinfo;
  17. {$ENDIF FPC_DOTTEDUNITS}
  18. interface
  19. {$S-}
  20. {$Q-}
  21. {$IF FPC_VERSION<3}
  22. type
  23. CodePointer = Pointer;
  24. {$ENDIF}
  25. function GetLineInfo(addr:ptruint;var func,source:string;var line:longint) : boolean;
  26. function StabBackTraceStr(addr:CodePointer):shortstring;
  27. procedure CloseStabs;
  28. var
  29. // Allows more efficient operation by reusing previously loaded debug data
  30. // when the target module filename is the same. However, if an invalid memory
  31. // address is supplied then further calls may result in an undefined behaviour.
  32. // In summary: enable for speed, disable for resilience.
  33. AllowReuseOfLineInfoData: Boolean = True;
  34. implementation
  35. {$IFDEF FPC_DOTTEDUNITS}
  36. uses
  37. System.ExeInfo,System.Strings;
  38. {$ELSE FPC_DOTTEDUNITS}
  39. uses
  40. exeinfo,strings;
  41. {$ENDIF FPC_DOTTEDUNITS}
  42. const
  43. N_Function = $24;
  44. N_TextLine = $44;
  45. N_DataLine = $46;
  46. N_BssLine = $48;
  47. N_SourceFile = $64;
  48. N_IncludeFile = $84;
  49. maxstabs = 40; { size of the stabs buffer }
  50. var
  51. { GDB after 4.18 uses offset to function begin
  52. in text section but OS/2 version still uses 4.16 PM }
  53. StabsFunctionRelative: boolean;
  54. type
  55. pstab=^tstab;
  56. tstab=packed record
  57. strpos : longint;
  58. ntype : byte;
  59. nother : byte;
  60. ndesc : word;
  61. nvalue : dword;
  62. end;
  63. { We use static variable so almost no stack is required, and is thus
  64. more safe when an error has occurred in the program }
  65. {$WARNING This code is not thread-safe, and needs improvement }
  66. var
  67. e : TExeFile;
  68. stabcnt, { amount of stabs }
  69. stablen,
  70. stabofs, { absolute stab section offset in executable }
  71. stabstrlen,
  72. stabstrofs : longint; { absolute stabstr section offset in executable }
  73. dirlength : longint; { length of the dirctory part of the source file }
  74. stabs : array[0..maxstabs-1] of tstab; { buffer }
  75. funcstab, { stab with current function info }
  76. linestab, { stab with current line info }
  77. dirstab, { stab with current directory info }
  78. filestab : tstab; { stab with current file info }
  79. filename,
  80. lastfilename, { store last processed file }
  81. dbgfn : ansistring;
  82. lastopenstabs: Boolean; { store last result of processing a file }
  83. function OpenStabs(addr : pointer) : boolean;
  84. var
  85. baseaddr : pointer;
  86. begin
  87. // False by default
  88. OpenStabs:=false;
  89. // Empty so can test if GetModuleByAddr has worked
  90. filename := '';
  91. // Get filename by address using GetModuleByAddr
  92. GetModuleByAddr(addr,baseaddr,filename);
  93. {$ifdef DEBUG_LINEINFO}
  94. writeln(stderr,filename,' Baseaddr: ',hexstr(ptruint(baseaddr),sizeof(baseaddr)*2));
  95. {$endif DEBUG_LINEINFO}
  96. // Check if GetModuleByAddr has worked
  97. if filename = '' then
  98. exit;
  99. // If target filename same as previous, then re-use previous result
  100. if AllowReuseOfLineInfoData and (filename = lastfilename) then
  101. begin
  102. {$ifdef DEBUG_LINEINFO}
  103. writeln(stderr,'Reusing debug data');
  104. {$endif DEBUG_LINEINFO}
  105. OpenStabs:=lastopenstabs;
  106. exit;
  107. end;
  108. // Close previously opened stabs
  109. CloseStabs;
  110. // Reset last open stabs result
  111. lastopenstabs := false;
  112. // Save newly processed filename
  113. lastfilename := filename;
  114. // Open exe file or debug link
  115. if not OpenExeFile(e,filename) then
  116. exit;
  117. if ReadDebugLink(e,dbgfn) then
  118. begin
  119. CloseExeFile(e);
  120. if not OpenExeFile(e,dbgfn) then
  121. exit;
  122. end;
  123. // Find stab section
  124. {$ifdef BeOS}
  125. { Do not change ProcessAddress field for BeOS/Haiku
  126. if baseAddr is lower than ProcessAdress }
  127. if ptruint(baseaddr)>ptruint(e.processaddress) then
  128. {$endif BeOS}
  129. e.processaddress:=ptruint(baseaddr)-e.processaddress;
  130. StabsFunctionRelative := E.FunctionRelative;
  131. if FindExeSection(e,'.stab',stabofs,stablen) and
  132. FindExeSection(e,'.stabstr',stabstrofs,stabstrlen) then
  133. begin
  134. stabcnt:=stablen div sizeof(tstab);
  135. lastopenstabs:=true;
  136. OpenStabs:=true;
  137. end
  138. else
  139. CloseExeFile(e);
  140. end;
  141. procedure CloseStabs;
  142. begin
  143. if e.isopen then
  144. CloseExeFile(e);
  145. // Reset last processed filename
  146. lastfilename := '';
  147. end;
  148. function GetLineInfo(addr:ptruint;var func,source:string;var line:longint) : boolean;
  149. var
  150. res,
  151. stabsleft,
  152. stabscnt,i : longint;
  153. found : boolean;
  154. lastfunc : tstab;
  155. begin
  156. GetLineInfo:=false;
  157. {$ifdef DEBUG_LINEINFO}
  158. writeln(stderr,'GetLineInfo called');
  159. {$endif DEBUG_LINEINFO}
  160. fillchar(func,high(func)+1,0);
  161. fillchar(source,high(source)+1,0);
  162. line:=0;
  163. if not OpenStabs(pointer(addr)) then
  164. exit;
  165. { correct the value to the correct address in the file }
  166. { processaddress is set in OpenStabs }
  167. addr := dword(addr - e.processaddress);
  168. {$ifdef DEBUG_LINEINFO}
  169. writeln(stderr,'Addr: ',hexstr(addr,sizeof(addr)*2));
  170. {$endif DEBUG_LINEINFO}
  171. fillchar(funcstab,sizeof(tstab),0);
  172. fillchar(filestab,sizeof(tstab),0);
  173. fillchar(dirstab,sizeof(tstab),0);
  174. fillchar(linestab,sizeof(tstab),0);
  175. fillchar(lastfunc,sizeof(tstab),0);
  176. found:=false;
  177. seek(e.f,stabofs);
  178. stabsleft:=stabcnt;
  179. repeat
  180. if stabsleft>maxstabs then
  181. stabscnt:=maxstabs
  182. else
  183. stabscnt:=stabsleft;
  184. blockread(e.f,stabs,stabscnt*sizeof(tstab),res);
  185. stabscnt:=res div sizeof(tstab);
  186. for i:=0 to stabscnt-1 do
  187. begin
  188. case stabs[i].ntype of
  189. N_BssLine,
  190. N_DataLine,
  191. N_TextLine :
  192. begin
  193. if (stabs[i].ntype=N_TextLine) and StabsFunctionRelative then
  194. inc(stabs[i].nvalue,lastfunc.nvalue);
  195. if (stabs[i].nvalue<=addr) and
  196. (stabs[i].nvalue>linestab.nvalue) then
  197. begin
  198. { if it's equal we can stop and take the last info }
  199. if stabs[i].nvalue=addr then
  200. found:=true
  201. else
  202. linestab:=stabs[i];
  203. end;
  204. end;
  205. N_Function :
  206. begin
  207. lastfunc:=stabs[i];
  208. if (stabs[i].nvalue<=addr) and
  209. (stabs[i].nvalue>funcstab.nvalue) then
  210. begin
  211. funcstab:=stabs[i];
  212. fillchar(linestab,sizeof(tstab),0);
  213. end;
  214. end;
  215. N_SourceFile,
  216. N_IncludeFile :
  217. begin
  218. if (stabs[i].nvalue<=addr) and
  219. (stabs[i].nvalue>=filestab.nvalue) then
  220. begin
  221. { if same value and type then the first one
  222. contained the directory PM }
  223. if (stabs[i].nvalue=filestab.nvalue) and
  224. (stabs[i].ntype=filestab.ntype) then
  225. dirstab:=filestab
  226. else
  227. fillchar(dirstab,sizeof(tstab),0);
  228. filestab:=stabs[i];
  229. fillchar(linestab,sizeof(tstab),0);
  230. { if new file then func is not valid anymore PM }
  231. if stabs[i].ntype=N_SourceFile then
  232. begin
  233. fillchar(funcstab,sizeof(tstab),0);
  234. fillchar(lastfunc,sizeof(tstab),0);
  235. end;
  236. end;
  237. end;
  238. end;
  239. end;
  240. dec(stabsleft,stabscnt);
  241. until found or (stabsleft=0);
  242. { get the line,source,function info }
  243. line:=linestab.ndesc;
  244. if dirstab.ntype<>0 then
  245. begin
  246. seek(e.f,stabstrofs+dirstab.strpos);
  247. blockread(e.f,source[1],high(source)-1,res);
  248. dirlength:=strlen(@source[1]);
  249. SetLength(source,dirlength);
  250. end
  251. else
  252. dirlength:=0;
  253. if filestab.ntype<>0 then
  254. begin
  255. seek(e.f,stabstrofs+filestab.strpos);
  256. blockread(e.f,source[dirlength+1],high(source)-(dirlength+1),res);
  257. SetLength(source,strlen(@source[1]));
  258. end;
  259. if funcstab.ntype<>0 then
  260. begin
  261. seek(e.f,stabstrofs+funcstab.strpos);
  262. blockread(e.f,func[1],high(func)-1,res);
  263. SetLength(func,strlen(@func[1]));
  264. i:=pos(':',func);
  265. if i>0 then
  266. Delete(func,i,255);
  267. end;
  268. if not AllowReuseOfLineInfoData then
  269. CloseStabs;
  270. GetLineInfo:=true;
  271. end;
  272. function StabBackTraceStr(addr:CodePointer):shortstring;
  273. var
  274. func,
  275. source : string;
  276. hs : string;
  277. line : longint;
  278. Store : TBackTraceStrFunc;
  279. Success : boolean;
  280. begin
  281. {$ifdef DEBUG_LINEINFO}
  282. writeln(stderr,'StabBackTraceStr called');
  283. {$endif DEBUG_LINEINFO}
  284. { reset to prevent infinite recursion if problems inside the code PM }
  285. Success:=false;
  286. Store:=BackTraceStrFunc;
  287. BackTraceStrFunc:=@SysBackTraceStr;
  288. Success:=GetLineInfo(ptruint(addr),func,source,line);
  289. { create string }
  290. {$ifdef netware}
  291. { we need addr relative to code start on netware }
  292. dec(addr,ptruint(system.NWGetCodeStart));
  293. StabBackTraceStr:=' CodeStart + $'+HexStr(ptruint(addr),sizeof(ptruint)*2);
  294. {$else}
  295. StabBackTraceStr:=' $'+HexStr(ptruint(addr),sizeof(ptruint)*2);
  296. {$endif}
  297. if Success then
  298. begin
  299. if func<>'' then
  300. StabBackTraceStr:=StabBackTraceStr+' '+func;
  301. if source<>'' then
  302. begin
  303. if func<>'' then
  304. StabBackTraceStr:=StabBackTraceStr+', ';
  305. if line<>0 then
  306. begin
  307. str(line,hs);
  308. StabBackTraceStr:=StabBackTraceStr+' line '+hs;
  309. end;
  310. StabBackTraceStr:=StabBackTraceStr+' of '+source;
  311. end;
  312. end;
  313. BackTraceStrFunc:=Store;
  314. end;
  315. initialization
  316. lastfilename := '';
  317. lastopenstabs := false;
  318. BackTraceStrFunc:=@StabBackTraceStr;
  319. finalization
  320. CloseStabs;
  321. end.