View Issue Details

IDProjectCategoryView StatusLast Update
0013518FPCRTLpublic2016-01-15 18:19
ReporterBoguslaw BrandysAssigned ToMichael Van Canneyt 
PrioritynormalSeverityminorReproducibilityalways
Status resolvedResolutionfixed 
PlatformwindowsOSwindows xp homeOS Version32-bit
Product VersionProduct Buildtrunk 2.3.1 2009/04/13 
Target Version4.0.0Fixed in Version3.0.0 
Summary0013518: Only first backtrace in big program has attached source nad line information
DescriptionIn big program like GUI created by Lazarus, only first backtrace can contain source and line informations. This is due to StabBackTraceStr function from lineinfo unit changing BackTraceStrFunc pointer to SysBackTraceStr during generation of backtrace. In case of long backtrace with incorrect pointers,that pointer is not returned back to lineinfo function which causes described problem with any subsequent generated backtrace.
TagsBackTraceStrFunc, debug, Exception, lineinfo, patch
Fixed in Revision31026
FPCOldBugId
FPCTarget
Attached Files
  • lineinfo.patch (4,139 bytes)
    Index: rtl/inc/exeinfo.pp
    ===================================================================
    --- rtl/inc/exeinfo.pp	(revision 13014)
    +++ rtl/inc/exeinfo.pp	(working copy)
    @@ -46,7 +46,7 @@
     function CloseExeFile(var e:TExeFile):boolean;
     function ReadDebugLink(var e:TExeFile;var dbgfn:string):boolean;
     
    -procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string);
    +function GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string):boolean;
     
     implementation
     
    @@ -55,14 +55,16 @@
     
     {$ifdef unix}
     
    -  procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string);
    +  function GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string):boolean;
         begin
    +      GetModuleByAddr := false;
           if assigned(UnixGetModuleByAddrHook) then
             UnixGetModuleByAddrHook(addr,baseaddr,filename)
           else
             begin
               baseaddr:=nil;
               filename:=ParamStr(0);
    +          GetModuleByAddr := true;
             end;
         end;
     
    @@ -76,11 +78,15 @@
     {$else wince}
         TST: array[0..Max_Path] of Char;
     {$endif wince}
    -  procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string);
    +  function GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string):boolean;
         begin
    +      GetModuleByAddr := false;
           baseaddr:= nil;
           if VirtualQuery(addr, @Tmm, SizeOf(Tmm))<>sizeof(Tmm) then
    -        filename:=ParamStr(0)
    +      begin
    +        filename:=ParamStr(0);
    +        GetModuleByAddr := true;
    +      end
           else
             begin
               TST[0]:= #0;
    @@ -95,8 +101,9 @@
     
     {$else windows}
     
    -  procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string);
    +  function GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string):boolean;
         begin
    +      GetModuleByAddr := true;
           baseaddr:= nil;
           filename:=ParamStr(0);
         end;
    Index: rtl/inc/lineinfo.pp
    ===================================================================
    --- rtl/inc/lineinfo.pp	(revision 13014)
    +++ rtl/inc/lineinfo.pp	(working copy)
    @@ -77,12 +77,30 @@
     function OpenStabs(addr : pointer) : boolean;
       var
         baseaddr : pointer;
    +    dbgfile,exemodule : Boolean;
     begin
       OpenStabs:=false;
    +  dbgfile := false;
       if staberr then
         exit;
     
    -  GetModuleByAddr(addr,baseaddr,filename);
    +  {the best method would be a cache of files (names) whith debug sections}
    +  {under windows common problem of code below is parsing system dll's like user32.dll }
    +
    +
    +  exemodule := GetModuleByAddr(addr,baseaddr,filename);
    +
    +
    +  if (e.filename<>filename) then
    +   CloseExeFile(e)
    +  else
    +  begin
    +   OpenStabs := true;
    +   exit;
    +  end;
    +
    +
    +
     {$ifdef DEBUG_LINEINFO}
       writeln(stderr,filename,' Baseaddr: ',hexstr(ptruint(baseaddr),sizeof(baseaddr)*2));
     {$endif DEBUG_LINEINFO}
    @@ -94,6 +112,7 @@
           CloseExeFile(e);
           if not OpenExeFile(e,dbgfn) then
             exit;
    +      dbgfile := true;
         end;
       e.processaddress:=e.processaddress+dword(baseaddr);
       StabsFunctionRelative := E.FunctionRelative;
    @@ -105,7 +124,9 @@
         end
       else
         begin
    -      staberr:=true;
    +      e.filename := '';//reset filename to avoid recursion
    +      CloseExeFile(e);
    +      if exemodule or dbgfile then  staberr:=true;
           exit;
         end;
     end;
    @@ -134,12 +155,10 @@
       line:=0;
       if staberr then
         exit;
    -  if not e.isopen then
    -   begin
    -     if not OpenStabs(pointer(addr)) then
    -      exit;
    -   end;
     
    +  if not OpenStabs(pointer(addr)) then  exit;
    +
    +
       { correct the value to the correct address in the file }
       { processaddress is set in OpenStabs                   }
       addr := addr - e.processaddress;
    @@ -246,8 +265,6 @@
          if i>0 then
           Delete(func,i,255);
        end;
    -  if e.isopen then
    -    CloseStabs;
       GetLineInfo:=true;
     end;
     
    @@ -290,13 +307,13 @@
           end;
          StabBackTraceStr:=StabBackTraceStr+' of '+source;
        end;
    -  if Success then
         BackTraceStrFunc:=Store;
     end;
     
     
     initialization
       BackTraceStrFunc:=@StabBackTraceStr;
    +  e.filename := '';
     
     finalization
       if e.isopen then
    
    lineinfo.patch (4,139 bytes)
  • lineinfo2.patch (4,251 bytes)
    Index: rtl/inc/exeinfo.pp
    ===================================================================
    --- rtl/inc/exeinfo.pp	(revision 13340)
    +++ rtl/inc/exeinfo.pp	(working copy)
    @@ -46,7 +46,7 @@
     function CloseExeFile(var e:TExeFile):boolean;
     function ReadDebugLink(var e:TExeFile;var dbgfn:string):boolean;
     
    -procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string);
    +function GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string):boolean;
     
     implementation
     
    @@ -55,14 +55,16 @@
     
     {$ifdef unix}
     
    -  procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string);
    +  function GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string):boolean;
         begin
    +      GetModuleByAddr := false;
           if assigned(UnixGetModuleByAddrHook) then
             UnixGetModuleByAddrHook(addr,baseaddr,filename)
           else
             begin
               baseaddr:=nil;
               filename:=ParamStr(0);
    +          GetModuleByAddr := true;
             end;
         end;
     
    @@ -76,11 +78,15 @@
     {$else wince}
         TST: array[0..Max_Path] of Char;
     {$endif wince}
    -  procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string);
    +  function GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string):boolean;
         begin
    -      baseaddr:=nil;
    -      if VirtualQuery(addr, @Tmm, SizeOf(Tmm))<>sizeof(Tmm) then
    -        filename:=ParamStr(0)
    +      GetModuleByAddr := false;
    +      baseaddr:= nil;
    +    if VirtualQuery(addr, @Tmm, SizeOf(Tmm))<>sizeof(Tmm) then
    +      begin
    +        filename:=ParamStr(0);
    +        GetModuleByAddr := true;
    +      end
           else
             begin
               baseaddr:=Tmm.AllocationBase;
    @@ -96,8 +102,9 @@
     
     {$else windows}
     
    -  procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string);
    +  function GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string):boolean;
         begin
    +      GetModuleByAddr := true;
           baseaddr:= nil;
           filename:=ParamStr(0);
         end;
    Index: rtl/inc/lineinfo.pp
    ===================================================================
    --- rtl/inc/lineinfo.pp	(revision 13340)
    +++ rtl/inc/lineinfo.pp	(working copy)
    @@ -77,12 +77,30 @@
     function OpenStabs(addr : pointer) : boolean;
       var
         baseaddr : pointer;
    +    dbgfile,exemodule : Boolean;
     begin
       OpenStabs:=false;
    +  dbgfile := false;
       if staberr then
         exit;
     
    -  GetModuleByAddr(addr,baseaddr,filename);
    +  {the best method would be a cache of files (names) whith debug sections}
    +  {under windows common problem of code below is parsing system dll's like user32.dll }
    +
    +
    +  exemodule := GetModuleByAddr(addr,baseaddr,filename);
    +
    +
    +  if (e.filename<>filename) then
    +   CloseExeFile(e)
    +  else
    +  begin
    +   OpenStabs := true;
    +   exit;
    +  end;
    +
    +
    +
     {$ifdef DEBUG_LINEINFO}
       writeln(stderr,filename,' Baseaddr: ',hexstr(ptruint(baseaddr),sizeof(baseaddr)*2));
     {$endif DEBUG_LINEINFO}
    @@ -94,6 +112,7 @@
           CloseExeFile(e);
           if not OpenExeFile(e,dbgfn) then
             exit;
    +      dbgfile := true;
         end;
       e.processaddress:=ptruint(baseaddr)-e.processaddress;
       StabsFunctionRelative := E.FunctionRelative;
    @@ -105,7 +124,9 @@
         end
       else
         begin
    -      staberr:=true;
    +      e.filename := '';//reset filename to avoid recursion
    +      CloseExeFile(e);
    +      if exemodule or dbgfile then  staberr:=true;
           exit;
         end;
     end;
    @@ -134,12 +155,10 @@
       line:=0;
       if staberr then
         exit;
    -  if not e.isopen then
    -   begin
    -     if not OpenStabs(pointer(addr)) then
    -      exit;
    -   end;
     
    +  if not OpenStabs(pointer(addr)) then  exit;
    +
    +
       { correct the value to the correct address in the file }
       { processaddress is set in OpenStabs                   }
       addr := dword(addr - e.processaddress);
    @@ -246,8 +265,6 @@
          if i>0 then
           Delete(func,i,255);
        end;
    -  if e.isopen then
    -    CloseStabs;
       GetLineInfo:=true;
     end;
     
    @@ -290,13 +307,13 @@
           end;
          StabBackTraceStr:=StabBackTraceStr+' of '+source;
        end;
    -  if Success then
         BackTraceStrFunc:=Store;
     end;
     
     
     initialization
       BackTraceStrFunc:=@StabBackTraceStr;
    +  e.filename := '';
     
     finalization
       if e.isopen then
    
    lineinfo2.patch (4,251 bytes)
  • unCustomLineInfo.pas (8,192 bytes)
    {
        This file is part of the Free Pascal run time library.
        Copyright (c) 2000 by Peter Vreman
    
        Stabs Line Info Retriever
    
        See the file COPYING.FPC, included in this distribution,
        for details about the copyright.
    
        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    
     **********************************************************************}
    {
      This unit should not be compiled in objfpc mode, since this would make it
      dependent on objpas unit.
    }
    unit unCustomLineInfo;
    interface
    
    {$S-}
    {$Q-}
    
    function GetLineInfo(addr:ptruint;var func,source:shortstring;var line:longint) : boolean;
    function StabBackTraceStr(addr:Pointer):shortstring;
    
    implementation
    
    uses
      exeinfo,strings;
    
    const
      N_Function    = $24;
      N_TextLine    = $44;
      N_DataLine    = $46;
      N_BssLine     = $48;
      N_SourceFile  = $64;
      N_IncludeFile = $84;
    
      maxstabs = 40; { size of the stabs buffer }
    
    var
      { GDB after 4.18 uses offset to function begin
        in text section but OS/2 version still uses 4.16 PM }
      StabsFunctionRelative: boolean;
    
    type
      pstab=^tstab;
      tstab=packed record
        strpos  : longint;
        ntype   : byte;
        nother  : byte;
        ndesc   : word;
        nvalue  : dword;
      end;
    
    { We use static variable so almost no stack is required, and is thus
      more safe when an error has occured in the program }
    var
      e          : TExeFile;
      stabcnt,              { amount of stabs }
      stablen,
      stabofs,              { absolute stab section offset in executable }
      stabstrlen,
      stabstrofs : longint; { absolute stabstr section offset in executable }
      dirlength  : longint; { length of the dirctory part of the source file }
      stabs      : array[0..maxstabs-1] of tstab;  { buffer }
      funcstab,             { stab with current function info }
      linestab,             { stab with current line info }
      dirstab,              { stab with current directory info }
      filestab   : tstab;   { stab with current file info }
      filename,
      dbgfn : shortstring;
    
    
    function OpenStabs(addr : pointer) : boolean;
      var
        baseaddr : pointer;
    begin
      OpenStabs:=false;
    
      GetModuleByAddr(addr,baseaddr,filename);
    {$ifdef DEBUG_LINEINFO}
      writeln(stderr,filename,' Baseaddr: ',hexstr(ptruint(baseaddr),sizeof(baseaddr)*2));
    {$endif DEBUG_LINEINFO}
    
      if not OpenExeFile(e,filename) then
        exit;
      if ReadDebugLink(e,dbgfn) then
        begin
          CloseExeFile(e);
          if not OpenExeFile(e,dbgfn) then
            exit;
        end;
      e.processaddress:=ptruint(baseaddr)-e.processaddress;
      StabsFunctionRelative := E.FunctionRelative;
      if FindExeSection(e,'.stab',stabofs,stablen) and
         FindExeSection(e,'.stabstr',stabstrofs,stabstrlen) then
        begin
          stabcnt:=stablen div sizeof(tstab);
          OpenStabs:=true;
        end
      else
        begin
          CloseExeFile(e);
          exit;
        end;
    end;
    
    
    procedure CloseStabs;
    begin
      CloseExeFile(e);
    end;
    
    
    function GetLineInfo(addr:ptruint;var func,source:shortstring;var line:longint) : boolean;
    var
      res,
      stabsleft,
      stabscnt,i : longint;
      found : boolean;
      lastfunc : tstab;
    begin
      GetLineInfo:=false;
    {$ifdef DEBUG_LINEINFO}
      writeln(stderr,'GetLineInfo called');
    {$endif DEBUG_LINEINFO}
      fillchar(func,high(func)+1,0);
      fillchar(source,high(source)+1,0);
      line:=0;
      if not e.isopen then
       begin
         if not OpenStabs(pointer(addr)) then
          exit;
       end;
    
      { correct the value to the correct address in the file }
      { processaddress is set in OpenStabs                   }
      addr := dword(addr - e.processaddress);
    
    {$ifdef DEBUG_LINEINFO}
      writeln(stderr,'Addr: ',hexstr(addr,sizeof(addr)*2));
    {$endif DEBUG_LINEINFO}
    
      fillchar(funcstab,sizeof(tstab),0);
      fillchar(filestab,sizeof(tstab),0);
      fillchar(dirstab,sizeof(tstab),0);
      fillchar(linestab,sizeof(tstab),0);
      fillchar(lastfunc,sizeof(tstab),0);
      found:=false;
      seek(e.f,stabofs);
      stabsleft:=stabcnt;
      repeat
        if stabsleft>maxstabs then
         stabscnt:=maxstabs
        else
         stabscnt:=stabsleft;
        blockread(e.f,stabs,stabscnt*sizeof(tstab),res);
        stabscnt:=res div sizeof(tstab);
        for i:=0 to stabscnt-1 do
         begin
           case stabs[i].ntype of
             N_BssLine,
             N_DataLine,
             N_TextLine :
               begin
                 if (stabs[i].ntype=N_TextLine) and StabsFunctionRelative then
                   inc(stabs[i].nvalue,lastfunc.nvalue);
                 if (stabs[i].nvalue<=addr) and
                    (stabs[i].nvalue>linestab.nvalue) then
                  begin
                    { if it's equal we can stop and take the last info }
                    if stabs[i].nvalue=addr then
                     found:=true
                    else
                     linestab:=stabs[i];
                  end;
               end;
             N_Function :
               begin
                 lastfunc:=stabs[i];
                 if (stabs[i].nvalue<=addr) and
                    (stabs[i].nvalue>funcstab.nvalue) then
                  begin
                    funcstab:=stabs[i];
                    fillchar(linestab,sizeof(tstab),0);
                  end;
               end;
             N_SourceFile,
             N_IncludeFile :
               begin
                 if (stabs[i].nvalue<=addr) and
                    (stabs[i].nvalue>=filestab.nvalue) then
                  begin
                    { if same value and type then the first one
                      contained the directory PM }
                    if (stabs[i].nvalue=filestab.nvalue) and
                       (stabs[i].ntype=filestab.ntype) then
                      dirstab:=filestab
                    else
                      fillchar(dirstab,sizeof(tstab),0);
                    filestab:=stabs[i];
                    fillchar(linestab,sizeof(tstab),0);
                    { if new file then func is not valid anymore PM }
                    if stabs[i].ntype=N_SourceFile then
                      begin
                        fillchar(funcstab,sizeof(tstab),0);
                        fillchar(lastfunc,sizeof(tstab),0);
                      end;
                  end;
               end;
           end;
         end;
        dec(stabsleft,stabscnt);
      until found or (stabsleft=0);
    
    { get the line,source,function info }
      line:=linestab.ndesc;
      if dirstab.ntype<>0 then
       begin
         seek(e.f,stabstrofs+dirstab.strpos);
         blockread(e.f,source[1],high(source)-1,res);
         dirlength:=strlen(@source[1]);
         source[0]:=chr(dirlength);
       end
      else
       dirlength:=0;
      if filestab.ntype<>0 then
       begin
         seek(e.f,stabstrofs+filestab.strpos);
         blockread(e.f,source[dirlength+1],high(source)-(dirlength+1),res);
         source[0]:=chr(strlen(@source[1]));
       end;
      if funcstab.ntype<>0 then
       begin
         seek(e.f,stabstrofs+funcstab.strpos);
         blockread(e.f,func[1],high(func)-1,res);
         func[0]:=chr(strlen(@func[1]));
         i:=pos(':',func);
         if i>0 then
          Delete(func,i,255);
       end;
      if e.isopen then
        CloseStabs;
      GetLineInfo:=true;
    end;
    
    
    function StabBackTraceStr(addr:Pointer):shortstring;
    var
      func,
      source : shortstring;
      hs     : string[32];
      line   : longint;
      Store  : TBackTraceStrFunc;
      Success : boolean;
    begin
    {$ifdef DEBUG_LINEINFO}
      writeln(stderr,'StabBackTraceStr called');
    {$endif DEBUG_LINEINFO}
      { reset to prevent infinite recursion if problems inside the code PM }
      Success:=false;
      Store:=BackTraceStrFunc;
      BackTraceStrFunc:=@SysBackTraceStr;
      Success:=GetLineInfo(ptruint(addr),func,source,line);
    { create string }
    {$ifdef netware}
      { we need addr relative to code start on netware }
      dec(addr,ptruint(system.NWGetCodeStart));
      StabBackTraceStr:='  CodeStart + $'+HexStr(ptruint(addr),sizeof(ptruint)*2);
    {$else}
      StabBackTraceStr:='  $'+HexStr(ptruint(addr),sizeof(ptruint)*2);
    {$endif}
      if func<>'' then
        StabBackTraceStr:=StabBackTraceStr+'  '+func;
      if source<>'' then
       begin
         if func<>'' then
          StabBackTraceStr:=StabBackTraceStr+', ';
         if line<>0 then
          begin
            str(line,hs);
            StabBackTraceStr:=StabBackTraceStr+' line '+hs;
          end;
         StabBackTraceStr:=StabBackTraceStr+' of '+source;
       end;
      if Success then
        BackTraceStrFunc:=Store;
    end;
    
    
    initialization
      BackTraceStrFunc:=@StabBackTraceStr;
    
    finalization
      if e.isopen then
       CloseStabs;
    end.
    
    unCustomLineInfo.pas (8,192 bytes)
  • unStackTrace.pas (2,573 bytes)
    unit unStackTrace;
    
    {$mode objfpc}{$H+}
    
    interface
    
    uses
      Classes, SysUtils, Contnrs, unCustomLineInfo;
    
    type
      TStackFrameInfo = class
        Index: Integer;
        LineNumber: Integer;
        Address: Integer;
        FunctionName: string;
        Source: string;
        procedure GetFrameInfo(Addr: Pointer);
      end;
    
      TStackTrace = class(TObjectList)
        MaxDepth: Integer;
        procedure GetExceptionBackTrace;
        procedure GetCallStack;
      end;
    
    
    implementation
    
    procedure TStackFrameInfo.GetFrameInfo(Addr: Pointer);
    var
      Func: shortstring;
      SourceStr: shortstring;
      Line: LongInt;
      Store: TBackTraceStrFunc;
      Success: Boolean;
    begin
      // Reset to prevent infinite recursion if problems inside the code PM
      Store := BackTraceStrFunc;
      BackTraceStrFunc := @SysBackTraceStr;
      Success := GetLineInfo(ptruint(Addr), Func, SourceStr, Line);
      Address := Integer(Addr);
      FunctionName := Func;
      LineNumber := Line;
      Source := SourceStr;
      BackTraceStrFunc := Store;
    end;
    
    procedure TStackTrace.GetCallStack;
    var
      I: Longint;
      prevbp: Pointer;
      CallerFrame,
      CallerAddress,
      bp: Pointer;
      StackFrameInfo: TStackFrameInfo;
    begin
      Clear;
      //routine adapted from fpc source
    
      //This trick skip SendCallstack item
      bp := get_frame;
      //bp:= get_caller_frame(get_frame);
      try
        prevbp := bp - 1;
        I := 0;
        //is_dev:=do_isdevice(textrec(f).Handle);
        while bp > prevbp do begin
           CallerAddress := get_caller_addr(bp);
           CallerFrame := get_caller_frame(bp);
           if (CallerAddress = nil) then
             Break;
           StackFrameInfo := TStackFrameInfo.Create;
           StackFrameInfo.GetFrameInfo(CallerAddress);
           StackFrameInfo.Index := I + 1;
           Add(StackFrameInfo);
           Inc(I);
           if (I >= MaxDepth) or (CallerFrame = nil) then
             Break;
           prevbp := bp;
           bp := CallerFrame;
         end;
       except
         { prevent endless dump if an exception occured }
       end;
    end;
    
    procedure TStackTrace.GetExceptionBackTrace;
    var
      FrameCount: Integer;
      Frames: PPointer;
      FrameNumber: Integer;
      StackFrameInfo: TStackFrameInfo;
    begin
      Clear;
      StackFrameInfo := TStackFrameInfo.Create;
      StackFrameInfo.GetFrameInfo(ExceptAddr);
      StackFrameInfo.Index := 1;
      Add(StackFrameInfo);
      FrameCount := ExceptFrameCount;
      Frames := ExceptFrames;
      if FrameCount > MaxDepth then FrameCount := MaxDepth;
      for FrameNumber := 0 to FrameCount - 1 do begin
        StackFrameInfo := TStackFrameInfo.Create;
        StackFrameInfo.GetFrameInfo(Frames[FrameNumber]);
        StackFrameInfo.Index := FrameNumber + 1;
        Add(StackFrameInfo);
      end;
    end;
    
    
    
    end.
    
    
    unStackTrace.pas (2,573 bytes)
  • lineinfo.pp (9,646 bytes)
    {
        This file is part of the Free Pascal run time library.
        Copyright (c) 2000 by Peter Vreman
    
        Stabs Line Info Retriever
    
        See the file COPYING.FPC, included in this distribution,
        for details about the copyright.
    
        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    
     **********************************************************************}
    {
      This unit should not be compiled in objfpc mode, since this would make it
      dependent on objpas unit.
    }
    unit lineinfo;
    interface
    
    {$S-}
    {$Q-}
    
    function GetLineInfo(addr:ptruint;var func,source:string;var line:longint) : boolean;
    function StabBackTraceStr(addr:{$IF FPC_VERSION>=3}CodePointer{$ELSE}Pointer{$ENDIF}):string;
    procedure CloseStabs;
    
    implementation
    
    uses
      exeinfo,strings;
    
    const
      N_Function    = $24;
      N_TextLine    = $44;
      N_DataLine    = $46;
      N_BssLine     = $48;
      N_SourceFile  = $64;
      N_IncludeFile = $84;
    
      maxstabs = 40; { size of the stabs buffer }
    
    var
      { GDB after 4.18 uses offset to function begin
        in text section but OS/2 version still uses 4.16 PM }
      StabsFunctionRelative: boolean;
    
    type
      pstab=^tstab;
      tstab=packed record
        strpos  : longint;
        ntype   : byte;
        nother  : byte;
        ndesc   : word;
        nvalue  : dword;
      end;
    
    { We use static variable so almost no stack is required, and is thus
      more safe when an error has occured in the program }
    {$WARNING This code is not thread-safe, and needs improvement }  
    var
      e          : TExeFile;
      stabcnt,              { amount of stabs }
      stablen,
      stabofs,              { absolute stab section offset in executable }
      stabstrlen,
      stabstrofs : longint; { absolute stabstr section offset in executable }
      dirlength  : longint; { length of the dirctory part of the source file }
      stabs      : array[0..maxstabs-1] of tstab;  { buffer }
      funcstab,             { stab with current function info }
      linestab,             { stab with current line info }
      dirstab,              { stab with current directory info }
      filestab   : tstab;   { stab with current file info }
      filename,
      lastfilename,         { store last processed file }
      dbgfn : string;
      lastopenstabs: Boolean; { store last result of processing a file }
    
    
    function OpenStabs(addr : pointer) : boolean;
      var
        baseaddr : pointer;
    begin
      // False by default
      OpenStabs:=false;
    
      // Empty so can test if GetModuleByAddr has worked
      filename := '';
    
      // Get filename by address using GetModuleByAddr
      GetModuleByAddr(addr,baseaddr,filename);
    {$ifdef DEBUG_LINEINFO}
      writeln(stderr,filename,' Baseaddr: ',hexstr(ptruint(baseaddr),sizeof(baseaddr)*2));
    {$endif DEBUG_LINEINFO}
    
      // Check if GetModuleByAddr has work
      if filename = '' then
        exit;
    
      // If target filename same as previous, then re-use previous result
      if filename = lastfilename then
      begin
        OpenStabs:=lastopenstabs;
        exit;
      end;
    
      // Close previously opened stabs
      CloseStabs;
    
      // Reset last open stabs result
      lastopenstabs := false;
    
      // Save newly processed filename
      lastfilename := filename;
    
      // Open exe file or debug link
      if not OpenExeFile(e,filename) then
        exit;
      if ReadDebugLink(e,dbgfn) then
        begin
          CloseExeFile(e);
          if not OpenExeFile(e,dbgfn) then
            exit;
        end;
    
      // Find stab section
    {$ifdef BeOS}
      { Do not change ProcessAddress field for BeOS/Haiku
        if baseAddr is lower than ProcessAdress }
      if ptruint(baseaddr)>ptruint(e.processaddress) then
    {$endif BeOS}
        e.processaddress:=ptruint(baseaddr)-e.processaddress;
      StabsFunctionRelative := E.FunctionRelative;
      if FindExeSection(e,'.stab',stabofs,stablen) and
         FindExeSection(e,'.stabstr',stabstrofs,stabstrlen) then
        begin
          stabcnt:=stablen div sizeof(tstab);
          lastopenstabs:=true;
          OpenStabs:=true;
        end
      else
        begin
          CloseExeFile(e);
          exit;
        end;
    end;
    
    
    procedure CloseStabs;
    begin
      if e.isopen then
      begin
        CloseExeFile(e);
    
        // Reset last processed filename
        lastfilename := '';
      end;
    end;
    
    
    function GetLineInfo(addr:ptruint;var func,source:string;var line:longint) : boolean;
    var
      res,
      stabsleft,
      stabscnt,i : longint;
      found : boolean;
      lastfunc : tstab;
    begin
      GetLineInfo:=false;
    {$ifdef DEBUG_LINEINFO}
      writeln(stderr,'GetLineInfo called');
    {$endif DEBUG_LINEINFO}
      fillchar(func,high(func)+1,0);
      fillchar(source,high(source)+1,0);
      line:=0;
    
      if not OpenStabs(pointer(addr)) then
        exit;
    
      { correct the value to the correct address in the file }
      { processaddress is set in OpenStabs                   }
      addr := dword(addr - e.processaddress);
    
    {$ifdef DEBUG_LINEINFO}
      writeln(stderr,'Addr: ',hexstr(addr,sizeof(addr)*2));
    {$endif DEBUG_LINEINFO}
    
      fillchar(funcstab,sizeof(tstab),0);
      fillchar(filestab,sizeof(tstab),0);
      fillchar(dirstab,sizeof(tstab),0);
      fillchar(linestab,sizeof(tstab),0);
      fillchar(lastfunc,sizeof(tstab),0);
      found:=false;
      seek(e.f,stabofs);
      stabsleft:=stabcnt;
      repeat
        if stabsleft>maxstabs then
         stabscnt:=maxstabs
        else
         stabscnt:=stabsleft;
        blockread(e.f,stabs,stabscnt*sizeof(tstab),res);
        stabscnt:=res div sizeof(tstab);
        for i:=0 to stabscnt-1 do
         begin
           case stabs[i].ntype of
             N_BssLine,
             N_DataLine,
             N_TextLine :
               begin
                 if (stabs[i].ntype=N_TextLine) and StabsFunctionRelative then
                   inc(stabs[i].nvalue,lastfunc.nvalue);
                 if (stabs[i].nvalue<=addr) and
                    (stabs[i].nvalue>linestab.nvalue) then
                  begin
                    { if it's equal we can stop and take the last info }
                    if stabs[i].nvalue=addr then
                     found:=true
                    else
                     linestab:=stabs[i];
                  end;
               end;
             N_Function :
               begin
                 lastfunc:=stabs[i];
                 if (stabs[i].nvalue<=addr) and
                    (stabs[i].nvalue>funcstab.nvalue) then
                  begin
                    funcstab:=stabs[i];
                    fillchar(linestab,sizeof(tstab),0);
                  end;
               end;
             N_SourceFile,
             N_IncludeFile :
               begin
                 if (stabs[i].nvalue<=addr) and
                    (stabs[i].nvalue>=filestab.nvalue) then
                  begin
                    { if same value and type then the first one
                      contained the directory PM }
                    if (stabs[i].nvalue=filestab.nvalue) and
                       (stabs[i].ntype=filestab.ntype) then
                      dirstab:=filestab
                    else
                      fillchar(dirstab,sizeof(tstab),0);
                    filestab:=stabs[i];
                    fillchar(linestab,sizeof(tstab),0);
                    { if new file then func is not valid anymore PM }
                    if stabs[i].ntype=N_SourceFile then
                      begin
                        fillchar(funcstab,sizeof(tstab),0);
                        fillchar(lastfunc,sizeof(tstab),0);
                      end;
                  end;
               end;
           end;
         end;
        dec(stabsleft,stabscnt);
      until found or (stabsleft=0);
    
    { get the line,source,function info }
      line:=linestab.ndesc;
      if dirstab.ntype<>0 then
       begin
         seek(e.f,stabstrofs+dirstab.strpos);
         blockread(e.f,source[1],high(source)-1,res);
         dirlength:=strlen(@source[1]);
         source[0]:=chr(dirlength);
       end
      else
       dirlength:=0;
      if filestab.ntype<>0 then
       begin
         seek(e.f,stabstrofs+filestab.strpos);
         blockread(e.f,source[dirlength+1],high(source)-(dirlength+1),res);
         source[0]:=chr(strlen(@source[1]));
       end;
      if funcstab.ntype<>0 then
       begin
         seek(e.f,stabstrofs+funcstab.strpos);
         blockread(e.f,func[1],high(func)-1,res);
         func[0]:=chr(strlen(@func[1]));
         i:=pos(':',func);
         if i>0 then
          Delete(func,i,255);
       end;
    
      GetLineInfo:=true;
    end;
    
    
    function StabBackTraceStr(addr:{$IF FPC_VERSION>=3}CodePointer{$ELSE}Pointer{$ENDIF}):string;
    var
      func,
      source : string;
      hs     : string;
      line   : longint;
      Store  : TBackTraceStrFunc;
      Success : boolean;
    begin
    {$ifdef DEBUG_LINEINFO}
      writeln(stderr,'StabBackTraceStr called');
    {$endif DEBUG_LINEINFO}
      { reset to prevent infinite recursion if problems inside the code PM }
      Success:=false;
      Store:=BackTraceStrFunc;
      BackTraceStrFunc:=@SysBackTraceStr;
      Success:=GetLineInfo(ptruint(addr),func,source,line);
    { create string }
    {$ifdef netware}
      { we need addr relative to code start on netware }
      dec(addr,ptruint(system.NWGetCodeStart));
      StabBackTraceStr:='  CodeStart + $'+HexStr(ptruint(addr),sizeof(ptruint)*2);
    {$else}
      StabBackTraceStr:='  $'+HexStr(ptruint(addr),sizeof(ptruint)*2);
    {$endif}
      if func<>'' then
        StabBackTraceStr:=StabBackTraceStr+'  '+func;
      if source<>'' then
       begin
         if func<>'' then
          StabBackTraceStr:=StabBackTraceStr+', ';
         if line<>0 then
          begin
            str(line,hs);
            StabBackTraceStr:=StabBackTraceStr+' line '+hs;
          end;
         StabBackTraceStr:=StabBackTraceStr+' of '+source;
       end;
      BackTraceStrFunc:=Store;
    end;
    
    
    initialization
      lastfilename := '';
      lastopenstabs := false;
      BackTraceStrFunc:=@StabBackTraceStr;
    
    finalization
      CloseStabs;
    
    end.
    
    lineinfo.pp (9,646 bytes)

Relationships

has duplicate 0021370 resolvedJonas Maebe The debug doesn't generate the info of the error. 
has duplicate 0028245 resolvedJonas Maebe Exceptions in TApplication.OnException have no line info 

Activities

Boguslaw Brandys

2009-04-15 21:57

reporter   ~0026775

Last edited: 2009-04-15 22:07

There is also a problem when GetModuleByAddr returns system libraries like user32.dll for some pointers, stabs cannot be found there and staberr:= true is set, and it's set permanently. IMHO GetModuleByAddr should return only modules with debug info inside ,in other case maybe ParamStr(0).

2009-04-16 16:58

 

lineinfo.patch (4,139 bytes)
Index: rtl/inc/exeinfo.pp
===================================================================
--- rtl/inc/exeinfo.pp	(revision 13014)
+++ rtl/inc/exeinfo.pp	(working copy)
@@ -46,7 +46,7 @@
 function CloseExeFile(var e:TExeFile):boolean;
 function ReadDebugLink(var e:TExeFile;var dbgfn:string):boolean;
 
-procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string);
+function GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string):boolean;
 
 implementation
 
@@ -55,14 +55,16 @@
 
 {$ifdef unix}
 
-  procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string);
+  function GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string):boolean;
     begin
