逆向随笔 - 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语句中传入不同类型的参数时,编译器为其生 ...
随机推荐
- hdu4490 Mad Veterinarian(bfs)
Mad Veterinarian Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) ...
- SQL:1999基本语法(学习笔记)
SQL:1999基本语法 SELECT [DISTINCT] * | 列名称 [AS]别名,........ FROM 表名称1 [别名1][CROSS JOIN表名称2 别名2]| [NATURAL ...
- ora01219数据库未打开
今天连接数据后,一看提示ora01219数据库未打开,关了服务重开仍然是这样,在度娘找了下才发现问题 应该是我删除了一个数据文件,看下解决办法 错误原因: 直接关闭数据库,然后删除DBF文件.即表空间 ...
- 算法笔记_195:历届试题 错误票据(Java)
目录 1 问题描述 2 解决方案 1 问题描述 问题描述 某涉密单位下发了某种票据,并要在年终全部收回. 每张票据有唯一的ID号.全年所有票据的ID号是连续的,但ID的开始数码是随机选定的. 因为 ...
- ThinkCMF X2.2.2多处SQL注入漏洞分析
1. 漏洞描述 ThinkCMF是一款基于ThinkPHP+MySQL开发的中文内容管理框架,其中X系列基于ThinkPHP 3.2.3开发,最后更新到2.2.2版本.最近刚好在渗透测试 ...
- java面试第十二天
多线程: 多线程的同步: 多线程并发访问同一个对象(临界资源),如果不对线程进行同步控制,破坏了原子操作(不可再分的操作),则会造成临界资源(两个线程同时访问的资源)的数据不一致. 每一个对象都有一个 ...
- Windows安装MySQL解压版
1:解压 2:设置环境变量 3:修改my.ini [mysqld] basedir = D:\MySQL\Server\mysql--win32 datadir = D:\MySQL\Server\d ...
- Caffe源代码中Solver文件分析
Caffe源代码(caffe version commit: 09868ac , date: 2015.08.15)中有一些重要的头文件,这里介绍下include/caffe/solver.hpp文件 ...
- exception http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application
http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed w ...
- python之函数用法iter()
# -*- coding: utf-8 -*- #python 27 #xiaodeng #python之函数用法iter() #iter() #说明:对一个对象调用 iter() 就可以得到它的迭代 ...