2016年12月25日 星期日

c++ exception handling 的實作 (1) - 原理篇


20161227 補充
原本的文章寫的實在是太爛了, 先寫出來是為了讓自己記得改善, 只是沒想到這麼快就可以改善了。我竟然忘了參考 binary hacks 繁體中文版 item 38, 39, 40, 41, 以前看過好幾次都看不懂, 這次竟然看懂了, 趕緊補上像樣點的心得, 若你能看到我的 git log, 一定可以理解我花在這篇文章的心血。

一直以來都會以一個小程式來說明某個概念, 而不是形而上的理論/觀念的敘述, 可以用 gdb 來追蹤, 觀察執行後的結果, 反組譯執行檔, 這是我認為在程式學習上很重要的步驟, 只有口頭敘述, 沒有將它具象化, 感覺好像明白, 但又有什麼沒搞懂的模糊感。

你一定有興趣:
c++ exception handling (2) - 使用 g++ 5.4.0

fig 0. 金字塔知識門檻

c++ exception handling 還真不是普通的複雜, 我目前僅僅知道其實作原理, 但實作細節太複雜, 沒能搞懂。面試 c++ 時常會看到 virtaul function 如何實作的考題, 但卻沒看過問 c++ exception handling 怎麼實作, 沒有別的原因, 就是因為它難到只有很少人才知道怎麼實作, 不知道怎麼實做 exception handling 一點都不丟臉, 因為連 cfront 也搞不定

挑戰這麼難的東西, 又沒有什麼經濟效益, 我一定是阿達了。廢話不多說, 來看看 gcc 怎麼實作 c++ exception handling。

vc 和 gcc 有不同的作法, 我研究的是 gcc 的作法。

看了不少參考資料, 本篇文章以 binary hacks 繁體中文版 item 38, 39, 40, 41 為主, 因為有個小程式可以用來實驗以及說明 exception handle。

下面這 3 個函式是最主要的關鍵:
1 __cxa_throw
2 _Unwind_RaiseException
3 __gxx_personality_v0 (int version, _Unwind_Action actions, _Unwind_Exception_Class exception_class, struct _Unwind_Exception *ue_header, struct _Unwind_Context *context)

這些函式的 source code 在 gcc libgcc 目錄下, libgcc 是一個很神秘的 library, 裡頭幾乎是 gcc 特異功能的實做。unwind, 軟體浮點數 ... 都是在這裡。

gcc-3.4.4/gcc
gcc-5.4.0/libgcc

_Unwind_SjLj_RaiseException
_Unwind_RaiseException
gcc-5.4.0/libgcc/unwind-sjlj.c
gcc-5.4.0/libgcc/unwind.inc
#define PERSONALITY_FUNCTION __gxx_personality_v0

PERSONALITY_FUNCTION (int version,
_Unwind_Action actions,
_Unwind_Exception_Class exception_class,
struct _Unwind_Exception *ue_header,
struct _Unwind_Context *context)

/gcc-5.4.0/libstdc++-v3/libsupc++/eh_personality.cc
__cxa_throw
extern "C" void __cxxabiv1::__cxa_throw (void *obj, std::type_info *tinfo, void (_GLIBCXX_CDTOR_CALLABI *dest) (void *))
gcc-5.4.0/libstdc++-v3/libsupc++/eh_throw.cc

a.cpp L116 throw 100;
會轉成呼叫 (ref a.cpp L118 ~ 120)
__cxa_allocate_exception()
__cxa_throw()

__cxa_throw() 發動時的流程:
__cxxabiv1::__cxa_throw
->
執行的是 _Unwind_SjLj_RaiseException

#ifdef _GLIBCXX_SJLJ_EXCEPTIONS
_Unwind_SjLj_RaiseException (&header->exc.unwindHeader);
#else
_Unwind_RaiseException (&header->exc.unwindHeader);
#endif
|
|
|->   __gxx_personality_sj0
|
|
|-> uw_install_context

uw_install_context 會呼叫 longjmp 回到上一個函式, 以 a.cpp 來說, 就是 func1()。

__gxx_personality_sj0 是幹麻用的? 搜尋是不是有對應的 catch statement, 或是有那個物件需要解構, 得去執行解構函式, 要跳去的那個位址有個很厲害的術語叫做 landing_pad, source code 會看到 landing_pad = info.LPStart + cs_lp;, 就是用來找到要去執行解構函式或是 catch statement 的位址, 一旦 uw_install_context 執行之後, 就會跳去那個位址。

