C++ exceptions under the hood 13: setting the context for a landing pad

Last time we finally wrote an almost working personality function. We can detect for each stack frame which landing pads are available and then tell _Unwind_ we want to run a specific landing pad. We hit a small issue, though: although we set the context for _Unwind_ to continue executing on the correct landing pad we didn’t set the current exception on the register. This, in turn, means that the landing pad won’t know which exception should be handling, so it will say “I can’t handle this”. _Unwind_ will then say “please try the next landing pad” but our ABI is so simple that it has no idea how it should find another landing pad and just tries the same. Over and over again. We have probably invented the most contrived example for a while(true)!

Let’s set the correct context for the landing pad and clean up a bit our ABI:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

namespace __cxxabiv1 {
    struct __class_type_info {
        virtual void foo() {}
    } ti;
}

#define EXCEPTION_BUFF_SIZE 255
char exception_buff[EXCEPTION_BUFF_SIZE];

extern "C" {

void* __cxa_allocate_exception(size_t thrown_size)
{
    printf("alloc ex %i\n", thrown_size);
    if (thrown_size > EXCEPTION_BUFF_SIZE) printf("Exception too big");
    return &exception_buff;
}

void __cxa_free_exception(void *thrown_exception);


#include <unwind.h>

typedef void (*unexpected_handler)(void);
typedef void (*terminate_handler)(void);

struct __cxa_exception { 
	std::type_info *	exceptionType;
	void (*exceptionDestructor) (void *); 
	unexpected_handler	unexpectedHandler;
	terminate_handler	terminateHandler;
	__cxa_exception *	nextException;

	int			handlerCount;
	int			handlerSwitchValue;
	const char *		actionRecord;
	const char *		languageSpecificData;
	void *			catchTemp;
	void *			adjustedPtr;

	_Unwind_Exception	unwindHeader;
};

void __cxa_throw(void* thrown_exception,
                 struct type_info *tinfo,
                 void (*dest)(void*))
{
    printf("__cxa_throw called\n");

    __cxa_exception *header = ((__cxa_exception *) thrown_exception - 1);
    _Unwind_RaiseException(&header->unwindHeader);

    // __cxa_throw never returns
    printf("no one handled __cxa_throw, terminate!\n");
    exit(0);
}


void __cxa_begin_catch()
{
    printf("begin FTW\n");
}

void __cxa_end_catch()
{
    printf("end FTW\n");
}




/***********************************************************************/

/**
 * The LSDA is a read only place in memory; we'll create a typedef for
 * this to avoid a const mess later on; LSDA_ptr refers to readonly and
 * &LSDA_ptr will be a non-const pointer to a const place in memory
 */
typedef const uint8_t* LSDA_ptr;

struct LSDA_Header {
    /**
     * Read the LSDA table into a struct; advances the lsda pointer
     * as many bytes as read
     */
    LSDA_Header(LSDA_ptr *lsda) {
        LSDA_ptr read_ptr = *lsda;

        // Copy the LSDA fields
        start_encoding = read_ptr[0];
        type_encoding = read_ptr[1];
        ttype = read_ptr[2];

        // Advance the lsda pointer
        *lsda = read_ptr + sizeof(LSDA_Header);
    }

    uint8_t start_encoding;
    uint8_t type_encoding;
    uint8_t ttype;
};

struct LSDA_CS_Header {
    // Same as other LSDA constructors
    LSDA_CS_Header(LSDA_ptr *lsda) {
        LSDA_ptr read_ptr = *lsda;
        encoding = read_ptr[0];
        length = read_ptr[1];
        *lsda = read_ptr + sizeof(LSDA_CS_Header);
    }

    uint8_t encoding;
    uint8_t length;
};

struct LSDA_CS {
    // Same as other LSDA constructors
    LSDA_CS(LSDA_ptr *lsda) {
        LSDA_ptr read_ptr = *lsda;
        start = read_ptr[0];
        len = read_ptr[1];
        lp = read_ptr[2];
        action = read_ptr[3];
        *lsda = read_ptr + sizeof(LSDA_CS);
    }

    // Note start, len and lp would be void*'s, but they are actually relative
    // addresses: start and lp are relative to the start of the function, len
    // is relative to start
 
    // Offset into function from which we could handle a throw
    uint8_t start;
    // Length of the block that might throw
    uint8_t len;
    // Landing pad
    uint8_t lp;
    // Offset into action table + 1 (0 means no action)
    // Used to run destructors
    uint8_t action;
};

/*********************************************************************/


_Unwind_Reason_Code __gxx_personality_v0 (
                             int version,
                             _Unwind_Action actions,
                             uint64_t exceptionClass,
                             _Unwind_Exception* unwind_exception,
                             _Unwind_Context* context)
{
    if (actions & _UA_SEARCH_PHASE)
    {
        printf("Personality function, lookup phase\n");
        return _URC_HANDLER_FOUND;
    } else if (actions & _UA_CLEANUP_PHASE) {
        printf("Personality function, cleanup\n");

        // Pointer to the beginning of the raw LSDA
        LSDA_ptr lsda = (uint8_t*)_Unwind_GetLanguageSpecificData(context);

        // Read LSDA headerfor the LSDA
        LSDA_Header header(&lsda);

        // Read the LSDA CS header
        LSDA_CS_Header cs_header(&lsda);

        // Calculate where the end of the LSDA CS table is
        const LSDA_ptr lsda_cs_table_end = lsda + cs_header.length;

        // Loop through each entry in the CS table
        while (lsda < lsda_cs_table_end)
        {
            LSDA_CS cs(&lsda);

            if (cs.lp)
            {
                int r0 = __builtin_eh_return_data_regno(0);
                int r1 = __builtin_eh_return_data_regno(1);

                _Unwind_SetGR(context, r0, (uintptr_t)(unwind_exception));
                // Note the following code hardcodes the exception type;
                // we'll fix that later on
                _Unwind_SetGR(context, r1, (uintptr_t)(1));

                uintptr_t func_start = _Unwind_GetRegionStart(context);
                _Unwind_SetIP(context, func_start + cs.lp);
                break;
            }
        }

        return _URC_INSTALL_CONTEXT;
    } else {
        printf("Personality function, error\n");
        return _URC_FATAL_PHASE1_ERROR;
    }
}


}

