AliMei Introduction
The author encountered C++ exceptions while investigating a bug, and takes this opportunity to clarify the C++ exception mechanism for everyone’s reference.
Test Program
#include <stdio.h>
struct A {
A() { printf("A\n"); }
~A() { printf("~A\n"); }};
struct E {
E() { printf("E\n"); }
~E() { printf("~E\n"); }};
void f(){
throw E();}
void g(){
A a;
f();}
int main(){
try {
g();
} catch (int n) {
printf("catch int %d\n", n);
} catch (const E& e) {
printf("catch E %p\n", &e);
}
return 0;
}
Throwing Exceptions
void f(){
// throw E();
E* e = __cxa_allocate_exception(sizeof(struct E)); // Allocate exception object from heap
e->E(); // Construct exception object
__cxa_throw( // Throw exception
e, // Exception object
&typeid(struct E), // Type of the exception object, a static object generated at compile time
&E::~E); // Destructor of the exception object
}
These __cxa prefixed functions are provided by the C++ runtime library.
Propagation of Exceptions
void g() {
// A a;
A a; // Allocate a object a on the stack
a.A(); // Construct a object
// f();
f(); // Call f()
a.~A(); // If f() returns normally, it reaches here
goto end_of_catch; // If f() throws an exception, it jumps here.
// Although g() has no catch, a still needs to destruct, so it has a landing pad.
// At this point, rax points to the exception object header, rdx indicates the action.
a.~A(); // Destruct a object
_Unwind_Resume(e); // No matching catch, continue to unwind the stack frame
end_of_catch:
return;
}
Catching Exceptions
int main(){
// try {
// g();
// }
g(); // Call g() // If try { ... } has other code after g(), it will be placed here
goto end_of_catch; // If g() returns normally, it reaches here // Here is the landing pad for throw.
// $rax points to the exception object.
// $rdx indicates the action: // 0 means no catch, continue to unwind the stack frame; // 1 means match the first catch; // 2 means match the second catch.
void *p = rax;
int action = rdx; // If try { ... } has objects to destruct, destruct them here.
// Now we start matching catch statements.
// catch (int n) { // printf("catch int %d\n", n);
// }
if (action == 1) {
n = *(int *) e;
printf("catch int %d\n", n);
goto end_of_catch;
}
// catch (const E& e) { // printf("catch E\n");
// }
if (action == 2) {
E *e = __cxa_begin_catch(p);
printf("catch E %p\n", e);
__cxa_end_catch(); // Internally destruct e object
goto end_of_catch;
}
_Unwind_Resume(p); // If no matching catch, continue to unwind the stack frame.
end_of_catch:
return 0;
}
Other Details
References:
1、Itanium C++ ABI: Exception Handling:https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html
2、Exception Handling ABI for the Arm Architecture:https://github.com/ARM-software/abi-aa/blob/844a79fd4c77252a11342709e3b27b2c9f590cf1/ehabi32/ehabi32.rst
3、libunwind LLVM Unwinder:https://github.com/llvm/llvm-project/blob/main/libunwind/docs/index.rst
4、Linux Stack Unwinding (x86_64):https://zhuanlan.zhihu.com/p/302726082
5、.eh_frame:https://www.airs.com/blog/archives/460
Aliyun Developer Community, the choice of millions of developers
Aliyun Developer Community offers millions of quality technical contents, thousands of free system courses, rich experiential scenarios, and active community activities. Industry experts share and communicate. Welcome to click 【Read Original】 to join us.