sjlj (setjump/longjump)與dwarf-2為mingw32兩種異常處理模型的實現。sjlj有著開銷,而隨linux發行的mingw32開發庫包都是用sjlj版編譯的,而Qt卻採用dwarf-2版,那麼兩者之間有多少差異,本文就這問題對兩版的異常程式碼的反彙編進行分析比較。
我使用mingw-w65-i686-810的sjlj與dwarf-2兩個版本對下面異常程式碼編譯。
__attribute__((dllimport)) int dllfunc(); int main() { dllfunc(); //_create_locale(LC_ALL, "C"); printf("abc"); //return 0; try { try { throw std::exception(); } catch(std::exception&) { std::rethrow_exception(std::current_exception()); } } catch(int) { } catch(std::exception& e) { std::cout << e.what() << std::endl; } catch(...) { std::cout << "unknown" << std::endl; } return 0; }
程式碼邏輯:
兩層 try/catch,
1. 裡層 try/catch
1.1 try塊, throw 異常
1.2 catch塊, rethrow
2. 外層 try/catch
2.1 有三catch分支。
開刷前,先定義一下。
如果將 try/catch 去除 c++語言特性後,基本就是一種由c++庫還有c++編譯器共同管理的 goto。
throw相當於goto, catch相當於label(一種以型別區分的)。
那麼c++編譯器與c++庫為我們提供了什麼樣的管理呢?
c++編譯器
0. 利用c++支援物件析構進行try塊保護。
1. 將 throw 關鍵字生成彙編 call __cxa_throw,呼叫 c++庫的函式。
2. 為每個catch塊生成程式碼片斷,只能通過jmp跳轉進來。
2.1 開頭 call __cxa_begin_catch。
2.2 結尾 call __cxa_end_catch。
2.3 最後跳出到 try/catch塊邏輯程式碼的下條執行指令。
3. 為同一try/catch塊的所有catch塊產生分支控制程式碼。
4. 為try塊的析構程式碼產生跳轉入口。
5. 為每一層try/catch塊生成 uncaught 程式碼塊,呼叫 _Unwind_Resume。
c++庫:
1. __cxa_throw,馬上_Unwind_RaiseException。跳轉到當前最裡面一層 try/catch的支路控制程式碼片斷。
2. _Unwind_Resume,向上繼續展開。
3. std::rethrow_exception,呼叫 __gcclibcxx_demangle_callback,
3.1 要麼有 catch可達跳回到原來程式碼的控制流,直接離開std::rethrow_exception的呼叫上下文。
3.2 要麼從__gcclibcxx_demangle_callback返回,執行terminate結束程式。
sjlj 版的反彙編程式碼比 dwarf-2 版的多了50行。
先來看dwarf-2的反彙編程式碼
1 <+0>: lea 0x4(%esp),%ecx 2 <+4>: and $0xfffffff0,%esp 3 <+7>: pushl -0x4(%ecx) 4 <+10>: push %ebp 5 <+11>: mov %esp,%ebp 6 <+13>: push %esi 7 <+14>: push %ebx 8 <+15>: push %ecx 9 <+16>: sub $0x2c,%esp 10 <+19>: call 0x401890 <__main> 11 <+24>: mov 0x4071a4,%eax 12 <+29>: call *%eax 13 <+31>: movl $0x404045,(%esp) 14 <+38>: call 0x4027c4 <printf> 15 <+43>: movl $0x4,(%esp) 16 <+50>: call 0x4017ac <__cxa_allocate_exception> 17 <+55>: mov %eax,%ebx 18 <+57>: mov %ebx,%ecx 19 <+59>: call 0x402890 <std::exception::exception()> 20 <+64>: movl $0x4017d4,0x8(%esp) 21 <+72>: movl $0x4042a8,0x4(%esp) 22 <+80>: mov %ebx,(%esp) 23 <+83>: call 0x401794 <__cxa_throw> 24 <+88>: mov $0x0,%eax 25 <+93>: jmp 0x401723 <main()+355> 26 <+98>: mov %edx,%ecx 27 <+100>: cmp $0x2,%ecx 28 <+103>: je 0x40162b <main()+107> 29 <+105>: jmp 0x401663 <main()+163> 30 <+107>: mov %eax,(%esp) 31 <+110>: call 0x4017a4 <__cxa_begin_catch> 32 <+115>: mov %eax,-0x1c(%ebp) 33 <+118>: lea -0x28(%ebp),%eax 34 <+121>: mov %eax,(%esp) 35 <+124>: call 0x4017cc <_ZSt17current_exceptionv> 36 <+129>: lea -0x28(%ebp),%eax 37 <+132>: mov %eax,(%esp) 38 <+135>: call 0x4017c4 <_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE> 39 <+140>: mov %eax,%esi 40 <+142>: mov %edx,%ebx 41 <+144>: lea -0x28(%ebp),%eax 42 <+147>: mov %eax,%ecx 43 <+149>: call 0x4017ec <_ZNSt15__exception_ptr13exception_ptrD1Ev> 44 <+154>: call 0x40179c <__cxa_end_catch> 45 <+159>: mov %esi,%eax 46 <+161>: mov %ebx,%edx 47 <+163>: cmp $0x1,%edx 48 <+166>: je 0x40166f <main()+175> 49 <+168>: cmp $0x2,%edx 50 <+171>: je 0x401683 <main()+195> 51 <+173>: jmp 0x4016ca <main()+266> 52 <+175>: mov %eax,(%esp) 53 <+178>: call 0x4017a4 <__cxa_begin_catch> 54 <+183>: mov (%eax),%eax 55 <+185>: mov %eax,-0x24(%ebp) 56 <+188>: call 0x40179c <__cxa_end_catch> 57 <+193>: jmp 0x401618 <main()+88> 58 <+195>: mov %eax,(%esp) 59 <+198>: call 0x4017a4 <__cxa_begin_catch> 60 <+203>: mov %eax,-0x20(%ebp) 61 <+206>: mov -0x20(%ebp),%eax 62 <+209>: mov (%eax),%eax 63 <+211>: add $0x8,%eax 64 <+214>: mov (%eax),%eax 65 <+216>: mov -0x20(%ebp),%edx 66 <+219>: mov %edx,%ecx 67 <+221>: call *%eax 68 <+223>: mov %eax,0x4(%esp) 69 <+227>: movl $0x6ff07a00,(%esp) 70 <+234>: call 0x4017b4 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc> 71 <+239>: movl $0x4017bc,(%esp) 72 <+246>: mov %eax,%ecx 73 <+248>: call 0x4017f4 <_ZNSolsEPFRSoS_E> 74 <+253>: sub $0x4,%esp 75 <+256>: call 0x40179c <__cxa_end_catch> 76 <+261>: jmp 0x401618 <main()+88> 77 <+266>: mov %eax,(%esp) 78 <+269>: call 0x4017a4 <__cxa_begin_catch> 79 <+274>: movl $0x404049,0x4(%esp) 80 <+282>: movl $0x6ff07a00,(%esp) 81 <+289>: call 0x4017b4 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc> 82 <+294>: movl $0x4017bc,(%esp) 83 <+301>: mov %eax,%ecx 84 <+303>: call 0x4017f4 <_ZNSolsEPFRSoS_E> 85 <+308>: sub $0x4,%esp 86 <+311>: call 0x40179c <__cxa_end_catch> 87 <+316>: jmp 0x401618 <main()+88> 88 <+321>: mov %eax,%ebx 89 <+323>: call 0x40179c <__cxa_end_catch> 90 <+328>: mov %ebx,%eax 91 <+330>: mov %eax,(%esp) 92 <+333>: call 0x402770 <_Unwind_Resume> 93 <+338>: mov %eax,%ebx 94 <+340>: call 0x40179c <__cxa_end_catch> 95 <+345>: mov %ebx,%eax 96 <+347>: mov %eax,(%esp) 97 <+350>: call 0x402770 <_Unwind_Resume> 98 <+355>: lea -0xc(%ebp),%esp 99 <+358>: pop %ecx 100 <+359>: pop %ebx 101 <+360>: pop %esi 102 <+361>: pop %ebp 103 <+362>: lea -0x4(%ecx),%esp 104 <+365>: ret
我們的主要程式碼邏輯只有20-30條指令
當 throw時,__cxa_throw函式是不會返回的, 如同goto最後是跳轉到他處,若被本層catch處理完才會跳轉回來<+88>。
然後看c++編譯器為我們生成的異常程式碼 。
對於沒有發生異常時,程式碼執行路徑基本不會去涉及到異常程式碼支路,開銷幾近為0,只是程式碼量增大。
下面來看 sjlj 版的彙編程式碼,
1 function main(): 2 <+0>: lea 0x4(%esp),%ecx 3 <+4>: and $0xfffffff0,%esp 4 <+7>: pushl -0x4(%ecx) 5 <+10>: push %ebp 6 <+11>: mov %esp,%ebp 7 <+13>: push %edi 8 <+14>: push %esi 9 <+15>: push %ebx 10 <+16>: push %ecx 11 <+17>: sub $0x68,%esp 12 <+20>: movl $0x4017ac,-0x44(%ebp) 13 <+27>: movl $0x402958,-0x40(%ebp) 14 <+34>: lea -0x3c(%ebp),%eax 15 <+37>: lea -0x18(%ebp),%ebx 16 <+40>: mov %ebx,(%eax) 17 <+42>: mov $0x4015b4,%edx 18 <+47>: mov %edx,0x4(%eax) 19 <+50>: mov %esp,0x8(%eax) 20 <+53>: lea -0x5c(%ebp),%eax 21 <+56>: mov %eax,(%esp) 22 <+59>: call 0x402790 <_Unwind_SjLj_Register> 23 <+64>: call 0x4018b0 <__main> 24 <+69>: mov 0x406194,%eax 25 <+74>: movl $0xffffffff,-0x58(%ebp) 26 <+81>: call *%eax 27 <+83>: movl $0x404001,(%esp) 28 <+90>: call 0x4027e4 <printf> 29 <+95>: movl $0x4,(%esp) 30 <+102>: call 0x4017cc <__cxa_allocate_exception> 31 <+107>: mov %eax,-0x60(%ebp) 32 <+110>: mov %eax,%ecx 33 <+112>: call 0x4028b0 <std::exception::exception()> 34 <+117>: movl $0x4017f4,0x8(%esp) 35 <+125>: movl $0x404264,0x4(%esp) 36 <+133>: mov -0x60(%ebp),%eax 37 <+136>: mov %eax,(%esp) 38 <+139>: movl $0x1,-0x58(%ebp) 39 <+146>: call 0x4017b4 <__cxa_throw> 40 <+151>: mov $0x0,%eax 41 <+156>: mov %eax,-0x60(%ebp) 42 <+159>: jmp 0x401733 <main()+547> 43 <+164>: lea 0x18(%ebp),%ebp 44 <+167>: mov -0x54(%ebp),%edx 45 <+170>: mov -0x50(%ebp),%ecx 46 <+173>: mov -0x58(%ebp),%eax 47 <+176>: test %eax,%eax 48 <+178>: je 0x4015e6 <main()+214> 49 <+180>: sub $0x1,%eax 50 <+183>: test %eax,%eax 51 <+185>: je 0x40161b <main()+267> 52 <+187>: sub $0x1,%eax 53 <+190>: test %eax,%eax 54 <+192>: je 0x4016f8 <main()+488> 55 <+198>: sub $0x1,%eax 56 <+201>: test %eax,%eax 57 <+203>: je 0x401712 <main()+514> 58 <+209>: sub $0x1,%eax 59 <+212>: ud2 60 <+214>: mov %edx,%eax 61 <+216>: mov %ecx,%edx 62 <+218>: mov %edx,%ecx 63 <+220>: cmp $0x2,%ecx 64 <+223>: je 0x4015f3 <main()+227> 65 <+225>: jmp 0x401642 <main()+306> 66 <+227>: mov %eax,(%esp) 67 <+230>: call 0x4017c4 <__cxa_begin_catch> 68 <+235>: mov %eax,-0x1c(%ebp) 69 <+238>: lea -0x28(%ebp),%eax 70 <+241>: mov %eax,(%esp) 71 <+244>: call 0x4017ec <_ZSt17current_exceptionv> 72 <+249>: lea -0x28(%ebp),%eax 73 <+252>: mov %eax,(%esp) 74 <+255>: movl $0x2,-0x58(%ebp) 75 <+262>: call 0x4017e4 <_ZSt17rethrow_exceptionNSt15__exception_ptr13exception_ptrE> 76 <+267>: mov %edx,-0x60(%ebp) 77 <+270>: mov %ecx,-0x64(%ebp) 78 <+273>: lea -0x28(%ebp),%eax 79 <+276>: mov %eax,%ecx 80 <+278>: call 0x40180c <_ZNSt15__exception_ptr13exception_ptrD1Ev> 81 <+283>: mov -0x60(%ebp),%eax 82 <+286>: mov %eax,-0x60(%ebp) 83 <+289>: mov -0x64(%ebp),%esi 84 <+292>: mov %esi,-0x64(%ebp) 85 <+295>: call 0x4017bc <__cxa_end_catch> 86 <+300>: mov -0x60(%ebp),%eax 87 <+303>: mov -0x64(%ebp),%edx 88 <+306>: cmp $0x1,%edx 89 <+309>: je 0x40164e <main()+318> 90 <+311>: cmp $0x2,%edx 91 <+314>: je 0x401665 <main()+341> 92 <+316>: jmp 0x4016b3 <main()+419> 93 <+318>: mov %eax,(%esp) 94 <+321>: call 0x4017c4 <__cxa_begin_catch> 95 <+326>: mov (%eax),%eax 96 <+328>: mov %eax,-0x20(%ebp) 97 <+331>: call 0x4017bc <__cxa_end_catch> 98 <+336>: jmp 0x4015a7 <main()+151> 99 <+341>: mov %eax,(%esp) 100 <+344>: call 0x4017c4 <__cxa_begin_catch> 101 <+349>: mov %eax,-0x24(%ebp) 102 <+352>: mov -0x24(%ebp),%eax 103 <+355>: mov (%eax),%eax 104 <+357>: add $0x8,%eax 105 <+360>: mov (%eax),%eax 106 <+362>: mov -0x24(%ebp),%edx 107 <+365>: mov %edx,%ecx 108 <+367>: call *%eax 109 <+369>: mov %eax,0x4(%esp) 110 <+373>: movl $0x6ff29a00,(%esp) 111 <+380>: movl $0x3,-0x58(%ebp) 112 <+387>: call 0x4017d4 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc> 113 <+392>: movl $0x4017dc,(%esp) 114 <+399>: mov %eax,%ecx 115 <+401>: call 0x401814 <_ZNSolsEPFRSoS_E> 116 <+406>: sub $0x4,%esp 117 <+409>: call 0x4017bc <__cxa_end_catch> 118 <+414>: jmp 0x4015a7 <main()+151> 119 <+419>: mov %eax,(%esp) 120 <+422>: call 0x4017c4 <__cxa_begin_catch> 121 <+427>: movl $0x404005,0x4(%esp) 122 <+435>: movl $0x6ff29a00,(%esp) 123 <+442>: movl $0x4,-0x58(%ebp) 124 <+449>: call 0x4017d4 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc> 125 <+454>: movl $0x4017dc,(%esp) 126 <+461>: mov %eax,%ecx 127 <+463>: call 0x401814 <_ZNSolsEPFRSoS_E> 128 <+468>: sub $0x4,%esp 129 <+471>: movl $0xffffffff,-0x58(%ebp) 130 <+478>: call 0x4017bc <__cxa_end_catch> 131 <+483>: jmp 0x4015a7 <main()+151> 132 <+488>: mov %edx,-0x60(%ebp) 133 <+491>: call 0x4017bc <__cxa_end_catch> 134 <+496>: mov -0x60(%ebp),%eax 135 <+499>: mov %eax,(%esp) 136 <+502>: movl $0xffffffff,-0x58(%ebp) 137 <+509>: call 0x402788 <_Unwind_SjLj_Resume> 138 <+514>: mov %edx,-0x60(%ebp) 139 <+517>: movl $0x0,-0x58(%ebp) 140 <+524>: call 0x4017bc <__cxa_end_catch> 141 <+529>: mov -0x60(%ebp),%eax 142 <+532>: mov %eax,(%esp) 143 <+535>: movl $0xffffffff,-0x58(%ebp) 144 <+542>: call 0x402788 <_Unwind_SjLj_Resume> 145 <+547>: lea -0x5c(%ebp),%eax 146 <+550>: mov %eax,(%esp) 147 <+553>: call 0x402780 <_Unwind_SjLj_Unregister> 148 <+558>: mov -0x60(%ebp),%eax 149 <+561>: lea -0x10(%ebp),%esp 150 <+564>: pop %ecx 151 <+565>: pop %ebx 152 <+566>: pop %esi 153 <+567>: pop %edi 154 <+568>: pop %ebp 155 <+569>: lea -0x4(%ecx),%esp 156 <+572>: ret
下面的分析只列出不同的地方
支路控制程式碼:
可以看出,支路選路控制指令多而且複雜,還有就是跳轉多。
最後是函式結束前。
可以看出在 sjlj 版本中,即使程式碼不發生異常,函式在進入與離開時都要為登記維護付出一此成本,當涉及異常程式碼時,支路選路控制更加複雜更多跳轉。這裡有一個成本比例,你的函式邏輯簡單,上面的開銷比重就越大,如果是頻繁呼叫的輕量函式就要考慮不用exception這樣的error handle。
還有就是當發生異常時,需要交給c++庫去管理,不同異常處理模型的實現,有著不同的開銷,本文並沒有涉及到。只是單純從c++庫以外的程式碼進行分析,也足夠看出他們之間有著一定的差別。