+      GetModuleByAddr := false;
       if assigned(UnixGetModuleByAddrHook) then
         UnixGetModuleByAddrHook(addr,baseaddr,filename)
       else
         begin
           baseaddr:=nil;
           filename:=ParamStr(0);
+          GetModuleByAddr := true;
         end;
     end;
 
@@ -76,11 +78,15 @@
 {$else wince}
     TST: array[0..Max_Path] of Char;
 {$endif wince}
-  procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string);
+  function GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string):boolean;
     begin
+      GetModuleByAddr := false;
       baseaddr:= nil;
       if VirtualQuery(addr, @Tmm, SizeOf(Tmm))<>sizeof(Tmm) then
-        filename:=ParamStr(0)
+      begin
+        filename:=ParamStr(0);
+        GetModuleByAddr := true;
+      end
       else
         begin
           TST[0]:= #0;
@@ -95,8 +101,9 @@
 
 {$else windows}
 
-  procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string);
+  function GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string):boolean;
     begin
+      GetModuleByAddr := true;
       baseaddr:= nil;
       filename:=ParamStr(0);
     end;
Index: rtl/inc/lineinfo.pp
===================================================================
--- rtl/inc/lineinfo.pp	(revision 13014)
+++ rtl/inc/lineinfo.pp	(working copy)
@@ -77,12 +77,30 @@
 function OpenStabs(addr : pointer) : boolean;
   var
     baseaddr : pointer;
