Today I was at the Delphi Frühstück (breakfast) at Comcept GmbH in Cologne. It was quite an inspiring talk there. Thanks to the guys who organized it, I am looking forward to going there again.
One of the questions that came up was how to use jclDebug for getting source code location and call stack for error reporting. Since I have been using it at work for several years I decided to blog about it. So, here it goes:
First, you need the JCL (Jedi Code Library), in partiuclar you need the unit jclDebug and any other unit it depends on.
You include jclDebug in the uses clause of your program and initialize stack tracing like this:
initialization // Enable raw mode (default mode uses stack frames which aren't always generated by the compiler) Include(JclStackTrackingOptions, stRawMode); // Disable stack tracking in dynamically loaded modules (it makes stack tracking code a bit faster) Include(JclStackTrackingOptions, stStaticModuleList); // Initialize Exception tracking JclStartExceptionTracking; finalization JclStopExceptionTracking; end.
I usually put this code in the unit of the main form of my programs, but it can go anywhere, maybe into a unit you always include into your programs, like logging?
Once you have done that, you need to patch into the exception handling code of your application. One option is to assign the Application.OnException event:
Application.OnException := HandleAppException;
In that event you then have access to the call stack of the exception:
procedure TMainForm.HandleAppException(_Sender: TObject; _e: Exception); begin LogException(_e); Tf_dzDialog.ShowException(_e, Self); end;
Of course, the above is not the interesting part. That’s in either LogException or Tf_dzDialog.ShowException. Both are part of my dzlib library.
procedure TAbstractLogger.LogException(_e: exception; const _Where: string; _IncludeCallstack: boolean); var s: string; sl: TStringList; begin if _Where <> '' then s := _Where + ': ' else s := ''; LogError(s + _e.ClassName + ': ' + _e.Message); if _IncludeCallstack and Assigned(gblOnGetCallstack) then begin sl := TStringList.Create; try LogDebug('<begin call stack>'); gblOnGetCallstack(sl); for s in sl do LogDebug(s); LogDebug('<end call stack>'); finally sl.Free; end; end; end;
So, now we still need the code called from gblOnGetCallstack. Here it is:
procedure GetCallstack(_sl: TStrings); begin JclLastExceptStackListToStrings(_sl, False, True, True, False); end;
That’s about half the work. With the above code you only get a call stack containing the addresses, which may be helpful, but not as helpful as the names of the methods would be. Fort this you need the information from the map file. The linker only creates it it you configure it to do so. Select “Detailed” for the “Map file” option.
Once you put a map file into the same directory as the executable, your call stack will contain the names of the methods rather than just the addresses.
Unfortunately map files have two disadvantages:
- They are plain text so they become very large. Also, they could possibly be used to reverse engineer your program.
- They are a separate file and it is easy to forget adding them whenever you distribute a new executable. If you use the wrong or out of date map file you will get some rather strange call stacks. (Trust me, I have been there.)
The guys who develop the JCL have thought about that and came up with solutions for both points:
makejcldbg is a tool that creates a jcldbg file from the map file. This is a binary format that is much smaller than the map file and also more difficult to read (it’s probably still possible to use it for reverse engineering though). Also it has an option to add the created debug information to the executable, so you can’t forget to distribute it.
One call to
makejcldbg -e executablefile.map
does the trick. That’s it. Now you can have exception reporting with a clear text call stack.
In a previous blog post I have written about the batch files I use for building projects. One task they do, is call makejcldbg from the post build event. Feel free to use them in your own projects.