Note: For a more detailed description of the LSDA tables check here and for the full source code check my github repo.

Finally, it worked. You should see something like this if you run it:

./app
alloc ex 1
__cxa_throw called
Personality function, lookup phase
Personality function, cleanup
begin FTW
Caught a Fake_Exception!
end FTW
try_but_dont_catch handled the exception
catchit handled the exception

Of course we are lying a bit to _Unwind_: we are saying here that we will handle every exception, no mater what. This turns our catch(Exception&) into a catch(…), and all hell will break loose if the first function up in the call frame doesn’t have a catch statement. But still, we reached the first milestone for a very simple ABI.

Can we now improve it and make it handle the correct exception on the correct frame? May be next time.

Advertisements

C++ exceptions under the hood 14: multiple landing pads & the teachings of the guru

After a lot of hard work, last time we finally got a working personality function capable of handling exceptions without help of libstdc++. It will indiscriminately handle all exceptions, but it does work. There is a big question we haven’t answered yet: if we go back to the LSDA (language specific data area) we’ll see something like this:

.local_lsda_call_site_table:
	.uleb128 .LEHB0-.LFB1
	.uleb128 .LEHE0-.LEHB0
	.uleb128 .L8-.LFB1
	.uleb128 0x1

	.uleb128 .LEHB1-.LFB1
	.uleb128 .LEHE1-.LEHB1
	.uleb128 0
	.uleb128 0

    .uleb128 .LEHB2-.LFB1
	.uleb128 .LEHE2-.LEHB2
	.uleb128 .L9-.LFB1
	.uleb128 0
.local_lsda_call_site_table_end:

There are 3 landing pads defined there, even though we wrote a single try/catch statement. What is going on there?

If you read carefully the last entry on this topic maybe you noticed I added some comments to the definition of struct LSDA_CS:

struct LSDA_CS {
    // Note start, len and lp would be void*'s, but they are actually relative
    // addresses: start and lp are relative to the start of the function, len
    // is relative to start
 
    // Offset into function from which we could handle a throw
    uint8_t start;
    // Length of the block that might throw
    uint8_t len;
    // Landing pad
    uint8_t lp;
    // Offset into action table + 1 (0 means no action)
    // Used to run destructors
    uint8_t action;
};

Note: You can download the full sourcecode for this project in my github repo.

Something very interesting is going on here, but lets first analyze this struct field by field with the following example:

void foo() {
    L0:
        try {
            do_something();
    L1:
        } catch (const Exception1& ex) {
            ...
        } catch (const Exception2& ex) {
            ...
        } catch (const ExceptionN& ex) {
            ...
        } catch (...) {
        }
    L2:
}
  • lp: the offset from the start of the function where the landing pad starts. The value of lp for the following example would be L1 – addr_of(foo)
  • action: an offset into an action table. This is used to run the cleanup actions while unwinding the stack. We haven’t reached this point yet, we can ignore it for now.
  • start: the offset from the start of the function where the try block begins: in the example this would be L0 – addr_of(foo)
  • len: the length of the try block. On the example this would be L1 – L0

The interesting fields now are start and len: in a function with multiple try/catch blocks we can know whether we should handle an exception by checking if the instruction pointer for the current frame is between start and start + len.

That solves the mystery of how a function with multiple try/catch blocks can handle an exception but we still have another question: why are there three call sites when we only specified a single landing pad? The other three are places where an exception might be thrown so they get added as a possible place for cleanup actions or landing pads. If we learned anything from GOTW it should be that exceptions can be thrown in the places we least expect. There is an entry in the call site table for our throw because it’s a block that might throw; the compiler also detected another three.

Now that we know what the start and len fields do, let’s change our personality function so the correct landing pad can handle the exception being thrown. Go ahead. My implementation for the next time.


C++ exceptions under the hood 12: and suddenly, reflexion in C++

