Sign in Join
Wiki copytemporary

Logging exceptions: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: Logging exceptions

OldNewLine
1+## Introduction
2+
3+A **stacktrace** is sometimes called **backtrace** or **call stack**. This is a list of stack frames placed on the stack containing return address and local variables. A stacktrace is therefore useful to trace back the path of execution of your program (following the calls to procedures).
4+
5+In order to obtain a stacktrace, the compiler first has to be directed to generate debug information using switches:
6+
7+- -g - generate debug information (in the default output format for the platform concerned, which is dwarf2 on a lot of platforms)
8+- -gl - generate line numbers for debug information
9+- -gs - generate older stabs debug information; don't use both -gs and -gw
10+- -gw - generate dwarf2 debug information; don't use both -gs and -gw
11+- -Xg - use external debug symbol file
12+
13+#### SysUtils unit
14+
15+[SysUtils](https://www.freepascal.org/docs-html/rtl/sysutils/exception.html) contains some routines useful for debugging exceptions.
16+
17+``` pascal
18+{ Exception handling routines }
19+function ExceptObject: TObject;
20+function ExceptAddr: Pointer;
21+function ExceptFrameCount: Longint;
22+function ExceptFrames: PPointer;
23+```
24+
25+#### System unit
26+
27+The system unit contains some stack related routines:
28+
29+``` pascal
30+function SysBackTraceStr(Addr:Pointer): ShortString; // Default address to string converter assigned to BackTraceStrFunc
31+procedure Dump_Stack(var f : text;bp:pointer); // Dump stack to text file
32+procedure DumpExceptionBackTrace(var f:text); // Dump backtrace to text file
33+```
34+
35+Procedural variable **BackTraceStrFunc** responsible to translate memory address to string debug information. Default behavior is implemented by **SysBackTraceStr**.
36+
37+#### Line information
38+
39+If the line info debug output is selected (compiler switch -gl), the unit **lineinfo** is automatically included in the program. This unit makes sure that debuggers/exception handlers can find the line numbers of running code. It can be useful if you do not want to deploy a production executable with full debug info, but you do want useful information when errors occur.
40+
41+#### Stabs
42+
43+If the old stabs format (-gs) is used, the **BackTraceStrFunc** function is remapped to **StabBackTraceStr**.
44+
45+#### DWARF
46+
47+If the dwarf debugging format is selected (compiler switch -gw), the unit **lnfodwrf** is automatically included in the program and **BackTraceStrFunc** function is remapped to **DwarfBacktraceStr**.
48+
49+### Unit LCLProc
50+
51+This Lazarus unit has some debug-related functions:
52+
53+``` pascal
54+// Debugging
55+procedure RaiseGDBException(const Msg: string);
56+procedure RaiseAndCatchException;
57+procedure DumpExceptionBackTrace;
58+procedure DumpStack;
59+function GetStackTrace(UseCache: boolean): string;
60+procedure GetStackTracePointers(var AStack: TStackTracePointers);
61+function StackTraceAsString(const AStack: TStackTracePointers; UseCache: boolean): string;
62+function GetLineInfo(Addr: Pointer; UseCache: boolean): string;
63+```
64+
65+## Dump current call stack
66+
67+See also FPC help on dumping stack/exception details:
68+
69+- [DumpExceptionBackTrace](http://www.freepascal.org/docs-html/rtl/system/dumpexceptionbacktrace.html)
70+- [dump_stack](http://www.freepascal.org/docs-html/rtl/system/dump_stack.html)
71+
72+``` pascal
73+procedure DumpCallStack;
74+var
75+ I: Longint;
76+ prevbp: Pointer;
77+ CallerFrame,
78+ CallerAddress,
79+ bp: Pointer;
80+ Report: string;
81+const
82+ MaxDepth = 20;
83+begin
84+ Report := '';
85+ bp := get_frame;
86+ // This trick skip SendCallstack item
87+ // bp:= get_caller_frame(get_frame);
88+ try
89+ prevbp := bp - 1;
90+ I := 0;
91+ while bp > prevbp do begin
92+ CallerAddress := get_caller_addr(bp);
93+ CallerFrame := get_caller_frame(bp);
94+ if (CallerAddress = nil) then
95+ Break;
96+ Report := Report + BackTraceStrFunc(CallerAddress) + LineEnding;
97+ Inc(I);
98+ if (I >= MaxDepth) or (CallerFrame = nil) then
99+ Break;
100+ prevbp := bp;
101+ bp := CallerFrame;
102+ end;
103+ except
104+ { prevent endless dump if an exception occured }
105+ end;
106+ ShowMessage(Report);
107+end;
108+```
109+
110+## Dump exception call stack
111+
112+The call stack of an exception can be obtained through SysUtils functions **ExceptAddr**, **ExceptFrames** and **ExceptFrameCount**.
113+
114+``` pascal
115+uses SysUtils;
116+
117+procedure DumpExceptionCallStack(E: Exception);
118+var
119+ I: Integer;
120+ Frames: PPointer;
121+ Report: string;
122+begin
123+ Report := 'Program exception! ' + LineEnding +
124+ 'Stacktrace:' + LineEnding + LineEnding;
125+ if E <> nil then begin
126+ Report := Report + 'Exception class: ' + E.ClassName + LineEnding +
127+ 'Message: ' + E.Message + LineEnding;
128+ end;
129+ Report := Report + BackTraceStrFunc(ExceptAddr);
130+ Frames := ExceptFrames;
131+ for I := 0 to ExceptFrameCount - 1 do
132+ Report := Report + LineEnding + BackTraceStrFunc(Frames[I]);
133+ ShowMessage(Report);
134+ Halt; // End of program execution
135+end;
136+```
137+
138+## Handling exceptions
139+
140+### Manual exception handling
141+
142+Manual executions of exception handler can be inserted in many places in code.
143+
144+``` pascal
145+try
146+ // ... some operation which should raise an exception...
147+ raise Exception.Create('Test error');
148+except
149+ on E: Exception do
150+ DumpExceptionCallStack(E);
151+end;
152+```
153+
154+### System ExceptProc
155+
156+If an unhandled exception occurs, the ExceptProc procedure variable is executed. Default behaviour is initialized by procedure **InitExceptions** in unit **System**. If you want to, this procedure can be reassigned to a custom handler.
157+
158+An example:
159+
160+``` pascal
161+procedure CatchUnhandledException(Obj: TObject; Addr: Pointer; FrameCount: Longint; Frames: PPointer);
162+var
163+ Message: string;
164+ i: LongInt;
165+ hstdout: ^Text;
166+begin
167+ hstdout := @stdout;
168+ Writeln(hstdout^, 'An unhandled exception occurred at $', sysBackTraceStr(addr), ' :');
169+ if Obj is exception then
170+ begin
171+ Message := Exception(Obj).ClassName + ' : ' + Exception(Obj).Message;
172+ Writeln(hstdout^, Message);
173+ end
174+ else
175+ Writeln(hstdout^, 'Exception object ', Obj.ClassName, ' is not of class Exception.');
176+ Writeln(hstdout^, BackTraceStrFunc(Addr));
177+ if (FrameCount > 0) then
178+ begin
179+ for i := 0 to FrameCount - 1 do
180+ Writeln(hstdout^, BackTraceStrFunc(Frames[i]));
181+ end;
182+ Writeln(hstdout^,'');
183+end;
184+```
185+
186+### TApplication.OnException
187+
188+This event can be used to override default application wide exceptions handling. A custom logging mechanism could provide show custom dialog, log to file, console, sending report to mail, logging to HTTP server, e.g.
189+
190+``` pascal
191+procedure TMainForm.CustomExceptionHandler(Sender: TObject; E: Exception);
192+begin
193+ DumpExceptionCallStack;
194+ Halt; // End of program execution
195+end;
196+
197+procedure TMainForm.FormCreate(Sender: TObject);
198+begin
199+ Application.OnException := @CustomExceptionHandler;
200+end;
201+
202+procedure TMainForm.ButtonClick(Sender: TObject);
203+begin
204+ raise Exception.Create('Test');
205+end;
206+```
207+
208+### Handling thread exceptions
209+
210+Handling exceptions which are raised in threads has to be done manually. The main thread TThread method **Execute** is called from the function **ThreadProc** located in unit **Classes**.
211+
212+``` pascal
213+function ThreadProc(ThreadObjPtr: Pointer): PtrInt;
214+begin
215+ ...
216+ try
217+ Thread.Execute;
218+ except
219+ Thread.FFatalException := TObject(AcquireExceptionObject);
220+ end;
221+ ...
222+end;
223+```
224+
225+In this function, the Execute method is enclosed in a try-except block and all exceptions are handled by assigning the exception object to the **FatalException** property of TThread object as last occurred exception object. So exceptions are not displayed to the user at all.
226+
227+In every thread in the application, a separate try-except block should be inserted to catch all unhandled exceptions by a custom exception handler.
228+
229+``` pascal
230+procedure TMyThread.Execute;
231+begin
232+ try
233+ // some erroneous code
234+ except
235+ on E: Exception do
236+ CustomExceptionThreadHandler(Self, E);
237+ end;
238+end;
239+```
240+
241+Then **CustomExceptionThreadHandler** can get the exception call stack and manage showing error messages or logging log reports to file. As the handler is executed from a thread, showing message dialog has to be done thread safe using the **Synchronize** method.
242+
243+``` pascal
244+procedure TMainForm.CustomExceptionHandler(Thread: TThread; E: Exception);
245+begin
246+ Thread.Synchronize(DumpExceptionCallStack);
247+end;
248+```
249+
250+### Using map file
251+
252+Use compiler switch -Xm to generate map file.
253+
254+### Exceptions in DLL
255+
256+todo
257+
258+## See also
259+
260+- [[Profiling]]
261+- [[Creating a Backtrace with GDB]]
262+- [[MultiLog]] - A logging package
263+- [[log4delphi]] - A logging package
264+
265+## External links
266+
267+- [esprinter](http://www.ciuly.com/tools/programming/esprinter/index.html) - tool to get stacktrace from running FreePascal program specified by process id and thread id (for Win32)
268+- [PascalBugReports](https://github.com/r3code/pascalbugreports) - project intended to mimic EurekaLog/MadExcept but has not been worked on for a while
269+
270+---
271+*From the Free Pascal wiki, page "Logging exceptions", as it stood on 2020-11-14 after a change by Kirinn, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*
272+Category: Debugging
273+Category: FPC
274+Category: Lazarus

Back to history