楼主: 
descent (“雄辩是银,沉默是金”)   
2020-01-10 21:17:18c++ 的 exception handling 特性真是鬼斧神工之作, 这么复杂的实作机制, 最初到底是
怎么想到的?
一样介绍的是 gcc 的实作方式, 使用 dwarf 格式, 不是 setjmp/longump 那套。
eh.cpp
135 int one()
136 {
145   throw 100;
146   #if 0
147   void *throw_obj = __cxa_allocate_exception(sizeof(int));
148   *(int *)throw_obj = 98;
149   __cxa_throw (throw_obj, &typeid(int), 0);
150   #endif
151 }
170
171 void main()
172 {
173   try
174   {
180     one();
182   }
184   catch (std::exception &e)
185   {
186     printf("get excetption: %s\n", e.what());
187   }
189   catch (int a)
191   {
192     printf("got excetption: %d\n", a);
193   }
198 }
eh.cpp 是一个很简单的范例, 表面上看起来很简单的 try/catch statement, 背后蕴藏
著超级复杂的实作机制。可以看到 list 2 反组译被插入一些额外的程式码,
_Unwind_Resume, __cxa_begin_catch, __cxa_end_catch, 由于这个例子很简单, 被插入
的额外程式码不多。
这次要介绍的是 __gxx_personality_v0(), 从 throw 100 到 catch(int) 之间, 会发生
很多事情, stack unwind 应该是大家比较熟知的部份, 而 __gxx_personality_v0() 可
能较为陌生。
在 stack unwind 期间, 有一段程式码就是在呼叫 __gxx_personality_v0(), 而
__gxx_personality_v0() 会被呼叫 2 次, 类似 list 1 那样。
list 1
1 code = (*fs.personality) (1, _UA_SEARCH_PHASE, exc->exception_class, exc,
&cur_context);
2 code = (*fs.personality) (1, _UA_CLEANUP_PHASE | match_handler,
exc->exception_class, exc, context);
eh_throw.cc L83 _Unwind_RaiseException() 会呼叫 2 次 __gxx_personality_v0(),
list 1 L1 第一次呼叫 __gxx_personality_v0() 传入 _UA_SEARCH_PHASE, 这次的目的
是找出 landing_pad, landing_pad 的值会是 list 2 L357, 0x10048a, 什么是
landing_pad, 简单来说就是在 throw 100 之后, 程式要往哪里执行呢? 那个位址就是
landing_pad, 计算出这个位址之后还没完, 要比对丢出的物件 int 和 catch statement
的物件有没有吻合, eh.cpp 有 2 个 catch statement, 显然是第二个 catch (int a)
才吻合。
list 1 L1 第二次呼叫 __gxx_personality_v0() 传入 _UA_CLEANUP_PHASE, 这次的目的
是跳到 landing_pad 去执行, 所以在执行完之后, 程式会跳到 0x10048a, 神奇吧!
这个跳跃使用的是 libunwind 的函式, 很复杂, 没能搞懂其实作, 大概是这样:
_Unwind_RaiseException() 会呼叫 uw_install_context (&this_context, &
cur_context, frames);
#define uw_install_context(CURRENT, TARGET, FRAMES)                     \
  do                                                                    \
    {                                                                   \
      long offset = uw_install_context_1 ((CURRENT), (TARGET));         \
      void *handler = uw_frob_return_addr ((CURRENT), (TARGET));        \
      _Unwind_DebugHook ((TARGET)-c>fa, handler);                       \
      _Unwind_Frames_Extra (FRAMES);                                    \
      __builtin_eh_return (offset, handler);                            \
    }                                                                   \
  while (0)
