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.
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.