像 func1() 有個物件需要解構, __gxx_personality_sj0 知道這件事情, 所以才要讓 _Unwind_RaiseException 往 func1 跳, 很神奇是吧! 一但 func1() 拿掉 a.cpp L128 那個 Obj obj, 就不會跳回 func1()。

那 __gxx_personality_sj0 怎麼知道這些事情的, 這個就很複雜, 得靠 g++ 在編譯的時候塞入 dwarf 裡頭的資訊, 而要怎麼取出這些資訊也很神秘, 和 CIE 及 FDE 有關, 不過我不知道這 2 個是什麼東西, 也不知道怎麼取出來, 就算讀了 source code, 也還是看不懂。

另外 __gxx_personality_sj0 會比對丟出的例外物件和 catch 的例外物件, 如果一樣, landing_pad 才會往那個 catch 指定, 這就是為什麼 exception handle 需要 rtti 的支援, rtti 的 type_info 物件, 就是拿來比對這 2 個例外物件有沒有一致。

bt.cpp 只有模擬一半的功能, 使用 setjmp/longjmp, back_to_func 可以回到前一個 function, sjlj 就是用類似的方法串起這些 jmp_buf; 不過我不知道怎麼使用 .eh_frame, .gcc_except_table section 裡頭的資料來得知是不是有那個解構函式需要執行, 是不是有符合的 catch statement。

bt.cpp
 1 #include <setjmp.h>
 2 #include <string>
 3 #include <map>
 4 
 5 using namespace std;
 6 
 7 map<string, jmp_buf> stack_frame;
 8 
 9 void back_to_func(const string &fn)
10 {
11   //jmp_buf frame =  stack_frame[fn];
12   //stack_frame[fn];
13   longjmp(stack_frame[fn], 5);
14 }
15 
16 void f3()
17 {
18   printf("in f3\n");
19   back_to_func("f2");
20 }
21 
22 void f2()
23 {
24   jmp_buf frame; 
25   int ret = setjmp(frame);
26   if (ret == 0)
27   {
28     stack_frame.insert({"f2", frame});
29     f3();
30   }
31   else
32   {
33     printf("back to f2\n");
34     back_to_func("f1");
35   }
36 }
37 
38 void f1()
39 {
40   jmp_buf frame; 
41   int ret = setjmp(frame);
42   if (ret == 0)
43   {
44     stack_frame.insert({"f1", frame});
45     f2();
46   }
47   else
48   {
49     printf("back to f1\n");
50     back_to_func("main");
51   }
52 }
53 
54 int main(int argc, char *argv[])
55 {
56   jmp_buf frame; 
57   int ret = setjmp(frame);
58   if (ret == 0)
59   {
60     stack_frame.insert({"main", frame});
61     f1(); 
62   }
63   else
64   {
65     printf("back to main\n");
66   }
67   printf("end main\n");
68   return 0;
69 }

binary hacks 繁體中文版 item 38, 39, 40, 41 是用 gcc 3.4.4 講解, 雖然過時了, 但基本原理是一樣的, 就先從 gcc 3.4.4 的建構開始吧。

g++ 使用 setjmp/longjmp, dwarf 這兩種來支援 c++ exception handle, 目前的 gcc 5 似乎不使用 --enable-sjlj-exceptions, 我比較熟悉 setjmp/longjmp 的作法, dwarf2 太苦了, 我不想走這條路, 先以 --enable-sjlj-exceptions 來建構 gcc 3.4.4。

我以熟悉的 setjmp/long 來學習, 編譯 gcc 3.4.4 加上 --enable-sjlj-exceptions, 即使用以 setjmp/longjmp 實做的 exception handle。

setjmp/longjmp, dwarf 是用來處理 unwind, 就是從目前的函式回到上一個函式, 類似 bt.cpp 做的事情, dwarf 的作法需要去理解 dwarf 格式, 聽說是不得了的複雜, 我不想花時間在上頭, 而 setjmp/longjmp 我已經知道其實作原理, 不需要在花額外的功夫。

另外一個需要的能力就是知道要回到那一個 function, 這就是靠神秘的 LSDA 的內容來得知, g++ 會在 .gcc_except_table section 插入某些資訊, 讓 __gxx_personality_sj0 可以用來判斷要回到那個函式。

env:
32 bit debian

編譯 gcc-3.4.4
tar xvf gcc-3.4.4.tar.bz2
mkdir gcc-build
cd gcc-build
../gcc-3.4.4/configure --enable-languages=c,c++ --enable-sjlj-exceptions
make
make install

