Working with Delphi’s new Exception.StackTrace

One feature I often miss when using Delphi is the support for proper exception stack traces at run-time. You know, those useful stack traces that show you exactly where an exception occurred, ideally with the method name and line number of where the exception was raised. Both .NET and Java have excellent stack trace support built right into the framework and the Exception classes. You just call Exception.StackTrace (.NET) or Exception.getStackTrace (Java) and get a detailed analysis of where the exception was thrown and how it got passed to your exception handler.

Unfortunately, Delphi never had good (built-in) run-time support for stack traces. The features for stack traces during debugging in the IDE are/were okay, but there were nothing in the language or framework which helped you to find out programmatically where an exception occurred at run-time and, more importantly, how it got passed to your exception handler (besides the original exception address, maybe). So, I was happy to see that Delphi 2009 finally introduced a new StackTrace property which, I hoped, would return a full-blown stack trace when you caught an exception.

The initial happiness soon wore off when I realized that the StackTrace property was really just a placeholder to return a stack trace from a possible stack trace provider rather than a real stack trace implementation. So, without such a provider (and there’s none that comes directly with Delphi), there is still no way to get a stack trace for your exceptions. Though a bit disappointing, the good thing is that there’s now finally a standardized way to get a stack trace, even if it’s not implemented by default.

Exception reporting tools such as Eurekalog or madExcept or debug helpers such as the JclDebug unit can register themselves as providers and use their engines to return a stack trace when an exception is raised. I’ve built a small unit which demonstrates this with the Jcl in combination with our logging tool SmartInspect and I’ve heard Fabio of Eurekalog is working on a similar feature for his component:

[sourcecode language='delphi']
unit StackTrace;

interface

uses
SysUtils, Classes, JclDebug;

implementation

function GetExceptionStackInfoProc(P: PExceptionRecord): Pointer;
var
LLines: TStringList;
LText: String;
LResult: PChar;
begin
LLines := TStringList.Create;
try
JclLastExceptStackListToStrings(LLines, True, True, True, True);
LText := LLines.Text;
LResult := StrAlloc(Length(LText));
StrCopy(LResult, PChar(LText));
Result := LResult;
finally
LLines.Free;
end;
end;

function GetStackInfoStringProc(Info: Pointer): string;
begin
Result := string(PChar(Info));
end;

procedure CleanUpStackInfoProc(Info: Pointer);
begin
StrDispose(PChar(Info));
end;

initialization
// Start the Jcl exception tracking and register our Exception
// stack trace provider.
if JclStartExceptionTracking then
begin
Exception.GetExceptionStackInfoProc := GetExceptionStackInfoProc;
Exception.GetStackInfoStringProc := GetStackInfoStringProc;
Exception.CleanUpStackInfoProc := CleanUpStackInfoProc;
end;

finalization
// Stop Jcl exception tracking and unregister our provider.
if JclExceptionTrackingActive then
begin
Exception.GetExceptionStackInfoProc := nil;
Exception.GetStackInfoStringProc := nil;
Exception.CleanUpStackInfoProc := nil;
JclStopExceptionTracking;
end;
end.
[/sourcecode]

The unit merely starts and stops the exception tracking of the Jcl, implements a minimal stack trace provider and registers for the Exception provider events. The GetExceptionStackInfoProc and CleanUpStackInfoProc functions are automatically called by the RTL to give the provider the opportunity to initialize and cleanup the stack trace after an exception occurred. GetStackInfoStringProc is called indirectly when you access the StackTrace property and is responsible for returning the actual stack trace.

So, how do you use this unit? Let’s have a look at the following example:

[sourcecode language='delphi']

uses
…, StackTrace;

type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
procedure SomeMethod;
end;

implementation

procedure TForm1.Button1Click(Sender: TObject);
begin
try
SomeMethod;
except
// Log the exception: We use SmartInspect here because it has
// built-in support for Exception.StackTrace but you could also
// access the StackTrace property here directly.
SiMain.LogException;
end;
end;

