This is a translation (with some additions) of the articles I have written in 2012 in the old blog which does not exist anymore.

Originally I faced this issue in Qt when for some reason one of event handlers had thrown bad_alloc() exception. That was quite a large application, and it was not easy to understand which one and why that happened.

In Qt, if an application throws an exception which is never handled by the application, the exception lands to the Qt event loop, and you get a message like this:

Qt has caught an exception thrown from an event handler. Throwing
exceptions from an event handler is not supported in Qt. You must
reimplement QApplication::notify() and catch all exceptions there.

Then Qt rethrows the exception, and the application is usually terminated. With libstdc++ you may see some additional diagnostics like this:

terminate called after throwing an instance of ‘std::bad_alloc’
what(): std::bad_alloc

The issue is that you cannot get the backtrace from the uncaught exception handler: the backtrace will point you to the place from which the exception was rethrown, that is, into the Qt event loop.

To get the correct backtrace, you need to understand how C++ throws exceptions. The specification is available in C++ ABI for Itanium. In particular, you need Section 2.4 Throwing an Exception:

In broad outline, a possible implementation of the processing necessary to throw an exception includes the following steps:

  • Call __cxa_allocate_exception to create an exception object (see Section 2.4.2).
  • Evaluate the thrown expression, and copy it into the buffer returned by __cxa_allocate_exception, possibly using a copy constructor. If evaluation of the thrown expression exits by throwing an exception, that exception will propagate instead of the expression itself. Cleanup code must ensure that __cxa_free_exception is called on the just allocated exception object. (If the copy constructor itself exits by throwing an exception, terminate() is called.)
  • Call __cxa_throw to pass the exception to the runtime library (see Section 2.4.3). __cxa_throw never returns.

One of the possible solutions is to use gdb: you set a breakpoint on __cxa_throw ( b __cxa_throw), run the program, and when the breakpoint is reached, you use bt to get the backtrace.

Something like this:

However, this is not a pleasant task, especially if the application is multi-threaded or if it uses exceptions for error handling. In this case you may want to automate the process.

So, get back to the C++ ABI. We need Sections 2.4.3 Throwing the Exception Object and 2.5.4 Rethrowing Exceptions. Below are the prototypes for __cxa_throw() (throws an exceptions) and __cxa_rethrow() (rethrows the current exception) functions:

  • thrown_exception is the address of the thrown exception object (which points to the throw value, after the header — see Data Structures for the layout);
  • tinfo gives the static type of the throw argument as a std::type_info pointer, used for matching potential catch sites to the thrown exception;
  • dest is the destructor pointer to be used eventually to destroy the object.

Case 1: Throw Exception

The only thing we need here is tinfo: tinfo->name() will give us the name of the type of the thrown exception. However, the name of the type will look like St13runtime_error (the so-called mangled name). To get the human-readable name, we can use Demangler API:

Sample code:

We can check here whether the thrown exception is inherited from std::exception, and if so, we can print exception::what(). The issue here is that thrown_exception is of type void* (in C++, you can throw anything, not necessarily objects), and as a consequence, dynamic_cast will not work. To understand the magic behind dynamic_cast we need to read the ABI document again: this time, we need Run-Time Type Information.

Dynamic casts are implemented by __dynamic_cast() function:

The algorithm is as follows:

  1. We need to check if tinfo is an instance of abi::__class_type_info (see Section 2.9.5 RTTI Layout for details); if it isn’t, then the thrown exception is not an object, and there is nothing to do here.
  2. Cast &typeid(std::exception) to const abi::__class_type_info*.
  3. Call __dynamic_cast and check whether thrown_exception is inherited from std::exception.

All that is left is to get the backtrace. There is nothing difficult here though.

Case 2: Rethrow Exception

__cxa_rethrow() is invoked from a catch block and re-throws the caught exception. The function has no arguments, and therefore we need to get somehow the rethrown exception. If we read Section 2.2.2 Caught Exception Stack, we will find out that we can use the following function:

__cxa_exception is defined in Section 2.2.1 C++ Exception Objects:

_Unwind_Exception is defined in Section 1.2 Data Structures and is available in <unwind.h> system header.

__cxa_exception itself is the header; the thrown exception object follows it.

Done. Now we need to intercept __cxa_throw and __cxa_rethrow. This shlild not be difficult:

Putting it all together:

Sample output:

How to Get Line Numbers

Depending on how the executable was built, it may be very difficult to get source file and line number information from addresses in the backtrace.

For example, the code above was built with -pie (meaning it is position independent executable), and addr2line will not work as expected.

Position Independent Executables

If Address Space Layout Randomization is on (can be checked with sysctl kernel.randomize_va_space; if the value is non-zero, it is enabled), it needs to be disabled. Instead of disabling it globally, we can use setarch to disable it just for our binary:

Now, the executable will always be loaded at the same address. However, that address is not within the executable, and for addr2line to work, addresses need to be recalculated:

This will show something like:

In the example above, 0x0000555555554000 is the offset we need to subtract from the address.

Thus, if we get something like this from our program:

We need to feed 0x1033, 0x1382, 0x13d1, 0x15f9, 0xf3a to addr2line (if you look carefully, these are the addresses shown in parentheses; if your backtrace always lands to main(), you can calculate the amendment yourself by subtracting the short address from the long).

UPDATE: more information is available here.

Normal Executables

For non-PIE addresses shown by backtrace_symbols_fd() can be fed directly to addr2line.

Automated Solution that Works for Both Types

Calculating offsets and invoking addr2line manually can be tedious and of course needs to be automated. If you don’t want or cannot rebuild the executable with -no-pie, I still have a solution.

First, we need to install eu-addr2line program (from elfutils; in Debian-based systems this is as easy as sudo apt install elfutils).

Then we need to add some code to our program.

It will need one extra header:

and some code added to static void get_backtrace() right after backtrace_symbols_fd(buf, n, STDERR_FILENO);:

Let’s see what is going on here.

In line 1 we calculate the space needed for the command line:

  • (2*sizeof(void*) + 2 /* 0x */ + 1 /* space */) is the maximum size of a single hexadecimal address (void*); sizeof(void*) is multiplied by two to get the number of hexadecimal digits (1 byte = 2 digits), and added two bytes for 0x prefix, and one more byte for the space after the address;
  • the result is multiplied by n (number of entries in the backtrace);
  • the length of the fixed portion of the command line is added;
  • (std::numeric_limits::digits10 + 1) is the number of digits needed to display a value of pid_t;
  • finally, add one to account for the terminating zero.

Lines 5—12 generate the command line, line 15 prints the command to be run, line 16 executes it (if is added to suppress gcc’s warning about the not checked return code), and line 17 releases the memory we have allocated.

The output of the program will now look like this:

How to Get the Source of an Uncaught Exception in C++
Tagged on:         

Leave a Reply

Your email address will not be published. Required fields are marked *