March 10, 2005

Adding Stack Traces to C++ Exceptions...

One of the features that I love about Exceptions in Java, Python, and other languages is the stack trace that it provides you, detailing precisely where the exception was raised. This stack trace is invaluable when placed in log files or when you do not have a debugger available, since you can now know how your application got to the exception, which should give you some great context for starting to approach the issue.

In this weeks InformIT C++ Guide, Danny mentioned C++ exception-handling tricks for Linux from IBM. One of the tricks mentioned in the article is how to get a stack trace where the exception is thrown; the technique used is well documented in the GNU C Library Documentation.

The example code in the article demonstrates the concept well, however, displaying the stack trace in the constructor is not ideal, since this information should be made available to the code that catches the exception.  At that point, a decision can be made to log a summary of the exception, the full exception, or handle it in some other fashion based on the context.

When I went to run the first application with it, I was expecting a stack trace similar to Java. The actual output, though, was:

  • Something went dreadfully wrong.
  • at ./TestException [0x804aaaa]
  • at /lib/tls/libc.so.6(__libc_start_main+0xe0) [0x4012cb10]
  • at ./TestException(__gxx_personality_v0+0x119) [0x8048f01]

Using a GDB, you can use list *address or something similar (such as objdump) on that same executable, and with this, you can get the same information as what a Java or Python stack trace gives you, albeit with more work for you. Furthermore, this approach only works on non-Linux or HURD systems; hopefully the other operating systems you support have some mechanism similar to it.

In general, however, the most important piece of information in the stack trace is the first entry, viz. where the exception was thrown, and in many cases, you do not need to dig any further than it.  Because of this, we can reuse the C/C++ defines for __FILE__ and __LINE__, and have these passed into our constructor.  By using a quick and dirty macro, we can then do something like this:

  • MY_THROW( "Something went dreadfully wrong." );

Using this mechanism, it can now provide us with the following information:

  • Something went dreadfully wrong.
  • at TestException.cpp:13
  • at /lib/tls/libc.so.6(__libc_start_main+0xe0) [0x4012cb10]
  • at ./TestException(__gxx_personality_v0+0x119) [0x8048f01]

And now I have all the stack information if I need it, but I also have the filename and line number where the exception is raised. Furthermore, the filename and line number are available for all standard C or C++ compilers, and thus provide a portable mechanism to provide at least the most important information.

As such, I leave you with my refactoring of this exception class: ExceptionWithStack.h and ExceptionWithStack.cpp, and an example program, TestException.cpp.  I have only provided a printStackTrace() method similar to Java, however, a true environment would require other methods.  My class derives from std::exception simply to allow existing code to be updated to this mechanism where I am planning to use this class.  The code compiles on both g++ and Visual C++ 2003. This is a great extension; it is just too bad that it is not part of every exception.

Posted 17 years, 5 months ago on March 10, 2005
The trackback url for this post is https://www.eyt.ca/blog/bblog/trackback.php/108/

Comments have now been turned off for this post. If you want to share something, please e-mail me.


Recent Posts