C - yszheda/wiki GitHub Wiki



Standard

MISRA


ABI

Exception


CFI (call frame information)

LSDA (language specific data area)

if we use the nothrow specifier (or the empty throw specifier) then the compiler can omit the gcc_except_table for this method.

  • __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.

C++ exceptions under the hood: a summary and some final thoughts

  1. The C++ compiler actually does rather little to handle an exception, most of the magic actually happens in libstdc++.
  2. There are a few things the compiler does, though. Namely:
  • It creates the CFI information to unwind the stack.
  • It creates something called .gcc_except_table with information about landing pads (try/catch blocks). Kind of like reflexion info.
  • When we write a throw statement, the compiler will translate it into a pair of calls into libstdc++ functions that allocate the exception and then start the stack unwinding process by calling libstdc.
  1. When an exception is thrown at runtime __cxa_throw will be called, which will delegate the stack unwinding to libstdc.
  2. As the unwinder goes through the stack it will call a special function provided by libstdc++ (called personality routine) that checks for each function in the stack which exceptions can be caught.
  3. If no matching catch is found for the exception, std::terminate is called.
  4. If a matching catch is found, the unwinder now starts again on the top of the stack.
  5. As the unwinder goes through the stack a second time it will ask the personality routine to perform a cleanup for this method.
  6. The personality routine will check the .gcc_except_table for the current method. If there are any cleanup actions to be run, it will “jump” into the current stack frame and run the cleanup code. This will run the destructor for each object allocated at the current scope.
  7. Once the unwinder reaches the frame in the stack that can handle the exception it will jump into the proper catch statement.
  8. Upon finishing the execution of the catch statement, a cleanup function will be called to release the memory held for the exception.

C++ exceptions under the hood appendix: the true cost of an exception

two major ways of exception handling

  • With a lookup table and some metadata, like the Itanium ABI specifies.

  • Sj/Lj (ARM): Registering exception handling information upon entering or exiting a method.

  • Zero-cost exception handling

When using the noexcept specification while declaring a method (or an empty throw specifier, pre C++11) in the setup used for these articles the compiler would omit the creation of the .gcc_except_table. This will make the code more compact and it will improve the cache usage, but it’s very unlikely that will have a noticeable impact on the performance of the application.