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++库以外的代码进行分析,也足够看出他们之间有着一定的差别。
来源:oschina
链接:https://my.oschina.net/u/4350255/blog/4313921