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++编译器为我们生成的异常代码 。