uw_install_context 是个 macro, 之后就会跳回 landing_pad 的位址。
跳到 0x10048a 之后, 再来就是根据 cmp 判断式, 来执行 catch (int a) 这段程式。可
以参考 list 2 L358 ~ 361, rdx 为 2 的时候, 就是执行 catch (int a) 这段程式, 而
rdx 1 是执行 catch (std::exception)。
这当然不是巧合, 而是从 throw 100 开始一连串精心安排的结果, 一样没能搞懂这部份
。总之这是 c++ 编译器的精心杰作。
那如果没有 catch(int) 呢? __gxx_personality_v0() 只会执行一次, 因为没找到对应
的catch handle, 这时候会执行 eh_throw.cc L88 的 std::terminate(), 结束整个程式。
eh_throw.cc
  1 // -*- C++ -*- Exception handling routines for throwing.
 70
 71 extern "C" void
 72 __cxxabiv1::__cxa_throw (void *obj, std::type_info *tinfo,
 73                       void (*dest) (void *))
 74 {
 75   __cxa_eh_globals *globals = __cxa_get_globals ();
 76   globals->uncaughtExceptions += 1;
 77
 78   // Definitely a primary.
 79   __cxa_refcounted_exception *header =
 80     __cxa_init_primary_exception(obj, tinfo, dest);
 81   header->referenceCount = 1;
 82
 83   _Unwind_RaiseException (&header->exc.unwindHeader);
 84
 85   // Some sort of unwinding error.  Note that terminate is a handler.
 86   __cxa_begin_catch (&header->exc.unwindHeader);
 87
 88   std::terminate ();
 89 }
 90
list 2. objdump -DC eh.elf
   301
   302 00000000001003d2 <one()>:
   303   1003d2: 55                    push   %rbp
   304   1003d3: 48 89 e5              mov    %rsp,%rbp
   305   1003d6: bf 04 00 00 00        mov    $0x4,%edi
   306   1003db: e8 80 77 00 00        callq  107b60
<__cxa_allocate_exception>
   307   1003e0: c7 00 64 00 00 00     movl   $0x64,(%rax)
   308   1003e6: ba 00 00 00 00        mov    $0x0,%edx
   309   1003eb: be 48 84 10 00        mov    $0x108448,%esi
   310   1003f0: 48 89 c7              mov    %rax,%rdi
   311   1003f3: e8 6c 24 00 00        callq  102864 <__cxa_throw>
   337
   338 0000000000100441 <main>:
   339   100441: 55                    push   %rbp
   340   100442: 48 89 e5              mov    %rsp,%rbp
   341   100445: 53                    push   %rbx
   342   100446: 48 83 ec 28           sub    $0x28,%rsp
   343   10044a: 48 89 7d d8           mov    %rdi,-0x28(%rbp)
   344   10044e: c7 45 e0 63 00 00 00  movl   $0x63,-0x20(%rbp)
   345   100455: 8b 5d e0              mov    -0x20(%rbp),%ebx
   346   100458: e8 13 01 00 00        callq  100570 <f()>
   347   10045d: 89 c1                 mov    %eax,%ecx
   348   10045f: 8b 15 ef 2b 03 00     mov    0x32bef(%rip),%edx        #
133054 <def>
   349   100465: 8b 05 e5 2b 03 00     mov    0x32be5(%rip),%eax        #