編譯時可能會遇到一些 header 的問題, 我把 /usr/include/i386-linux-gnu/* link 到 /usr/include

root@debian32:/usr/include# ls -l sys
lrwxrwxrwx 1 root root 32 Dec 26 15:42 sys -> /usr/include/i386-linux-gnu/sys

沒支援 --enable-sjlj-exceptions g++ 的編譯錯誤
descent@debian64:eh_impl$ g++ -g -o a a.cpp
/tmp/ccRq4BBp.o: In function `main':
/home/descent/git/eh_impl/a.cpp:126: undefined reference to `__gxx_personality_sj0'
/home/descent/git/eh_impl/a.cpp:138: undefined reference to `_Unwind_SjLj_Register'
/home/descent/git/eh_impl/a.cpp:142: undefined reference to `_Unwind_SjLj_Unregister'
collect2: error: ld returned 1 exit status

支援 --enable-sjlj-exceptions 的 g++
descent@debian32:eh_impl$ g++ -v
Reading specs from /usr/local/lib/gcc/i686-pc-linux-gnu/3.4.4/specs
Configured with: ../gcc-3.4.4/configure --enable-languages=c,c++ --enable-sjlj-exceptions
Thread model: posix
gcc version 3.4.4

a.cpp 是 binary hack 書上提供的範例, 提供了對照, try/catch/throw 是怎麼轉成一般的 c++ 程式碼, 看上去就清楚了, 最麻煩的就是那個 lsda 到底是怎麼樣的資料結構, 可惜書上也沒寫得很清楚, 看來只能看第 0 手資料了。

list 1. a.cpp 執行結果
/usr/local/bin/g++ -g -o a a.cpp 

使用 ldd 查看 so

descent@debian32:eh_impl$ ldd a
linux-gate.so.1 (0xb77bf000)
libstdc++.so.6 => /usr/lib/i386-linux-gnu/libstdc++.so.6 (0xb763a000)
libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb75e5000)
libgcc_s.so.1 => /lib/i386-linux-gnu/libgcc_s.so.1 (0xb75c8000) # 沒有 dynamic link 到我們編譯的那個 libgcc_s.so.1
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7411000)
/lib/ld-linux.so.2 (0x8007d000)

debian32:eh_impl$ export LD_LIBRARY_PATH=/usr/local/lib # x86 32 bit environment

再一次 ldd
descent@debian32:eh_impl$ ldd a
linux-gate.so.1 (0xb779e000)
libstdc++.so.6 => /usr/local/lib/libstdc++.so.6 (0xb76b6000)
libm.so.6 => /lib/i386-linux-gnu/libm.so.6 (0xb764f000)
libgcc_s.so.1 => /usr/local/lib/libgcc_s.so.1 (0xb7647000) # link 到我們自己編譯的 gcc 3.4.4 的 libgcc.so
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7490000)
/lib/ld-linux.so.2 (0x800e2000)

這樣就對了。

descent@debian32:eh_impl$ ./a 
func1 begin
obj ctor
func2 begin
obj dtor
thrown_obj: 100

list 1 的結果可以成功呼叫解構函式, 以及跑到正確的 catch 程式碼。可以用 gdb 跑跑看, exception handle 的神秘感解除了一半, 另外一半還在 libunwind, libgcc 裡頭的函式。

a.cpp L166 就是 L118 ~ 120 那 3 行; a.cpp L137 ~ 145 就是 L149 ~ 167 那麼多行。

a.cpp
  1 // test c++ exception handle by g++ 3.4.4
  2 // example code from binary hacks chinese version, page 145
  3 
  4 #include <cstdio>
  5 #include <iostream>
  6 #include <typeinfo>
  7 using namespace std;
  8 
  9 #include <unwind.h>
 10 
 11 extern "C" 
 12 {
 13   // libsupc++/eh_alloc.cc
 14   void * __cxa_allocate_exception(std::size_t thrown_size);
 15 
 16   // libsupc++/eh_throw.cc
 17   //void __cxa_throw (void *obj, std::type_info *tinfo, void (*dest) (void *));
 18   void __cxa_throw (void *obj, void *tinfo, void (*dest) (void *));
 19 
 20   // libsupc++/eh_catch.cc
 21   void * __cxa_begin_catch (void *exc_obj_in);
 22   void __cxa_end_catch ();
 23 
 24 
 25   #define PERSONALITY_FUNCTION    __gxx_personality_sj0
 26 
 27   // libsupc++/eh_personality.cc
 28   _Unwind_Reason_Code PERSONALITY_FUNCTION (int version,
 29                       _Unwind_Action actions,
 30                       _Unwind_Exception_Class exception_class,
 31                       struct _Unwind_Exception *ue_header,
 32                       struct _Unwind_Context *context);
 33 
 34 
 35 }
 36 
 37 struct Lsda
 38 {
 39   unsigned char start_format;
 40   unsigned char type_format;
 41   unsigned char type_length;
 42   unsigned char call_site_format;
 43   unsigned char call_site_length;
 44   unsigned char call_site_table[2];
 45   signed char action_table[2];
 46   const std::type_info *catch_type[1];
 47 }__attribute__((packed));
 48 
 49 Lsda my_lsda=
 50 {
 51   0xff,
 52   0x00,
 53   10,
 54   0x01,
 55   2,
 56   {0,1},
 57   {1,0},
 58   &typeid(int),
 59 };
 60 
 61 
 62 // unwind-sjlj.c
 63 /* This structure is allocated on the stack of the target function.
 64    This must match the definition created in except.c:init_eh.  */
 65 struct SjLj_Function_Context
 66 {
 67   /* This is the chain through all registered contexts.  It is
 68      filled in by _Unwind_SjLj_Register.  */
 69   struct SjLj_Function_Context *prev;
 70   
 71   /* This is assigned in by the target function before every call
 72      to the index of the call site in the lsda.  It is assigned by
 73      the personality routine to the landing pad index.  */
 74   int call_site;
 75   
 76   /* This is how data is returned from the personality routine to
 77      the target function's handler.  */
 78   _Unwind_Word data[4];
 79   
 80   /* These are filled in once by the target function before any
 81      exceptions are expected to be handled.  */
 82   _Unwind_Personality_Fn personality;
 83   void *lsda;
 84 
 85 #ifdef DONT_USE_BUILTIN_SETJMP
 86   /* We don't know what sort of alignment requirements the system
 87      jmp_buf has.  We over estimated in except.c, and now we have
 88      to match that here just in case the system *didn't* have more
 89      restrictive requirements.  */
 90   jmp_buf jbuf __attribute__((aligned));
 91 #else
 92   void *jbuf[];
 93 #endif 
 94 };
 95 
 96 //#define CXX_EH
 97 
 98 class Obj
 99 {
100   public:
101     Obj()
102     {
103       cout << "obj ctor" << endl;
104     }
105     ~Obj()
106     {
107       cout << "obj dtor" << endl;
108     }
109 
110 };
111 
112 void func2()
113 {
114   cout << "func2 begin" << endl;
115 #ifdef CXX_EH
116   throw 100;
117 #else
118   void *throw_obj = __cxa_allocate_exception(sizeof(int));
119   *(int*)throw_obj = 100; // 這就是那個 throw 100, 的那個 100
120   __cxa_throw(throw_obj, (std::type_info*)&typeid(int), NULL);
121 #endif
122   cout << "func2 end" << endl;
123 }
124 
125 void func1()
126 {
127   cout << "func1 begin" << endl;
128   Obj obj;
129 
130   func2();
131   cout << "func1 end" << endl;
132 }
133 
134 int main(int argc, char *argv[])
135 {
136 #ifdef CXX_EH
137   try
138   {
139     cout << "hello" << endl; 
140     func1();
141   }
142   catch (int eh)
143   {
144     cout << "catch int: " << eh << endl; 
145   }
146 
147 #else
148   
149   SjLj_Function_Context sjlj;
150 
151   sjlj.personality = __gxx_personality_sj0;
152   sjlj.lsda = (void*)&my_lsda;
153   sjlj.call_site = 1;
154 
155   if (__builtin_setjmp(sjlj.jbuf) == 1)
156   {
157     void *thrown_obj = __cxa_begin_catch((void*)sjlj.data[0]);
158     printf("thrown_obj: %d\n", *(int*)thrown_obj);
159     __cxa_end_catch();
160   }
161   else
162   {
163     _Unwind_SjLj_Register(&sjlj);
164     //throw 100;
165     func1();
166   }
167   _Unwind_SjLj_Unregister(&sjlj);
168 #endif
169   return 0;
170 }