procedure TForm1.SomeMethod;
begin
raise Exception.Create(‘A test exception’);
end;


[/sourcecode]

As you can see, using this unit is just a matter of adding it to our uses clause. It won’t get any simpler than that. The unit will take care of registering/unregistering itself as a stack trace provider and when you now access the StackTrace property of an Exception object, you will get a detailed stack trace. To include the method names and line numbers in the stack trace, make sure to let the linker include debug symbols into your application and to enable the ‘Use debug .dcus’ option in case you also want line numbers from the VCL and RTL methods.


delphi-stacktrace-small

The SmartInspect Console showing the stack trace of an exception

Now, when you use SmartInspect for logging and have a stack trace provider registered, all your logged exceptions automatically include the exception’s call stack. Pretty useful, isn’t it?

This entry was posted in DelphiFeeds.com, Programming, SmartInspect and tagged . Bookmark the permalink. Both comments and trackbacks are currently closed.

14 Comments

  1. Warren
    Posted October 13, 2009 at 04:08 | Permalink

    I have used JclDebug for a long time. This provides absolutely no advantage to me in producing stack dumps as the JclDebug examples already provide clean functions for doing stack dumps.

    MadExcept as well. I am a bit annoyed that they didn’t build this into the RTL, but at least, perhaps in the future they might do so.

    I still don’t see what good it does that Exception now has this property. Until it’s automatic it’s no better than just using JCLDebug, or using Madexcept on their own.

    W

  2. Posted October 13, 2009 at 14:27 | Permalink

    Hello Warren,

    Thanks for your comment. I see at least two advantages of Exception.StackTrace over using JclDebug/madExcept/Eurekalog directly:

    - Your code does not directly depend on any exception reporting tools. You only use the Exception.StackTrace property in your application and can conditionally use the StackTrace unit. You can enable/disable the stack tracing in one central location (by including/omitting the StackTrace unit) or even replace one exception reporting tool with another if needed — without changing your application code at all — simply by using a different implementation for the StackTrace unit.

    - There’s finally a standard way to get an exception report for third-party developers and component vendors. For example, without such a property, it wouldn’t be possible/so easy to add support for stack traces to our logging tool SmartInspect. Now we just need to check the Exception.StackTrace property and can include the stack trace in the log if available. This is a big benefit for users of third-party debugging tools and we are glad that Embarcadero added this.

  3. Posted October 13, 2009 at 22:55 | Permalink

    Well said Tobias! Removing dependancies is a double-edged sword indeed.

    As for this new StackTrace-framework, I suppose SmartInspect (or a library like the Jcl) could back-port this to older Delphi-versions too (for example via a class helper, or if not available via a Inceptor class on Exception), bringing modern features to older compilers! Just a thought…

  4. Posted October 14, 2009 at 13:46 | Permalink

    Thanks, Patrick. Back-porting the StackTrace property sounds interesting. I’m not a big fan of the specific implementation of class helpers in Delphi though (if I recall correctly, it was said to be an internal feature for the VCL and has some limitations such as one allowed class helper per class only).

  5. LDS
    Posted October 16, 2009 at 11:33 | Permalink

    There’s a caveat using debug dcus: they may have far more debugging code than you need – I mean no optimization, range checks, etc. Good for debugging, but not for release applications. JCL can use map files, compress them and add them to the executable.

  6. Posted October 17, 2009 at 14:41 | Permalink

    Good point, thanks. We actually use the map file option in the SmartInspect Console.

  7. Posted October 21, 2009 at 07:50 | Permalink

    How does the new exception behaves for nested exceptions ?
    I use D2007 now and cannot upgrade because thirdparty tools issues right now.
    But if/then I upgrade it would be interresting to know if the following code will point out the original exception sourceline:

    procedure p1;
    begin
    try
    p2;
    except
    // Custom action, for example rollback database
    raise Exception.Create;
    end
    end;

    procedure p2;
    begin
    // a lot of source lines
    // one of them raise an exception
    end;

    I use JCL with debug dcu’s and logging of stacktrace to a textfile. The bad thing is that the original cause of the exception in p2 is hided. The reality can be even more complicated with several levels of try/except raise. If the new D2009 exception + JCL can solve this and point out the real cause of an exception it would alone be worth to upgrade to D2009.

  8. Posted October 23, 2009 at 15:23 | Permalink

    Hello Roland,

    Sorry for the late response. Nested exceptions should be covered as well by using the InnerException property as follows:

    procedure p1;
    begin
      try
        p2;
      except
        // Ugly, but seems to work!
        Exception.RaiseOuterException(Exception.Create('Outer exception'));
      end;
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      Ex: Exception;
    begin
      try
        p1;
      except
        on E: Exception do
        begin
          // Should be put in some function:
          Ex := E;
          while Assigned(Ex) do
          begin
            SiMain.LogException(Ex);
            Ex := Ex.InnerException;
          end;
        end;
      end;
    end;

    I’m not sure which Delphi version first introduced inner exceptions but it’s available in Delphi 2009.

  9. Pierre-Fr. Culand
    Posted February 1, 2010 at 19:13 | Permalink

    Sorry if my question is a bit trivial, I’m not sur to have correctly understood that stuff.

    What if I need a JCL stack trace for unhandled exceptions only ?

    Thank you.
    Pierre-François Culand

  10. Pierre-Fr. Culand
    Posted February 1, 2010 at 20:31 | Permalink

    I wonder I should be more precise concerning my need:

    First be aware I’m using Delphi 6 only…

    I tried to use JCLDebug and JCLHookExcept units to log unhandled exceptions of my multithreaded application with a full call stack dump. (Using the JclLastExceptStackList function).

    But I noticed that my exception notifier (installed with AddExceptNotifier) is called on EACH exception raised in my application, EVEN IF IT IS ALREADY HANDLED in my code.

    But I just want to address unhandled exceptions that make crash one thread of my app, getting the complete call stack dump that lead to the unhandled exception raising.

    How can I distinguish unhandled exceptions from handled ones ?

    I didn’t found how to do this…

    Thanks for any help.

  11. Posted February 4, 2010 at 11:44 | Permalink

    Hello Pierre-François,

    In the SmartInspect Console (standard forms application), we are calling JclLastExceptStackListToStrings in the application exception event to show/log/handle unhandled exceptions. Not sure if this event is available in Delphi 6 though (although I guess it is).

  12. Pierre-Fr. Culand
    Posted February 4, 2010 at 13:16 | Permalink

    Hi Tobias,

    Thank you for your answer.

    Actually, I have to admit that my question was a bit stupid… :-o
    I noticed just after having posted that the answers to my questions were already in the examples of the JCL…

    (in stacktrack and threadexcept examples)

    I have now a fantastic tool to debug my multithreaded Delphi 6 application ! Whatever the unhandled exception is raised in the main VCL thread or in one of my other threads…

    Thank you for your answer anyway.

  13. Moz
    Posted July 13, 2010 at 06:39 | Permalink

    In Delphi 2010 I find that this returns info from the previous exception – for the first exception thrown this is empty, then after that it’s one behind. Calling JclLastExceptStackListToStrings directly from within an except block is fine.

  14. Posted January 18, 2012 at 23:41 | Permalink

    Hello Tobias,

    Sound great to have stacktrace available. Unfortunately, including JclDebug is not enough. I ended up including large parts of the JCL.

    On my wishlist is Exception.Stacktrace as part of SmartInspect, without the need to have yet another component library.

    Regards, Ronald

3 Trackbacks

  1. [...] Working with Delphi’s new Exception.StackTrace Tagged with: Delphi • StackTrace  0 Comments Leave A Response [...]

  2. [...] [...]

  3. [...] [...]