• mingw32 exception在sjlj与dwarf差别-反汇编分析


    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++库以外的代码进行分析,也足够看出他们之间有着一定的差别。

  • 相关阅读:
    CSS3 鲜为人知的属性-webkit-tap-highlight-color的理解
    14 个折磨人的 JavaScript 面试题
    JavaScript 开发的45个技巧2
    JavaScript 开发的45个技巧
    JavaScript 中的 this !
    JavaScript里的循环方法:forEach,for-in,for-of
    JS类型判断typeof PK {}.toString.call(obj)
    Object.prototype.toString()
    MinGW gcc 生成动态链接库 dll 的一些问题汇总 (补充)
    Selenium之偷懒教程
  • 原文地址:https://www.cnblogs.com/bbqzsl/p/13144014.html
Copyright © 2020-2023  润新知