We left our mini-ABI project (link) capable of throwing exceptions, and we are now working on catching them; we implemented a personality function last time which was capable of detecting and handling exceptions but it was still a bit incomplete: even though it can properly notify the stack unwinder when it should stop but our version of __gxx_personality_v0 can’t run the code inside a catch block. We learned last time how to read the LSDA, so now it’s only a problem of putting all the pieces together to read the .gcc_except_table from within our personality function.

Let’s recap a bit: we figured out last time that our LSDA for the function which has the catch we want to run has the following call site table (that is, the following landing pads [that is, the following catch blocks]):

.local_lsda_call_site_table:
	.uleb128 .LEHB0-.LFB1
	.uleb128 .LEHE0-.LEHB0
	.uleb128 .L8-.LFB1
	.uleb128 0x1

	.uleb128 .LEHB1-.LFB1
	.uleb128 .LEHE1-.LEHB1
	.uleb128 0
	.uleb128 0

    .uleb128 .LEHB2-.LFB1
	.uleb128 .LEHE2-.LEHB2
	.uleb128 .L9-.LFB1
	.uleb128 0
.local_lsda_call_site_table_end:

All those labels can be mapped to different places in the assembly of our function, but it’s a bit too messy for a blog post (I do recommend you to disassemble the function yourself and try to match each label, a lot can be learned from doing it). Also, thanks to some random Internet page, we learned the format for this table.

Let’s do something like this to see if we’re on the right track (beware of read-alignment issues and keep in mind that defining CFI structures like this will only work for uint8’s and is probably not portable):

struct LSDA_Header {
    uint8_t lsda_start_encoding;
    uint8_t lsda_type_encoding;
    uint8_t lsda_call_site_table_length;
};

struct LSDA_Call_Site_Header {
    uint8_t encoding;
    uint8_t length;
};

struct LSDA_Call_Site {
    
    LSDA_Call_Site(const uint8_t *ptr) {
        cs_start = ptr[0];
        cs_len = ptr[1];
        cs_lp = ptr[2];
        cs_action = ptr[3];
    }

    uint8_t cs_start;
    uint8_t cs_len;
    uint8_t cs_lp;
    uint8_t cs_action;
};

_Unwind_Reason_Code __gxx_personality_v0 (
                     int version, _Unwind_Action actions, uint64_t exceptionClass,
                     _Unwind_Exception* unwind_exception, _Unwind_Context* context)
{
    if (actions & _UA_SEARCH_PHASE)
    {
        printf("Personality function, lookup phase\n");
        return _URC_HANDLER_FOUND;
    } else if (actions & _UA_CLEANUP_PHASE) {
        printf("Personality function, cleanup\n");

        const uint8_t* lsda = (const uint8_t*)
                                    _Unwind_GetLanguageSpecificData(context);

        LSDA_Header *header = (LSDA_Header*)(lsda);
        LSDA_Call_Site_Header *cs_header = (LSDA_Call_Site_Header*)
                                                (lsda + sizeof(LSDA_Header));

        size_t cs_in_table = cs_header->length / sizeof(LSDA_Call_Site);

        // We must declare cs_table_base as uint8, otherwise we risk an
        // unaligned access
        const uint8_t *cs_table_base = lsda + sizeof(LSDA_Header)
                                            + sizeof(LSDA_Call_Site_Header);

        // Go through every entry on the call site table
        for (size_t i=0; i < cs_in_table; ++i)
        {
            const uint8_t *offset = &cs_table_base[i * sizeof(LSDA_Call_Site)];
            LSDA_Call_Site cs(offset);
            printf("Found a CS:\n");
            printf("\tcs_start: %i\n", cs.cs_start);
            printf("\tcs_len: %i\n", cs.cs_len);
            printf("\tcs_lp: %i\n", cs.cs_lp);
            printf("\tcs_action: %i\n", cs.cs_action);
        }

        uintptr_t ip = _Unwind_GetIP(context);
        uintptr_t funcStart = _Unwind_GetRegionStart(context);
        uintptr_t ipOffset = ip - funcStart;


        return _URC_INSTALL_CONTEXT;
    } else {
        printf("Personality function, error\n");
        return _URC_FATAL_PHASE1_ERROR;
    }
}

Note: You can download the full sourcecode for this project in my github repo.

As you can see if you run this code, all entries in the call site table are relative. Relative to what? To the start of function. That means that if we want to get the EIP for a specific landing pad all we have to do is _Unwind_GetRegionStart + LSDA_Call_Site.cs_lp!

We should now be able to solve our exceptional problem: let’s try to modify our personality function to run the correct landing pad. We’ll now need to use another _Unwind_ function to specify where we want to resume execution: _Unwind_SetIP. Let’s change the personality function again to run the first landing pad available, which by inspecting the assembly we already know is the one we want:

        ...
        const uint8_t *cs_table_base = lsda + sizeof(LSDA_Header)
                                            + sizeof(LSDA_Call_Site_Header);
        for (size_t i=0; i < cs_in_table; ++i)
        {
            const uint8_t *offset = &cs_table_base[i * sizeof(LSDA_Call_Site)];
            LSDA_Call_Site cs(offset);

            if (cs.cs_lp)
            {
                uintptr_t func_start = _Unwind_GetRegionStart(context);
                _Unwind_SetIP(context, func_start + cs.cs_lp);
                break;
            }
        }

        return _URC_INSTALL_CONTEXT;