+    dbgfile,exemodule : Boolean;
 begin
   OpenStabs:=false;
+  dbgfile := false;
   if staberr then
     exit;
 
-  GetModuleByAddr(addr,baseaddr,filename);
+  {the best method would be a cache of files (names) whith debug sections}
+  {under windows common problem of code below is parsing system dll's like user32.dll }
+
+
+  exemodule := GetModuleByAddr(addr,baseaddr,filename);
+
+
+  if (e.filename<>filename) then
+   CloseExeFile(e)
+  else
+  begin
+   OpenStabs := true;
+   exit;
+  end;
+
+
+
 {$ifdef DEBUG_LINEINFO}
   writeln(stderr,filename,' Baseaddr: ',hexstr(ptruint(baseaddr),sizeof(baseaddr)*2));
 {$endif DEBUG_LINEINFO}
@@ -94,6 +112,7 @@
       CloseExeFile(e);
       if not OpenExeFile(e,dbgfn) then
         exit;
+      dbgfile := true;
     end;
   e.processaddress:=e.processaddress+dword(baseaddr);
   StabsFunctionRelative := E.FunctionRelative;
@@ -105,7 +124,9 @@
     end
   else
     begin
-      staberr:=true;
+      e.filename := '';//reset filename to avoid recursion
+      CloseExeFile(e);
+      if exemodule or dbgfile then  staberr:=true;
       exit;
     end;
 end;
@@ -134,12 +155,10 @@
   line:=0;
   if staberr then
     exit;
-  if not e.isopen then
-   begin
-     if not OpenStabs(pointer(addr)) then
-      exit;
-   end;
 
+  if not OpenStabs(pointer(addr)) then  exit;
+
+
   { correct the value to the correct address in the file }
   { processaddress is set in OpenStabs                   }
   addr := addr - e.processaddress;
@@ -246,8 +265,6 @@
      if i>0 then
       Delete(func,i,255);
    end;
-  if e.isopen then
-    CloseStabs;
   GetLineInfo:=true;
 end;
 
@@ -290,13 +307,13 @@
       end;
      StabBackTraceStr:=StabBackTraceStr+' of '+source;
    end;
-  if Success then
     BackTraceStrFunc:=Store;
 end;
 
 
 initialization
   BackTraceStrFunc:=@StabBackTraceStr;
+  e.filename := '';
 
 finalization
   if e.isopen then
lineinfo.patch (4,139 bytes)

Boguslaw Brandys

2009-04-16 17:02

reporter   ~0026797

Patch attached which solves problem I hope.Tested under win32 on single executable and with dll with stabs debug.
Please test against any recursion test cases. Untested under unix/linux, win64, wince. Please test under those systems if you can.
Patch allows unlimited backtraces per program execution.

Jonas Maebe

2009-05-10 11:36

manager   ~0027478

I think the main reason for only symbolicating the first backtrace, is to avoid getting in an endless loop if the second exception is due to an invalid memory access inside the lineinfo unit itself.

Boguslaw Brandys

2009-05-12 09:10

reporter   ~0027545

Well,I understand it, and I think that why temporarily during lineinfo work it point backtrace into bare method SysBackTraceStr to avoid infinite loop. That part was not changed.The change was mostly removing unnecessairly opening/closing the same file for each output line and the usage of "guard variable" - staberr to follow that.Now staberr is set only in case of real problem which is for example missing stab sections in binary.

But I agree that the definitive way is to check new code in case of memory corruption during building of backtrace output.I'll do it as soon as I find spare time.

I think similar issue could be true for lnfodwrf.pp

Boguslaw Brandys

2009-06-27 17:46

reporter   ~0028802

I tested patch against possible recursion and found no problem.
For example I simulated memory access problem inside lineinfo internal GetLineInfo function and result is :

E:\test>test
Runtime error 200 at $0040145B
Runtime error 216 at $00408A01
  $00408A01
  $00408DD8
  $004043F7
  $00407BD0

I recreated patch against current SVN fpc version trunk [2009/06/27]

Boguslaw Brandys

2009-06-27 17:47

reporter   ~0028803

New patch is named lineinfo2.patch

2009-06-27 17:50

 

lineinfo2.patch (4,251 bytes)
Index: rtl/inc/exeinfo.pp
===================================================================
--- rtl/inc/exeinfo.pp	(revision 13340)
+++ rtl/inc/exeinfo.pp	(working copy)
@@ -46,7 +46,7 @@
 function CloseExeFile(var e:TExeFile):boolean;
 function ReadDebugLink(var e:TExeFile;var dbgfn:string):boolean;
 
-procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string);
+function GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string):boolean;
 
 implementation
 
