Tools: Complete Guide to How catch-block selection works in exception handling

Tools: Complete Guide to How catch-block selection works in exception handling

Previously...

libunwind

A super-quick introduction to libunwind

The libunwind context

The libunwind cursor

Some info about the procedure in libunwind

What did we miss?

Summary N1

ELF, DWARF, and other xenos species

The .cfi directives

Reading binaries

Summary N2

The personality routine, lsda, and two types of unwinding

The personality routine algorithm

The lsda structure

Summary N3

The last mile and landing pad

Is this the end? If a pill knows what to treat, could an exception also understand when to stop its journey through the stack? In application programming, a description like this is often enough, but sometimes one wants more details. Well, let's dive in! Last time, we looked at how exceptions are thrown and caught, as well as the connection between these two processes. We've seen functions of the __cxa* and _Unwind* families, how they intertwine, and how the compiler embeds some of them into assembly code along with additional code blocks when compiling programs. Some questions still remain unanswered. We've moved on from SjLj exceptions, and we've paused the discussion of zero-cost exceptions at the most thrilling point: the personality routine. In this article, we'll pick up right where we left off to answer the question, "How does an exception know it has reached the right catch block?" Just like last time, we'll be using the libcxx library from LLVM. We'll also use the same compiler, Clang 21.1.0 for x86-64. While exploring the inner workings of the system, we came across two functions, _Unwind_RaiseException and _Unwind_ForcedUnwind, and thoroughly looked into how they work. The second function, which involved forced stack unwinding, was less exciting since it had no direct connection to our topic. However, the first one paved the way for understanding how exceptions are delivered through the program stack. It was executed in two stages: locating the appropriate catch block, and then delivering the exception along with calling automatic variable destructors. While the stack traversal part is now clear, the process of determining the correct exception handler remains a mystery. All we know about how it works is that at some point during the execution of both stages, code like this executes: In the code, the frameInfo variable of the unw_proc_info_t type is used to retrieve handler, which is a function that determines when to stop stack unwinding. We can see that calling the p function returns some result. The calling stack unwinding driver uses this result to determine what to do next with the unwinding. The frameInfo variable contains important information: a pointer to the handler itself and several other data members. They include pointers to the start and end of the function whose stack frame is being evaluated at the current stack unwinding stage, as well as other details. In the previous article, we encountered the __gxx_personality_v0 symbol, which was defined in the assembly code that Clang generated from our test project. This symbol may be related to the handler, but we haven't proven it yet. Let's revisit this project's code: We'll convert it to assembly code and work with that. What if we just search for the __gxx_personality_v0 symbol in the libcxx library code? Well, we'll find it. Cool, thanks... It's scary and confusing, but the most important thing is that we can't yet see a direct connection between this function and that handler. Well, let's try to determine it! We know one thing for certain: the handler is retrieved from a structure of the unw_proc_info_t type. It contains information about the function that created the current stack frame, as well as additional information needed to unwind the stack. To trace the connection between the personality routine and this structure, we first need to understand where the structure gets populated. That means diving into the libunwind library. This library enables reading of function stack frames. We'll take a look at how its context initialization works under the hood, the data structure it uses to reference stack frames during unwinding, and how those structures are populated. To move forward, let's go over the basics of working with libunwind. We can use the structure of the unw_cursor_t type to tell the library which frame we want to read. It's somewhat of a handler that enables us to traverse the stack. We get the frame information directly from the already familiar unw_proc_info_t structure. The cursor itself is retrieved from a context, which is a structure of the unw_context_t type. Once this structure is initialized, libunwind is ready to use. In our case, libunwind gets initialized in _Unwind_RaiseException, which we covered in the previous article. It serves as the entry point to the stack unwinding driver when an exception is thrown and is called almost immediately after it occurs. This happens within the abstraction layer defined by the the _cxa family of functions. The libunwind initialization in this function looks like this: There are two new types here: unw_context_t and unw_cursor_t. They have similar definitions: both contain data stored in arrays whose lengths are determined at compile time. It's impossible to say exactly what's stored there, since the data is located in a 64-bit unsigned integer array. This means that the types are opaque wrappers around other structures that are implementation details of the library. We can determine where the data is populated, though! As we can see from the code of the _Unwind_RaiseException function, the unw_context_t struct is populated in the __unw_getcontext function. Wow, we reached assembly code pretty quickly this time! It does fairly straightforward work—saving the register state into the unw_context_t structure, whose pointer we conveniently passed into the function beforehand. This function has platform-specific implementations that ensure the array within this struct is populated correctly. Okay, a bunch of movq statements means that libunwind is initialized by saving the function register state where the operation begins. In our case, this is _Unwind_RaiseException, the main stack unwinding driver. This makes sense, since above this function frame are the frames of our program's functions where the exception was thrown. Okay, where is the unw_cursor_t structure populated? The Unwind_RaiseException consists of two phases, which we covered in the previous article. Let's take a look at the beginning of the first phase: We can see pointers to the context and the cursor being passed down from the _Unwind_RaiseException function. We can also see the __unw_init_local function, which receives those pointers. From this point on, things are about to get really interesting. This function determines the register type for the target platform. In reality, however, the compiler fixes this at compile time. Then, the function constructs a new UnwindCursor object using placement new inside the array stored in the unw_cursor_t struct. By the way, you might recall that we came across the UnwindCursor type in the previous article. We saw how it handles jumping to the correct point in the program via the jumpto function. That's why we'll take a closer look at it! In any case, its constructor is easy to find. The most interesting aspect is how it initializes the _registers member variable using a pointer to unw_context_t. The constructor is located nearby, and the type is defined in an earlier step. Note that the cursor moves through different stack frames using the __unw_step function. This topic exceeds the scope of this article, so we'll leave it as homework for the most inquisitive minds. Here's the code that kicked off our investigation today: Now that we know the cursor's origin, we can see where the frameInfo variable comes from. In the code for both phases, we can see that this structure is populated by the __unw_get_proc_info function. It looks something like this: Inside __unw_get_proc_info, the pointer to unw_cursor_t is cast to the pointer to AbstractUnwindCursor, and then it calls the getInfo member function. We already saw how an instance of a specific UnwindCursor type is created inside the cursor. AbstractUnwindCursor is a virtual interface to objects of this class. Inside the getInfo member function, the unw_proc_info_t instance stored within the cursor class is copied into the variable we're looking for. But wait... I can't recall seeing anything written to that internal member variable. Moreover, I remember it being directly zeroed in the constructor. The address of our precious handler can't be null. Besides, the address of some handler can't possibly appear in the registers. Let's face it, up until now, everything we've seen has been focused on preserving register states. I don't know about you, but I don't remember the System V ABI—or any other, for that matter—describing a separate register for some handler. So, that information is stored somewhere else. Let's look into this some more and return to the __unw_init_local function. We deliberately omitted the call to the setInfoBasedOnIPRegister function there. Its name suggests that this might be exactly what we need. Perhaps the value of the data member in question is set there, among the many conditional compilation directives. We have two options here: we can either figure out how binary files are parsed on GNU/Linux to find information for stack tracing, or we can just stop. Since this doesn't directly relate to the topic of this article, I think it's best to avoid getting bogged down in such details. Still, let's break down the general points, because that's exactly what makes table-based exceptions, well, table-based. Why don't we make an interim summary? Before moving down the stack, it's necessary to record the current processor state—the values of all its registers. To do this, we'll use unw_context_t. It's populated using the __unw_getcontext function, which is written in the assembly language. Its task is to simply copy the current register values into this structure. The unw_cursor_t structure is used to navigate through stack frames. A cursor is an opaque pointer. In fact, it contains a hidden instance of the UnwindCursor class. The cursor is created using the context directly in the memory buffer of the unw_cursor_t structure. The unw_proc_info_t structure is used to retrieve information about the function that created the stack frame we're interested in. The __unw_get_proc_info function populates the structure. It takes our cursor and pulls details about the current function from its internal data (the _info data member). The key question is this: if the cursor constructor simply zeroes everything, where does it get function-specific data, such as the address of the exception handler (i.e., the personality routine)? To get a better grasp of the issue, let's expand a bit the assembly code we obtained from our test example at the very beginning. Oh no, we won't be adding anything to the original C++ source code. Instead, we'll take a look at the complete version of this assembly code. It contains the code fragment directly related to our C++ source code, as well as various additional directives. I won't include the code in the article itself, as it's way too long. Let's look at some fragments below; the full code is available here. The code has become much longer due to the additional metadata. We'll focus on the .cfi* family of directives that enable the debugger and profilers to traverse the stack, restore the frame state, and perform other tasks. They don't execute code, but instead create unwind tables, also known as exception frames. The tables aren't in the assembly file; the compiler creates them in the final binary. You can view them using, for example, the readelf -w command. Here's an example of running the 'readelf -w' main command: They allow the stack unwinder to restore stack frame states using only the instruction pointer value. It looks suspiciously similar to the debugger information in debug builds, because that's exactly what it is. The debugger does pretty much the same thing. The table format is defined in the DWARF standard, which is why the exception-handling approach discussed here is sometimes referred to as DWARF-based. These directives are all necessary for a program, such as a debugger, to retrieve information about the currently examined stack frame. This topic is beyond the scope of this article. There are three ways to learn more: read a brief note about the format of these tables and leave it at that, check out this excellent introductory article on the topic, or dig into the DWARF documentation (if you're a true rock and roller). However, we're interested in a few specific aspects. Take a look at the foo function prologue (the one that catches the exception in our example): Two directives deserve particular attention: .cfi_personality and .cfi_lsda. Since we're working with LLVM, let's consult their documentation. There's some interesting content, including a link to other documentation that specifically describes the .cfi directives we need. As the description states, the .cfi_personality directive specifies where the stack unwinder retrieves the personality routine. Note that in our code, this instruction contains the DW.ref.__gxx_personality_v0 symbol. The origin of this symbol has remained a mystery to us since the first article. Take a look at the code at the very bottom of the assembly file: The address of the __gxx_personality_v0 function is stored in memory. Next comes a section containing symbols that we've already encountered. The directive in .cfi_lsda specifies the location of the language-specific data area. What's this? Now look at the symbol located near the end of the foo function: Good news—we've finally found this thing called exception handling tables. More precisely, this is the language-specific data area (lsda), which contains the exception handling tables. There are several of these tables, and we'll take a look at them a little later. Okay, we still don't know exactly how the personality routine and lsda work, but at least we've found where their symbols are defined. In the case of lsda, we also found its exact location. The way these two components—the personality-routines and lsda—work is simple: the first looks to the second to determine what to do with the exception in each stack frame it encounters. We'll see how it works a little later. For now, let's make sure that the C++ runtime actually reads addresses of the personality routines and lsda. To do this, we'll go back to the breakdown of the setInfoBasedOnIPRegister function. The code contained many conditional compilation directives depending on the runtime's stack unwinding technique. Since we now know that the method in question is DWARF-based, we can locate the relevant parts of this function. There are two of them: one and two. Let's stop here— describing how to read DWARF structures would make this article endless. The resources listed above should give you a general idea of the topic. Long story short, it all comes down to the getInfoFromFdeCie function. It actually populates the _info data member using information obtained from the unwind tables, including the addresses of the personality routine and lsda. It seems like now's the time for a little summary. The compiler inserts special directives that begin with .cfi_ (Call Frame Information) when converting C++ code into assembly. The processor doesn't actually execute these directives—they instruct the linker to build unwinding tables from this data. We've already seen a few of them: After compilation, all .cfi directives are converted into binary structures, which we can view using the readelf -w command. They contain .eh_frame entries with general information on how to unwind the stack for each function. How exactly does the personality routine read the lsda, though? The challenge with the personality routine is that it performs many similar actions in similar scenarios that still need to be distinguished from one another. Combine this with various ways to unwind the stack and exceptions that might occur in other languages, and the result is a Frankenstein's monster. Still, all these parts serve the same goal, and that's where we'll start. The personality routine parses the lsda to determine how to handle the exception within the context of a specific stack frame. In other words, it must know how to parse the stack in order to retrieve from memory the data contained in the lsda. Although this routine involves low-level work, it's conceptually quite high up in the Itanium ABI, at a level that defines language-specific functionality. In the previous article, we looked at an interface from the _cxa* family responsible for exception allocation, throwing, and catching. This behavior applies only to the C++ language and isn't related to stack unwinding, which conceptually operates at a lower level. The personality routine is on the same level as the _cxa* family, and it acts as a callback invoked by other subsystems during stack unwinding. There are two unwinding contexts where the personality routine is called: an exception throw and something else. The first one is initiated in the _Unwind_RaiseException and _Unwind_SjLj_RaiseException functions, while _Unwind_ForcedUnwind is responsible for everything else (such as unwinding the stack when exiting a thread). An exception throw consists of two stages: finding the appropriate catch block and cleaning up the stack. We covered all of this in the previous article. The _Unwind_Action defines the specific context. It shows that during the second phase of exception handling, the routine can do one of two things: either clean up the current frame or transfer control to a catch block identified during the first phase. It also includes a separate case for reaching the end of the stack during unwinding without an exception. Okay, let's take a look at the code of this routine. We'll skip the details of parsing the lsda for now and try to grasp the general logic. For the sake of simplicity, we'll remove the conditional compilation that isn't relevant to our stack unwinding approach or platform. The simplest case occurs when, during the second phase, we reach the same frame that we marked in the first phase as having a valid catch block. At this point, the routine retrieves the information it gathered during the previous phase and returns the _URC_INSTALL_CONTEXT value. This tells unwind_phase2 that it's time to move to the catch block. The exact landing point comes from the cursor state passed when calling the routine and set in the set_registers function. In all other cases, that is, if we haven't reached the end of the exception throw's second phase, we need to parse the lsda to determine the next step: The following code is a bit unintuitive: _URC_CONTINUE_UNWIND can be set in either the first or second phase. The first one is clear: a handler hasn't been found at that point. In the second phase, this value is set if the stack in question contains neither catch blocks nor automatic variables whose destructors should be called when unwinding the stack. _URC_FATAL_PHASE1_ERROR is set when the parser finds something in lsda that shouldn't be there. This indicates an error. Otherwise, there's only one thing left to do in the first phase—celebrate finding the right handler! For native C++ exceptions, we store the relevant information for later (we saw it being used toward the end of the second phase earlier). For foreign exceptions, we simply report finding the handler. In both cases, the routine returns _URC_HANDLER_FOUND. At this point, only one option remains: we're in the second phase and need to perform some action in the current stack frame: What can it be? Given that we'd enter the valid catch block right at the beginning of the function, we have two options: to either call stack variable destructors, or handle exception specifications: The last line is all we need here. The _URC_INSTALL_CONTEXT value instructs the runtime to transfer control to the code fragment the routine found. This is a landing pad, which is a block of code that either handles an exception or an exception specification, or it calls destructors. The lsda is parsed in LLVM within the scan_eh_tab function. Analyzing how it works would be interesting, but instead, explaining what exactly this lsda contains—with varying degrees of clarity—seems more useful. As we previously learned, the lsda includes exception handling tables. The name kind of hints that there are multiple of those. The specification does confirm this. The first one is called the call sites table. It's a list of entries containing pointers to the start and end of an instruction sequence containing the instruction from which the exception was thrown. With the address of this instruction (the unwinder restores it from the DWARF tables we discussed above), we can understand which entry to use. The call site itself is a code snippet located either inside or outside the try block. It's kind of a critical section where specific actions happen during stack unwinding. The list of possible actions follows the call sites table, and the required action is identified by an index that is stored alongside pointers to the start and end of a specific call site. Right after the call site table comes the action table, which is a list of actions associated with a particular call site. Although it's called a "table," it's actually a linked list. In addition to a pointer to the next node, each element contains a type index, which is a multipurpose data member of an integer type. If it's zero, then destructors are called in the current stack frame. Control must be passed to the code that contains calls generated by the compiler. If it's greater than zero, then we're dealing with the catch block, and it's necessary to check whether the catch block type matches the exception type. In this case, the number acts as an index to a table of types supported by the catch blocks of this frame (that table follows the current one). If the index is negative, then we're dealing with an exception specification, and it's necessary to determine whether the function can throw an exception of that type. Following the action table is the type table, which contains references to the std::type_info objects for the types supported by the catch blocks of the current frame. This table is indexed by the positive indices contained in the nodes of the action table linked list. Somewhere nearby—depending on the implementation—is a list of references to the std::type_info types listed in the exception specification. If you're using older standards that allow such specifications, this is where the runtime handles unsupported types. In this case, the handler logic works in reverse: __cxa_call_unexpected is called when an exception lands in the generated code after violating the specification. That function ultimately behaves similarly to the deprecated std::unexpected. The structure of this mess looks something like this: That's the way the ball bounces. Let's summarize! The personality routine can parse the lsda. The lsda contains language-specific information about what should happen in a given stack frame when an exception is thrown. The lsda is generated by the compiler and appended to the function body. The personality routine is called from the _Unwind* family of functions, which are stack unwinding drivers. These functions form a layer independent of any programming language. In other words, a routine is a callback that logically belongs to a language-specific layer. We also learned about the "landing pad" concept. This is essentially a generalization over the catch block, which can handle exception specifications and destructor calls in addition to the exceptions. So, how does this landing pad actually work? We've slightly modified the code for clarity: The assembly code is below. The link leads to the same file, but it has additional compiler directives. First, let's look at this little example with the bar function: It's interesting because it shows how exception specifications are handled. If the function throws an exception of a type not listed in its specification, the personality routine will read the lsda and instruct the unwinder to transfer control to this point. Calling __cxa_call_unexpected is usually something that your program won't survive. Another interesting detail is in the foo function. It handles the selection of the specific catch block that processes the exception. The lsda provides the runtime with a pointer to a landing pad. However, that landing pad represents a handler in general rather than a specific one. With catch blocks, it points to the start of the entire catch block section that follows a given try block, rather than to a specific handler. Let's take, for example, an exception handler for the int type: In addition to the usual things like calling __cxa_begin_catch and __cxa_end_catch, we can see some code that looks strange at first glance: In this fragment, a conditional jump occurs if something isn't equal to 2. 2 of what? Why not 3? Think back to how we discussed tables within the lsda earlier. It contained a type table indexed by numbers found in entries of the action table. In this assembly code, 2 is an index that the runtime kindly stored in the __cxa_exception structure of the thrown exception when calling the personality routine. If the program had thrown a different exception type that still matched one of the available catch blocks, the runtime would've stored a different index to identify the correct type. That's it—it wasn't so bad after all! There isn't much to summarize here since this part doesn't introduce many new concepts. Dear friends, if you're reading this, I want you to know that you're real heroes, and I admire you. Getting through all of this wasn't easy. I hope it was worth the effort, and you learned something cool—or at least something new—along the way! One question remains unanswered: how do exceptions over the setjmp/ longjmp mechanism work? That's a great reason to revisit the topic in the future! I'd love to read your thoughts in the comments—hope to see you there! Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Code Block

Copy

_Unwind_Personality_Fn p = get_handler_function(&frameInfo); // .... _Unwind_Reason_Code personalityResult =(*p)( 1, action, exception_object->exception_class, exception_object, (struct _Unwind_Context *)(cursor) ); _Unwind_Personality_Fn p = get_handler_function(&frameInfo); // .... _Unwind_Reason_Code personalityResult =(*p)( 1, action, exception_object->exception_class, exception_object, (struct _Unwind_Context *)(cursor) ); _Unwind_Personality_Fn p = get_handler_function(&frameInfo); // .... _Unwind_Reason_Code personalityResult =(*p)( 1, action, exception_object->exception_class, exception_object, (struct _Unwind_Context *)(cursor) ); int bar() { throw -1; } int foo() { try { return bar(); } catch (...) { return -1; } } int main() { return foo(); } int bar() { throw -1; } int foo() { try { return bar(); } catch (...) { return -1; } } int main() { return foo(); } int bar() { throw -1; } int foo() { try { return bar(); } catch (...) { return -1; } } int main() { return foo(); } bar(): push rbp mov rbp, rsp mov edi, 4 call __cxa_allocate_exception@PLT mov rdi, rax mov dword ptr [rdi], -1 mov rsi, qword ptr [rip + typeinfo for int@GOTPCREL] xor eax, eax mov edx, eax call __cxa_throw@PLT foo(): push rbp mov rbp, rsp sub rsp, 16 call bar() mov dword ptr [rbp - 16], eax jmp .LBB1_1 .LBB1_1: mov eax, dword ptr [rbp - 16] add rsp, 16 pop rbp ret mov rcx, rax mov eax, edx mov qword ptr [rbp - 8], rcx mov dword ptr [rbp - 12], eax mov rdi, qword ptr [rbp - 8] call __cxa_begin_catch@PLT mov edi, 4 call __cxa_allocate_exception@PLT mov rdi, rax mov dword ptr [rdi], -1 mov rsi, qword ptr [rip + typeinfo for int@GOTPCREL] xor eax, eax mov edx, eax call __cxa_throw@PLT jmp .LBB1_8 mov rcx, rax mov eax, edx mov qword ptr [rbp - 8], rcx mov dword ptr [rbp - 12], eax call __cxa_end_catch@PLT jmp .LBB1_5 .LBB1_5: jmp .LBB1_6 .LBB1_6: mov rdi, qword ptr [rbp - 8] call _Unwind_Resume@PLT mov rdi, rax call __clang_call_terminate .LBB1_8: __clang_call_terminate: push rbp mov rbp, rsp call __cxa_begin_catch@PLT call std::terminate()@PLT main: push rbp mov rbp, rsp sub rsp, 16 mov dword ptr [rbp - 4], 0 call foo() add rsp, 16 pop rbp ret DW.ref.__gxx_personality_v0: .quad __gxx_personality_v0 bar(): push rbp mov rbp, rsp mov edi, 4 call __cxa_allocate_exception@PLT mov rdi, rax mov dword ptr [rdi], -1 mov rsi, qword ptr [rip + typeinfo for int@GOTPCREL] xor eax, eax mov edx, eax call __cxa_throw@PLT foo(): push rbp mov rbp, rsp sub rsp, 16 call bar() mov dword ptr [rbp - 16], eax jmp .LBB1_1 .LBB1_1: mov eax, dword ptr [rbp - 16] add rsp, 16 pop rbp ret mov rcx, rax mov eax, edx mov qword ptr [rbp - 8], rcx mov dword ptr [rbp - 12], eax mov rdi, qword ptr [rbp - 8] call __cxa_begin_catch@PLT mov edi, 4 call __cxa_allocate_exception@PLT mov rdi, rax mov dword ptr [rdi], -1 mov rsi, qword ptr [rip + typeinfo for int@GOTPCREL] xor eax, eax mov edx, eax call __cxa_throw@PLT jmp .LBB1_8 mov rcx, rax mov eax, edx mov qword ptr [rbp - 8], rcx mov dword ptr [rbp - 12], eax call __cxa_end_catch@PLT jmp .LBB1_5 .LBB1_5: jmp .LBB1_6 .LBB1_6: mov rdi, qword ptr [rbp - 8] call _Unwind_Resume@PLT mov rdi, rax call __clang_call_terminate .LBB1_8: __clang_call_terminate: push rbp mov rbp, rsp call __cxa_begin_catch@PLT call std::terminate()@PLT main: push rbp mov rbp, rsp sub rsp, 16 mov dword ptr [rbp - 4], 0 call foo() add rsp, 16 pop rbp ret DW.ref.__gxx_personality_v0: .quad __gxx_personality_v0 bar(): push rbp mov rbp, rsp mov edi, 4 call __cxa_allocate_exception@PLT mov rdi, rax mov dword ptr [rdi], -1 mov rsi, qword ptr [rip + typeinfo for int@GOTPCREL] xor eax, eax mov edx, eax call __cxa_throw@PLT foo(): push rbp mov rbp, rsp sub rsp, 16 call bar() mov dword ptr [rbp - 16], eax jmp .LBB1_1 .LBB1_1: mov eax, dword ptr [rbp - 16] add rsp, 16 pop rbp ret mov rcx, rax mov eax, edx mov qword ptr [rbp - 8], rcx mov dword ptr [rbp - 12], eax mov rdi, qword ptr [rbp - 8] call __cxa_begin_catch@PLT mov edi, 4 call __cxa_allocate_exception@PLT mov rdi, rax mov dword ptr [rdi], -1 mov rsi, qword ptr [rip + typeinfo for int@GOTPCREL] xor eax, eax mov edx, eax call __cxa_throw@PLT jmp .LBB1_8 mov rcx, rax mov eax, edx mov qword ptr [rbp - 8], rcx mov dword ptr [rbp - 12], eax call __cxa_end_catch@PLT jmp .LBB1_5 .LBB1_5: jmp .LBB1_6 .LBB1_6: mov rdi, qword ptr [rbp - 8] call _Unwind_Resume@PLT mov rdi, rax call __clang_call_terminate .LBB1_8: __clang_call_terminate: push rbp mov rbp, rsp call __cxa_begin_catch@PLT call std::terminate()@PLT main: push rbp mov rbp, rsp sub rsp, 16 mov dword ptr [rbp - 4], 0 call foo() add rsp, 16 pop rbp ret DW.ref.__gxx_personality_v0: .quad __gxx_personality_v0 unw_context_t uc; unw_cursor_t cursor; __unw_getcontext(&uc); unw_context_t uc; unw_cursor_t cursor; __unw_getcontext(&uc); unw_context_t uc; unw_cursor_t cursor; __unw_getcontext(&uc); static _Unwind_Reason_Code unwind_phase1(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *exception_object) { __unw_init_local(cursor, uc); .... } static _Unwind_Reason_Code unwind_phase1(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *exception_object) { __unw_init_local(cursor, uc); .... } static _Unwind_Reason_Code unwind_phase1(unw_context_t *uc, unw_cursor_t *cursor, _Unwind_Exception *exception_object) { __unw_init_local(cursor, uc); .... } // Use "placement new" to allocate UnwindCursor in the cursor buffer. new (reinterpret_cast<UnwindCursor<LocalAddressSpace, REGISTER_KIND> *>(cursor)) UnwindCursor<LocalAddressSpace, REGISTER_KIND>( context, LocalAddressSpace::sThisAddressSpace ); // Use "placement new" to allocate UnwindCursor in the cursor buffer. new (reinterpret_cast<UnwindCursor<LocalAddressSpace, REGISTER_KIND> *>(cursor)) UnwindCursor<LocalAddressSpace, REGISTER_KIND>( context, LocalAddressSpace::sThisAddressSpace ); // Use "placement new" to allocate UnwindCursor in the cursor buffer. new (reinterpret_cast<UnwindCursor<LocalAddressSpace, REGISTER_KIND> *>(cursor)) UnwindCursor<LocalAddressSpace, REGISTER_KIND>( context, LocalAddressSpace::sThisAddressSpace ); _Unwind_Personality_Fn p = get_handler_function(&frameInfo); // .... _Unwind_Reason_Code personalityResult =(*p)( 1, action, exception_object->exception_class, exception_object, (struct _Unwind_Context *)(cursor) ); _Unwind_Personality_Fn p = get_handler_function(&frameInfo); // .... _Unwind_Reason_Code personalityResult =(*p)( 1, action, exception_object->exception_class, exception_object, (struct _Unwind_Context *)(cursor) ); _Unwind_Personality_Fn p = get_handler_function(&frameInfo); // .... _Unwind_Reason_Code personalityResult =(*p)( 1, action, exception_object->exception_class, exception_object, (struct _Unwind_Context *)(cursor) ); unw_proc_info_t frameInfo; if (__unw_get_proc_info(cursor, &frameInfo) != UNW_ESUCCESS) { // .... } unw_proc_info_t frameInfo; if (__unw_get_proc_info(cursor, &frameInfo) != UNW_ESUCCESS) { // .... } unw_proc_info_t frameInfo; if (__unw_get_proc_info(cursor, &frameInfo) != UNW_ESUCCESS) { // .... } $ readelf -w main Contents of the .eh_frame section: 00000000 0000000000000014 00000000 CIE Version: 1 Augmentation: "zR" Code alignment factor: 1 Data alignment factor: -8 Return address column: 16 Augmentation data: 1b DW_CFA_def_cfa: r7 (rsp) ofs 8 DW_CFA_offset: r16 (rip) at cfa-8 DW_CFA_nop DW_CFA_nop 00000018 0000000000000014 0000001c FDE cie=00000000 pc=00000000000010a0..00000000000010c6 DW_CFA_advance_loc: 4 to 00000000000010a4 DW_CFA_undefined: r16 (rip) DW_CFA_nop DW_CFA_nop DW_CFA_nop DW_CFA_nop 00000030 0000000000000024 00000034 FDE cie=00000000 pc=0000000000001020..0000000000001090 DW_CFA_def_cfa_offset: 16 DW_CFA_advance_loc: 6 to 0000000000001026 DW_CFA_def_cfa_offset: 24 DW_CFA_advance_loc: 10 to 0000000000001030 DW_CFA_def_cfa_expression ; ... DW_CFA_nop DW_CFA_nop DW_CFA_nop DW_CFA_nop $ readelf -w main Contents of the .eh_frame section: 00000000 0000000000000014 00000000 CIE Version: 1 Augmentation: "zR" Code alignment factor: 1 Data alignment factor: -8 Return address column: 16 Augmentation data: 1b DW_CFA_def_cfa: r7 (rsp) ofs 8 DW_CFA_offset: r16 (rip) at cfa-8 DW_CFA_nop DW_CFA_nop 00000018 0000000000000014 0000001c FDE cie=00000000 pc=00000000000010a0..00000000000010c6 DW_CFA_advance_loc: 4 to 00000000000010a4 DW_CFA_undefined: r16 (rip) DW_CFA_nop DW_CFA_nop DW_CFA_nop DW_CFA_nop 00000030 0000000000000024 00000034 FDE cie=00000000 pc=0000000000001020..0000000000001090 DW_CFA_def_cfa_offset: 16 DW_CFA_advance_loc: 6 to 0000000000001026 DW_CFA_def_cfa_offset: 24 DW_CFA_advance_loc: 10 to 0000000000001030 DW_CFA_def_cfa_expression ; ... DW_CFA_nop DW_CFA_nop DW_CFA_nop DW_CFA_nop $ readelf -w main Contents of the .eh_frame section: 00000000 0000000000000014 00000000 CIE Version: 1 Augmentation: "zR" Code alignment factor: 1 Data alignment factor: -8 Return address column: 16 Augmentation data: 1b DW_CFA_def_cfa: r7 (rsp) ofs 8 DW_CFA_offset: r16 (rip) at cfa-8 DW_CFA_nop DW_CFA_nop 00000018 0000000000000014 0000001c FDE cie=00000000 pc=00000000000010a0..00000000000010c6 DW_CFA_advance_loc: 4 to 00000000000010a4 DW_CFA_undefined: r16 (rip) DW_CFA_nop DW_CFA_nop DW_CFA_nop DW_CFA_nop 00000030 0000000000000024 00000034 FDE cie=00000000 pc=0000000000001020..0000000000001090 DW_CFA_def_cfa_offset: 16 DW_CFA_advance_loc: 6 to 0000000000001026 DW_CFA_def_cfa_offset: 24 DW_CFA_advance_loc: 10 to 0000000000001030 DW_CFA_def_cfa_expression ; ... DW_CFA_nop DW_CFA_nop DW_CFA_nop DW_CFA_nop foo(): .Lfunc_begin1: .loc 1 7 0 .cfi_startproc .cfi_personality 155, DW.ref.__gxx_personality_v0 .cfi_lsda 27, .Lexception0 foo(): .Lfunc_begin1: .loc 1 7 0 .cfi_startproc .cfi_personality 155, DW.ref.__gxx_personality_v0 .cfi_lsda 27, .Lexception0 foo(): .Lfunc_begin1: .loc 1 7 0 .cfi_startproc .cfi_personality 155, DW.ref.__gxx_personality_v0 .cfi_lsda 27, .Lexception0 DW.ref.__gxx_personality_v0: .quad __gxx_personality_v0 .ident "clang version 21.1.0 .section ".note.GNU-stack","",@progbits .addrsig .addrsig_sym bar() .addrsig_sym __cxa_allocate_exception .addrsig_sym __cxa_throw .addrsig_sym foo() .addrsig_sym __gxx_personality_v0 .addrsig_sym __cxa_begin_catch .addrsig_sym __cxa_end_catch .addrsig_sym __clang_call_terminate .addrsig_sym _ZSt9terminatev .addrsig_sym _Unwind_Resume .addrsig_sym _ZTIi .section .debug_line,"",@progbits DW.ref.__gxx_personality_v0: .quad __gxx_personality_v0 .ident "clang version 21.1.0 .section ".note.GNU-stack","",@progbits .addrsig .addrsig_sym bar() .addrsig_sym __cxa_allocate_exception .addrsig_sym __cxa_throw .addrsig_sym foo() .addrsig_sym __gxx_personality_v0 .addrsig_sym __cxa_begin_catch .addrsig_sym __cxa_end_catch .addrsig_sym __clang_call_terminate .addrsig_sym _ZSt9terminatev .addrsig_sym _Unwind_Resume .addrsig_sym _ZTIi .section .debug_line,"",@progbits DW.ref.__gxx_personality_v0: .quad __gxx_personality_v0 .ident "clang version 21.1.0 .section ".note.GNU-stack","",@progbits .addrsig .addrsig_sym bar() .addrsig_sym __cxa_allocate_exception .addrsig_sym __cxa_throw .addrsig_sym foo() .addrsig_sym __gxx_personality_v0 .addrsig_sym __cxa_begin_catch .addrsig_sym __cxa_end_catch .addrsig_sym __clang_call_terminate .addrsig_sym _ZSt9terminatev .addrsig_sym _Unwind_Resume .addrsig_sym _ZTIi .section .debug_line,"",@progbits GCC_except_table1: .Lexception0: .byte 255 .byte 155 .uleb128 .Lttbase0-.Lttbaseref0 .Lttbaseref0: .byte 1 .uleb128 .Lcst_end0-.Lcst_begin0 ; ... GCC_except_table1: .Lexception0: .byte 255 .byte 155 .uleb128 .Lttbase0-.Lttbaseref0 .Lttbaseref0: .byte 1 .uleb128 .Lcst_end0-.Lcst_begin0 ; ... GCC_except_table1: .Lexception0: .byte 255 .byte 155 .uleb128 .Lttbase0-.Lttbaseref0 .Lttbaseref0: .byte 1 .uleb128 .Lcst_end0-.Lcst_begin0 ; ... if (actions == (_UA_CLEANUP_PHASE | _UA_HANDLER_FRAME) && native_exception) { // Reload the results from the phase 1 cache. __cxa_exception* exception_header = (__cxa_exception*)(unwind_exception + 1) - 1; results.ttypeIndex = exception_header->handlerSwitchValue; results.actionRecord = exception_header->actionRecord; results.languageSpecificData = exception_header->languageSpecificData; set_landing_pad(results, exception_header->catchTemp); results.adjustedPtr = exception_header->adjustedPtr; set_registers(unwind_exception, context, results); if (results.ttypeIndex < 0) { exception_header->catchTemp = 0; } return _URC_INSTALL_CONTEXT; } if (actions == (_UA_CLEANUP_PHASE | _UA_HANDLER_FRAME) && native_exception) { // Reload the results from the phase 1 cache. __cxa_exception* exception_header = (__cxa_exception*)(unwind_exception + 1) - 1; results.ttypeIndex = exception_header->handlerSwitchValue; results.actionRecord = exception_header->actionRecord; results.languageSpecificData = exception_header->languageSpecificData; set_landing_pad(results, exception_header->catchTemp); results.adjustedPtr = exception_header->adjustedPtr; set_registers(unwind_exception, context, results); if (results.ttypeIndex < 0) { exception_header->catchTemp = 0; } return _URC_INSTALL_CONTEXT; } if (actions == (_UA_CLEANUP_PHASE | _UA_HANDLER_FRAME) && native_exception) { // Reload the results from the phase 1 cache. __cxa_exception* exception_header = (__cxa_exception*)(unwind_exception + 1) - 1; results.ttypeIndex = exception_header->handlerSwitchValue; results.actionRecord = exception_header->actionRecord; results.languageSpecificData = exception_header->languageSpecificData; set_landing_pad(results, exception_header->catchTemp); results.adjustedPtr = exception_header->adjustedPtr; set_registers(unwind_exception, context, results); if (results.ttypeIndex < 0) { exception_header->catchTemp = 0; } return _URC_INSTALL_CONTEXT; } scan_eh_tab(results, actions, native_exception, unwind_exception, context); scan_eh_tab(results, actions, native_exception, unwind_exception, context); scan_eh_tab(results, actions, native_exception, unwind_exception, context); if (results.reason == _URC_CONTINUE_UNWIND || results.reason == _URC_FATAL_PHASE1_ERROR) return results.reason; if (actions & _UA_SEARCH_PHASE) { assert(results.reason == _URC_HANDLER_FOUND); if (native_exception) { __cxa_exception* exc = (__cxa_exception*)(unwind_exception + 1) - 1; exc->handlerSwitchValue = static_cast<int>(results.ttypeIndex); exc->actionRecord = results.actionRecord; exc->languageSpecificData = results.languageSpecificData; get_landing_pad(exc->catchTemp, results); exc->adjustedPtr = results.adjustedPtr; } return _URC_HANDLER_FOUND; } if (results.reason == _URC_CONTINUE_UNWIND || results.reason == _URC_FATAL_PHASE1_ERROR) return results.reason; if (actions & _UA_SEARCH_PHASE) { assert(results.reason == _URC_HANDLER_FOUND); if (native_exception) { __cxa_exception* exc = (__cxa_exception*)(unwind_exception + 1) - 1; exc->handlerSwitchValue = static_cast<int>(results.ttypeIndex); exc->actionRecord = results.actionRecord; exc->languageSpecificData = results.languageSpecificData; get_landing_pad(exc->catchTemp, results); exc->adjustedPtr = results.adjustedPtr; } return _URC_HANDLER_FOUND; } if (results.reason == _URC_CONTINUE_UNWIND || results.reason == _URC_FATAL_PHASE1_ERROR) return results.reason; if (actions & _UA_SEARCH_PHASE) { assert(results.reason == _URC_HANDLER_FOUND); if (native_exception) { __cxa_exception* exc = (__cxa_exception*)(unwind_exception + 1) - 1; exc->handlerSwitchValue = static_cast<int>(results.ttypeIndex); exc->actionRecord = results.actionRecord; exc->languageSpecificData = results.languageSpecificData; get_landing_pad(exc->catchTemp, results); exc->adjustedPtr = results.adjustedPtr; } return _URC_HANDLER_FOUND; } assert(actions & _UA_CLEANUP_PHASE); assert(results.reason == _URC_HANDLER_FOUND); assert(actions & _UA_CLEANUP_PHASE); assert(results.reason == _URC_HANDLER_FOUND); assert(actions & _UA_CLEANUP_PHASE); assert(results.reason == _URC_HANDLER_FOUND); set_registers(unwind_exception, context, results); if (results.ttypeIndex < 0) { __cxa_exception* exception_header = (__cxa_exception*)(unwind_exception + 1) - 1; exception_header->catchTemp = 0; } return _URC_INSTALL_CONTEXT; set_registers(unwind_exception, context, results); if (results.ttypeIndex < 0) { __cxa_exception* exception_header = (__cxa_exception*)(unwind_exception + 1) - 1; exception_header->catchTemp = 0; } return _URC_INSTALL_CONTEXT; set_registers(unwind_exception, context, results); if (results.ttypeIndex < 0) { __cxa_exception* exception_header = (__cxa_exception*)(unwind_exception + 1) - 1; exception_header->catchTemp = 0; } return _URC_INSTALL_CONTEXT; ======================== Lsda header LPStart Encoding LPStart (optional) TType Encoding TType Offset (optional) Call-Site Encoding Call-Site Table Length ======================== Call-Site Table ------------------------ start of a call site length of a call site landingPad (handler) offset (from landingPad base) actionEntry (1-based offset) ------------------------ ------------------------ start of a call site length of a call site landingPad (handler) offset (from landingPad base) actionEntry (1-based offset) ------------------------ ======================== Action Table ------------------------ ttypeIndex = 3 nextOffset = -3 ------------------------ ------------------------ ttypeIndex = 2 nextOffset = -3 ------------------------ ------------------------ ttypeIndex = 1 nextOffset = 0 ------------------------ ======================== Type Table (RTTI) ------------------------ index 1 ──> typeinfo(float) ------------------------ ------------------------ index 2 ──> typeinfo(int) ------------------------ ------------------------ index 3 ──> typeinfo(T) ------------------------ ======================== ======================== Lsda header LPStart Encoding LPStart (optional) TType Encoding TType Offset (optional) Call-Site Encoding Call-Site Table Length ======================== Call-Site Table ------------------------ start of a call site length of a call site landingPad (handler) offset (from landingPad base) actionEntry (1-based offset) ------------------------ ------------------------ start of a call site length of a call site landingPad (handler) offset (from landingPad base) actionEntry (1-based offset) ------------------------ ======================== Action Table ------------------------ ttypeIndex = 3 nextOffset = -3 ------------------------ ------------------------ ttypeIndex = 2 nextOffset = -3 ------------------------ ------------------------ ttypeIndex = 1 nextOffset = 0 ------------------------ ======================== Type Table (RTTI) ------------------------ index 1 ──> typeinfo(float) ------------------------ ------------------------ index 2 ──> typeinfo(int) ------------------------ ------------------------ index 3 ──> typeinfo(T) ------------------------ ======================== ======================== Lsda header LPStart Encoding LPStart (optional) TType Encoding TType Offset (optional) Call-Site Encoding Call-Site Table Length ======================== Call-Site Table ------------------------ start of a call site length of a call site landingPad (handler) offset (from landingPad base) actionEntry (1-based offset) ------------------------ ------------------------ start of a call site length of a call site landingPad (handler) offset (from landingPad base) actionEntry (1-based offset) ------------------------ ======================== Action Table ------------------------ ttypeIndex = 3 nextOffset = -3 ------------------------ ------------------------ ttypeIndex = 2 nextOffset = -3 ------------------------ ------------------------ ttypeIndex = 1 nextOffset = 0 ------------------------ ======================== Type Table (RTTI) ------------------------ index 1 ──> typeinfo(float) ------------------------ ------------------------ index 2 ──> typeinfo(int) ------------------------ ------------------------ index 3 ──> typeinfo(T) ------------------------ ======================== #include <stdlib.h> int bar() throw (int) { return (rand() % 2) ? throw -1 : -666; } int foo() { try { return bar(); } catch(float) { return 69; } catch(int) { return 42; } catch (...) { //skiped intentionally } return 666; } int main() { return foo(); } #include <stdlib.h> int bar() throw (int) { return (rand() % 2) ? throw -1 : -666; } int foo() { try { return bar(); } catch(float) { return 69; } catch(int) { return 42; } catch (...) { //skiped intentionally } return 666; } int main() { return foo(); } #include <stdlib.h> int bar() throw (int) { return (rand() % 2) ? throw -1 : -666; } int foo() { try { return bar(); } catch(float) { return 69; } catch(int) { return 42; } catch (...) { //skiped intentionally } return 666; } int main() { return foo(); } bar(): push rbp mov rbp, rsp sub rsp, 32 call rand@PLT mov ecx, 2 cdq idiv ecx mov byte ptr [rbp - 9], 0 cmp edx, 0 je .LBB0_2 mov edi, 4 call __cxa_allocate_exception@PLT mov rdi, rax mov qword ptr [rbp - 8], rdi mov byte ptr [rbp - 9], 1 mov dword ptr [rdi], -1 mov rsi, qword ptr [rip + typeinfo for int@GOTPCREL] xor eax, eax mov edx, eax call __cxa_throw@PLT jmp .LBB0_8 .LBB0_2: jmp .LBB0_3 .LBB0_3: mov eax, 4294966630 add rsp, 32 pop rbp ret mov rcx, rax mov eax, edx mov qword ptr [rbp - 24], rcx mov dword ptr [rbp - 28], eax cmp dword ptr [rbp - 28], 0 jge .LBB0_7 mov rdi, qword ptr [rbp - 24] call __cxa_call_unexpected@PLT .LBB0_7: mov rdi, qword ptr [rbp - 24] call _Unwind_Resume@PLT .LBB0_8: foo(): push rbp mov rbp, rsp sub rsp, 48 call bar() mov dword ptr [rbp - 32], eax jmp .LBB1_1 .LBB1_1: mov eax, dword ptr [rbp - 32] mov dword ptr [rbp - 4], eax jmp .LBB1_9 mov rcx, rax mov eax, edx mov qword ptr [rbp - 16], rcx mov dword ptr [rbp - 20], eax mov eax, dword ptr [rbp - 20] mov dword ptr [rbp - 36], eax mov ecx, 3 cmp eax, ecx jne .LBB1_5 mov rdi, qword ptr [rbp - 16] call __cxa_begin_catch@PLT movss xmm0, dword ptr [rax] movss dword ptr [rbp - 28], xmm0 mov dword ptr [rbp - 4], 69 call __cxa_end_catch@PLT jmp .LBB1_9 .LBB1_5: mov eax, dword ptr [rbp - 36] mov ecx, 2 cmp eax, ecx jne .LBB1_7 mov rdi, qword ptr [rbp - 16] call __cxa_begin_catch@PLT mov eax, dword ptr [rax] mov dword ptr [rbp - 24], eax mov dword ptr [rbp - 4], 42 call __cxa_end_catch@PLT jmp .LBB1_9 .LBB1_7: mov rdi, qword ptr [rbp - 16] call __cxa_begin_catch@PLT call __cxa_end_catch@PLT mov dword ptr [rbp - 4], 666 .LBB1_9: mov eax, dword ptr [rbp - 4] add rsp, 48 pop rbp ret main: push rbp mov rbp, rsp sub rsp, 16 mov dword ptr [rbp - 4], 0 call foo() add rsp, 16 pop rbp ret DW.ref.__gxx_personality_v0: .quad __gxx_personality_v0 bar(): push rbp mov rbp, rsp sub rsp, 32 call rand@PLT mov ecx, 2 cdq idiv ecx mov byte ptr [rbp - 9], 0 cmp edx, 0 je .LBB0_2 mov edi, 4 call __cxa_allocate_exception@PLT mov rdi, rax mov qword ptr [rbp - 8], rdi mov byte ptr [rbp - 9], 1 mov dword ptr [rdi], -1 mov rsi, qword ptr [rip + typeinfo for int@GOTPCREL] xor eax, eax mov edx, eax call __cxa_throw@PLT jmp .LBB0_8 .LBB0_2: jmp .LBB0_3 .LBB0_3: mov eax, 4294966630 add rsp, 32 pop rbp ret mov rcx, rax mov eax, edx mov qword ptr [rbp - 24], rcx mov dword ptr [rbp - 28], eax cmp dword ptr [rbp - 28], 0 jge .LBB0_7 mov rdi, qword ptr [rbp - 24] call __cxa_call_unexpected@PLT .LBB0_7: mov rdi, qword ptr [rbp - 24] call _Unwind_Resume@PLT .LBB0_8: foo(): push rbp mov rbp, rsp sub rsp, 48 call bar() mov dword ptr [rbp - 32], eax jmp .LBB1_1 .LBB1_1: mov eax, dword ptr [rbp - 32] mov dword ptr [rbp - 4], eax jmp .LBB1_9 mov rcx, rax mov eax, edx mov qword ptr [rbp - 16], rcx mov dword ptr [rbp - 20], eax mov eax, dword ptr [rbp - 20] mov dword ptr [rbp - 36], eax mov ecx, 3 cmp eax, ecx jne .LBB1_5 mov rdi, qword ptr [rbp - 16] call __cxa_begin_catch@PLT movss xmm0, dword ptr [rax] movss dword ptr [rbp - 28], xmm0 mov dword ptr [rbp - 4], 69 call __cxa_end_catch@PLT jmp .LBB1_9 .LBB1_5: mov eax, dword ptr [rbp - 36] mov ecx, 2 cmp eax, ecx jne .LBB1_7 mov rdi, qword ptr [rbp - 16] call __cxa_begin_catch@PLT mov eax, dword ptr [rax] mov dword ptr [rbp - 24], eax mov dword ptr [rbp - 4], 42 call __cxa_end_catch@PLT jmp .LBB1_9 .LBB1_7: mov rdi, qword ptr [rbp - 16] call __cxa_begin_catch@PLT call __cxa_end_catch@PLT mov dword ptr [rbp - 4], 666 .LBB1_9: mov eax, dword ptr [rbp - 4] add rsp, 48 pop rbp ret main: push rbp mov rbp, rsp sub rsp, 16 mov dword ptr [rbp - 4], 0 call foo() add rsp, 16 pop rbp ret DW.ref.__gxx_personality_v0: .quad __gxx_personality_v0 bar(): push rbp mov rbp, rsp sub rsp, 32 call rand@PLT mov ecx, 2 cdq idiv ecx mov byte ptr [rbp - 9], 0 cmp edx, 0 je .LBB0_2 mov edi, 4 call __cxa_allocate_exception@PLT mov rdi, rax mov qword ptr [rbp - 8], rdi mov byte ptr [rbp - 9], 1 mov dword ptr [rdi], -1 mov rsi, qword ptr [rip + typeinfo for int@GOTPCREL] xor eax, eax mov edx, eax call __cxa_throw@PLT jmp .LBB0_8 .LBB0_2: jmp .LBB0_3 .LBB0_3: mov eax, 4294966630 add rsp, 32 pop rbp ret mov rcx, rax mov eax, edx mov qword ptr [rbp - 24], rcx mov dword ptr [rbp - 28], eax cmp dword ptr [rbp - 28], 0 jge .LBB0_7 mov rdi, qword ptr [rbp - 24] call __cxa_call_unexpected@PLT .LBB0_7: mov rdi, qword ptr [rbp - 24] call _Unwind_Resume@PLT .LBB0_8: foo(): push rbp mov rbp, rsp sub rsp, 48 call bar() mov dword ptr [rbp - 32], eax jmp .LBB1_1 .LBB1_1: mov eax, dword ptr [rbp - 32] mov dword ptr [rbp - 4], eax jmp .LBB1_9 mov rcx, rax mov eax, edx mov qword ptr [rbp - 16], rcx mov dword ptr [rbp - 20], eax mov eax, dword ptr [rbp - 20] mov dword ptr [rbp - 36], eax mov ecx, 3 cmp eax, ecx jne .LBB1_5 mov rdi, qword ptr [rbp - 16] call __cxa_begin_catch@PLT movss xmm0, dword ptr [rax] movss dword ptr [rbp - 28], xmm0 mov dword ptr [rbp - 4], 69 call __cxa_end_catch@PLT jmp .LBB1_9 .LBB1_5: mov eax, dword ptr [rbp - 36] mov ecx, 2 cmp eax, ecx jne .LBB1_7 mov rdi, qword ptr [rbp - 16] call __cxa_begin_catch@PLT mov eax, dword ptr [rax] mov dword ptr [rbp - 24], eax mov dword ptr [rbp - 4], 42 call __cxa_end_catch@PLT jmp .LBB1_9 .LBB1_7: mov rdi, qword ptr [rbp - 16] call __cxa_begin_catch@PLT call __cxa_end_catch@PLT mov dword ptr [rbp - 4], 666 .LBB1_9: mov eax, dword ptr [rbp - 4] add rsp, 48 pop rbp ret main: push rbp mov rbp, rsp sub rsp, 16 mov dword ptr [rbp - 4], 0 call foo() add rsp, 16 pop rbp ret DW.ref.__gxx_personality_v0: .quad __gxx_personality_v0 .LBB0_3: mov eax, 4294966630 add rsp, 32 pop rbp ret mov rcx, rax mov eax, edx mov qword ptr [rbp - 24], rcx mov dword ptr [rbp - 28], eax cmp dword ptr [rbp - 28], 0 jge .LBB0_7 mov rdi, qword ptr [rbp - 24] call __cxa_call_unexpected@PLT .LBB0_3: mov eax, 4294966630 add rsp, 32 pop rbp ret mov rcx, rax mov eax, edx mov qword ptr [rbp - 24], rcx mov dword ptr [rbp - 28], eax cmp dword ptr [rbp - 28], 0 jge .LBB0_7 mov rdi, qword ptr [rbp - 24] call __cxa_call_unexpected@PLT .LBB0_3: mov eax, 4294966630 add rsp, 32 pop rbp ret mov rcx, rax mov eax, edx mov qword ptr [rbp - 24], rcx mov dword ptr [rbp - 28], eax cmp dword ptr [rbp - 28], 0 jge .LBB0_7 mov rdi, qword ptr [rbp - 24] call __cxa_call_unexpected@PLT .LBB1_5: mov eax, dword ptr [rbp - 36] mov ecx, 2 cmp eax, ecx jne .LBB1_7 mov rdi, qword ptr [rbp - 16] call __cxa_begin_catch@PLT mov eax, dword ptr [rax] mov dword ptr [rbp - 24], eax mov dword ptr [rbp - 4], 42 call __cxa_end_catch@PLT jmp .LBB1_9 ; ... .LBB1_9: mov eax, dword ptr [rbp - 4] add rsp, 48 pop rbp ret .LBB1_5: mov eax, dword ptr [rbp - 36] mov ecx, 2 cmp eax, ecx jne .LBB1_7 mov rdi, qword ptr [rbp - 16] call __cxa_begin_catch@PLT mov eax, dword ptr [rax] mov dword ptr [rbp - 24], eax mov dword ptr [rbp - 4], 42 call __cxa_end_catch@PLT jmp .LBB1_9 ; ... .LBB1_9: mov eax, dword ptr [rbp - 4] add rsp, 48 pop rbp ret .LBB1_5: mov eax, dword ptr [rbp - 36] mov ecx, 2 cmp eax, ecx jne .LBB1_7 mov rdi, qword ptr [rbp - 16] call __cxa_begin_catch@PLT mov eax, dword ptr [rax] mov dword ptr [rbp - 24], eax mov dword ptr [rbp - 4], 42 call __cxa_end_catch@PLT jmp .LBB1_9 ; ... .LBB1_9: mov eax, dword ptr [rbp - 4] add rsp, 48 pop rbp ret mov eax, dword ptr [rbp - 36] mov ecx, 2 cmp eax, ecx jne .LBB1_7 mov eax, dword ptr [rbp - 36] mov ecx, 2 cmp eax, ecx jne .LBB1_7 mov eax, dword ptr [rbp - 36] mov ecx, 2 cmp eax, ecx jne .LBB1_7 - .cfi_personality specifies the address of the exception handler (the personality routine) for the function; - .cfi_lsda points to the Language Specific Data Area (LSDA)—function-specific metadata that tells the runtime how to handle an exception when it processes a particular stack frame.