133050 <abc>
   350   10046b: 41 89 d9              mov    %ebx,%r9d
   351   10046e: 41 b8 00 00 00 00     mov    $0x0,%r8d
   352   100474: 89 c6                 mov    %eax,%esi
   353   100476: bf 68 80 10 00        mov    $0x108068,%edi
   354   10047b: b8 00 00 00 00        mov    $0x0,%eax
   355   100480: e8 20 05 00 00        callq  1009a5 <printf(char const*,
...)>
   356   100485: e8 48 ff ff ff        callq  1003d2 <one()>
   357   10048a: e9 9e 00 00 00        jmpq   10052d <main+0xec>
   358   10048f: 48 83 fa 01           cmp    $0x1,%rdx
   359   100493: 74 0e                 je     1004a3 <main+0x62>
   360   100495: 48 83 fa 02           cmp    $0x2,%rdx
   361   100499: 74 44                 je     1004df <main+0x9e>
   362   10049b: 48 89 c7              mov    %rax,%rdi
   363   10049e: e8 5d 5a 00 00        callq  105f00 <_Unwind_Resume>
   364   1004a3: 48 89 c7              mov    %rax,%rdi
   365   1004a6: e8 66 25 00 00        callq  102a11 <__cxa_begin_catch>
   366   1004ab: 48 89 45 e8           mov    %rax,-0x18(%rbp)
   367   1004af: 48 8b 45 e8           mov    -0x18(%rbp),%rax
   368   1004b3: 48 8b 00              mov    (%rax),%rax
   369   1004b6: 48 83 c0 10           add    $0x10,%rax
   370   1004ba: 48 8b 00              mov    (%rax),%rax
   371   1004bd: 48 8b 55 e8           mov    -0x18(%rbp),%rdx
   372   1004c1: 48 89 d7              mov    %rdx,%rdi
   373   1004c4: ff d0                 callq  *%rax
   374   1004c6: 48 89 c6              mov    %rax,%rsi
   375   1004c9: bf a4 80 10 00        mov    $0x1080a4,%edi
   376   1004ce: b8 00 00 00 00        mov    $0x0,%eax
   377   1004d3: e8 cd 04 00 00        callq  1009a5 <printf(char const*,
...)>
   378   1004d8: e8 19 26 00 00        callq  102af6 <__cxa_end_catch>
   379   1004dd: eb 4e                 jmp    10052d <main+0xec>
   380   1004df: 48 89 c7              mov    %rax,%rdi
   381   1004e2: e8 2a 25 00 00        callq  102a11 <__cxa_begin_catch>
   382   1004e7: 8b 00                 mov    (%rax),%eax
   383   1004e9: 89 45 e4              mov    %eax,-0x1c(%rbp)
   384   1004ec: 8b 45 e4              mov    -0x1c(%rbp),%eax
   385   1004ef: 89 c6                 mov    %eax,%esi
   386   1004f1: bf b8 80 10 00        mov    $0x1080b8,%edi
   387   1004f6: b8 00 00 00 00        mov    $0x0,%eax
   388   1004fb: e8 a5 04 00 00        callq  1009a5 <printf(char const*,
...)>
   389   100500: e8 f1 25 00 00        callq  102af6 <__cxa_end_catch>
   390   100505: eb 26                 jmp    10052d <main+0xec>
   391   100507: 48 89 c3              mov    %rax,%rbx
   392   10050a: e8 e7 25 00 00        callq  102af6 <__cxa_end_catch>
   393   10050f: 48 89 d8              mov    %rbx,%rax
   394   100512: 48 89 c7              mov    %rax,%rdi
   395   100515: e8 e6 59 00 00        callq  105f00 <_Unwind_Resume>
   396   10051a: 48 89 c3              mov    %rax,%rbx
   397   10051d: e8 d4 25 00 00        callq  102af6 <__cxa_end_catch>
   398   100522: 48 89 d8              mov    %rbx,%rax
   399   100525: 48 89 c7              mov    %rax,%rdi
   400   100528: e8 d3 59 00 00        callq  105f00 <_Unwind_Resume>
   401   10052d: 48 83 c4 28           add    $0x28,%rsp
   402   100531: 5b                    pop    %rbx
   403   100532: 5d                    pop    %rbp
   404   100533: c3                    retq
   405
 15543
 15548 Disassembly of section .gcc_except_table:
 15549
 15550 000000000010b704 <.gcc_except_table>:
 15551   10b704: ff                    (bad)
 15552   10b705: ff 01                 incl   (%rcx)
 15553   10b707: 00 ff                 add    %bh,%bh
 15554   10b709: ff 01                 incl   (%rcx)
 15555   10b70b: 0c 10                 or     $0x10,%al
 15556   10b70d: 05 00 00 15 05        add    $0x5150000,%eax
 15557   10b712: 28 00                 sub    %al,(%rax)
 15558   10b714: 3d 05 00 00 ff        cmp    $0xff000005,%eax
 15559   10b719: 03 29                 add    (%rcx),%ebp
 15560   10b71b: 01 19                 add    %ebx,(%rcx)
 15561   10b71d: 3f                    (bad)
 15562   10b71e: 0a 4e 03              or     0x3(%rsi),%cl
 15563   10b721: 5d                    pop    %rbp
 15564   10b722: 05 00 00 92 01        add    $0x1920000,%eax
 15565   10b727: 05 c6 01 00 ba        add    $0xba0001c6,%eax
 15566   10b72c: 01 05 d9 01 00 d4     add    %eax,-0x2bfffe27(%rip)
 15567   10b732: 01 18                 add    %ebx,(%rax)
 15568   10b734: 00 00                 add    %al,(%rax)
 15569   10b736: 02 00                 add    (%rax),%al
 15570   10b738: 01 7d 00              add    %edi,0x0(%rbp)
 15571   10b73b: 00 48 84              add    %cl,-0x7c(%rax)
 15572   10b73e: 10 00                 adc    %al,(%rax)
 15573   10b740: 78 8d                 js     10b6cf
 15574   10b742: 10 00                 adc    %al,(%rax)
 15575   10b744: ff 03                 incl   (%rbx)
 15576   10b746: 1d 01 12 de 01        sbb    $0x1de1201,%eax
 15577   10b74b: c9                    leaveq
 15578   10b74c: 06                    (bad)
 15579   10b74d: 00 00                 add    %al,(%rax)
 15580   10b74f: d5                    (bad)
 15581   10b750: 0a 05 b4 0c 01 91     or     -0x6efef34c(%rip),%al
 15582   10b756: 0b 9c 01 00 00 01 00  or     0x10000(%rcx,%rax,1),%ebx
 15583   10b75d: 00 00                 add    %al,(%rax)
 15584   10b75f: 00 00                 add    %al,(%rax)
 15585   10b761: 00 00                 add    %al,(%rax)
 15586   10b763: 00 ff                 add    %bh,%bh
 15587   10b765: ff 01                 incl   (%rcx)
 15588   10b767: 00 ff                 add    %bh,%bh
 15589   10b769: 03 1d 01 12 82 01     add    0x1821201(%rip),%ebx
 15590   10b76f: 05 87 01 01 cb        add    $0xcb010187,%eax
 15591   10b774: 01 86 01 dd 02 00     add    %eax,0x2dd01(%rsi)
 15592   10b77a: f7 02 05 00 00 01     testl  $0x1000005,(%rdx)
 15593  ...
 15594   10b788: ff 03                 incl   (%rbx)
 15595   10b78a: 0d 01 04 10 02        or     $0x2100401,%eax
 15596   10b78f: 17                    (bad)
 15597   10b790: 01 01                 add    %eax,(%rcx)
 15598   10b792: 00 00                 add    %al,(%rax)
 15599   10b794: 00 00                 add    %al,(%rax)
 15600  ...
 15601
