zoukankan      html  css  js  c++  java
  • 逆向随笔

    switch case 语句在c语言里还是比較简单的。可是被编译出来之后,优化结果往往让人非常疑惑。全然看不懂,以下我们一次次的尝试,看看编译器究竟把switch语句变成什么样了。

     

    ① 先上个最简单的:

     

    switch ( argc )
     {
     case 10:
      printf("case 10 ! 
    ");
      break;
    
     case 11:
      printf("case 11 ! 
    ");
      break;
    
     default:
      printf("default ! 
    ");
      break;
     }
    
     getchar();
    


    丢进OD里。看下反汇编代码:

     

    第三行開始,取值到eax中

    eax -= 10 ( 0xA )

    if (  eax == 0  )                // 假设 eax - 10 == 0,直接能够得出结论 eax == 10

      je 0x002C103E

    else

    {

       eax--;

       if ( eax == 0 )               // 刚上面 eax - 10 了。这里又减 1,一起就是 假设 eax - 11 == 0, 那么 eax == 11

           je 0x002C1026

       else

           default

    }

     

    仅仅有少数分支且case的值连续的时候,会用被推断的值 - 最小值,然后 dec 减1。je 推断

     

     

    ② 少数分支,但值不连续的时候

     

    switch ( argc )
    	{
    	case 10:
    		printf("case 10 ! 
    ");
    		break;
    
    	case 100:
    		printf("case 100 ! 
    ");
    		break;
    
    	default:
    		printf("default ! 
    ");
    		break;
    	}
    
    	getchar();
    


    反汇编:

     

    我们能够看到。直接就是cmp , je。类似于 if else 结构

     

    仅仅有少数分支。case的值不连续的时候,直接cmp , je

     

     

    ③ 当分支数量大于3个且连续的时候

     

     

    switch ( argc )
    	{
    	case 10:
    		printf("case 10 ! 
    ");
    		break;
    
    	case 11:
    		printf("case 11 ! 
    ");
    		break;
    
    	case 12:
    		printf("case 12 ! 
    ");
    		break;
    
    	case 13:
    		printf("case 13 ! 
    ");
    		break;
    
    
    	default:
    		printf("default ! 
    ");
    		break;
    	}
    
    	getchar();


    反汇编:

     

    依然是第三行開始,这次貌似代码不太一样了,没错,这又是一个新姿势了

     

    eax = eax - 10 ( 0xA )

    cmp eax, 3 这里是什么意思呢??? 为什么突然跟3比較?为嘛不是 4,5,6 ? 原来,这个时候为了达到更好的性能。编译器替我们生成了一张表。跳转表。这也是switch语句的精髓所在

    大跳转表(为嘛叫大表。后面解释),事实上就是一个地址数组

        下标范围:case最大值 - case最小值

        大小:case最大值 - case最小值 + 1

     

    我们看后面的寻址方式,jmp dword ptr ds:[ eax*4 + 0xFC1090 ]。典型的数组寻址,这个0xFC1090就是跳转表首地址,我们看看这个表,上图红色选中部分。我们发现里面存储的值刚好是case的地址。我们理清下思路:

     

    值 - case中的最小值 得到大表的索引。假设这个索引不在大表下标范围内,ja (无符号大于跳转)到 default,否则。jmp dword ptr ds:[ eax*4 + 0xFC1090 ],用这个索引在大表中取得 case 相应地址。直接过去。

     

    这个大表是编译器生成。我们不用去管。至于怎么生成?大家能够自己来尝试实现一下。

     

     

    ④ 当分支数量大于3个且部分不连续的时候(差值较小)

     

    switch ( argc )
    	{
    	case 10:
    		printf("case 10 ! 
    ");
    		break;
    
    	case 11:
    		printf("case 11 ! 
    ");
    		break;
    
    	case 13:
    		printf("case 13 ! 
    ");
    		break;
    
    	case 15:
    		printf("case 12 ! 
    ");
    		break;
    
    	default:
    		printf("default ! 
    ");
    		break;
    	}
    
    	getchar();


    反汇编:

     

     

    大表大小: 15 - 10 + 1 = 6 ,这个时候我们也仅仅case了4个值,可是大表仍然被补齐成6个了。观察发现,中间缺少的值被补成default

    当我们的值为14时,14 - 0xA = 4,  4 < 5,  [ 4*4 + 0xFC1090 ] =  0x00041075 -> default,是不是非常机智。

     

     

     

    ⑤ 当分支数量大于3个且部分不连续的时候(差值较大)

     

    switch ( argc )
    	{
    	case 10:
    		printf("case 10 ! 
    ");
    		break;
    
    	case 11:
    		printf("case 11 ! 
    ");
    		break;
    
    	case 12:
    		printf("case 12 ! 
    ");
    		break;
    
    	case 19:
    		printf("case 19 ! 
    ");
    		break;
    
    
    	default:
    		printf("default ! 
    ");
    		break;
    	}
    
    	getchar();

     

    反汇编:

     

    这个时候我们的两个值的差值是7。这个时候发现又不一样了,寻址方式变成了:movzx eax, byte ptr ds:[ eax + 0xE610A8 ]。dword 变 byte 了,我们数据窗体中看一下。如图选中内容,这就是小表。每一个元素仅仅占1个字节。小表大小也是最大值 19  - 最小值 10。接下来就是 jmp dword ptr ds:[ eax*4 + 0xE61094 ],这个当然,又是我们亲爱的大表了,联系上下文我们发现。小表里面存的就是大表的下标。为什么要这样设计呢? 由于大表占四个字节,当差距比較大时,生成的大表自然也会变得非常大,这个时候使用小表,能够更加节约内存。

     

     

    ⑥ 当分支数量大于3个且部分不连续的时候(差值很大)

     

    switch ( argc )
    	{
    	case 10:
    		printf("case 10 ! 
    ");
    		break;
    
    	case 11:
    		printf("case 11 ! 
    ");
    		break;
    
    	case 12:
    		printf("case 12 ! 
    ");
    		break;
    
    	case 600:
    		printf("case 600 ! 
    ");
    		break;
    
    
    	default:
    		printf("default ! 
    ");
    		break;
    	}
    
    	getchar();

     

    反汇编:


     

    最大值 600 - 最小值 10 = 590,。小表 590 字节 ? 这种话,小表就非常大了。所以,又进行了改变,连续的部分依旧使用第一种方法,不连续的使用 if else 结构,不再使用跳转表了。

     

     

    通过对 switch case 的一步步分析。我们发现情况还是非常多的,可能不同的编译器不一样的优化。搞清楚原理。才干真正游刃有余。

     

     

     

     

  • 相关阅读:
    PowerDesigner如何导出建表sql脚本(转)
    excel插入行时提示不能将对象移到工作表外,怎么解决!!
    Axure知识点
    移动互联网学习的点
    什么是大数据?
    [Android开源项目] GitHub开源项目总结 (转)
    程序员自我提高的几点建议 很实诚(转)
    程序员必须进行的10项投资(转)
    安卓版本的问题
    Android APK反编译详解(转)
  • 原文地址:https://www.cnblogs.com/yangykaifa/p/7346482.html
Copyright © 2011-2022 走看看