Try to run it, and watch a beautiful infinite loop. Can you guess what went wrong? The answer on the next article.


C++ exceptions under the hood 11: reading a CFI table

To properly handle exceptions from within the personality function we’ve been implementing for our ABI, we need to read the LSDA (language specific data area) to know which call frame (ie which function) can handle which exception, and to know where a landing pad (catch block) can be found). The LSDA table is in CFI format, and we’ll see in this chapter how to read it.

Reading the CFI data can be rather straight forward, but there are a few pitfalls we need to consider first. Two, actually:

  1. There is very little documentation about the .gcc_except_table format (actually, I only found some mails about it) so we’ll need to read a lot of source code and disassembles to understand it.
  2. Although the format itself is not terribly complicated, it uses a LEB encoding that makes reading this table not quite straightforward.

As far as I know most DWARF data is encoded like this, using a LEB format, which seems to be great for confusing programmers and to save code space while encoding arbitrary length ints. Luckily, we can cheat a bit in here: most of the time the LEB encoded numbers will readble with a plain uint8_t, because we won’t be dealing with large exception tables or anything like that.

Note: You can download the full sourcecode for this project in my github repo.

Let’s start by analyzing the CFI data directly from the disassembly, we’ll then see if we can build something to read it on our personality function. I’ll rename the labels to make them a bit more human-friendly. The LSDA will have three sections, try to spot them below:

.local_frame_entry:
	.globl	__gxx_personality_v0
	.section	.gcc_except_table,"a",@progbits
	.align 4

This one is very easy: it’s just a header to declare we’re going to use __gxx_personality_v0 as a global and to let the linker know we’re going to be declaring stuff for the .gcc_except_table section. Moving on:

.local_lsda_1:
    # This declares the encoding type. We don't care.
	.byte	0xff

    # This specifies the landing pads start; if zero, the func's ptr is
    # assumed (_Unwind_GetRegionStart)
	.byte	0

    # Length of the LSDA area: check that LLSDATT1 and LLSDATTD1 point to the
    # end and the beginning of the LSDA, respectively
	.uleb128 .local_lsda_end - .local_lsda_call_site_table_header

This now has some more info. Those labels are quite obscure but they do follow a pattern. LSDA means language specific data area, the L in front means local, so this is the local (to the translation unit, the .o file) language specific data area number one. Other labels follow similar patterns but I haven’t taken the job of figuring them out. We don’t really need to, anyway.

.local_lsda_call_site_table_header:
    # Encoding of items in the landing pad table. Again, we don't care.
	.byte	0x1.

    # The length of the call site table (ie the landing pads)
	.uleb128 .local_lsda_call_site_table_end - .local_lsda_call_site_table

Another boring header. Moving on:

.local_lsda_call_site_table:
	.uleb128 .LEHB0-.LFB1
	.uleb128 .LEHE0-.LEHB0
	.uleb128 .L8-.LFB1
	.uleb128 0x1

	.uleb128 .LEHB1-.LFB1
	.uleb128 .LEHE1-.LEHB1
	.uleb128 0
	.uleb128 0

    .uleb128 .LEHB2-.LFB1
	.uleb128 .LEHE2-.LEHB2
	.uleb128 .L9-.LFB1
	.uleb128 0
.local_lsda_call_site_table_end:

This is much more interesting, now we’re seeing the call site table itself. Somehow, in all these entries, we should be able to find our landing pad. According to some random internet page, the format for each call site entry should be:

struct lsda_call_site_entry {
    // Start of the IP range
    size_t cs_start;

    // Length of the IP range
    size_t cs_len;

    // Landing pad address
    size_t cs_lp;

    // Offset into action table
    size_t cs_action;
};

So we seem to be on the right track, though we don’t know yet why there are 3 call site entries when we only defined a single landing pad. In any case, we can cheat a little: by looking at the disassembly we can deduce that all the values on the CFI will be less than 128 and this means that in LEB encoding they can be read as plain uchars. This makes our CFI reading code so much easier, and we will see how to use it in our personality function next time.


C++ exceptions under the hood 10: _Unwind_ and call frame info

We left our mini-ABI project (link) capable of throwing exceptions, and we are now working on catching them; we implemented a personality function last time which was capable of detecting and handling exceptions but it was still a bit incomplete: even though it can properly notify the stack unwinder when it should stop but our version of __gxx_personality_v0 can’t run the code inside a catch block. It’s better than a coredump one might argue, but still a long way from a useful exception handling ABI. Can we improve it?