objdump -d a 看不到詳細的反組譯程式碼, 我使用 gdb 來反組譯, 這是意外的收穫。

list 2. dis.gdb
 1   >0x8048b16 <func1()+96>  lea    -0x28(%ebp),%eax
 2    0x8048b19 <func1()+99>  mov    %eax,(%esp)       
 3    0x8048b1c <func1()+102> call   0x8048d2e <Obj::Obj()>
 4    0x8048b21 <func1()+107> movl   $0x1,-0x58(%ebp)           
 5    0x8048b28 <func1()+114> call   0x8048a32 <func2()> 
 6    0x8048b2d <func1()+119> movl   $0x8048e5a,0x4(%esp)     
 7    0x8048b35 <func1()+127> movl   $0x804b080,(%esp)       
 8    0x8048b3c <func1()+134> call   0x80487d0 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 
 9    0x8048b41 <func1()+139> movl   $0x8048780,0x4(%esp)
10    0x8048b49 <func1()+147> mov    %eax,(%esp)        
11    0x8048b4c <func1()+150> call   0x8048770 <_ZNSolsEPFRSoS_E@plt> 
12    0x8048b51 <func1()+155> jmp    0x8048b8c <func1()+214>    
13    0x8048b53 <func1()+157> lea    0x18(%ebp),%ebp    
14    0x8048b56 <func1()+160> mov    -0x54(%ebp),%eax  
15    0x8048b59 <func1()+163> mov    %eax,-0x64(%ebp) 
16    0x8048b5c <func1()+166> mov    -0x64(%ebp),%edx 
17    0x8048b5f <func1()+169> mov    %edx,-0x60(%ebp)   
18    0x8048b62 <func1()+172> lea    -0x28(%ebp),%eax  
19    0x8048b65 <func1()+175> mov    %eax,(%esp)      
20    0x8048b68 <func1()+178> movl   $0x0,-0x58(%ebp) 
21    0x8048b6f <func1()+185> call   0x8048d02 <Obj::~Obj()>  the 1st dtor 
22    0x8048b74 <func1()+190> mov    -0x60(%ebp),%eax  
23    0x8048b77 <func1()+193> mov    %eax,-0x64(%ebp) 
24    0x8048b7a <func1()+196> mov    -0x64(%ebp),%edx
25    0x8048b7d <func1()+199> mov    %edx,(%esp)    
26    0x8048b80 <func1()+202> movl   $0xffffffff,-0x58(%ebp) 
27    0x8048b87 <func1()+209> call   0x80487e0 <_Unwind_SjLj_Resume@plt> 
28    0x8048b8c <func1()+214> lea    -0x28(%ebp),%eax      
29    0x8048b8f <func1()+217> mov    %eax,(%esp)          
30    0x8048b92 <func1()+220> movl   $0xffffffff,-0x58(%ebp)
31    0x8048b99 <func1()+227> call   0x8048d02 <Obj::~Obj()>          the 2nd dtor  
32    0x8048b9e <func1()+232> lea    -0x5c(%ebp),%eax 
33    0x8048ba1 <func1()+235> mov    %eax,(%esp)     
34    0x8048ba4 <func1()+238> call   0x8048810 <_Unwind_SjLj_Unregister@plt>
35    0x8048ba9 <func1()+243> add    $0x6c,%esp     
36    0x8048bac <func1()+246> pop    %ebx          
37    0x8048bad <func1()+247> pop    %esi         
38    0x8048bae <func1()+248> pop    %edi        
39    0x8048baf <func1()+249> pop    %ebp       
40    0x8048bb0 <func1()+250> ret   

