How Is the C++ Exception Handling Runtime Implemented

How is the C++ exception handling runtime implemented?

Implementations may differ, but there are some basic ideas that follow from requirements.

The exception object itself is an object created in one function, destroyed in a caller thereof. Hence, it's typically not feasible to create the object on the stack. On the other hand, many exception objects are not very big. Ergo, one can create e.g a 32 byte buffer and overflow to heap if a bigger exception object is actually needed.

As for the actual transfer of control, two strategies exist. One is to record enough information in the stack itself to unwind the stack. This is basically a list of destructors to run and exception handlers that might catch the exception. When an exception happens, run back the stack executing those destructors until you find a matching catch.

The second strategy moves this information into tables outside the stack. Now, when an exception occurs, the call stack is used to find out which scopes are entered but not exited. Those are then looked up in the static tables to determine where the thrown exception will be handled, and which destructors run in between. This means there is less exception overhead on the stack; return addresses are needed anyway. The tables are extra data, but the compiler can put them in a demand-loaded segment of the program.

How are exceptions implemented under the hood?

Exceptions are just a specific example of a more general case of advanced non-local flow control constructs. Other examples are:

  • notifications (a generalization of exceptions, originally from some old Lisp object system, now implemented in e.g. CommonLisp and Ioke),
  • continuations (a more structured form of GOTO, popular in high-level, higher-order languages),
  • coroutines (a generalization of subroutines, popular especially in Lua),
  • generators à la Python (essentially a restricted form of coroutines),
  • fibers (cooperative light-weight threads) and of course the already mentioned
  • GOTO.

(I'm sure there's many others I missed.)

An interesting property of these constructs is that they are all roughly equivalent in expressive power: if you have one, you can pretty easily build all the others.