How can we tell _Unwind_ where is our landing pad, so we can execute the code inside the catch statement? If we go back to the ABI specification, there are a few context management functions which might help us:

  • _Unwind_GetLanguageSpecificData, to get the LSDA for this stack frame. We should be able to find the landing pads and the destructors to run using it.
  • _Unwind_GetRegionStart, to get the instruction pointer for the beginning of the function for stack frame currently under analysis by the personality function (that is, the function pointer for the current stack frame).
  • _Unwind_GetIP, to get the instruction pointer inside the current stack frame (a pointer to the place where the function call to the next stack frame was done. It should be clearer with the example below).

Note: You can download the full sourcecode for this project in my github repo.

Let’s check these functions with gdb. On my machine:

Breakpoint 1, __gxx_personality_v0 (version=1, actions=6, exceptionClass=134515400, unwind_exception=0x804a060, context=0xbffff0f0)
    at mycppabi.cpp:77

84	        const uint8_t* lsda = (const uint8_t*)_Unwind_GetLanguageSpecificData(context);
85	        uintptr_t ip = _Unwind_GetIP(context) - 1;
86	        uintptr_t funcStart = _Unwind_GetRegionStart(context);
87	        uintptr_t ipOffset = ip - funcStart;

If we inspect those variables we can see that indeed _Unwind_GetRegionStart points to the current stack frame (try_but_dont_catch) and that _Unwind_GetIP is the IP for the position where the call to the next stack frame was done. The _Unwind_GetRegionStart is pointing us to the place where the exception was first thrown; it’s a bit complicated to explain and we’ll use that later, not now. Also, we don’t see the LSDA here, but we can deduce it’s after the function’s code since _Unwind_GetLanguageSpecificData points directly after the function’s end:

_Unwind_GetIP = (void *) 0x804861d
_Unwind_GetRegionStart = (void *) 0x8048612
_Unwind_GetLanguageSpecificData = (void *) 0x8048e3c
function pointer to try_but_dont_catch = 0x8048612 <try_but_dont_catch()>

(gdb) disassemble /m try_but_dont_catch
Dump of assembler code for function try_but_dont_catch():
10	void try_but_dont_catch() {
        [...]
11	    try {
12	        raise();
   0x08048619 <+7>:	call   0x80485e8 <raise()>

13	    } catch(Fake_Exception&) {
   0x08048651 <+63>:	call   0x804874a <__cxa_begin_catch()>
   0x08048665 <+83>:	call   0x804875e <__cxa_end_catch()>
   0x0804866a <+88>:	jmp    0x804861e <try_but_dont_catch()+12>

14	        printf("Caught a Fake_Exception!\n");
   0x08048659 <+71>:	movl   $0x8048971,(%esp)
   0x08048660 <+78>:	call   0x80484c0 <puts@plt>

15	    }
16	
17	    printf("try_but_dont_catch handled the exception\n");
   0x0804861e <+12>:	movl   $0x8048948,(%esp)
   0x08048625 <+19>:	call   0x80484c0 <puts@plt>

18	}
   0x0804862a <+24>:	add    $0x24,%esp

With the help of _Unwind_ we are now able to get enough information about the current stack frame to decide whether we can or not handle an exception, an also how should we handle it. One more step is needed before we can detect the landing pad we want: we will need to interpret the CFI (call frame information) at the end of the function. This is part of the DWARF spec, the same gdb uses for debugging purposes, and it’s not an easy spec to implement. Like we are doing with our ABI, we’ll keep this to the bare minimum.


C++ exceptions under the hood 9: catching our first exception

We finished last chapter on the series about C++ exceptions by adding a personality function that _Unwind_ was able to call and then analyzing the parameters that the personality function receives. Now it’s time to begin adding some real behavior to __gxx_personality_v0: when __gxx_personality_v0 is called we should say “yes, this stack frame can indeed handle this exception”.

We have been building up to this point quite a bit: the time where we can implement for the first time a personality function capable of detecting when an exception is thrown, and then saying “yes, I will handle this exception”. For that we had to learn how the two-phase lookup work, so we can now reimplement our personality function and our throw test file:

#include <stdio.h>
#include "throw.h"

struct Fake_Exception {};

void raise() {
    throw Exception();
}

void try_but_dont_catch() {
    try {
        raise();
    } catch(Fake_Exception&) {
        printf("Caught a Fake_Exception!\n");
    }

    printf("try_but_dont_catch handled the exception\n");
}

void catchit() {
    try {
        try_but_dont_catch();
    } catch(Exception&) {
        printf("Caught an Exception!\n");
    }

    printf("catchit handled the exception\n");
}

extern "C" {
    void seppuku() {
        catchit();
    }
}

And our personality function:

_Unwind_Reason_Code __gxx_personality_v0 (
                     int version, _Unwind_Action actions, uint64_t exceptionClass,
                     _Unwind_Exception* unwind_exception, _Unwind_Context* context)
{
    if (actions & _UA_SEARCH_PHASE)
    {
        printf("Personality function, lookup phase\n");
        return _URC_HANDLER_FOUND;
    } else if (actions & _UA_CLEANUP_PHASE) {
        printf("Personality function, cleanup\n");
        return _URC_INSTALL_CONTEXT;
    } else {
        printf("Personality function, error\n");
        return _URC_FATAL_PHASE1_ERROR;
    }
}