list 2 L21, L31 有 2 個 dtor, 很奇怪吧, L21 是給 exception handle 用的, 當從 throw 回到 func1 時, 會莫名的抵達這裡, 事實上是回到 L13 0x8048b53 這裡, 然後在執行 L27 回到上一個 stack frame (本例來說就是 main); L31 則是給正常執行流程呼叫的 dtor, L12 有個狡猾的 jmp, 真是機關算盡。

list 3 是 g++ 3.4.4 的反組譯版本, 更清楚了, 我應該早點想到的, 它不只為我解除了 2 個 dtor 的疑惑, 還把莫名會抵達 func1() 的原因也找了出來, 甚至連那個 Lsda 也幫我釐清了, 也因為知道 Lsda 的內容, 我連帶改出 g++ 5.4.0 的版本了。

list 3 是使用 try/catch/throw 的版本, list 3 L302, 303, 是不是和自己填入 a.cpp L151 ~ 153 一樣呢?

list 3 L303, L387 就是那個該死的 lsda, 從 list 3 L387 ~ L402, 在 .gcc_except_table section (就是 LSDA - Language Specific Data Area), 又是另外一個狡猾的地方。

至於 g++ 5.4.0 我怎麼改出來的呢? 就是用 g++ 5.4.0 去反組譯 try/catch/throw 的版本, 把 .gcc_except_table section, 填到那個 lsda 就好了, 果然還真的不同。

再來是那個莫名回到 func1 的動作是怎麼作到的呢? 這個困擾我好久, 用 gdb 追也找不出所以然, 照理說應該要有一個 setjmp 在這裡, 才能透過 longjmp 回到這, 但我就一直找不到哪裡有 call setjmp, 直到我用 g++ -S 之後才看到, 原來 g++ 在 func1 安插了類似 setjmp 的程式碼, 這才讓 _Unwind_RaiseException 有能力回到 func1。

