Logging exceptions

From Lazarus wiki
Revision as of 10:37, 13 September 2010 by Chronos (talk | contribs)

Introduction

Stacktrace is sometimes called Backtrace or Call stack and have meaning of list of stack frames placed on stack containing return address and local variables. Stacktrace is useful to trace back path of execution of nesting procedures.


Compiler have to be directed to get debug information by switches.

  • -g - generate debug information
  • -gl - generate line numbers for debug information
  • -gw - generate older dwarf debug information
  • -Xg - use external debug symbol file


Unit SysUtils

Contains some routines useful for debugging exceptions.

<delphi>{ Exception handling routines } function ExceptObject: TObject; function ExceptAddr: Pointer; function ExceptFrameCount: Longint; function ExceptFrames: PPointer;</delphi>

Unit System

Contains some stack related routines:

<delphi>function SysBackTraceStr(Addr:Pointer): ShortString; // Default address to string converter assigned to BackTraceStrFunc procedure Dump_Stack(var f : text;bp:pointer); // Dump stack to text file procedure DumpExceptionBackTrace(var f:text); // Dump backtrace to text file</delphi>

Procedural variable BackTraceStrFunc responsible to translate memory address to string debug information. Default behavior is implemented by SysBackTraceStr.

STAB

If STAB debugging information is selected (compiler switch -gl), unit lineinfo is automatically included to program and BackTraceStrFunc function is remapped to StabBackTraceStr.

DWARF

If DWARF debugging information is selected (compiler switch -gw), unit lnfodwrf is automatically included to program and BackTraceStrFunc function is remapped to DwarfBacktraceStr.


Unit LCLProc

<delphi>// Debugging procedure RaiseGDBException(const Msg: string); procedure RaiseAndCatchException; procedure DumpExceptionBackTrace; procedure DumpStack; function GetStackTrace(UseCache: boolean): string; procedure GetStackTracePointers(var AStack: TStackTracePointers); function StackTraceAsString(const AStack: TStackTracePointers;

                           UseCache: boolean): string;

function GetLineInfo(Addr: Pointer; UseCache: boolean): string;</delphi>


FPC help:


Dump current call stack

<delphi>procedure DumpCallStack; var

 I: Longint;
 prevbp: Pointer;
 CallerFrame,
 CallerAddress,
 bp: Pointer;
 Report: string;

const

 MaxDepth = 20;

begin

 Report := ;
 bp := get_frame;
 // This trick skip SendCallstack item
 // bp:= get_caller_frame(get_frame);
 try
   prevbp := bp - 1;
   I := 0;
   while bp > prevbp do begin
      CallerAddress := get_caller_addr(bp);
      CallerFrame := get_caller_frame(bp);
      if (CallerAddress = nil) then
        Break;
      Report := Report + BackTraceStrFunc(CallerAddress) + LineEnding;
      Inc(I);
      if (I >= MaxDepth) or (CallerFrame = nil) then
        Break;
      prevbp := bp;
      bp := CallerFrame;
    end;
  except
    { prevent endless dump if an exception occured }
  end;
 ShowMessage(Report);

end;</delphi>

Dump exception call stack

Call stack of exception can be obtained through SysUtils functions ExceptAddr, ExceptFrames and ExceptFrameCount.

<delphi>uses SysUtils;

procedure DumpExceptionCallStack(E: Exception); var

 I: Integer;
 Frames: PPointer;
 Report: string;

begin

 Report := 'Program exception! ' + LineEnding +
   'Stacktrace:' + LineEnding + LineEnding;
 if E <> nil then begin
   Report := Report + 'Exception class: ' + E.ClassName + LineEnding +
   'Message: ' + E.Message + LineEnding;
 end;
 Report := Report + BackTraceStrFunc(ExceptAddr);
 Frames := ExceptFrames;
 for I := 0 to ExceptFrameCount - 1 do
   Report := Report + LineEnding + BackTraceStrFunc(Frames[I]);
 ShowMessage(Report);
 Halt; // End of program execution

end;</delphi>

Handling exceptions

Manual exception handling

Manual executions of exception handler can be inserted to many places in code.

<delphi> try

 // Some operation which raise exception
 raise Exception.Create('Test error');

except

 on E: Exception do 
   DumpExceptionCallStack(E);

end;</delphi>


System ExceptProc

If unhandled exception occurs than ExceptProc procedure variable is executed. Default behavior initialized by procedure InitExceptions in unit System can be redefine to custom handler. As example implementation default procedure CatchUnhandledException can be taken.

<delphi>procedure CatchUnhandledException(Obj: TObject; Addr: Pointer; FrameCount: Longint; Frames: PPointer); var

 Message: string;
 i: LongInt;
 hstdout: ^Text;

begin

 hstdout := @stdout;
 Writeln(hstdout^, 'An unhandled exception occurred at $', HexStr(PtrUInt(Addr), SizeOf(PtrUInt) * 2), ' :');
 if Obj is exception then
  begin
    Message := Exception(Obj).ClassName + ' : ' + Exception(Obj).Message;
    Writeln(hstdout^, Message);
  end
 else
   Writeln(hstdout^, 'Exception object ', Obj.ClassName, ' is not of class Exception.');
 Writeln(hstdout^, BackTraceStrFunc(Addr));
 if (FrameCount > 0) then
   begin
     for i := 0 to FrameCount - 1 do
       Writeln(hstdout^, BackTraceStrFunc(Frames[i]));
   end;
 Writeln(hstdout^,);

end;</delphi>

TApplication.OnException

This event can be used to override default application wide exceptions handling. Custom logging mechanism could provide show custom dialog, log to file, console, sending report to mail, logging to HTTP server, e.g.

<delphi>procedure TMainForm.CustomExceptionHandler(Sender: TObject; E: Exception); begin

 DumpExceptionCallStack;
 Halt; // End of program execution

end;

procedure TMainForm.FormCreate(Sender: TObject); begin

 Application.OnException := @CustomExceptionHandler;

end;

procedure TMainForm.ButtonClick(Sender: TObject); begin

 raise Exception.Create('Test');

end;</delphi>

Handling thread exceptions

Handling exceptions which are raised in thread have to be done manually. Thread main TThread method Execute is called form function ThreadProc located in unit Classes. <delphi>function ThreadProc(ThreadObjPtr: Pointer): PtrInt; begin

 ...
 try
   Thread.Execute;
 except
   Thread.FFatalException := TObject(AcquireExceptionObject);
 end; 
 ...

end;</delphi>

In this function Execute method is enclosed in try-except block and all exceptions are handled in way of assigning exception object to FatalException property of TThread objec as last occurred exception object. So exceptions are not displayed to user at all.


In every thread in application own try-except block should be inserted to catch all unhandled exceptions by custom exception handler.

<delphi>procedure TMyThread.Execute; begin

 try
   // some erroneous code
 except
   on E: Exception do 
     CustomExceptionThreadHandler(Self, E);
 end;

end;</delphi>

Then CustomExceptionThreadHandler can get exception call stack and manage showing error message or logging log report to file. As handler is executed from thread showing message dialog have to be done thread safe using Synchronize method.

<delphi>procedure TMainForm.CustomExceptionHandler(Thread: TThread; E: Exception); begin

 Thread.Synchronize(DumpExceptionCallStack);

end;</delphi>

Using map file

Use compiler switch -Xm to generate map file.

Exceptions in DLL

See also

External links

  • esprinter - tool to get stacktrace from running FreePascal program specified by process id and thread id (for Win32)