1.前置知识
switch的用法参见22.【C语言】选择结构之switch
附:《C语言程序与设计》 第四版对switch的解释
注意画红线的等值比较,下面会重点讲
2.C语言代码
运行环境:VS2010+Win32
sourcecode1
int main()
{int num=2;int c1=0;int c2=0;switch (num){case 1:c1=3;case 2:c2=1;}return 0;
}
sourcecode2
int main()
{int num=2;int c1=0;int c2=0;switch (num){case 1:c1=3;break;case 2:c2=1;break;}return 0;
}
3.反汇编分析
按F11进入调试模式,右击转到反汇编
sourcecode1的反汇编代码
int main()
{push ebp mov ebp,esp sub esp,0E8h push ebx push esi push edi lea edi,[ebp-0E8h] mov ecx,3Ah mov eax,0CCCCCCCCh rep stos dword ptr es:[edi] int num=2;mov dword ptr [num],2 int c1=0;mov dword ptr [c1],0 int c2=0;mov dword ptr [c2],0 switch (num)mov eax,dword ptr [num] mov dword ptr [ebp-0E8h],eax cmp dword ptr [ebp-0E8h],1 je main+50h (2513A0h) cmp dword ptr [ebp-0E8h],2 je main+57h (2513A7h) jmp main+5Eh (2513AEh) {case 1:c1=3;mov dword ptr [c1],3 case 2:c2=1;mov dword ptr [c2],1 }return 0;xor eax,eax
}pop edi pop esi pop ebx mov esp,ebp pop ebp ret
逐条分析
1.栈区初始化,内存按0xCCCCCCCC(dword)循环填充
push ebp mov ebp,esp sub esp,0E8h push ebx push esi push edi lea edi,[ebp-0E8h] mov ecx,3Ah mov eax,0CCCCCCCCh rep stos dword ptr es:[edi]
2.变量的定义,赋初值
int num=2;mov dword ptr [num],2 int c1=0;mov dword ptr [c1],0 int c2=0;mov dword ptr [c2],0
有关为什么用mov指令为变量初始化参见动态内存管理练习题的反汇编代码分析(底层),不是本文的重点
3.将参数num的写入内存
mov eax,dword ptr [num] mov dword ptr [ebp-0E8h],eax
这个是mov eax,dword ptr [num]未执行前,各个寄存器的值
当mov eax,dword ptr [num]执行后,再观察变动的寄存器的值
EIP寄存器(Extend Instruction Pointer 扩展指令指针寄存器)的值+3,表明mov eax,dword ptr [num]机器码占3个字节,此时EIP指向下一条指令mov dword ptr [ebp-0E8h],eax机器码的第一个字节
不过EIP的值不是我们所关心的,注意看EAX的值为0000 0002
将这两个指令合在一起看
mov dword ptr [num],2
;......mov eax,dword ptr [num]
等价为C语言的
dword ptr[num] = 2;
eax = dword ptr [num];
Q:为什么编译器不直接反汇编成下面的指令,从等价为C语言的eax = 2;呢?
mov eax,2
A:这个是编译器的优化过程,和x86处理器的架构有一定关系,不做深究
mov dword ptr [ebp-0E8h],eax
可以通过内存窗口观察mov的过程
ebp == 00F3FA80,00F3FA80-0E8==00F3F998
打开内存窗口,输入0x00F3F998
发现从0x00F3F998开始,是VS开辟好的栈帧空间(以0xCC填充),等待写入数据
指令mov dword ptr [ebp-0E8h],eax执行后,再次观察
以"dword+小端序"的形式写入eax的值
4.重点指令(cmp+je实现等值比较)的分析
cmp指令
格式:cmp dest,src
作用:比较dest和src的值,将比较后的结果写入标志寄存器(x86下的EFL:Extend FLags),但不改变dest和src原来的值
je指令
格式:je address(32位的地址)
作用:执行时,先去读ZF(Zero Flag)的值,如果ZF==1(说明dest和src的值相等),则转到address处执行,否则继续执行je的下一条指令
cmp dword ptr [ebp-0E8h],1 je main+50h (2513A0h) cmp dword ptr [ebp-0E8h],2 je main+57h (2513A7h) jmp main+5Eh (2513AEh)
cmp dword ptr [ebp-0E8h],1 指令执行后,观察变动的寄存器的值,
果然EFL的值改变
执行je main+50h (2513A0h)时,并没有跳转到main+50h处继续执行,原因是dword ptr [ebp-0E8h]和1不相等
cmp dword ptr [ebp-0E8h],2 指令执行后,观察变动的寄存器的值,
果然EFL的值改变
执行 je main+57h (2513A7h)时,由于dword ptr [ebp-0E8h]和2相等,则跳转到main+57h(即地址0x002513A7)处继续执行
可以通过内存窗口验证上述的说法
右击选择显示代码字节,内存窗口输入0x002513A7
注意到两处的机器码是吻合的,说法正确
5.程序main函数的返回,空间被销毁,交还给操作系统(非本文重点)
xor eax,eax pop edi pop esi pop ebx mov esp,ebp pop ebp ret
如果为sourcecode1的case后都添加break;反汇编指令是什么样的?
sourcecode2的反汇编代码
截取的部分代码
int num=2;mov dword ptr [num],2 int c1=0;mov dword ptr [c1],0 int c2=0;mov dword ptr [c2],0 switch (num)mov eax,dword ptr [num] mov dword ptr [ebp-0E8h],eax cmp dword ptr [ebp-0E8h],1 je main+50h (7513A0h) cmp dword ptr [ebp-0E8h],2 je main+59h (7513A9h) jmp main+60h (7513B0h) {case 1:c1=3;break;mov dword ptr [c1],3 jmp main+60h (7513B0h) case 2:c2=1;break;mov dword ptr [c2],1
大部分的分析过程和sourcecode1一样,不再赘述
如果加break;则在非最后的case处添加一个跳转指令(上方的case 2处没有jmp指令)
那 jmp main+60h (7513B0h) 跳转到哪里执行呢?
如果按C语言的逻辑分析,是跳出switch的结构,来到return 0;处执行
验证说法:
和之前的操作一样,找0x007513B0处的机器码,右击选择显示代码字节,内存窗口输入0x007513B0
return 0;对应的机器码是33 C0,说法正确