C++异常初探(当然了,自己学的还不是很明白
原文地址:C++异常处理(1) C++异常处理(2) 
大多是对原文的一些总结和引用,建议阅读原文。后面的一些测试🌰是自己思考的一些东西。
异常处理过程 
抛出异常 
若没有在当前函数内被catch,就沿着调用链继续往上抛,直到走完整个调用链 
如果走完调用链都没有找到相应的 catch,那么 std::terminate() 就会被调用(用于直接终止程序);如果找到相应的catch,就进入该catch执行相应的代码(称为Landing pad )。 
 
stack unwind(栈展开) :从抛异常开始到执行landing pad的整个过程。
Itanium C++ ABI Itanium ABI 定义了一系列函数及相应的数据结构来建立整个异常处理的流程及框架。
_Unwind_RaiseException()_Unwind_RaiseException() 函数用于进行 stack unwind,它在用户执行 throw 时被调用,主要功能是从当前函数开始,对调用链上每个函数都调用一个叫作 personality routine 的函数(__gxx_personality_v0),该函数由上层的语言定义及提供实现。_Unwind_RaiseException() 会在内部把当前函数栈的调用现场重建,然后传给 personality routine。personality routine主要负责做两件事情:
检查当前函数是否含有相应 catch 可以处理上面抛出的异常。 
清掉调用栈上的局部变量。personality routine 来完成,它相当于一个 callback。 
 
1 2 3 4 5 6 7 _Unwind_Reason_Code (*__personality_routine)         (int  version,          _Unwind_Action actions,          uint64 exceptionClass,          struct  _Unwind_Exception *exceptionObject,          struct  _Unwind_Context *context); 
每个函数在 unwind 的过程中都会被 personality routine 遍历两次。
如下伪代码展示了 _Unwind_RaiseException() 内部的大概实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 _Unwind_RaiseException(exception) {     bool  found = false ;     while  (1 )      {                    context = build_context();          if  (!context) break ;          found = personality_routine(exception, context, SEARCH);          if  (found or reach the end) break ;      }     while  (found)     {         context = build_context();         if  (!context) break ;         personality_routine(exception, context, UNWIND);         if  (reach_catch_function) break ;     } } 
ABI 中的函数使用到了两个自定义的数据结构,用来传递一些内部的信息。
1 2 3 4 5 6 7 8 struct  _Unwind_Context ;struct  _Unwind_Exception  {  uint64     exception_class;   _Unwind_Exception_Cleanup_Fn exception_cleanup;   uint64     private_1;   uint64     private_2; }; 
C++ ABI 基于前面介绍的 Itanium ABI,编译器层面也定义了一系列的 ABI 来与之交互。当我们在代码中写下 “throw xxx” 时,编译器会分配一个数据结构来表示该异常,该异常有一个头部,定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct  __cxa_exception  {  std ::type_info *    exceptionType;   void  (*exceptionDestructor) (void  *);    unexpected_handler    unexpectedHandler;   terminate_handler    terminateHandler;   __cxa_exception *    nextException;   int      handlerCount;   int      handlerSwitchValue;   const  char  *     actionRecord;   const  char  *     languageSpecificData;   void  *     catchTemp;   void  *     adjustedPtr;   _Unwind_Exception    unwindHeader; }; 
当用户throw一个异常,编译器会调用相应的函数分配出如下一个结构:
__cxa_exception 
 
 
exception obj  
其中 _cxa_exception 就是头部,exception_obj 则是 “throw xxx” 中的 xxx,这两部分在内存中是连续的。异常对象由函数 __cxa_allocate_exception() 进行创建,最后由 __cxa_free_exception() 进行销毁。
当我们在程序里执行了抛出异常后,编译器为我们做了如下的事情:
调用 __cxa_allocate_exception 函数,分配一个异常对象。 
调用 __cxa_throw 函数,这个函数会将异常对象做一些初始化。 
__cxa_throw() 调用 Itanium ABI 里的 _Unwind_RaiseException() 从而开始 unwind。 
_Unwind_RaiseException() 对调用链上的函数进行 unwind 时,调用 personality routine。 
该异常如能被处理(有相应的 catch),则 personality routine 会依次对调用链上的函数进行清理。 
_Unwind_RaiseException() 将控制权转到相应的catch代码。 
unwind 完成,用户代码继续执行。 
 
不同编译器的实现 g++ x64 测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include  <stdio.h>  void  test_func3 ()     throw  3 ;     puts ( "test func3"  ); } void  test_func2 ()     puts ( "test func2"  );     try      {         test_func3 ();     }     catch  (int )     {         puts ( "catch 2"  );     } } void  test_func1 ()     puts ( "test func1"  );     try      {         test_func2 ();     }     catch  (...)     {         puts ( "catch 1"  );     } } int  main ()     test_func1 ();     return  0 ; } 
fun3的结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 .text:0000000000401550 ; =============== S U B R O U T I N E ======================================= .text:0000000000401550 .text:0000000000401550 ; Attributes: noreturn bp-based frame .text:0000000000401550 .text:0000000000401550 ; void __cdecl test_func3() .text:0000000000401550                 public _Z10test_func3v .text:0000000000401550 _Z10test_func3v proc near               ; CODE XREF: test_func2(void)+1A↓p .text:0000000000401550                                         ; DATA XREF: .pdata:000000000040506C↓o .text:0000000000401550                 push    rbp .text:0000000000401551                 mov     rbp, rsp .text:0000000000401554                 sub     rsp, 20h .text:0000000000401558                 mov     ecx, 4          ; thrown_size .text:000000000040155D                 call    __cxa_allocate_exception .text:0000000000401562                 mov     dword ptr [rax], 3 .text:0000000000401568                 mov     r8d, 0          ; void (__fastcall *)(void *) .text:000000000040156E                 mov     rdx, cs:_refptr__ZTIi ; lptinfo .text:0000000000401575                 mov     rcx, rax        ; void * .text:0000000000401578                 call    __cxa_throw .text:0000000000401578 ; --------------------------------------------------------------------------- .text:000000000040157D                 align 2 .text:000000000040157D _Z10test_func3v endp 
可以看到fun3中的throw对应了上述两个过程,即调用__cxa_allocate_exception分配一个异常对象,以及调用 __cxa_throw 函数对异常对象初始化。
fun2的结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 .text:000000000040157E ; void __cdecl test_func2() .text:000000000040157E                 public _Z10test_func2v .text:000000000040157E _Z10test_func2v proc near               ; CODE XREF: test_func1(void)+1A↓p .text:000000000040157E                                         ; DATA XREF: .pdata:000000000040506C↓o ... .text:000000000040157E .text:000000000040157E var_14          = dword ptr -14h .text:000000000040157E .text:000000000040157E ; __unwind { // __gxx_personality_seh0 .text:000000000040157E                 push    rbp .text:000000000040157F                 push    rbx .text:0000000000401580                 sub     rsp, 38h .text:0000000000401584                 lea     rbp, [rsp+80h] .text:000000000040158C                 lea     rcx, Buffer     ; "test func2" .text:0000000000401593                 call    puts .text:0000000000401598 ;   try { .text:0000000000401598                 call    _Z10test_func3v ; test_func3(void) .text:0000000000401598 ;   } // starts at 401598 .text:000000000040159D ; --------------------------------------------------------------------------- .text:000000000040159D                 jmp     short loc_4015E1 .text:000000000040159F ; --------------------------------------------------------------------------- .text:000000000040159F ;   catch(int) // owned by 401598 .text:000000000040159F                 cmp     rdx, 1 .text:00000000004015A3                 jz      short loc_4015AD .text:00000000004015A5                 mov     rcx, rax .text:00000000004015A8                 call    _Unwind_Resume .text:00000000004015AD ; --------------------------------------------------------------------------- .text:00000000004015AD .text:00000000004015AD loc_4015AD:                             ; CODE XREF: test_func2(void)+25↑j .text:00000000004015AD                 mov     rcx, rax        ; void * .text:00000000004015B0                 call    __cxa_begin_catch .text:00000000004015B5                 mov     eax, [rax] .text:00000000004015B7                 mov     [rbp-40h+var_14], eax .text:00000000004015BA                 lea     rcx, aCatch2    ; "catch 2" .text:00000000004015C1 ;   try { .text:00000000004015C1                 call    puts .text:00000000004015C1 ;   } // starts at 4015C1 .text:00000000004015C6                 call    __cxa_end_catch .text:00000000004015CB                 jmp     short loc_4015E1 .text:00000000004015CD ; --------------------------------------------------------------------------- .text:00000000004015CD ;   cleanup() // owned by 4015C1 .text:00000000004015CD                 mov     rbx, rax .text:00000000004015D0                 call    __cxa_end_catch .text:00000000004015D5                 mov     rax, rbx .text:00000000004015D8                 mov     rcx, rax .text:00000000004015DB                 call    _Unwind_Resume .text:00000000004015DB ; --------------------------------------------------------------------------- .text:00000000004015E0                 db 90h .text:00000000004015E1 ; --------------------------------------------------------------------------- .text:00000000004015E1 .text:00000000004015E1 loc_4015E1:                             ; CODE XREF: test_func2(void)+1F↑j .text:00000000004015E1                                         ; test_func2(void)+4D↑j .text:00000000004015E1                 add     rsp, 38h .text:00000000004015E5                 pop     rbx .text:00000000004015E6                 pop     rbp .text:00000000004015E7                 retn .text:00000000004015E7 ; } // starts at 40157E .text:00000000004015E7 _Z10test_func2v endp 
try块包裹fun3,不抛出异常的话就跳转到loc_4015E1来正常结束函数。_Unwind_Resume恢复现场,是的话调用__cxa_begin_catch开始处理异常。__cxa_end_catch来结束异常处理,然后正常结束fun2;__cxa_end_catch来结束异常处理,调用_Unwind_Resume恢复现场,然后正常结束fun2。__cxa_end_catch处理的应该是上层try块里的fun3中抛出的异常,不是puts函数抛出的异常。如果puts抛出异常,那就相当于是只执行到了抛异常的部分。而cleanup()中的_Unwind_Resume恢复的现场是由puts抛出的异常。
g++ x86 与x64基本一样。
MSVC x64 康不懂,爬了
MSVC x86 跟这个不一样,学了再来补。
先贴个代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 .text:00401100 ?test_func3@@YAXXZ proc near            ; CODE XREF: test_func2(void)+3E↑p .text:00401100 .text:00401100 pExceptionObject= dword ptr -4 .text:00401100 .text:00401100                 push    ebp .text:00401101                 mov     ebp, esp .text:00401103                 push    ecx .text:00401104                 mov     [ebp+pExceptionObject], 3 .text:0040110B                 push    offset __TI1H   ; pThrowInfo .text:00401110                 lea     eax, [ebp+pExceptionObject] .text:00401113                 push    eax             ; pExceptionObject .text:00401114                 call    __CxxThrowException@8 ; _CxxThrowException(x,x) .text:00401114 ?test_func3@@YAXXZ endp .text:00401114 .text:00401119 ; --------------------------------------------------------------------------- .text:00401119                 push    offset aTestFunc3 ; "test func3" .text:0040111E                 call    ds:__imp__puts .text:00401124                 add     esp, 4 .text:00401127                 mov     esp, ebp .text:00401129                 pop     ebp .text:0040112A                 retn .text:0040112A ; --------------------------------------------------------------------------- .text:0040112B                 align 10h 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 .text:00401080 ; void __cdecl test_func2() .text:00401080 ?test_func2@@YAXXZ proc near            ; CODE XREF: test_func1(void)+3E↑p .text:00401080 .text:00401080 var_10          = dword ptr -10h .text:00401080 var_C           = dword ptr -0Ch .text:00401080 var_4           = dword ptr -4 .text:00401080 arg_4           = dword ptr  0Ch .text:00401080 .text:00401080 ; FUNCTION CHUNK AT .text:00401E30 SIZE 0000001D BYTES .text:00401080 .text:00401080 ; __unwind { // __ehhandler$?test_func2@@YAXXZ .text:00401080                 push    ebp .text:00401081                 mov     ebp, esp .text:00401083                 push    0FFFFFFFFh .text:00401085                 push    offset __ehhandler$?test_func2@@YAXXZ .text:0040108A                 mov     eax, large fs:0 .text:00401090                 push    eax .text:00401091                 push    ecx .text:00401092                 push    ebx .text:00401093                 push    esi .text:00401094                 push    edi .text:00401095                 mov     eax, ___security_cookie .text:0040109A                 xor     eax, ebp .text:0040109C                 push    eax .text:0040109D                 lea     eax, [ebp+var_C] .text:004010A0                 mov     large fs:0, eax .text:004010A6                 mov     [ebp+var_10], esp .text:004010A9                 push    offset aTestFunc2 ; "test func2" .text:004010AE                 call    ds:__imp__puts .text:004010B4                 add     esp, 4 .text:004010B7 ;   try { .text:004010B7                 mov     [ebp+var_4], 0 .text:004010BE                 call    ?test_func3@@YAXXZ ; test_func3(void) .text:004010C3 ; --------------------------------------------------------------------------- .text:004010C3                 jmp     short loc_4010D9 .text:004010C5 ; --------------------------------------------------------------------------- .text:004010C5 .text:004010C5 __catch$?test_func2@@YAXXZ$0:           ; DATA XREF: .rdata:stru_402638↓o .text:004010C5 ;   catch(int) // owned by 4010B7 .text:004010C5                 push    offset aCatch2  ; "catch 2" .text:004010CA                 call    ds:__imp__puts .text:004010D0                 add     esp, 4 .text:004010D3                 mov     eax, offset $LN7_0 .text:004010D8                 retn .text:004010D8 ;   } // starts at 4010B7 .text:004010D9 ; --------------------------------------------------------------------------- .text:004010D9 .text:004010D9 loc_4010D9:                             ; CODE XREF: test_func2(void)+43↑j .text:004010D9                 mov     [ebp+var_4], 0FFFFFFFFh .text:004010E0                 jmp     short loc_4010E9 .text:004010E2 ; --------------------------------------------------------------------------- .text:004010E2 .text:004010E2 $LN7_0:                                 ; CODE XREF: test_func2(void)+58↑j .text:004010E2                                         ; DATA XREF: test_func2(void)+53↑o .text:004010E2                 mov     [ebp+var_4], 0FFFFFFFFh .text:004010E9 .text:004010E9 loc_4010E9:                             ; CODE XREF: test_func2(void)+60↑j .text:004010E9                 mov     ecx, [ebp+var_C] .text:004010EC                 mov     large fs:0, ecx .text:004010F3                 pop     ecx .text:004010F4                 pop     edi .text:004010F5                 pop     esi .text:004010F6                 pop     ebx .text:004010F7                 mov     esp, ebp .text:004010F9                 pop     ebp .text:004010FA                 retn .text:004010FA ; --------------------------------------------------------------------------- .text:004010FB                 align 10h .text:004010FB ; } // starts at 401080 .text:004010FB ?test_func2@@YAXXZ endp 
.eh_frame区域刚开始疑惑了好久为什么我g++编译出来的程序没有.eh_frame段。后来发现,啊,只有ELF文件有这玩意啊,那煤逝了。
对一个elf文件通过如下命令:readelf -Wwf xxx,可以读取其中关于 .eh_frame 的数据
🕊️🕊️🕊️