The Secret Life of C++: Day 3: Exceptions
    Exceptions are wonderful things. They let you report error
    conditions in vaguely correct ways. But C++ exceptions have to
    live in a highly hostile environment. No virtual machine to coddle
    them, they have to leap over large blocks of legacy C code
    unharmed. TODO: More flavortext.
    
    For more detail about exception handling, check out the Itanium
    Exception handling ABI.
    
How Exceptions Work
    Exceptions are basically a non-local-goto. They allow a program to
    transfer control up the stack, in cases when the code at the
    bottom of the stack doesn't know what else to do. They are even
    more flexible then this becuase there is a registry system, so
    that an exception is thrown up the stack until it hits the first
    stack frame where some expressed interest in dealing with it (ie,
    a catch () handler).
    
    But this is C++, and nothing is easy. So on our way up the stack,
    it is important that we properly destroy our stack frames. In C,
    we could just ignore them, becuase there is no such thing as a
    destructor. Of course, this simplicity makes it very hard to make
    C programs that properly handle non-local gotos. But we are
    talking about C++ and complexity.
    
    So the basic way in which we handle exceptions is to walk up the
    stack, calling the cleanup code as we go, until we find someone
    who wants to handle our exception.
    
    Exceptions are also objects, and the fact that they can use
    inheritance is important. They have an object lifecycle, they get
    created and destroyed. We will look at this in more detail.
    
Hello World-ish example.
    A Hello Worldish example of exceptions:
    hello-exceptions.cc,
    hello-exceptions.s,
    hello-exceptions.listing.
    
    Time for our good old list of symbols:
    
    - _ZNSaIcEC1E
 - std::allocator<char>::allocator
    
 - _ZNSsC1EPKcRKSaIcE
 - string(const char *)
    
 - _ZNSsD1Ev
 - ~string()
    
 - __cxa_allocate_exception
 - Allocate memory for an exception.
    Generally on the heap. Has access to a last-resort piece of memory
    for this purpose, so we can throw out of memory exceptions.
    
 - __cxa_throw
 - External interface to throw in the C++ support
    library. Takes three arguments: an exception object, a typeinfo
    for that object, and a pointer to the destructor to call when we
    are done with that object.
    
 - _Unwind_RaiseException
 - Function called by __cxa_throw.
    
    
 - _Unwind_Resume
 - Resume the unwind process, called at the end
    of cleanup code that didn't return to the normal thread of
    execution (ie, not a catch).
    
 - __cxa_begin_catch
 - Keeps track of which exceptions are being
    caught in which order, pushes this exceptoin on the stack of
    exceptions that are being handled.
    
 - __cxa_end_catch
 - Take the exception we are processing off
    the stack and free it. When it returns, we should be in our normal
    execution thread.
    
 
    Unwinding the Stack
    How does unwinding the stack really work? It happens in two passes.
    
    On the first pass (Phase One), we walk up the stack until we find
    an exception handler that wants to handle our exception. it is
    even possible to find a handler up the stack that tells us to
    ignore the exception. I'm told this functionality is used in
    Common Lisp implementations.
    
    The second pass (Phase Two) walks up the stack, executing the
    cleanup code, until we get to the frame which is going to do the
    catch.
    
    There are two specific methods:
    
SjLj stack unwinding
    In SjLj (Setjmp, Longjmp) stack unwinding, we do a setjmp-ish call
    each time we enter a function. As we go up the stack, we just
    longjmp to each setjmp point in succession.
    
    An example of SjLj exceptions:
    hello-exceptions.cc,
    hello-exceptions.listing.sjlj,
    
Dwarf2 stack unwinding
    In Dwarf2 stack unwinding we don't have to do any work as long as
    there are no exceptions, but complexity is increased. We create a
    symbol table similar to debugging symbols that lets us find out
    the right places to walk up the stack to.
    
    Forced Unwind versus Regular
    There is the concept of a Forced unwind of the stack. A Forced
    unwind is not caused by an exception being thrown. A forced unwind
    is when the exception handlers on the call stack aren't allowed to
    catch an exception, and some other code takes care of knowing when
    to stop. Two examples of forced unwind are longjmp() and
    pthread_cancel().
    Rethrowing
    
    An example of rethrowing a caught exception:
    rethrow.cc,
    rethrow.s,
    rethrow.listing,
    rethrow.listing.sjlj.
    Catching and Throwing a Different Exception
    See the above example. Pretty straightforward. unless we want to
    throw and catch an exception while handling an exception.
    throw() specification on a function
    The throw() specification will cause the unwind Phase One to fail
    with unexepected exception.
    Passing through code that doesn't know about exceptions
    THis works out, partly because code that doesn't know about
    exceptions can't have destructors to be called. More succinctly,
    longjmp just works in native C code, stack frames can just be
    discarded.
    
    Richard Tibbetts
Last modified: Wed Jan 21 17:36:31 EST 2004