list 3 L182 _Unwind_SjLj_Register 的動作類似 bt.cpp 那個 map<string, jmp_buf>, 把每一個 fuction 要回來的位置記起來, 它的參數 SjLj_Function_Context 裡頭有 jmp_buf, 得先把 jmp_buf 填好才行, 讓 uw_install_context 的 longjmp 回到這裡。

由於是 g++ 插入的 code, 得從組合語言去看出來才行, 還真是難。L177 的 .L18 就是 setjmp 紀錄起來的值, 這裡就是在填上面說的 jmp_buf 的部份, 但並不是產生呼叫 setjmp 的程式碼, 而是填入那個 jmp_buf 所需要的值就可以了, 所以 _Unwind_RaiseException 發動 uw_install_context, 就會回到 L202, 和 gdb 的顯示是一樣的。

把 func1() Obj obj; 拿掉, 再看 g++ 產生的 a.s, 就會發現那個 func1 和 c 的長相一樣, 不會被偷偷插入那麼多程式碼了。

#define uw_install_context(CURRENT, TARGET)       \
  do                                              \
  {                                               \
    _Unwind_SjLj_SetContext ((TARGET)->fc);    \
    longjmp ((TARGET)->fc->jbuf, 1);        \
  }                                               \
  while (0)

list 3 L172 ~ 173 是不是有類似的行為, 塞入 __gxx_personality_sj0, lsda 這些資料, lsda 是我目前還無法突破的部份。

list 3. g++-3.4.4 -S a.cpp a-3.3.4.s
  1  .file "a.cpp"
  2  .text
  3  .align 2
  4  .type _ZSt17__verify_groupingPKcjRKSs, @function