Note: You can download the full sourcecode for this project in my github repo.

Let’s run it, see what happens:

alloc ex 1
__cxa_throw called
Personality function, lookup phase
Personality function, cleanup
try_but_dont_catch handled the exception
catchit handled the exception

It works, but something is missing: the catch inside the catch/try block is not being executed! This is happening because the personality function tells Unwind to “install a context” (ie to resume execution) but it never says which context. In this case it’s probably resuming executing from after the landing pad, but I’d bet this is actually undefined behavior. We’ll see next time how we can specify we want to resume executing from a specific landing pad using the information available on .gcc_except_table (our old friend, the LSDA).


C++ exceptions under the hood 8: two-phase handling

We finished last chapter on the series about C++ exceptions by adding a personality function that _Unwind_ was able to call. It didn’t do much but there it was. The ABI we have been implementing can now throw exceptions and the catch is already halfway implemented, but the personality function needed to properly choose the catch block (landing pad) is bit dumb so far. Let’s start this new chapter by trying to understand what are the parameters that the personality function receives and next time we’ll begin adding some real behavior to __gxx_personality_v0: when __gxx_personality_v0 is called we should say “yes, this stack frame can indeed handle this exception”.

We already said we won’t care for the version or the exceptionClass for our mini ABI. Let’s ignore the context too, for now: we’ll just handle every exception with the first stack frame above the function throwing; note this implies there must be a try/catch block on the function immediately above the throwing function, otherwise everything will break. This also implies the catch will ignore its exception specification, effectively turning it into a catch(…). How do we let _Unwind_ know we want to handle the current exception?

_Unwind_Reason_Code is the return value from the personality functions; this tells _Unwind_ whether we found a landing pad to handle the exception or not. Let’s implement our personality function to return _URC_HANDLER_FOUND then, and see what happens:

alloc ex 1
__cxa_throw called
Personality function FTW
Personality function FTW
no one handled __cxa_throw, terminate!

See that? We told _Unwind_ we found a handler, and it called the personality function yet again! What is going on there?

Remember the action parameter? That’s how _Unwind_ tells us what he is expecting, and that is because the exception catching is handled in two phases: lookup and cleanup (or _UA_SEARCH_PHASE and _UA_CLEANUP_PHASE). Let’s go again over our exception throwing and catching recipe:

  • __cxa_throw/__cxa_allocate_exception will create an exception and forward it to a lower-level unwind library by calling _Unwind_RaiseException
  • Unwind will use CFI to know which functions are on the stack (ie to know how to start the stack unwinding)
  • Each function has have an LSDA (language specific data area) part, added into something called “.gcc_except_table”
  • Unwind will try to locate a landing pad for the exception:
    • Unwind will call the personality function with the action _UA_SEARCH_PHASE and a context pointing to the current stack frame.
    • The personality function will check if the current stack frame can handle the exception being thrown by analyzing the LSDA.
    • If the exception can be handled it will return _URC_HANDLER_FOUND.
    • If the exception can not be handled it will return _URC_CONTINUE_UNWIND and Unwind will then try the next stack frame.
  • If no landing pad was found, the default exception handler will be called (normally std::terminate).
  • If a landing pad was found:
    • Unwind will iterate the stack again, calling the personality function with the action _UA_CLEANUP_PHASE.
    • The personality function will check if it can handle the current exception again:
    • If this frame can’t handle the exception it will then run a cleanup function described by the LSDA and tell Unwind to continue with the next frame (this is actually a very important step: the cleanup function will run the destructor of all the objects allocated in this stack frame!)
    • If this frame can handle the exception, don’t run any cleanup code: tell Unwind we want to resume execution on this landing pad.

There are two important bits of information to note here:

  1. Running a two-phase exception handling procedure means that in case no handler was found then the default exception handler can get the original exception’s stack trace (if we were to unwind the stack as we go it would get no stack trace, or we would need to keep a copy of it somehow!).
  2. Running a _UA_CLEANUP_PHASE and calling a second time each frame, even though we already know the frame that will handle the exception, is also really important: the personality function will take this chance to run all the destructors for objects built on this scope. It is what makes RAII an exception safe idiom!

Now that we understand how the catch lookup phase works we can continue our personality function implementation. The next time.


C++ exceptions under the hood 7: a nice personality

On our journey to learn about exceptions we have learned so far how a throw is done, that something called “call frame information” helps a library called Unwind to do the stack unwinding, and that the compiler writes something called LSDA, language specific data area, to know which exceptions can a method handle. And we know by now that a lot of magic is done on the personality function; we’ve never seen it in action though. Let’s recap in a bit more of detail about how an exception will be thrown and catched (or, more precisely, how we know so far it will be thrown catched):

  • The compiler will translate our throw statement into a pair of __cxa_allocate_exception/__cxa_throw
  • __cxa_allocate_exception will create the exception in memory
  • __cxa_throw will initialize a bunch of stuff and forward this exception to a lower-level unwind library by calling _Unwind_RaiseException
  • Unwind will use CFI to know which functions are on the stack (ie to know how to start the stack unwinding)
  • Each function will have an LSDA (language specific data area) part, added into something called “.gcc_except_table”
  • Unwind will invoke the personality function with the current stack frame and the LSDA; this function should reply to unwind whether this stack can handle the exception or not