再来谈谈怎么找到 landing_pad 的, 需要透过 list 2 L15550 的 .gcc_except_table
section 里头的资料, 它有个专业术语, 叫做 language specific data area (lsda),
不过这个资料并无法直接从这些 16 进制的数字解读, 需要在 runtime 时, 透过一个小型
直译器解译出这些资料, 复杂之余又添复杂。
类似 list 3 这些程式, 实际上远远比 list 3 列出的还复杂。
list 3 解读 .gcc_except_table section 程式码
p = read_encoded_value (0, info.call_site_encoding, p, &cs_start);
p = read_encoded_value (0, info.call_site_encoding, p, &cs_len);
p = read_encoded_value (0, info.call_site_encoding, p, &cs_lp);
p = read_uleb128 (p, &cs_action);
.gcc_except_table section 的资料格式请参考: c++ 异常处理 (2), exception
handling tables, 我一样没搞懂, 大致有 3 个表格。
 1. call site table: 每一笔 call site record 有 4 个资讯, 就是 list 3 那 4 个,
    但我不清楚其中关系, 怎么透过这些资讯定位出 landing_pad。
    参考“c++ 异常处理 (2)”这篇,
    LSDA 表头之后紧跟着的是 call site table,该表用于记录程序中哪些指令有可能
会抛异常,表中每条记录共有4个字段:
    1)cs_start: 可能会抛异常的指令的地址,该地址是距 Landing pad 起始地址的偏
移,编码方式由 LSDA 表头中第一个字段指明。
    2)cs_len: 可能抛异常的指令的区域长度,该字段与 1)一起表示一系列连续的指
令,编码方式与 1)相同。
    3)cs_lp: 用于处理上述指令的 Landing pad 的位移,这个值如果为 0 则表示不存
在相应的 landing pad。
    4)cs_action: 指明要采取哪些 action,这是一个 unsigned LEB128 的值,该值减
