逆向随笔 - 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语句中传入不同类型的参数时,编译器为其生 ...
随机推荐
- iOS 应用上传所需 Icon图片大小
iPhone-only Apps Include the following in your application's Resources group in the Xcode project: T ...
- 用JavaScript 来创建 mac os x 程序这样是否好
在网上看到的文章: 用 JavaScript 编写 OS X 应用 (Tyler Gaw) 这个文章的内容是不错的. 可是思路呢? 我们假设想学一种方法或工具,这样做好吗? 我看了上面的代码.假设 ...
- eval、exec、execfile
# -*- coding: utf-8 -*- #python 27 #xiaodeng #http://blog.csdn.net/azhao_dn/article/details/6921654 ...
- java 实现文本格式转换
代码如下,不太规范,仅作学习用 import java.io.*; public class CharSetTest { public static void main(String[] args) ...
- 生产服务器环境最小化安装后 Centos 6.5优化配置备忘
生产服务器环境最小化安装后 Centos 6.5优化配置备忘 作者:Memory 发布于:2014-8-13 15:00 Wednesday 服务器 本文 centos 6.5 优化 的项有18处: ...
- 【钉钉PC】PC端钉钉清除缓存
最近被钉钉的PC端坑的要死要死的,一个缓存问题弄了我一天时间,去你大爷的,放在这里防止其他的人被坑. 1.右键点击微应用,选择devtools 2.展开的页面,就像web端的F12,勾选network ...
- SqlServer强制断开数据库已有连接的方法(转)
在master数据库中执行如下代码 declare @i INT declare cur cursor for select spid from sysprocesses where db_name ...
- ubuntu修改固定ip
1.vi /etc/network/interfasces,添加红框内的内容:
- 利用pushState开发无刷页面切换(转)
相关文档:https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulatingthebrowser_history 实现目标 ...
- ASP.NET中UrlEncode应该用Uri.EscapeDataString()
今天,茄子_2008反馈他博客中的“C++”标签失效.检查了一下代码,生成链接时用的是HttpUtility.UrlEncode(url),从链接地址获取标签时用的是HttpUtility.UrlDe ...