@@ -55,14 +55,16 @@
 
 {$ifdef unix}
 
-  procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string);
+  function GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string):boolean;
     begin
+      GetModuleByAddr := false;
       if assigned(UnixGetModuleByAddrHook) then
         UnixGetModuleByAddrHook(addr,baseaddr,filename)
       else
         begin
           baseaddr:=nil;
           filename:=ParamStr(0);
+          GetModuleByAddr := true;
         end;
     end;
 
@@ -76,11 +78,15 @@
 {$else wince}
     TST: array[0..Max_Path] of Char;
 {$endif wince}
-  procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string);
+  function GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string):boolean;
     begin
-      baseaddr:=nil;
-      if VirtualQuery(addr, @Tmm, SizeOf(Tmm))<>sizeof(Tmm) then
-        filename:=ParamStr(0)
+      GetModuleByAddr := false;
+      baseaddr:= nil;
+    if VirtualQuery(addr, @Tmm, SizeOf(Tmm))<>sizeof(Tmm) then
+      begin
+        filename:=ParamStr(0);
+        GetModuleByAddr := true;
+      end
       else
         begin
           baseaddr:=Tmm.AllocationBase;
@@ -96,8 +102,9 @@
 
 {$else windows}
 
-  procedure GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string);
+  function GetModuleByAddr(addr: pointer; var baseaddr: pointer; var filename: string):boolean;
     begin
