逆向随笔 - switch 语句深入分析
switch case 语句在c语言里还是比較简单的。可是被编译出来之后,优化结果往往让人非常疑惑。全然看不懂,以下我们一次次的尝试,看看编译器究竟把switch语句变成什么样了。
① 先上个最简单的:
switch ( argc )
{
case 10:
printf("case 10 ! \r\n");
break; case 11:
printf("case 11 ! \r\n");
break; default:
printf("default ! \r\n");
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 ! \r\n");
break; case 100:
printf("case 100 ! \r\n");
break; default:
printf("default ! \r\n");
break;
} getchar();
反汇编:
我们能够看到。直接就是cmp , je。类似于 if else 结构
仅仅有少数分支。case的值不连续的时候,直接cmp , je
③ 当分支数量大于3个且连续的时候
switch ( argc )
{
case 10:
printf("case 10 ! \r\n");
break; case 11:
printf("case 11 ! \r\n");
break; case 12:
printf("case 12 ! \r\n");
break; case 13:
printf("case 13 ! \r\n");
break; default:
printf("default ! \r\n");
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 ! \r\n");
break; case 11:
printf("case 11 ! \r\n");
break; case 13:
printf("case 13 ! \r\n");
break; case 15:
printf("case 12 ! \r\n");
break; default:
printf("default ! \r\n");
break;
} getchar();
反汇编:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvQW50aUhpcHM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast">
大表大小: 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 ! \r\n");
break; case 11:
printf("case 11 ! \r\n");
break; case 12:
printf("case 12 ! \r\n");
break; case 19:
printf("case 19 ! \r\n");
break; default:
printf("default ! \r\n");
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 ! \r\n");
break; case 11:
printf("case 11 ! \r\n");
break; case 12:
printf("case 12 ! \r\n");
break; case 600:
printf("case 600 ! \r\n");
break; default:
printf("default ! \r\n");
break;
} getchar();
反汇编:
最大值 600 - 最小值 10 = 590,。小表 590 字节 ? 这种话,小表就非常大了。所以,又进行了改变,连续的部分依旧使用第一种方法,不连续的使用 if else 结构,不再使用跳转表了。
通过对 switch case 的一步步分析。我们发现情况还是非常多的,可能不同的编译器不一样的优化。搞清楚原理。才干真正游刃有余。
逆向随笔 - switch 语句深入分析的更多相关文章
- 黑马程序员_毕向东_Java基础视频教程——switch语句练习(随笔)
switch(练习) /* if和 switch 语句很像. 具体什么场景下使用什么语句呢? 如果判断的具体数值不多且符合byte.short.int.char.String类型,虽然两个语句都可以使 ...
- switch语句的妙用
switch语句的普通用法很简单,如下: var a = 3; switch (a) { case 1: console.log(a); break; case 2: case 3: console. ...
- 106运用SWITCH语句打印星期几的单词
package com.chongrui.test;/*运用SWITCH语句打印星期几的单词 * */ public class TypeConvertion { public static void ...
- 通过goto语句学习if...else、switch语句并简单优化
goto语句在C语言中实现的就是无条件跳转,第二章一上来就介绍goto语句就是要通过goto语句来更加清楚直观的了解控制结构. 我理解的goto语句其实跟switch语句有相似之处,都是进行跳转.不同 ...
- Java中简单的操作(if语句、常用操作符、switch语句、变量赋值等)
---------------------if语句介绍--------------------------------------------------- class IfDemo { public ...
- Switch语句的case穿透
Switch语句的case穿透 一 switch语句几点说明: 1. case后面只能是常量,不能是变量,而且,多个case后面的值不能出现相同的. 2.case后面表达式可以接受: 基本数据类型,b ...
- ECMA中的switch语句
switch借鉴自其他语言,但也有自己的特色. 1.可以在switch语句中使用任何数据类型(数值.字符串.对象等),很多其他语言中只能使用数值. 2.每个case的值不一定是常量,可以是变量或者表达 ...
- switch语句下的变量声明和定义
switch语句下的变量声明和定义的问题: switch...case...语句中存在声明和定义会出现一些问题.这个由switch语法特性决定的, switch中每个case都是平等的层次,区别于一般 ...
- 透过IL看C#:switch语句(转)
透过IL看C# switch语句(上) 摘要: switch语句是 C#中常用的跳转语句,可以根据一个参数的不同取值执行不同的代码.本文介绍了当向 switch语句中传入不同类型的参数时,编译器为其生 ...
随机推荐
- 大型应用的javascript架构
来源:http://blog.leezhong.com/tech/2010/11/29/javascript-arch.html 目前很多网站基本没有明确的前端架构,大多是服务端渲染视图页,输出到浏览 ...
- ZH奶酪:PHP图片压缩(TinyPNG在线API)和(使用Imagick扩展)
1.调用TinyPng网站提供的API 1.1.须知 (1)tinypng的官网:https://tinypng.com/ 不知道国内访问会不会很慢,在Singapore打开这个网站很流畅: (2)A ...
- curl库的使用,32-64
http 使用curl发起https请求 http://www.cnblogs.com/ainiaa/archive/2011/11/08/2241385.html Curl配置及编译: CFLAGS ...
- UILabel文字竖排
方法一: UILabel *mindName = [[UILabel alloc]initWithFrame:kCR(, , ,)]; mindName.text = @"苏\n小\n明&q ...
- JDK5.0特性,使用ProcessBuilder执行本地命令
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.IO ...
- POJ 1436 Horizontally Visible Segments (线段树·区间染色)
题意 在坐标系中有n条平行于y轴的线段 当一条线段与还有一条线段之间能够连一条平行与x轴的线不与其他线段相交 就视为它们是可见的 问有多少组三条线段两两相互可见 先把全部线段存下来 并按x ...
- ubuntu 下安装摄像头驱动
sudo apt-get install cheese sudo apt-get install camorama 然后可以打开应用cheese,观察可以得到图像. 也可以通过代码获取图像.pytho ...
- vue 虚拟dom
https://segmentfault.com/a/1190000008291645 一个VNode的实例对象包含了以下属性 tag: 当前节点的标签名 data: 当前节点的数据对象 VNode可 ...
- java发送email(含代理方式,ssl方式,传统方式)
package spring.vhostall.com; import java.security.Security; import java.util.Date; import java.util. ...
- 转:【HTTP】常见错误码说明
一些常见的状态码为: 200 - 服务器成功返回网页404 - 请求的网页不存在503 - 服务不可用详细分解: 1xx(临时响应)表示临时响应并需要请求者继续执行操作的状态代码. 代码 说明100 ...