Difference between revisions of "Logging exceptions"

From Lazarus wiki
m (Text replace - "delphi>" to "syntaxhighlight>")
Line 16: Line 16:
 
Contains some routines useful for debugging exceptions.
 
Contains some routines useful for debugging exceptions.
  
<delphi>{ Exception handling routines }
+
<syntaxhighlight>{ Exception handling routines }
 
function ExceptObject: TObject;
 
function ExceptObject: TObject;
 
function ExceptAddr: Pointer;
 
function ExceptAddr: Pointer;
 
function ExceptFrameCount: Longint;
 
function ExceptFrameCount: Longint;
function ExceptFrames: PPointer;</delphi>
+
function ExceptFrames: PPointer;</syntaxhighlight>
  
 
===Unit System===
 
===Unit System===
 
Contains some stack related routines:
 
Contains some stack related routines:
  
<delphi>function SysBackTraceStr(Addr:Pointer): ShortString; // Default address to string converter assigned to BackTraceStrFunc
+
<syntaxhighlight>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 Dump_Stack(var f : text;bp:pointer); // Dump stack to text file
procedure DumpExceptionBackTrace(var f:text); // Dump backtrace to text file</delphi>
+
procedure DumpExceptionBackTrace(var f:text); // Dump backtrace to text file</syntaxhighlight>
  
 
Procedural variable '''BackTraceStrFunc''' responsible to translate memory address to string debug information. Default behavior is implemented by '''SysBackTraceStr'''.
 
Procedural variable '''BackTraceStrFunc''' responsible to translate memory address to string debug information. Default behavior is implemented by '''SysBackTraceStr'''.
Line 42: Line 42:
 
===Unit LCLProc===
 
===Unit LCLProc===
  
<delphi>// Debugging
+
<syntaxhighlight>// Debugging
 
procedure RaiseGDBException(const Msg: string);
 
procedure RaiseGDBException(const Msg: string);
 
procedure RaiseAndCatchException;
 
procedure RaiseAndCatchException;
Line 51: Line 51:
 
function StackTraceAsString(const AStack: TStackTracePointers;
 
function StackTraceAsString(const AStack: TStackTracePointers;
 
                             UseCache: boolean): string;
 
                             UseCache: boolean): string;
function GetLineInfo(Addr: Pointer; UseCache: boolean): string;</delphi>
+
function GetLineInfo(Addr: Pointer; UseCache: boolean): string;</syntaxhighlight>
  
  
Line 61: Line 61:
 
==Dump current call stack==
 
==Dump current call stack==
  
<delphi>procedure DumpCallStack;
+
<syntaxhighlight>procedure DumpCallStack;
 
var
 
var
 
   I: Longint;
 
   I: Longint;
Line 95: Line 95:
 
   end;
 
   end;
 
   ShowMessage(Report);
 
   ShowMessage(Report);
end;</delphi>
+
end;</syntaxhighlight>
  
 
==Dump exception call stack==
 
==Dump exception call stack==
Line 101: Line 101:
 
Call stack of exception can be obtained through SysUtils functions '''ExceptAddr''', '''ExceptFrames''' and '''ExceptFrameCount'''.
 
Call stack of exception can be obtained through SysUtils functions '''ExceptAddr''', '''ExceptFrames''' and '''ExceptFrameCount'''.
  
<delphi>uses SysUtils;
+
<syntaxhighlight>uses SysUtils;
  
 
procedure DumpExceptionCallStack(E: Exception);
 
procedure DumpExceptionCallStack(E: Exception);
Line 121: Line 121:
 
   ShowMessage(Report);
 
   ShowMessage(Report);
 
   Halt; // End of program execution
 
   Halt; // End of program execution
end;</delphi>
+
end;</syntaxhighlight>
  
 
==Handling exceptions==
 
==Handling exceptions==
Line 129: Line 129:
 
Manual executions of exception handler can be inserted to many places in code.
 
Manual executions of exception handler can be inserted to many places in code.
  
<delphi>
+
<syntaxhighlight>
 
try
 
try
 
   // Some operation which raise exception
 
   // Some operation which raise exception
Line 136: Line 136:
 
   on E: Exception do  
 
   on E: Exception do  
 
     DumpExceptionCallStack(E);
 
     DumpExceptionCallStack(E);
end;</delphi>
+
end;</syntaxhighlight>
  
  
Line 143: Line 143:
 
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.
 
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);
+
<syntaxhighlight>procedure CatchUnhandledException(Obj: TObject; Addr: Pointer; FrameCount: Longint; Frames: PPointer);
 
var
 
var
 
   Message: string;
 
   Message: string;
Line 165: Line 165:
 
     end;
 
     end;
 
   Writeln(hstdout^,'');
 
   Writeln(hstdout^,'');
end;</delphi>
+
end;</syntaxhighlight>
  
 
===TApplication.OnException===
 
===TApplication.OnException===
Line 171: Line 171:
 
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.
 
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);
+
<syntaxhighlight>procedure TMainForm.CustomExceptionHandler(Sender: TObject; E: Exception);
 
begin
 
begin
 
   DumpExceptionCallStack;
 
   DumpExceptionCallStack;
Line 185: Line 185:
 
begin
 
begin
 
   raise Exception.Create('Test');
 
   raise Exception.Create('Test');
end;</delphi>
+
end;</syntaxhighlight>
  
 
===Handling thread exceptions===
 
===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'''.  
 
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;
+
<syntaxhighlight>function ThreadProc(ThreadObjPtr: Pointer): PtrInt;
 
begin
 
begin
 
   ...
 
   ...
Line 199: Line 199:
 
   end;  
 
   end;  
 
   ...
 
   ...
end;</delphi>
+
end;</syntaxhighlight>
  
 
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 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.
Line 206: Line 206:
 
In every thread in application own try-except block should be inserted to catch all unhandled exceptions by custom exception handler.
 
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;
+
<syntaxhighlight>procedure TMyThread.Execute;
 
begin
 
begin
 
   try
 
   try
Line 214: Line 214:
 
       CustomExceptionThreadHandler(Self, E);
 
       CustomExceptionThreadHandler(Self, E);
 
   end;
 
   end;
end;</delphi>
+
end;</syntaxhighlight>
  
 
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.
 
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);
+
<syntaxhighlight>procedure TMainForm.CustomExceptionHandler(Thread: TThread; E: Exception);
 
begin
 
begin
 
   Thread.Synchronize(DumpExceptionCallStack);
 
   Thread.Synchronize(DumpExceptionCallStack);
end;</delphi>
+
end;</syntaxhighlight>
  
 
===Using map file===
 
===Using map file===

Revision as of 15:41, 24 March 2012

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.

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

Unit System

Contains some stack related routines:

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

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

// 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;


FPC help:


Dump current call stack

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;

Dump exception call stack

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

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;

Handling exceptions

Manual exception handling

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

try
  // Some operation which raise exception
  raise Exception.Create('Test error');
except
  on E: Exception do 
    DumpExceptionCallStack(E);
end;


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.

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;

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.

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;

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.

function ThreadProc(ThreadObjPtr: Pointer): PtrInt;
begin
  ...
  try
    Thread.Execute;
  except
    Thread.FFatalException := TObject(AcquireExceptionObject);
  end; 
  ...
end;

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.

procedure TMyThread.Execute;
begin
  try
    // some erroneous code
  except
    on E: Exception do 
      CustomExceptionThreadHandler(Self, E);
  end;
end;

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.

procedure TMainForm.CustomExceptionHandler(Thread: TThread; E: Exception);
begin
  Thread.Synchronize(DumpExceptionCallStack);
end;

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)