lineinfo.pp 9.7 KB

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