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
下面的分析只列出不同的地方
上圖的註釋有誤沒有勘誤過,lea是不訪問內存,通常代替add指令做加法,應該是6條指令要訪問內存。
支路控制代碼:
可以看出,支路選路控制指令多而且複雜,還有就是跳轉多。
最後是函數結束前。
可以看出在 sjlj 版本中,即使代碼不發生異常,函數在進入與離開時都要為登記維護付出一此成本,當涉及異常代碼時,支路選路控制更加複雜更多跳轉。這裡有一個成本比例,你的函數邏輯簡單,上面的開銷比重就越大,如果是頻繁調用的輕量函數就要考慮不用exception這樣的error handle。
還有就是當發生異常時,需要交給c++庫去管理,不同異常處理模型的實現,有着不同的開銷,本文並沒有涉及到。只是單純從c++庫以外的代碼進行分析,也足夠看出他們之間有着一定的差別。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※帶您來了解什麼是 USB CONNECTOR ?
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!
※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※教你寫出一流的銷售文案?