+      GetModuleByAddr := true;
       baseaddr:= nil;
       filename:=ParamStr(0);
     end;
Index: rtl/inc/lineinfo.pp
===================================================================
--- rtl/inc/lineinfo.pp	(revision 13340)
+++ rtl/inc/lineinfo.pp	(working copy)
@@ -77,12 +77,30 @@
 function OpenStabs(addr : pointer) : boolean;
   var
     baseaddr : pointer;
+    dbgfile,exemodule : Boolean;
 begin
   OpenStabs:=false;
+  dbgfile := false;
   if staberr then
     exit;
 
-  GetModuleByAddr(addr,baseaddr,filename);
+  {the best method would be a cache of files (names) whith debug sections}
+  {under windows common problem of code below is parsing system dll's like user32.dll }
+
+
+  exemodule := GetModuleByAddr(addr,baseaddr,filename);
+
+
+  if (e.filename<>filename) then
+   CloseExeFile(e)
+  else
+  begin
+   OpenStabs := true;
+   exit;
+  end;
+
+
+
 {$ifdef DEBUG_LINEINFO}
   writeln(stderr,filename,' Baseaddr: ',hexstr(ptruint(baseaddr),sizeof(baseaddr)*2));
 {$endif DEBUG_LINEINFO}
@@ -94,6 +112,7 @@
       CloseExeFile(e);
       if not OpenExeFile(e,dbgfn) then
         exit;
+      dbgfile := true;
     end;
   e.processaddress:=ptruint(baseaddr)-e.processaddress;
   StabsFunctionRelative := E.FunctionRelative;
@@ -105,7 +124,9 @@
     end
   else
     begin
-      staberr:=true;
+      e.filename := '';//reset filename to avoid recursion
+      CloseExeFile(e);
+      if exemodule or dbgfile then  staberr:=true;
       exit;
     end;
 end;
@@ -134,12 +155,10 @@
   line:=0;
   if staberr then
     exit;
-  if not e.isopen then
-   begin
-     if not OpenStabs(pointer(addr)) then
-      exit;
-   end;
 
+  if not OpenStabs(pointer(addr)) then  exit;
+
+
   { correct the value to the correct address in the file }
   { processaddress is set in OpenStabs                   }
   addr := dword(addr - e.processaddress);
@@ -246,8 +265,6 @@
      if i>0 then
       Delete(func,i,255);
    end;
-  if e.isopen then
-    CloseStabs;
   GetLineInfo:=true;
 end;
 
@@ -290,13 +307,13 @@
       end;
      StabBackTraceStr:=StabBackTraceStr+' of '+source;
    end;
-  if Success then
     BackTraceStrFunc:=Store;
 end;
 
 
 initialization
   BackTraceStrFunc:=@StabBackTraceStr;
+  e.filename := '';
 
 finalization
   if e.isopen then
lineinfo2.patch (4,251 bytes)

cobines

2009-11-15 05:57

reporter   ~0032142

Failing to read debug line info should not be considered an error. Once a module is encountered without debug info (like the system libraries) the line info completely stops working.

The possible infinite loop is an unrelated issue, but there is already a guard for that. The guard however is supposed to work in case of a second exception, but it also applies when no debug info was found in a module.

Why the staberr, DwarfErr and Success variables cannot simply be removed?

2012-02-29 13:36

 

unCustomLineInfo.pas (8,192 bytes)
{
    This file is part of the Free Pascal run time library.
    Copyright (c) 2000 by Peter Vreman

    Stabs Line Info Retriever

    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 **********************************************************************}
{
  This unit should not be compiled in objfpc mode, since this would make it
  dependent on objpas unit.
}
unit unCustomLineInfo;
interface

{$S-}
{$Q-}

function GetLineInfo(addr:ptruint;var func,source:shortstring;var line:longint) : boolean;
function StabBackTraceStr(addr:Pointer):shortstring;

implementation

uses
  exeinfo,strings;

const
  N_Function    = $24;
  N_TextLine    = $44;
  N_DataLine    = $46;
  N_BssLine     = $48;
  N_SourceFile  = $64;
  N_IncludeFile = $84;

  maxstabs = 40; { size of the stabs buffer }

var
  { GDB after 4.18 uses offset to function begin
    in text section but OS/2 version still uses 4.16 PM }
  StabsFunctionRelative: boolean;

type
  pstab=^tstab;
  tstab=packed record
    strpos  : longint;
    ntype   : byte;
    nother  : byte;
    ndesc   : word;
    nvalue  : dword;
  end;

{ We use static variable so almost no stack is required, and is thus
  more safe when an error has occured in the program }
var
  e          : TExeFile;
  stabcnt,              { amount of stabs }
  stablen,
  stabofs,              { absolute stab section offset in executable }
  stabstrlen,
  stabstrofs : longint; { absolute stabstr section offset in executable }
  dirlength  : longint; { length of the dirctory part of the source file }
  stabs      : array[0..maxstabs-1] of tstab;  { buffer }
  funcstab,             { stab with current function info }
  linestab,             { stab with current line info }
  dirstab,              { stab with current directory info }
  filestab   : tstab;   { stab with current file info }
  filename,
  dbgfn : shortstring;


function OpenStabs(addr : pointer) : boolean;
  var
    baseaddr : pointer;
begin
  OpenStabs:=false;

  GetModuleByAddr(addr,baseaddr,filename);
{$ifdef DEBUG_LINEINFO}
  writeln(stderr,filename,' Baseaddr: ',hexstr(ptruint(baseaddr),sizeof(baseaddr)*2));
{$endif DEBUG_LINEINFO}

  if not OpenExeFile(e,filename) then
    exit;
  if ReadDebugLink(e,dbgfn) then
    begin
      CloseExeFile(e);
      if not OpenExeFile(e,dbgfn) then
        exit;
    end;
  e.processaddress:=ptruint(baseaddr)-e.processaddress;
  StabsFunctionRelative := E.FunctionRelative;
  if FindExeSection(e,'.stab',stabofs,stablen) and
     FindExeSection(e,'.stabstr',stabstrofs,stabstrlen) then
    begin
      stabcnt:=stablen div sizeof(tstab);
      OpenStabs:=true;
    end
  else
    begin
      CloseExeFile(e);
      exit;
    end;
end;


procedure CloseStabs;
begin
  CloseExeFile(e);
end;


function GetLineInfo(addr:ptruint;var func,source:shortstring;var line:longint) : boolean;
var
  res,
  stabsleft,
  stabscnt,i : longint;
  found : boolean;
  lastfunc : tstab;
begin
  GetLineInfo:=false;
{$ifdef DEBUG_LINEINFO}
  writeln(stderr,'GetLineInfo called');
{$endif DEBUG_LINEINFO}
  fillchar(func,high(func)+1,0);
  fillchar(source,high(source)+1,0);
  line:=0;
  if not e.isopen then
   begin
     if not OpenStabs(pointer(addr)) then
      exit;
   end;

  { correct the value to the correct address in the file }
  { processaddress is set in OpenStabs                   }
  addr := dword(addr - e.processaddress);

{$ifdef DEBUG_LINEINFO}
  writeln(stderr,'Addr: ',hexstr(addr,sizeof(addr)*2));
{$endif DEBUG_LINEINFO}

  fillchar(funcstab,sizeof(tstab),0);
  fillchar(filestab,sizeof(tstab),0);
  fillchar(dirstab,sizeof(tstab),0);
  fillchar(linestab,sizeof(tstab),0);
  fillchar(lastfunc,sizeof(tstab),0);
  found:=false;
  seek(e.f,stabofs);
  stabsleft:=stabcnt;
  repeat
    if stabsleft>maxstabs then
     stabscnt:=maxstabs
    else
     stabscnt:=stabsleft;
    blockread(e.f,stabs,stabscnt*sizeof(tstab),res);
    stabscnt:=res div sizeof(tstab);
    for i:=0 to stabscnt-1 do
     begin
       case stabs[i].ntype of
         N_BssLine,
         N_DataLine,
         N_TextLine :
           begin
             if (stabs[i].ntype=N_TextLine) and StabsFunctionRelative then
               inc(stabs[i].nvalue,lastfunc.nvalue);
             if (stabs[i].nvalue<=addr) and
                (stabs[i].nvalue>linestab.nvalue) then
              begin
                { if it's equal we can stop and take the last info }
                if stabs[i].nvalue=addr then
                 found:=true
                else
                 linestab:=stabs[i];
              end;
           end;
         N_Function :
           begin
             lastfunc:=stabs[i];
             if (stabs[i].nvalue<=addr) and
                (stabs[i].nvalue>funcstab.nvalue) then
              begin
                funcstab:=stabs[i];
                fillchar(linestab,sizeof(tstab),0);
              end;
           end;
         N_SourceFile,
         N_IncludeFile :
           begin
             if (stabs[i].nvalue<=addr) and
                (stabs[i].nvalue>=filestab.nvalue) then
              begin
                { if same value and type then the first one
                  contained the directory PM }
                if (stabs[i].nvalue=filestab.nvalue) and
                   (stabs[i].ntype=filestab.ntype) then
                  dirstab:=filestab
                else
                  fillchar(dirstab,sizeof(tstab),0);
                filestab:=stabs[i];
                fillchar(linestab,sizeof(tstab),0);
                { if new file then func is not valid anymore PM }
                if stabs[i].ntype=N_SourceFile then
                  begin
                    fillchar(funcstab,sizeof(tstab),0);
                    fillchar(lastfunc,sizeof(tstab),0);
                  end;
              end;
           end;
       end;
     end;
    dec(stabsleft,stabscnt);
  until found or (stabsleft=0);

{ get the line,source,function info }
  line:=linestab.ndesc;
  if dirstab.ntype<>0 then
   begin
     seek(e.f,stabstrofs+dirstab.strpos);
     blockread(e.f,source[1],high(source)-1,res);
     dirlength:=strlen(@source[1]);
     source[0]:=chr(dirlength);
   end
  else
   dirlength:=0;
  if filestab.ntype<>0 then
   begin
     seek(e.f,stabstrofs+filestab.strpos);
     blockread(e.f,source[dirlength+1],high(source)-(dirlength+1),res);
     source[0]:=chr(strlen(@source[1]));
   end;
  if funcstab.ntype<>0 then
   begin
     seek(e.f,stabstrofs+funcstab.strpos);
     blockread(e.f,func[1],high(func)-1,res);
     func[0]:=chr(strlen(@func[1]));
     i:=pos(':',func);
     if i>0 then
      Delete(func,i,255);
   end;
  if e.isopen then
    CloseStabs;
  GetLineInfo:=true;
end;


function StabBackTraceStr(addr:Pointer):shortstring;
var
  func,
  source : shortstring;
  hs     : string[32];
  line   : longint;
  Store  : TBackTraceStrFunc;
  Success : boolean;
begin
{$ifdef DEBUG_LINEINFO}
  writeln(stderr,'StabBackTraceStr called');
{$endif DEBUG_LINEINFO}
  { reset to prevent infinite recursion if problems inside the code PM }
  Success:=false;
  Store:=BackTraceStrFunc;
  BackTraceStrFunc:=@SysBackTraceStr;
  Success:=GetLineInfo(ptruint(addr),func,source,line);
{ create string }
{$ifdef netware}
  { we need addr relative to code start on netware }
  dec(addr,ptruint(system.NWGetCodeStart));
  StabBackTraceStr:='  CodeStart + $'+HexStr(ptruint(addr),sizeof(ptruint)*2);
{$else}
  StabBackTraceStr:='  $'+HexStr(ptruint(addr),sizeof(ptruint)*2);
{$endif}
  if func<>'' then
    StabBackTraceStr:=StabBackTraceStr+'  '+func;
  if source<>'' then
   begin
     if func<>'' then
      StabBackTraceStr:=StabBackTraceStr+', ';
     if line<>0 then
      begin
        str(line,hs);
        StabBackTraceStr:=StabBackTraceStr+' line '+hs;
      end;
     StabBackTraceStr:=StabBackTraceStr+' of '+source;
   end;
  if Success then
    BackTraceStrFunc:=Store;
end;


initialization
  BackTraceStrFunc:=@StabBackTraceStr;

finalization
  if e.isopen then
   CloseStabs;
end.
unCustomLineInfo.pas (8,192 bytes)

2012-02-29 13:37

 

unStackTrace.pas (2,573 bytes)
unit unStackTrace;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Contnrs, unCustomLineInfo;

type
  TStackFrameInfo = class
    Index: Integer;
    LineNumber: Integer;
    Address: Integer;
    FunctionName: string;
    Source: string;
    procedure GetFrameInfo(Addr: Pointer);
  end;

  TStackTrace = class(TObjectList)
    MaxDepth: Integer;
    procedure GetExceptionBackTrace;
    procedure GetCallStack;
  end;


implementation

procedure TStackFrameInfo.GetFrameInfo(Addr: Pointer);
var
  Func: shortstring;
  SourceStr: shortstring;
  Line: LongInt;
  Store: TBackTraceStrFunc;
  Success: Boolean;
begin
  // Reset to prevent infinite recursion if problems inside the code PM
  Store := BackTraceStrFunc;
  BackTraceStrFunc := @SysBackTraceStr;
  Success := GetLineInfo(ptruint(Addr), Func, SourceStr, Line);
  Address := Integer(Addr);
  FunctionName := Func;
  LineNumber := Line;
  Source := SourceStr;
  BackTraceStrFunc := Store;
end;

procedure TStackTrace.GetCallStack;
var
  I: Longint;
  prevbp: Pointer;
  CallerFrame,
  CallerAddress,
  bp: Pointer;
  StackFrameInfo: TStackFrameInfo;
begin
  Clear;
  //routine adapted from fpc source

  //This trick skip SendCallstack item
  bp := get_frame;
  //bp:= get_caller_frame(get_frame);
  try
    prevbp := bp - 1;
    I := 0;
    //is_dev:=do_isdevice(textrec(f).Handle);
    while bp > prevbp do begin
       CallerAddress := get_caller_addr(bp);
       CallerFrame := get_caller_frame(bp);
       if (CallerAddress = nil) then
         Break;
       StackFrameInfo := TStackFrameInfo.Create;
       StackFrameInfo.GetFrameInfo(CallerAddress);
       StackFrameInfo.Index := I + 1;
       Add(StackFrameInfo);
       Inc(I);
       if (I >= MaxDepth) or (CallerFrame = nil) then
         Break;
       prevbp := bp;
       bp := CallerFrame;
     end;
   except
     { prevent endless dump if an exception occured }
   end;
end;

procedure TStackTrace.GetExceptionBackTrace;
var
  FrameCount: Integer;
  Frames: PPointer;
  FrameNumber: Integer;
  StackFrameInfo: TStackFrameInfo;
begin
  Clear;
  StackFrameInfo := TStackFrameInfo.Create;
  StackFrameInfo.GetFrameInfo(ExceptAddr);
  StackFrameInfo.Index := 1;
  Add(StackFrameInfo);
  FrameCount := ExceptFrameCount;
  Frames := ExceptFrames;
  if FrameCount > MaxDepth then FrameCount := MaxDepth;
  for FrameNumber := 0 to FrameCount - 1 do begin
    StackFrameInfo := TStackFrameInfo.Create;
    StackFrameInfo.GetFrameInfo(Frames[FrameNumber]);
    StackFrameInfo.Index := FrameNumber + 1;
    Add(StackFrameInfo);
  end;
end;



end.

unStackTrace.pas (2,573 bytes)

Everton Vieira

2012-02-29 13:37

reporter   ~0057163

I use this two units i just uploaded to do this.

Everton Vieira

2012-03-05 13:52

reporter   ~0057281

With this two units:

 unCustomLineInfo.pas [^] (8,192 bytes) 2012-02-29 13:36
 unStackTrace.pas [^] (2,573 bytes) 2012-02-29 13:37

I was able to do the exception back trace of the application.
Without simply doesn't work.

Everton Vieira

2012-03-08 17:34

reporter   ~0057411

I'm using on the Application.onException

Denis Kozlov

2015-06-05 15:39

reporter   ~0084267

Over 6 years old... I'm startled.

How can we build reliable software if we can't reliably get debug info from exceptions?

Jonas Maebe

2015-06-06 11:26

manager   ~0084280

> How can we build reliable software if we can't reliably get debug info from exceptions?

The same as on platforms where lineinfo is not supported at all: by using the debugger to determine the corresponding line numbers.

In gdb: info line *0xaddress
In lldb: image lookup -v --address 0xaddress

Boguslaw Brandys

2015-06-07 10:18

reporter   ~0084285

Welcome to the time machine Denis :-) I remember I spent lot of time to solve this problem yet no reaction from developers who can solve it in no time I think.Maybe add a voting system to appreciate what is really important for fpc users to fix first ?

Denis Kozlov

2015-06-08 17:30

reporter  

lineinfo.pp (9,646 bytes)
{
    This file is part of the Free Pascal run time library.
    Copyright (c) 2000 by Peter Vreman

    Stabs Line Info Retriever

    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 **********************************************************************}
{
  This unit should not be compiled in objfpc mode, since this would make it
  dependent on objpas unit.
}
unit lineinfo;
interface

{$S-}
{$Q-}

function GetLineInfo(addr:ptruint;var func,source:string;var line:longint) : boolean;
function StabBackTraceStr(addr:{$IF FPC_VERSION>=3}CodePointer{$ELSE}Pointer{$ENDIF}):string;
procedure CloseStabs;

implementation

uses
  exeinfo,strings;

const
  N_Function    = $24;
  N_TextLine    = $44;
  N_DataLine    = $46;
  N_BssLine     = $48;
  N_SourceFile  = $64;
  N_IncludeFile = $84;

  maxstabs = 40; { size of the stabs buffer }

var
  { GDB after 4.18 uses offset to function begin
    in text section but OS/2 version still uses 4.16 PM }
  StabsFunctionRelative: boolean;

type
  pstab=^tstab;
  tstab=packed record
    strpos  : longint;
    ntype   : byte;
    nother  : byte;
    ndesc   : word;
    nvalue  : dword;
  end;

{ We use static variable so almost no stack is required, and is thus
  more safe when an error has occured in the program }
{$WARNING This code is not thread-safe, and needs improvement }  
var
  e          : TExeFile;
  stabcnt,              { amount of stabs }
  stablen,
  stabofs,              { absolute stab section offset in executable }
  stabstrlen,
  stabstrofs : longint; { absolute stabstr section offset in executable }
  dirlength  : longint; { length of the dirctory part of the source file }
  stabs      : array[0..maxstabs-1] of tstab;  { buffer }
  funcstab,             { stab with current function info }
  linestab,             { stab with current line info }
  dirstab,              { stab with current directory info }
  filestab   : tstab;   { stab with current file info }
  filename,
  lastfilename,         { store last processed file }
  dbgfn : string;
  lastopenstabs: Boolean; { store last result of processing a file }


function OpenStabs(addr : pointer) : boolean;
  var
    baseaddr : pointer;
begin
  // False by default
  OpenStabs:=false;

  // Empty so can test if GetModuleByAddr has worked
  filename := '';

  // Get filename by address using GetModuleByAddr
  GetModuleByAddr(addr,baseaddr,filename);
{$ifdef DEBUG_LINEINFO}
  writeln(stderr,filename,' Baseaddr: ',hexstr(ptruint(baseaddr),sizeof(baseaddr)*2));
{$endif DEBUG_LINEINFO}

  // Check if GetModuleByAddr has work
  if filename = '' then
    exit;

  // If target filename same as previous, then re-use previous result
  if filename = lastfilename then
  begin
    OpenStabs:=lastopenstabs;
    exit;
  end;

  // Close previously opened stabs
  CloseStabs;

  // Reset last open stabs result
  lastopenstabs := false;

  // Save newly processed filename
  lastfilename := filename;

  // Open exe file or debug link
  if not OpenExeFile(e,filename) then
    exit;
  if ReadDebugLink(e,dbgfn) then
    begin
      CloseExeFile(e);
      if not OpenExeFile(e,dbgfn) then
        exit;
    end;

  // Find stab section
{$ifdef BeOS}
  { Do not change ProcessAddress field for BeOS/Haiku
    if baseAddr is lower than ProcessAdress }
  if ptruint(baseaddr)>ptruint(e.processaddress) then
{$endif BeOS}
    e.processaddress:=ptruint(baseaddr)-e.processaddress;
  StabsFunctionRelative := E.FunctionRelative;
  if FindExeSection(e,'.stab',stabofs,stablen) and
     FindExeSection(e,'.stabstr',stabstrofs,stabstrlen) then
    begin
      stabcnt:=stablen div sizeof(tstab);
      lastopenstabs:=true;
      OpenStabs:=true;
    end
  else
    begin
      CloseExeFile(e);
      exit;
    end;
end;


procedure CloseStabs;
begin
  if e.isopen then
  begin
    CloseExeFile(e);

    // Reset last processed filename
    lastfilename := '';
  end;
end;


function GetLineInfo(addr:ptruint;var func,source:string;var line:longint) : boolean;
var
  res,
  stabsleft,
  stabscnt,i : longint;
  found : boolean;
  lastfunc : tstab;
begin
  GetLineInfo:=false;
{$ifdef DEBUG_LINEINFO}
  writeln(stderr,'GetLineInfo called');
{$endif DEBUG_LINEINFO}
  fillchar(func,high(func)+1,0);
  fillchar(source,high(source)+1,0);
  line:=0;

  if not OpenStabs(pointer(addr)) then
    exit;

  { correct the value to the correct address in the file }
  { processaddress is set in OpenStabs                   }
  addr := dword(addr - e.processaddress);

{$ifdef DEBUG_LINEINFO}
  writeln(stderr,'Addr: ',hexstr(addr,sizeof(addr)*2));
{$endif DEBUG_LINEINFO}

  fillchar(funcstab,sizeof(tstab),0);
  fillchar(filestab,sizeof(tstab),0);
  fillchar(dirstab,sizeof(tstab),0);
  fillchar(linestab,sizeof(tstab),0);
  fillchar(lastfunc,sizeof(tstab),0);
  found:=false;
  seek(e.f,stabofs);
  stabsleft:=stabcnt;
  repeat
    if stabsleft>maxstabs then
     stabscnt:=maxstabs
    else
     stabscnt:=stabsleft;
    blockread(e.f,stabs,stabscnt*sizeof(tstab),res);
    stabscnt:=res div sizeof(tstab);
    for i:=0 to stabscnt-1 do
     begin
       case stabs[i].ntype of
         N_BssLine,
         N_DataLine,
         N_TextLine :
           begin
             if (stabs[i].ntype=N_TextLine) and StabsFunctionRelative then
               inc(stabs[i].nvalue,lastfunc.nvalue);
             if (stabs[i].nvalue<=addr) and
                (stabs[i].nvalue>linestab.nvalue) then
              begin
                { if it's equal we can stop and take the last info }
                if stabs[i].nvalue=addr then
                 found:=true
                else
                 linestab:=stabs[i];
              end;
           end;
         N_Function :
           begin
             lastfunc:=stabs[i];
             if (stabs[i].nvalue<=addr) and
                (stabs[i].nvalue>funcstab.nvalue) then
              begin
                funcstab:=stabs[i];
                fillchar(linestab,sizeof(tstab),0);
              end;
           end;
         N_SourceFile,
         N_IncludeFile :
           begin
             if (stabs[i].nvalue<=addr) and
                (stabs[i].nvalue>=filestab.nvalue) then
              begin
                { if same value and type then the first one
                  contained the directory PM }
                if (stabs[i].nvalue=filestab.nvalue) and
                   (stabs[i].ntype=filestab.ntype) then
                  dirstab:=filestab
                else
                  fillchar(dirstab,sizeof(tstab),0);
                filestab:=stabs[i];
                fillchar(linestab,sizeof(tstab),0);
                { if new file then func is not valid anymore PM }
                if stabs[i].ntype=N_SourceFile then
                  begin
                    fillchar(funcstab,sizeof(tstab),0);
                    fillchar(lastfunc,sizeof(tstab),0);
                  end;
              end;
           end;
       end;
     end;
    dec(stabsleft,stabscnt);
  until found or (stabsleft=0);

{ get the line,source,function info }
  line:=linestab.ndesc;
  if dirstab.ntype<>0 then
   begin
     seek(e.f,stabstrofs+dirstab.strpos);
     blockread(e.f,source[1],high(source)-1,res);
     dirlength:=strlen(@source[1]);
     source[0]:=chr(dirlength);
   end
  else
   dirlength:=0;
  if filestab.ntype<>0 then
   begin
     seek(e.f,stabstrofs+filestab.strpos);
     blockread(e.f,source[dirlength+1],high(source)-(dirlength+1),res);
     source[0]:=chr(strlen(@source[1]));
   end;
  if funcstab.ntype<>0 then
   begin
     seek(e.f,stabstrofs+funcstab.strpos);
     blockread(e.f,func[1],high(func)-1,res);
     func[0]:=chr(strlen(@func[1]));
     i:=pos(':',func);
     if i>0 then
      Delete(func,i,255);
   end;

  GetLineInfo:=true;
end;


function StabBackTraceStr(addr:{$IF FPC_VERSION>=3}CodePointer{$ELSE}Pointer{$ENDIF}):string;
var
  func,
  source : string;
  hs     : string;
  line   : longint;
  Store  : TBackTraceStrFunc;
  Success : boolean;
begin
{$ifdef DEBUG_LINEINFO}
  writeln(stderr,'StabBackTraceStr called');
{$endif DEBUG_LINEINFO}
  { reset to prevent infinite recursion if problems inside the code PM }
  Success:=false;
  Store:=BackTraceStrFunc;
  BackTraceStrFunc:=@SysBackTraceStr;
  Success:=GetLineInfo(ptruint(addr),func,source,line);
{ create string }
{$ifdef netware}
  { we need addr relative to code start on netware }
  dec(addr,ptruint(system.NWGetCodeStart));
  StabBackTraceStr:='  CodeStart + $'+HexStr(ptruint(addr),sizeof(ptruint)*2);
{$else}
  StabBackTraceStr:='  $'+HexStr(ptruint(addr),sizeof(ptruint)*2);
{$endif}
  if func<>'' then
    StabBackTraceStr:=StabBackTraceStr+'  '+func;
  if source<>'' then
   begin
     if func<>'' then
      StabBackTraceStr:=StabBackTraceStr+', ';
     if line<>0 then
      begin
        str(line,hs);
        StabBackTraceStr:=StabBackTraceStr+' line '+hs;
      end;
     StabBackTraceStr:=StabBackTraceStr+' of '+source;
   end;
  BackTraceStrFunc:=Store;
end;


initialization
  lastfilename := '';
  lastopenstabs := false;
  BackTraceStrFunc:=@StabBackTraceStr;

finalization
  CloseStabs;

end.
lineinfo.pp (9,646 bytes)

Denis Kozlov

2015-06-08 17:31

reporter   ~0084309

I didn't like previously submitted patches (several reasons). So, I have made an attempt at fixing basic issues with "lineinfo.pp".

I've tried to keep changes to a minimum and maintain currently used conventions to make validation easier. Instead of separate patches for 2.6, 3.0 and trunk branches I will attach a full "lineinfo.pp" unit which accommodates all branches.

Summary of changes:
1) Removed use of "staberr" variable which permanently disabled line info functionality on first failed attempt to read stabs.
2) Reuse last opened file in OpenStabs, using new "lastfilename" and "lastopenstabs" variables.
3) GetLineInfo no longer closes stabs, as this will not allow for reuse.
4) Restore original BackTraceStrFunc even if GetLineInfo has failed.
5) Conditional parameter type in StabBackTraceStr to handle both FPC v2 and v3 (due to CodePointer type).
6) Added StabBackTraceStr and CloseStabs to the interface section.

