400850: 55 push %rbp
400851: 48 89 e5 mov %rsp,%rbp
400854: 48 83 ec 10 sub $0x10,%rsp
400858: bf 08 00 00 00 mov $0x8,%edi
40085d: e8 8e fe ff ff callq 4006f0 <__cxa_allocate_exception@plt>
400862: 48 ba 00 00 00 00 00 movabs $0x3ff0000000000000,%rdx
400869: 00 f0 3f
40086c: 48 89 10 mov %rdx,(%rax)
40086f: ba 00 00 00 00 mov $0x0,%edx
400874: be a0 0d 60 00 mov $0x600da0,%esi
400879: 48 89 c7 mov %rax,%rdi
40087c: e8 7f fe ff ff callq 400700 <__cxa_throw@plt>
400881: b8 00 00 00 00 mov $0x0,%eax
400886: eb 74 jmp 4008fc
400888: 48 83 fa 02 cmp $0x2,%rdx
40088c: 74 27 je 4008b5
40088e: 48 83 fa 03 cmp $0x3,%rdx
400892: 74 3e je 4008d2
400894: 48 83 fa 01 cmp $0x1,%rdx
400898: 75 53 jne 4008ed
40089a: 48 89 c7 mov %rax,%rdi
40089d: e8 7e fe ff ff callq 400720 <__cxa_begin_catch@plt>
4008a2: 8b 00 mov (%rax),%eax
4008a4: 89 45 fc mov %eax,-0x4(%rbp)
4008a7: c7 45 fc ad de 00 00 movl $0xdead,-0x4(%rbp)
4008ae: e8 5d fe ff ff callq 400710 <__cxa_end_catch@plt>
4008b3: eb cc jmp 400881
4008b5: 48 89 c7 mov %rax,%rdi
4008b8: e8 63 fe ff ff callq 400720 <__cxa_begin_catch@plt>
4008bd: 8b 00 mov (%rax),%eax
4008bf: 89 45 f8 mov %eax,-0x8(%rbp)
4008c2: 8b 05 cc 00 00 00 mov 0xcc(%rip),%eax # 400994 <_IO_stdin_used+0x4>
4008c8: 89 45 f8 mov %eax,-0x8(%rbp)
4008cb: e8 40 fe ff ff callq 400710 <__cxa_end_catch@plt>
4008d0: eb af jmp 400881
4008d2: 48 89 c7 mov %rax,%rdi
4008d5: e8 46 fe ff ff callq 400720 <__cxa_begin_catch@plt>
4008da: 48 89 45 f0 mov %rax,-0x10(%rbp)
4008de: 48 c7 45 f0 ff 00 00 movq $0xff,-0x10(%rbp)
4008e5: 00
4008e6: e8 25 fe ff ff callq 400710 <__cxa_end_catch@plt>
4008eb: eb 94 jmp 400881
4008ed: 48 89 c7 mov %rax,%rdi
4008f0: e8 2b fe ff ff callq 400720 <__cxa_begin_catch@plt>
4008f5: e8 16 fe ff ff callq 400710 <__cxa_end_catch@plt>
4008fa: eb 85 jmp 400881
4008fc: c9 leaveq
4008fd: c3 retq
And when I rewrite it in C++:
#define TINFO (std::type_info *) 0x600da0
int main()
{
double * obj = __cxxabiv1::__cxa_allocate_exception(8); // this is malloc()
*obj = 1.0;
int handler_switch_value = __cxxabiv1::__cxa_throw(obj, TINFO, 0);
switch(handler_switch_value) {
case 1: // 'int' handler
__cxxabiv1::__cxa_begin_catch(); // we might decide to re-throw
int x = 0xdead;
__cxxabiv1::__cxa_end_catch(); // clean up after begin_catch
break;
case 2: // 'float' handler
__cxxabiv1::__cxa_begin_catch();
float f = 0xf00d;
__cxxabiv1::__cxa_end_catch();
break;
case 3: // 'char *' handler
__cxxabiv1::__cxa_begin_catch();
char * c = (char *) 0xff;
__cxxabiv1::__cxa_end_catch();
break;
case 4: // "null catch type is a catch-all handler" @ eh_personality.cc:585
__cxxabiv1::__cxa_begin_catch();
__cxxabiv1::__cxa_end_catch();
break;
}
return 0;
}
Basically the EH machanism can be broken into two phases: (1) unwind the stack and (2) install the new context (e.g. find the matching handler). Note that the libsupc++ in charge of unwinding is language-agnostic (this is gcc's internal mechanism for stack unwinding for languages like Java, Objc, C++, etc.) so type matching (== catch handler searching) is done via more C++-ish personality() function. I'm not going to dig into the unwind process (Phase 1) here (read references if you're interested), I'll focus on Phase 2 instead.
So how this works:
1) first we construct the thrown obj (and compiler stores its std::type_info at TINFO) and the table of implemented exception catchers (personality() will later get it from frame context).
2) __cxa_throw() is called and passes execution to libsupc++'s _Unwind_RaiseException()
3) _Unwind_RaiseException() does phase 1 of EH: its goal is to find the stack frame where catch handlers are - personality()'s (__cxxabiv1::__gxx_personality_v0) routine task here is to find at least one handler. If there is none then we are outside of try(){ } block and std::terminate() is called (note that there is always a "just jmp outside" "handler" even if there are no catch(){}-es). If we found a handler then we call _Unwind_RaiseException_Phase2()
4) _Unwind_RaiseException_Phase2 does the second part of EH: it destroys the stack frames until we get to the context with handlers and calls personality routine again. Now its task is different: first it gets the LSDA table - Lysergic acid diethylamide Language Specific Data Area (which is basicly a list of std::type_info's and ids for switch cases for each catch(){}) from current context (the implementation via LSDA table is called Zero-Cost EH) and in the order in which catch statements appear in code searches for a handler whos type can catch trown object's type (literally: __do_catch() { return catch_type == thrown_type; } if (__do_catch()) { yay(); return true; })
5) When we are back in main() we get into switch that jmps us to the handler (if a handler was found) or outside the try/catch block if no handler could match.
In the above example the thrown_type is double (float handler doesn't match it) and the exception is caught by ellipsis handler.
Random notes:
1) RTTI owns here
2) if we catch(...) before other handlers then personality() never reaches them
3) "If the type of the thrown object is const or volatile, the catch argument must also be a const or volatile for a match to occur. However, a const, volatile, or reference type catch argument can match a nonconstant, nonvolatile, or nonreference object type. A nonreference catch argument type matches a reference to an object of the same type."
4) if we match a user defined object inside personality() then __do_catch() walks up the vtable to find if we can match some of the base classes's type_info: basically this means that we can match Derived object with a Base type catch.
References:
http://libcxxabi.llvm.org/spec.html
http://mentorembedded.github.io/cxx-abi/abi-eh.html
http://gcc.gnu.org/wiki/Dwarf2EHNewbiesHowto
http://mortoray.com/2013/09/12/the-true-cost-of-zero-cost-exceptions/
http://www.hexblog.com/?p=704 (slides)
http://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/topic/com.ibm.xlcpp8l.doc/language/ref/cplr151.htm#CPLR151