Knowing this, it’s about time we implement our own personality function. Our ABI used to print this when an exception was thrown:

alloc ex 1
__cxa_throw called
no one handled __cxa_throw, terminate!

Let’s go back to our mycppabi and let’s add something like this (link to full mycppabi.cpp file):

void __gxx_personality_v0()
{
    printf("Personality function FTW\n");
}

Note: You can download the full sourcecode for this project in my github repo.

And sure enough, when we run it we should see our personality function being called. We know we’re on the right track and now we have an idea of what we want for our personality function; let’s start using the proper definition for this function:

_Unwind_Reason_Code __gxx_personality_v0 (
                     int version, _Unwind_Action actions, uint64_t exceptionClass,
                     _Unwind_Exception* unwind_exception, _Unwind_Context* context);

If we put that into our mycppabi.cpp file we get:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

namespace __cxxabiv1 {
    struct __class_type_info {
        virtual void foo() {}
    } ti;
}

#define EXCEPTION_BUFF_SIZE 255
char exception_buff[EXCEPTION_BUFF_SIZE];

extern "C" {

void* __cxa_allocate_exception(size_t thrown_size)
{
    printf("alloc ex %i\n", thrown_size);
    if (thrown_size > EXCEPTION_BUFF_SIZE) printf("Exception too big");
    return &exception_buff;
}

void __cxa_free_exception(void *thrown_exception);


#include <unwind.h>

typedef void (*unexpected_handler)(void);
typedef void (*terminate_handler)(void);

struct __cxa_exception { 
	std::type_info *	exceptionType;
	void (*exceptionDestructor) (void *); 
	unexpected_handler	unexpectedHandler;
	terminate_handler	terminateHandler;
	__cxa_exception *	nextException;

	int			handlerCount;
	int			handlerSwitchValue;
	const char *		actionRecord;
	const char *		languageSpecificData;
	void *			catchTemp;
	void *			adjustedPtr;

	_Unwind_Exception	unwindHeader;
};

void __cxa_throw(void* thrown_exception, struct type_info *tinfo, void (*dest)(void*))
{
    printf("__cxa_throw called\n");

    __cxa_exception *header = ((__cxa_exception *) thrown_exception - 1);
    _Unwind_RaiseException(&header->unwindHeader);

    // __cxa_throw never returns
    printf("no one handled __cxa_throw, terminate!\n");
    exit(0);
}


void __cxa_begin_catch()
{
    printf("begin FTW\n");
}

void __cxa_end_catch()
{
    printf("end FTW\n");
}

_Unwind_Reason_Code __gxx_personality_v0 (
                     int version, _Unwind_Action actions, uint64_t exceptionClass,
                     _Unwind_Exception* unwind_exception, _Unwind_Context* context)
{
    printf("Personality function FTW!\n");
}


}

Code @ my github repo.

Let’s compile and link everything, then run it and start by analyzing each param to this function with some help of gdb:

Breakpoint 1, __gxx_personality_v0 (version=1, actions=1, exceptionClass=134514792, unwind_exception=0x804a060, context=0xbffff0f0)
  • The version and the exceptionClass are related to language/ABI/compiler toolchain/native or non-native exception, etc. We don’t need to worry about it for our mini ABI, we’ll just handle all the exceptions.
  • Actions: this is what _Unwind_ uses to tell the personality function what it should do (more on that later)
  • unwind_exception: the exception allocated by __cxa_allocate_exception (kind of… there’s a lot of pointer arithmetic going on but that pointer can be used to access our original exception anyway)
  • context: this holds all the information regarding the current stack frame, for example the language specific data area (LSDA). This is what we will be using to detect whether this stack can handle the thrown exception (and also to detect whether we need to run any destructors)

So there we have it, a working (well, linkeable) personality function. Doesn’t do much, though, so next time we’ll start adding some real behavior and try to make it handle an exception.


C++ exceptions under the hood 6: gcc_except_table and the personality function

We learned last time that, just as a throw statement is translated into a pair of __cxa_allocate_exception/throw calls, a catch block is translated into a pair of __cxa_begin/end_catch calls, plus something called CFI (call frame information) to find the landing pads, the points on a function where an exception can be handled.

What we don’t yet know is how does _Unwind_* know where the landing pads are. When an exception is thrown there are a bunch of functions in the stack; all the CFI stuff will let Unwind know which functions these are but it’s also necessary to know which landing pads each function provides so we can call each one and check if it wants to handle the exception (and we’re ignoring functions with multiple try/catch blocks!).

To know where the landing pads are, something called gcc_except_table is used. This can be found (with a bunch of CFI stuff) after the function’s end:

.LFE1:
	.globl	__gxx_personality_v0
	.section	.gcc_except_table,"a",@progbits
    [...]