Tested on Windows 7 with GUI and Console apps.

lineinfo.pp [^] (9,646 bytes) 2015-06-08 16:30

Michael Van Canneyt

2015-06-12 09:18

administrator   ~0084381

Applied the patch with a minor change for improved readability.
Thank you very much.

(@note to self: we should check the DWARF unit for the same error?)

Issue History

Date Modified Username Field Change
2009-04-15 09:08 Boguslaw Brandys New Issue
2009-04-15 21:57 Boguslaw Brandys Note Added: 0026775
2009-04-15 22:07 Boguslaw Brandys Note Edited: 0026775
2009-04-16 16:58 Boguslaw Brandys File Added: lineinfo.patch
2009-04-16 17:02 Boguslaw Brandys Note Added: 0026797
2009-05-10 11:36 Jonas Maebe Note Added: 0027478
2009-05-12 09:10 Boguslaw Brandys Note Added: 0027545
2009-06-27 17:46 Boguslaw Brandys Note Added: 0028802
2009-06-27 17:47 Boguslaw Brandys Note Added: 0028803
2009-06-27 17:50 Boguslaw Brandys File Added: lineinfo2.patch
2009-11-15 05:57 cobines Note Added: 0032142
2012-02-26 01:00 Jonas Maebe Relationship added has duplicate 0021370
2012-02-29 13:36 Everton Vieira File Added: unCustomLineInfo.pas
2012-02-29 13:37 Everton Vieira File Added: unStackTrace.pas
2012-02-29 13:37 Everton Vieira Note Added: 0057163
2012-03-05 13:52 Everton Vieira Note Added: 0057281
2012-03-08 17:34 Everton Vieira Note Added: 0057411
2015-06-05 15:10 Jonas Maebe Relationship added has duplicate 0028245
2015-06-05 15:39 Denis Kozlov Note Added: 0084267
2015-06-06 11:26 Jonas Maebe Note Added: 0084280
2015-06-07 10:18 Boguslaw Brandys Note Added: 0084285
2015-06-08 17:30 Denis Kozlov File Added: lineinfo.pp
2015-06-08 17:31 Denis Kozlov Note Added: 0084309
2015-06-08 17:34 Denis Kozlov Tag Attached: BackTraceStrFunc
2015-06-08 17:34 Denis Kozlov Tag Attached: Exception
2015-06-08 17:34 Denis Kozlov Tag Attached: patch
2015-06-08 17:34 Denis Kozlov Tag Attached: debug
2015-06-08 17:34 Denis Kozlov Tag Attached: lineinfo
2015-06-12 09:18 Michael Van Canneyt Fixed in Revision => 31026
2015-06-12 09:18 Michael Van Canneyt Note Added: 0084381
2015-06-12 09:18 Michael Van Canneyt Status new => resolved
2015-06-12 09:18 Michael Van Canneyt Fixed in Version => 3.1.1
2015-06-12 09:18 Michael Van Canneyt Resolution open => fixed
2015-06-12 09:18 Michael Van Canneyt Assigned To => Michael Van Canneyt
2015-06-12 09:18 Michael Van Canneyt Target Version => 4.0.0
2015-08-30 22:38 Joost van der Sluis Fixed in Version 3.1.1 => 3.0.1