1后作为下标获取 action table 中相应记录。
    “.gcc_except_table”也有类似的叙述
     1. The start of the instructions for the current call site, a byte offset
        from the landing pad base. This is encoded using the encoding from the
        header.
     2. The length of the instructions for the current call site, in bytes.
        This is encoded using the encoding from the header.
     3. A pointer to the landing pad for this sequence of instructions, or 0
        if  here isn’t one. This is a byte offset from the landing pad base.
        This is encoded using the encoding from the header.
     4. The action to take, an unsigned LEB128. This is 1 plus a byte offset
        into the action table. The value zero means that there is no action.
    应该还是很模糊吧! 我依然有看没有懂。cs_lp 可以和 info.LPStart 加总得到
    ladning_pad, cs_action 可以和 info.action_table 计算得到 action_record 的
    位址。cs_start, cs_len 不懂其用意。
    程式码: list 5. L294
 2. action table: 里头的资讯可以用来取得 catch 的所有 type, 以 eh.cpp 来说, 有
    2 个 catch statement, catch (std::exception &e), catch (int a), 就可以透过
    action table 来取得 std::exception, int 的 type_info。
    在和 throw 的物件做比对 (这边的例子是丢出整数 100), 便可以知道这个
    landing_pad 是不是 catch handle, 如果没有吻合, 这个 landing_pad 有可能只是
    要呼叫某个物件的解构函式, 用来清除该物件。
 3. type table: 纪录著所有 catch 的 type。
list 5. L365 在取得 catch statement 的 type_info, 很复杂, 会透过
 1. info->ttype_encoding
 2. info->ttype_base
 3. info->TType
以及 action_record 的 ar_filter
353 p = action_record;
354 p = read_sleb128 (p, &ar_filter);
p = read_encoded_value (0, info.call_site_encoding, p, &cs_lp);
p = read_uleb128 (p, &cs_action);
p = read_sleb128 (p, &ar_filter);
这些函式都是用来读取 .gcc_except_table section 的内容, 由于这些值有经过压缩,
所以得做个还原的动作。
list 5. libstdc++-v3/libsupc++/eh_personality.cc
  1 // -*- C++ -*- The GNU C++ exception personality routine.
  2 // Copyright (C) 2001-2018 Free Software Foundation, Inc.
  3 //
  4 // This file is part of GCC.
 83 // Return an element from a type table.
 84
 85 static const std::type_info *
 86 get_ttype_entry (lsda_header_info *info, _uleb128_t i)
 87 {
 88   _Unwind_Ptr ptr;
 89
 90   i *= size_of_encoded_value (info->ttype_encoding);
 91   read_encoded_value_with_base (info->ttype_encoding, info->ttype_base,
info->TType - i, &ptr);
 93
 94   return reinterpret_cast<const std::type_info *>(ptr);
 95 }
 96
213 namespace __cxxabiv1
214 {
215
216 extern "C"
217 _Unwind_Reason_Code
218 __gxx_personality_v0 (int version,
219         _Unwind_Action actions,
220         _Unwind_Exception_Class exception_class,
221         struct _Unwind_Exception *ue_header,
222         struct _Unwind_Context *context)
223 {
224   enum found_handler_type
225   {
226     found_nothing,
227     found_terminate,
228     found_cleanup,
229     found_handler
230   } found_type;
231
232   lsda_header_info info;
233   const unsigned char *language_specific_data;
234   const unsigned char *action_record;
235   const unsigned char *p;
236   _Unwind_Ptr landing_pad, ip;
237   int handler_switch_value;
238   void* thrown_ptr = 0;
239   bool foreign_exception;
240   int ip_before_insn = 0;
241
242   __cxa_exception* xh = __get_exception_header_from_ue(ue_header);
243
244   // Interface version check.
245   if (version != 1)
246     return _URC_FATAL_PHASE1_ERROR;
247   foreign_exception = !__is_gxx_exception_class(exception_class);
248
249   // Shortcut for phase 2 found handler for domestic exception.
250   if (actions == (_UA_CLEANUP_PHASE | _UA_HANDLER_FRAME)
251       && !foreign_exception)
252     {
253       restore_caught_exception(ue_header, handler_switch_value,
254           language_specific_data, landing_pad);
255       found_type = (landing_pad == 0 ? found_terminate : found_handler);
256       goto install_context;
257     }
258
259   language_specific_data = (const unsigned char *)
260     _Unwind_GetLanguageSpecificData (context);
261
262   // If no LSDA, then there are no handlers or cleanups.
263   if (! language_specific_data)
264     CONTINUE_UNWINDING;
265
266   // Parse the LSDA header.
267   p = parse_lsda_header (context, language_specific_data, &info);
268   info.ttype_base = base_of_encoded_value (info.ttype_encoding, context);
269   ip = _Unwind_GetIPInfo (context, &ip_before_insn);
270   if (! ip_before_insn)
271