.LLSDACSE1:
	.long	_ZTI14Fake_Exception

The section .gcc_except_table is where all information to locate a landing pad is stored, and we’ll see more about it once we get to analyzing the personality function; for now, we’ll just say that LSDA means language specific data area and it’s the place where the personality function will check if there are any landing pads for a function (it is also used to run the destructors when unwinding the stack).

To wrap it up: for every function where at least a catch is found, the compiler will translate this statement into a pair of __cxa_begin_catch/__cxa_end_catch calls and then the personality function, which will be called by __cxa_throw, will read the gcc_except_table for every method in the stack, to find something call LSDA. The personality function will then check in the LSDA whether a catch can handle an exception and if there is any cleanup code to run (this is what triggers the destructors when needed).

We can also draw an interesting conclusion here: if we use the nothrow specifier (or the empty throw specifier) then the compiler can omit the gcc_except_table for this method. The way gcc implements exceptions, that won’t have a great impact on performance but it will indeed reduce code size. What’s the catch? If an exception is thrown when nothrow was specified the LSDA won’t be there and the personality function won’t know what to do. When the personality function doesn’t know what to do it will invoke the default exception handler, meaning that in most cases throwing from a nothrow method will end up calling std::terminate.

Now that we have an idea of what the personality function does, can we implement one? We’ll see how next time.


C++ exceptions under the hood 5: magic around __cxa_begin_catch and __cxa_end_catch

After learning how exceptions are thrown we are now on our way to learn how they are caught. Last time we added to our example application a bunch of try/catch statements to see what they did, and sure enough we got a bunch of linker errors, just like we did when we were trying to find out what does the throw statement do. This is what the linker says when trying to process throw.o:

Note: You can download the full sourcecode for this project in my github repo.

> g++ -c -o throw.o -O0 -ggdb throw.cpp
> gcc main.o throw.o mycppabi.o -O0 -ggdb -o app
throw.o: In function `try_but_dont_catch()':
throw.cpp:12: undefined reference to `__cxa_begin_catch'
throw.cpp:12: undefined reference to `__cxa_end_catch'

throw.o: In function `catchit()':
throw.cpp:20: undefined reference to `__cxa_begin_catch'
throw.cpp:20: undefined reference to `__cxa_end_catch'

throw.o:(.eh_frame+0x47): undefined reference to `__gxx_personality_v0'

collect2: ld returned 1 exit status

And our theory, of course, is that a catch statement is translated by the compiler into a pair of __cxa_begin_catch/end_catch calls into libstdc++, plus something new called the personality function of which we know nothing yet.

Let’s begin by checking if our theory about __cxa_begin_catch and __cxa_end_catch holds. Let’s compile throw.cpp with -S and analyze the assembly. There is a lot to see but if I strip it to the bare minimum this is what I get:

_Z5raisev:
	call	__cxa_allocate_exception
	call	__cxa_throw

So far so good: the same old definition we got for raise(), just throw an exception.

_Z18try_but_dont_catchv:
	.cfi_startproc
	.cfi_personality 0,__gxx_personality_v0
	.cfi_lsda 0,.LLSDA1

The definition for try_but_dont_catch(), mangled by the compiler. There is something new, though: a reference to __gxx_personality_v0 and to something else called LSDA. These are seemingly innocent declarations but they are actually quite important:

  • The linker will use these according to a CFI specification; CFI stands for call frame information, and here there is a full spec for it. It will be used, mostly, to unwind the stack.
  • LSDA on the other hand means language specific data area, and it will be used by the personality function to know which exceptions can be handled by this function

We’ll be talking a lot more about CFI and LSDA in the next articles; don’t forget about them, but for now let’s move on:

    [...]
	call	_Z5raisev
	jmp	.L8

Another easy one: just call “raise”, and then jump to L8; L8 will return normally from this function. If raise didn’t execute properly then the execution (somehow, we don’t know how yet!) shouldn’t resume in the next instruction but in the exception handlers (which in ABI-speak are called landing pads. More on that later).

	cmpl	$1, %edx
	je	.L5

.LEHB1:
	call	_Unwind_Resume
.LEHE1:

.L5:
	call	__cxa_begin_catch
	call	__cxa_end_catch

This is quite difficult to follow but it’s actually quite straight forward. Here most of the magic will happen: first we check if this is an exception we can handle, if we can’t then we say so by calling _Unwind_Resume, if it is then we call __cxa_begin_catch and __cxa_end_catch; after calling these functions the execution should resume normally and thus L8 will be executed (that is, L8 is right below our catch block):

.L8:
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc

Just a normal return from our function… with some CFI stuff on it.

So this is it for exception catching, although we don’t know yet how __cxa_begin/end_catch work, we have an idea that these pair forms what’s called a landing pad, a place in the function to handle the raised exception. What we don’t know yet is how the landing pads are found. _Unwind_ must somehow go through all the calls in the stack, check if any call (stack frame, to be precise) has a valid try block with a landing pad that can catch the exception, and then resume the execution there.

This is no small feat, and we’ll see how that works next time.