116 .LC0:
117  .string "func2 begin"
118 .LC1:
119  .string "func2 end"
120  .text
121  .align 2
122 .globl _Z5func2v
123  .type _Z5func2v, @function
124 _Z5func2v:
125  pushl %ebp
126  movl %esp, %ebp
127  subl $24, %esp
128  movl $.LC0, 4(%esp)
129  movl $_ZSt4cout, (%esp)
130  call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
131  movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
132  movl %eax, (%esp)
133  call _ZNSolsEPFRSoS_E
134  movl $4, (%esp)
135  call __cxa_allocate_exception
136  movl $100, (%eax)
137 .L11:
138  movl $0, 8(%esp)
139  movl $_ZTIi, 4(%esp)
140  movl %eax, (%esp)
141  call __cxa_throw
142  movl $.LC1, 4(%esp)
143  movl $_ZSt4cout, (%esp)
144  call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
145  movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
146  movl %eax, (%esp)
147  call _ZNSolsEPFRSoS_E
148  leave
149  ret
150 .L10:
151  .size _Z5func2v, .-_Z5func2v
152 .globl _Unwind_SjLj_Resume
153 .globl __gxx_personality_sj0
154 .globl _Unwind_SjLj_Register
155 .globl _Unwind_SjLj_Unregister
156  .section .rodata
157 .LC2:
158  .string "func1 begin"
159 .LC3:
160  .string "func1 end"
161  .text
162  .align 2
163 .globl _Z5func1v
164  .type _Z5func1v, @function
165 _Z5func1v:
166  pushl %ebp
167  movl %esp, %ebp
168  pushl %edi
169  pushl %esi
170  pushl %ebx
171  subl $108, %esp
172  movl $__gxx_personality_sj0, -68(%ebp)
173  movl $.LLSDA1420, -64(%ebp)
174  leal -60(%ebp), %eax
175  leal -24(%ebp), %edx
176  movl %edx, (%eax)
177  movl $.L18, %edx
178  movl %edx, 4(%eax)
179  movl %esp, 8(%eax)
180  leal -92(%ebp), %eax
181  movl %eax, (%esp)
182  call _Unwind_SjLj_Register
183  movl $.LC2, 4(%esp)
184  movl $_ZSt4cout, (%esp)
185  movl $-1, -88(%ebp)
186  call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
187  movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
188  movl %eax, (%esp)
189  call _ZNSolsEPFRSoS_E
190  leal -40(%ebp), %eax
191  movl %eax, (%esp)
192  call _ZN3ObjC1Ev
193  movl $1, -88(%ebp)
194  call _Z5func2v
195  movl $.LC3, 4(%esp)
196  movl $_ZSt4cout, (%esp)
197  call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
198  movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
199  movl %eax, (%esp)
200  call _ZNSolsEPFRSoS_E
201  jmp .L15
202 .L18:
203  leal 24(%ebp), %ebp
204  movl -84(%ebp), %eax
205  movl %eax, -100(%ebp)
206 .L14:
207  movl -100(%ebp), %edx
208  movl %edx, -96(%ebp)
209  leal -40(%ebp), %eax
210  movl %eax, (%esp)
211  movl $0, -88(%ebp)
212  call _ZN3ObjD1Ev
213  movl -96(%ebp), %eax
214  movl %eax, -100(%ebp)
215 .L16:
216  movl -100(%ebp), %edx
217  movl %edx, (%esp)
218  movl $-1, -88(%ebp)
219  call _Unwind_SjLj_Resume
220 .L15:
221  leal -40(%ebp), %eax
222  movl %eax, (%esp)
223  movl $-1, -88(%ebp)
224  call _ZN3ObjD1Ev
225 .L13:
226  leal -92(%ebp), %eax
227  movl %eax, (%esp)
228  call _Unwind_SjLj_Unregister
229  addl $108, %esp
230  popl %ebx
231  popl %esi
232  popl %edi
233  popl %ebp
234  ret
235  .size _Z5func1v, .-_Z5func1v
236  .section .gcc_except_table,"a",@progbits
237 .LLSDA1420:
238  .byte 0xff
239  .byte 0xff
240  .byte 0x1
241  .uleb128 .LLSDACSE1420-.LLSDACSB1420
242 .LLSDACSB1420:
243  .uleb128 0x0
244  .uleb128 0x0
245 .LLSDACSE1420:
246  .text
247  .section .rodata
248 .LC4:
249  .string "obj dtor\n"
250  .section .gnu.linkonce.t._ZN3ObjD1Ev,"ax",@progbits
251  .align 2
252  .weak _ZN3ObjD1Ev
253  .type _ZN3ObjD1Ev, @function
254 _ZN3ObjD1Ev:
255  pushl %ebp
256  movl %esp, %ebp
257  subl $8, %esp
258  movl $.LC4, (%esp)
259  call printf
260  leave
261  ret
262  .size _ZN3ObjD1Ev, .-_ZN3ObjD1Ev
263  .section .rodata
264 .LC5:
265  .string "obj ctor\n"
266  .section .gnu.linkonce.t._ZN3ObjC1Ev,"ax",@progbits
267  .align 2
268  .weak _ZN3ObjC1Ev
269  .type _ZN3ObjC1Ev, @function
270 _ZN3ObjC1Ev:
271  pushl %ebp
272  movl %esp, %ebp
273  subl $8, %esp
274  movl $.LC5, (%esp)
275  call printf
276  leave
277  ret
278  .size _ZN3ObjC1Ev, .-_ZN3ObjC1Ev
279  .section .rodata
280 .LC6:
281  .string "hello"
282 .LC7:
283  .string "catch int: "
284  .text
285  .align 2
286 .globl main
287  .type main, @function
288 main:
289  pushl %ebp
290  movl %esp, %ebp
291  pushl %edi
292  pushl %esi
293  pushl %ebx
294  subl $92, %esp
295  andl $-16, %esp
296  movl $0, %eax
297  addl $15, %eax
298  addl $15, %eax
299  shrl $4, %eax
300  sall $4, %eax
301  subl %eax, %esp
302  movl $__gxx_personality_sj0, -44(%ebp)
303  movl $.LLSDA1421, -40(%ebp)
304  leal -36(%ebp), %eax
305  leal -12(%ebp), %edx
306  movl %edx, (%eax)
307  movl $.L31, %edx
308  movl %edx, 4(%eax)
309  movl %esp, 8(%eax)
310  leal -68(%ebp), %eax
311  movl %eax, (%esp)
312  call _Unwind_SjLj_Register
313  movl $.LC6, 4(%esp)
314  movl $_ZSt4cout, (%esp)
315  movl $2, -64(%ebp)
316  call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
317  movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
318  movl %eax, (%esp)
319  call _ZNSolsEPFRSoS_E
320  call _Z5func1v
321  jmp .L24
322 .L30:
323  cmpl $1, -84(%ebp)
324  je .L25
325  movl -76(%ebp), %eax
326  movl %eax, (%esp)
327  movl $-1, -64(%ebp)
328  call _Unwind_SjLj_Resume
329 .L25:
330  movl -76(%ebp), %edx
331  movl %edx, (%esp)
332  movl $-1, -64(%ebp)
333  call __cxa_begin_catch
334  movl (%eax), %eax
335  movl %eax, -16(%ebp)
336  movl $.LC7, 4(%esp)
337  movl $_ZSt4cout, (%esp)
338  movl $1, -64(%ebp)
339  call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
340  movl %eax, %edx
341  movl -16(%ebp), %eax
342  movl %eax, 4(%esp)
343  movl %edx, (%esp)
344  call _ZNSolsEi
345  movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, 4(%esp)
346  movl %eax, (%esp)
347  call _ZNSolsEPFRSoS_E
348  jmp .L27
349 .L31:
350  leal 12(%ebp), %ebp
351  movl -64(%ebp), %eax
352  movl -60(%ebp), %edx
353  movl %edx, -76(%ebp)
354  movl -56(%ebp), %edx
355  movl %edx, -84(%ebp)
356  cmpl $1, %eax
357  je .L30
358 .L26:
359  movl -76(%ebp), %eax
360  movl %eax, -80(%ebp)
361  call __cxa_end_catch
362  movl -80(%ebp), %edx
363  movl %edx, -76(%ebp)
364 .L28:
365  movl -76(%ebp), %eax
366  movl %eax, (%esp)
367  movl $-1, -64(%ebp)
368  call _Unwind_SjLj_Resume
369 .L27:
370  call __cxa_end_catch
371 .L24:
372  movl $0, -72(%ebp)
373 .L23:
374  leal -68(%ebp), %eax
375  movl %eax, (%esp)
376  call _Unwind_SjLj_Unregister
377  movl -72(%ebp), %eax
378  leal -12(%ebp), %esp
379  popl %ebx
380  popl %esi
381  popl %edi
382  popl %ebp
383  ret
384  .size main, .-main
385  .section .gcc_except_table
386  .align 4
387 .LLSDA1421:
388  .byte 0xff
389  .byte 0x0
390  .uleb128 .LLSDATT1421-.LLSDATTD1421
391 .LLSDATTD1421:
392  .byte 0x1
393  .uleb128 .LLSDACSE1421-.LLSDACSB1421
394 .LLSDACSB1421:
395  .uleb128 0x0
396  .uleb128 0x0
397  .uleb128 0x1
398  .uleb128 0x1
399 .LLSDACSE1421:
400  .byte 0x1
401  .byte 0x0
402  .align 4
403  .long _ZTIi
404 .LLSDATT1421:
405  .text
406  .section .gnu.linkonce.t._ZSt3minIjERKT_S2_S2_,"ax",@progbits
407  .align 2
408  .weak _ZSt3minIjERKT_S2_S2_
409  .type _ZSt3minIjERKT_S2_S2_, @function
430  .text
431  .align 2
454  .align 2
455  .type _GLOBAL__I_my_lsda, @function
456 _GLOBAL__I_my_lsda:
457  pushl %ebp
458  movl %esp, %ebp
459  subl $8, %esp
460  movl $65535, 4(%esp)
461  movl $1, (%esp)
462  call _Z41__static_initialization_and_destruction_0ii
463  leave
464  ret
465  .size _GLOBAL__I_my_lsda, .-_GLOBAL__I_my_lsda
466  .section .ctors,"aw",@progbits
467  .align 4
468  .long _GLOBAL__I_my_lsda
469  .text
470  .align 2
471  .type _GLOBAL__D_my_lsda, @function
472 _GLOBAL__D_my_lsda:
473  pushl %ebp
474  movl %esp, %ebp
475  subl $8, %esp
476  movl $65535, 4(%esp)
477  movl $0, (%esp)
478  call _Z41__static_initialization_and_destruction_0ii
479  leave
480  ret
481  .size _GLOBAL__D_my_lsda, .-_GLOBAL__D_my_lsda
482  .section .dtors,"aw",@progbits
483  .align 4
484  .long _GLOBAL__D_my_lsda
494  .section .note.GNU-stack,"",@progbits
495  .ident "GCC: (GNU) 3.4.4"

由於用到 typeinfo 來判斷型別, 這是為什麼 exception handle 需要有 rtti 支援的原因。

從 global object, static object, virtaul function, rtti 到 exception handle, 現在你知道 c++ 有那麼多的黑魔法, c++ 真是不簡單, 這也是為人所詬病的一個特性, 太黑箱了。

在 c++ 這麼多的特性, 我最有興趣的是 virtual function 和 exception handle 的實作, 我已經找了多年的資料, 有點收穫真是開心。

typeid ref:
  • typeid详解
  • 執行時期型態資訊(RTTI)
  • A General-Purpose Run-Time Type Information System for C++
  • http://www.cs.rug.nl/~alext/SOFTWARE/RTTI/rtti_doc.html
  • https://pdfs.semanticscholar.org/ca44/58d8cb126fe6eae8f19cab6efb9b9fe47c88.pdf
ref:
Visual C++ 的 exception handle:

dwarf:


沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。