So, how you best implement exceptions depends on what other constructs you have available:

  • Every CPU has GOTO, therefore you can always fall back to that, if you must.
  • C has setjmp/longjmp which are basically MacGyver continuations (built out of duct-tape and toothpicks, not quite the real thing, but will at least get you out of the immediate trouble if you don't have something better available).
  • The JVM and CLI have exceptions of their own, which means that if the exception semantics of your language match Java's/C#'s, you are home free (but if not, then you are screwed).
  • The Parrot VM as both exceptions and continuations.
  • Windows has its own framework for exception handling, which language implementors can use to build their own exceptions on top.

A very interesting use case, both of the usage of exceptions and the implementation of exceptions is Microsoft Live Lab's Volta Project. (Now defunct.) The goal of Volta was to provide architectural refactoring for Web applications at the push of a button. So, you could turn your one-tier web application into a two- or three-tier application just by putting some [Browser] or [DB] attributes on your .NET code and the code would then automagically run on the client or in the DB. In order to do that, the .NET code had to be translated to JavaScript source code, obviously.

Now, you could just write an entire VM in JavaScript and run the bytecode unmodified. (Basically, port the CLR from C++ to JavaScript.) There are actually projects that do this (e.g. the HotRuby VM), but this is both inefficient and not very interoperable with other JavaScript code.

So, instead, they wrote a compiler which compiles CIL bytecode to JavaScript sourcecode. However, JavaScript lacks certain features that .NET has (generators, threads, also the two exception models aren't 100% compatible), and more importantly it lacks certain features that compiler writers love (either GOTO or continuations) and that could be used to implement the above-mentioned missing features.

However, JavaScript does have exceptions. So, they used JavaScript Exceptions to implement Volta Continuations and then they used Volta Continuations to implement .NET Exceptions, .NET Generators and even .NET Managed Threads(!!!)

So, to answer your original question:

How are exceptions implemented under the hood?

With Exceptions, ironically! At least in this very specific case, anyway.

Another great example is some of the exception proposals on the Go mailing list, which implement exceptions using Goroutines (something like a mixture of concurrent coroutines ans CSP processes). Yet another example is Haskell, which uses Monads, lazy evaluation, tail call optimization and higher-order functions to implement exceptions. Some modern CPUs also support basic building blocks for exceptions (for example the Vega-3 CPUs that were specifically designed for the Azul Systems Java Compute Accelerators).

Example of how Objective-C's @try-@catch implementation is executed at runtime?

So you want to know how the runtime does exception handling?

Prepare to be disappointed.

Because it doesn't. ObjC doesn't have an exception-handling ABI, only SPI which you've already found. No doubt you've also discovered that the Objective-C exception ABI is actually the exact same one as the C++ exception handling ABI. To that end, let's get started with some code.

#include <Foundation/Foundation.h>

int main(int argc, char **argv) {
@try {
@throw [NSException exceptionWithName:@"ExceptionalCircumstances" reason:@"Drunk on power" userInfo:nil];
} @catch(...) {
NSLog(@"Catch");
} @finally {
NSLog(@"Finally");
}
}

Run through clang with -ObjC -O3 (and stripped of a disgusting amount of debug information) we get this:

_main:                                  ## @main
push rbp
mov rbp, rsp
push r14
push rbx

mov rdi, qword ptr [rip + L_OBJC_CLASSLIST_REFERENCES_$_]
mov rsi, qword ptr [rip + L_OBJC_SELECTOR_REFERENCES_]

lea rdx, qword ptr [rip + L__unnamed_cfstring_]
lea rcx, qword ptr [rip + L__unnamed_cfstring_2]
xor r8d, r8d
call qword ptr [rip + _objc_msgSend@GOTPCREL]

mov rdi, rax
call _objc_exception_throw
LBB0_2:
mov rdi, rax
call _objc_begin_catch

lea rdi, qword ptr [rip + L__unnamed_cfstring_4]
xor eax, eax
call _NSLog

call _objc_end_catch

xor ebx, ebx
LBB0_8:
lea rdi, qword ptr [rip + L__unnamed_cfstring_6]
xor eax, eax
call _NSLog

test bl, bl
jne LBB0_10
LBB0_11:
xor eax, eax
pop rbx
pop r14
pop rbp
ret
LBB0_5:
mov rbx, rax
call _objc_end_catch
jmp LBB0_7
LBB0_6:
mov rbx, rax
LBB0_7:
mov rdi, rbx
call _objc_begin_catch
mov bl, 1
jmp LBB0_8
LBB0_12:
mov r14, rax
test bl, bl
je LBB0_14
jmp LBB0_13
LBB0_10:
call _objc_exception_rethrow
jmp LBB0_11
LBB0_16: ## %.thread
mov r14, rax
LBB0_13:
call _objc_end_catch
LBB0_14:
mov rdi, r14
call __Unwind_Resume
LBB0_15:
call _objc_terminate

If you compile it with ObjC++ nothing changes. (Well, that's not entirely true. The last _objc_terminate turns into a jump into clang's personal ___clang_call_terminate routine). Anyhow, this code can be divided into 3 important sections. The first is from _main to the start of LBB0_2, or where our try block happens. Because we're blatantly throwing an exception and catching it in our try block, the compiler has gone ahead and removed the branch around LBB0_2 and moved straight to the catch handlers. At this point Objective-C, or more accurately CoreFoundation, has set up an exception object for us and libC++ has begun searching for an exception handler during the requisite unwinding phase.

The second important block of code is from LBB0_2 to the end of LBB0_11 where our catch and finally blocks live. Because all is well, all the code below this is dead (and hopefully gets stripped in release), but let's imagine it wasn't.

The third part is from LBB0_8 on down where the compiler would have emitted a jump to from the NSLog in LBB0_2 if we'd done something stupid like, say, tried not to catch our exception. This handler instead flips a bit after calling into the objc_begin_catch that causes us to branch around the ret and move onto the objc_exception_rethrow() that tells the unwind handler that we've dropped the ball and to continue searching for handlers somewhere else. Of course, we're main, so there are no other handlers, and std::terminate gets invoked as we leave.

All this to say you're gonna have a bad time if you want to try to write this stuff out by hand. All the __cxa_* and ObjC SPI functions throw around exception objects in ways you can't rely on and (rather pessimistically many) handlers are emitted in a very tight order to make sure the C++ ABI contract is fulfilled because if it isn't the spec mandates std::terminate be called. If you'd like to take an active listening role, you are allowed to redefine the exception handling stuff with your own functions and Objective-C has objc_setUncaughtExceptionHandler, objc_setExceptionMatcher objc_setExceptionPreprocessor.

How to handle runtime errors in C++?

Generally speaking, the way to handle such errors (like any errors) depends on the needs of your program as a whole - and you have not specified that. So there is no one-size-fits-all "general rule".

There are options and trade-offs.

One option is for your State enumerated type to provide an enumerator value that represents undetermined or an invalid state, such as

 enum class State {MG, PR, Undetermined};

Then, in your function, return the undetermined value, e.g.

State s_toState(const std::string &state)
{
State return_value = State::Undetermined;
if (state == "MG")
return_value = State::MG;
else if (state == "PR")
return_value = State::PR;
// etc

return return_value;
}

With this approach the function always returns a valid value of type State. If the error conditions aren't critical (i.e. the program can continue if an invalid string is supplied) then the caller can decide if it needs to check the return value. Multiple types of error condition can be reported (e.g. by having multiple enum values representing different errors) A down-side is that the caller may forget to check the return value and behave incorrectly.

Another option is to throw an exception, for example;

State s_toState(const std::string &state)
{
if (state == "MG")
return State::MG;
else if (state == "PR")
return State::PR;
// etc

throw std::invalid_argument("Bad input string");
}

This option requires some sensible choice of what type of exception to throw (e.g. what information needs to be conveyed about the error state). This approach may be preferable if the caller (or the program as a whole) cannot sensibly continue if a bad string is provided since, by throwing an exception, the caller is forced to catch and take any recovery action to avoid being terminated. As such, this approach may not be suitable for non-critical errors - for example, if execution can sensibly continue if a bad string is provided.

Another option (C++17 and later) is to return std::optional<State>. This allows the caller to check if an error has occurred (e.g. std::option::has_value() return false) occurs or, if the value is accessed without checking, cause an exception of type std::bad_optional_access to be thrown (which may be suitable for the caller, or may be uninformative).

It's also possible to use assert() - which forces termination if a specified condition is untrue. Generally, I prefer throwing an exception over using assert() but you may prefer otherwise.

How do exceptions work (behind the scenes) in c++

Instead of guessing, I decided to actually look at the generated code with a small piece of C++ code and a somewhat old Linux install.

class MyException
{
public:
MyException() { }
~MyException() { }
};

void my_throwing_function(bool throwit)
{
if (throwit)
throw MyException();
}

void another_function();
void log(unsigned count);

void my_catching_function()
{
log(0);
try
{
log(1);
another_function();
log(2);
}
catch (const MyException& e)
{
log(3);
}
log(4);
}

I compiled it with g++ -m32 -W -Wall -O3 -save-temps -c, and looked at the generated assembly file.

    .file   "foo.cpp"
.section .text._ZN11MyExceptionD1Ev,"axG",@progbits,_ZN11MyExceptionD1Ev,comdat
.align 2
.p2align 4,,15
.weak _ZN11MyExceptionD1Ev
.type _ZN11MyExceptionD1Ev, @function
_ZN11MyExceptionD1Ev:
.LFB7:
pushl %ebp
.LCFI0:
movl %esp, %ebp
.LCFI1:
popl %ebp
ret
.LFE7:
.size _ZN11MyExceptionD1Ev, .-_ZN11MyExceptionD1Ev

_ZN11MyExceptionD1Ev is MyException::~MyException(), so the compiler decided it needed a non-inline copy of the destructor.

.globl __gxx_personality_v0
.globl _Unwind_Resume
.text
.align 2
.p2align 4,,15
.globl _Z20my_catching_functionv
.type _Z20my_catching_functionv, @function
_Z20my_catching_functionv:
.LFB9:
pushl %ebp
.LCFI2:
movl %esp, %ebp
.LCFI3:
pushl %ebx
.LCFI4:
subl $20, %esp
.LCFI5:
movl $0, (%esp)
.LEHB0:
call _Z3logj
.LEHE0:
movl $1, (%esp)
.LEHB1:
call _Z3logj
call _Z16another_functionv
movl $2, (%esp)
call _Z3logj
.LEHE1:
.L5:
movl $4, (%esp)
.LEHB2:
call _Z3logj
addl $20, %esp
popl %ebx
popl %ebp
ret
.L12:
subl $1, %edx
movl %eax, %ebx
je .L16
.L14:
movl %ebx, (%esp)
call _Unwind_Resume
.LEHE2:
.L16:
.L6:
movl %eax, (%esp)
call __cxa_begin_catch
movl $3, (%esp)
.LEHB3:
call _Z3logj
.LEHE3:
call __cxa_end_catch
.p2align 4,,3
jmp .L5
.L11:
.L8:
movl %eax, %ebx
.p2align 4,,6
call __cxa_end_catch
.p2align 4,,6
jmp .L14
.LFE9:
.size _Z20my_catching_functionv, .-_Z20my_catching_functionv
.section .gcc_except_table,"a",@progbits
.align 4
.LLSDA9:
.byte 0xff
.byte 0x0
.uleb128 .LLSDATT9-.LLSDATTD9
.LLSDATTD9:
.byte 0x1
.uleb128 .LLSDACSE9-.LLSDACSB9
.LLSDACSB9:
.uleb128 .LEHB0-.LFB9
.uleb128 .LEHE0-.LEHB0
.uleb128 0x0
.uleb128 0x0
.uleb128 .LEHB1-.LFB9
.uleb128 .LEHE1-.LEHB1
.uleb128 .L12-.LFB9
.uleb128 0x1
.uleb128 .LEHB2-.LFB9
.uleb128 .LEHE2-.LEHB2
.uleb128 0x0
.uleb128 0x0
.uleb128 .LEHB3-.LFB9
.uleb128 .LEHE3-.LEHB3
.uleb128 .L11-.LFB9
.uleb128 0x0
.LLSDACSE9:
.byte 0x1
.byte 0x0
.align 4
.long _ZTI11MyException
.LLSDATT9:

Surprise! There are no extra instructions at all on the normal code path. The compiler instead generated extra out-of-line fixup code blocks, referenced via a table at the end of the function (which is actually put on a separate section of the executable). All the work is done behind the scenes by the standard library, based on these tables (_ZTI11MyException is typeinfo for MyException).

OK, that was not actually a surprise for me, I already knew how this compiler did it. Continuing with the assembly output:

    .text
.align 2
.p2align 4,,15
.globl _Z20my_throwing_functionb
.type _Z20my_throwing_functionb, @function
_Z20my_throwing_functionb:
.LFB8:
pushl %ebp
.LCFI6:
movl %esp, %ebp
.LCFI7:
subl $24, %esp
.LCFI8:
cmpb $0, 8(%ebp)
jne .L21
leave
ret
.L21:
movl $1, (%esp)
call __cxa_allocate_exception
movl $_ZN11MyExceptionD1Ev, 8(%esp)
movl $_ZTI11MyException, 4(%esp)
movl %eax, (%esp)
call __cxa_throw
.LFE8:
.size _Z20my_throwing_functionb, .-_Z20my_throwing_functionb

Here we see the code for throwing an exception. While there was no extra overhead simply because an exception might be thrown, there is obviously a lot of overhead in actually throwing and catching an exception. Most of it is hidden within __cxa_throw, which must:

  • Walk the stack with the help of the exception tables until it finds a handler for that exception.
  • Unwind the stack until it gets to that handler.
  • Actually call the handler.

Compare that with the cost of simply returning a value, and you see why exceptions should be used only for exceptional returns.

To finish, the rest of the assembly file:

    .weak   _ZTI11MyException
.section .rodata._ZTI11MyException,"aG",@progbits,_ZTI11MyException,comdat
.align 4
.type _ZTI11MyException, @object
.size _ZTI11MyException, 8
_ZTI11MyException:
.long _ZTVN10__cxxabiv117__class_type_infoE+8
.long _ZTS11MyException
.weak _ZTS11MyException
.section .rodata._ZTS11MyException,"aG",@progbits,_ZTS11MyException,comdat
.type _ZTS11MyException, @object
.size _ZTS11MyException, 14
_ZTS11MyException:
.string "11MyException"

The typeinfo data.

    .section    .eh_frame,"a",@progbits
.Lframe1:
.long .LECIE1-.LSCIE1
.LSCIE1:
.long 0x0
.byte 0x1
.string "zPL"
.uleb128 0x1
.sleb128 -4
.byte 0x8
.uleb128 0x6
.byte 0x0
.long __gxx_personality_v0
.byte 0x0
.byte 0xc
.uleb128 0x4
.uleb128 0x4
.byte 0x88
.uleb128 0x1
.align 4
.LECIE1:
.LSFDE3:
.long .LEFDE3-.LASFDE3
.LASFDE3:
.long .LASFDE3-.Lframe1
.long .LFB9
.long .LFE9-.LFB9
.uleb128 0x4
.long .LLSDA9
.byte 0x4
.long .LCFI2-.LFB9
.byte 0xe
.uleb128 0x8
.byte 0x85
.uleb128 0x2
.byte 0x4
.long .LCFI3-.LCFI2
.byte 0xd
.uleb128 0x5
.byte 0x4
.long .LCFI5-.LCFI3
.byte 0x83
.uleb128 0x3
.align 4
.LEFDE3:
.LSFDE5:
.long .LEFDE5-.LASFDE5
.LASFDE5:
.long .LASFDE5-.Lframe1
.long .LFB8
.long .LFE8-.LFB8
.uleb128 0x4
.long 0x0
.byte 0x4
.long .LCFI6-.LFB8
.byte 0xe
.uleb128 0x8
.byte 0x85
.uleb128 0x2
.byte 0x4
.long .LCFI7-.LCFI6
.byte 0xd
.uleb128 0x5
.align 4
.LEFDE5:
.ident "GCC: (GNU) 4.1.2 (Ubuntu 4.1.2-0ubuntu4)"
.section .note.GNU-stack,"",@progbits

Even more exception handling tables, and assorted extra information.

So, the conclusion, at least for GCC on Linux: the cost is extra space (for the handlers and tables) whether or not exceptions are thrown, plus the extra cost of parsing the tables and executing the handlers when an exception is thrown. If you use exceptions instead of error codes, and an error is rare, it can be faster, since you do not have the overhead of testing for errors anymore.

In case you want more information, in particular what all the __cxa_ functions do, see the original specification they came from:

  • Itanium C++ ABI

How are Exceptions Handled generally in any programming language?

Well, I tried implementing simple exception-handling in C 2 years ago. Let me show it to you.

https://github.com/dlarudgus20/CTryCatch

(In fact, I made the github repository to practice as soon as I saw you question - it's my first repository >o< Thank you to give me the opportunity to use github!)

How are try/catch blocks implemented?

this is not a c#/java/c++ question. How it works internally,how the line knows to go catch statement.

How this works internally makes this pretty much a c#/java/C++ question (because it will be implemented differently).

In Java, a try block installs itself into a special table (in the class file). When the JVM throws an exception, it looks at that table to see where the next catch or finally block to go to is.

Behavior of c++ exceptions escaping into c program

From what I see about C++ exceptions, in this example which I took from MSDN, GCC seems to include the following assembly in the catch statement:

    call    __cxa_end_catch
jmp .L37
movq %rax, %rbx
call __cxa_end_catch
movq %rbx, %rax
movq %rax, %rdi
call _Unwind_Resume

Which makes use of what I can only assume are C++ library calls to functions that deal with exceptions (e.g. _Unwind_resume). So if the C code links against your library it will have to provide these symbols/functions which means that the code is going to be entering the C++ library to deal with the exceptions.

However, I don't yet know what the C++ library requires in order to do its job. I would expect it to be self contained but I'm not certain of it.

Edit: The answer to this question likely lies in the answers to the following two existing questions (and their interpretation):

  1. How is the C++ exception handling runtime implemented?
  2. How are exceptions implemented under the hood?
  3. How do exceptions work (behind the scenes) in c++

Edit 2: From this answer, it seems that since __cxa_throw uses a table for keeping track of available handlers. I would assume that when the table is exhausted, which in our case occurs when we enter C code, the function would call std::terminate. Hence, the C++ runtime (against which you must have linked) should take care of this for you without you needing to put up a catch all clause.

Since I'm still uncertain I will write up a test of this theory and update the answer with the results.



Related Topics



Leave a reply



Submit