转载代码:https://blog.csdn.net/tangyanzhi1111/article/details/8931611
参考链接:VC++6.0反汇编代码
今天写一个最初带我进入C语言的一个代码吧–Hello World代码。
#include "stdafx.h"
int main(int argc, char* argv[])
{
printf("Hello World!\n");
return 0;
}
如上,这个程序很简单,但是对于他其中蕴含的汇编代码却很少被关注到了。接下来让我们一起去看一下这个程序的反汇编吧!
//几个寄存器的含义
ebp:基址指针 esp:堆栈指针
eax:累加器 ebx:基址寄存器
ecx:计数寄存器 edx:数据寄存器
esi:源变址寄存器 edi:目的变址寄存器
eip:指令指针
_chkesp:用于检测堆栈平衡的函数,如果esp不等于函数调用前保存的值,就会转到错误处理代码。
int3:留给调试工具使用的中断,调试工具运行后会替换int3的向量。
首先,先来解释几个简单的语句吧。
push ebp
mov ebp, esp
push:把一个32位操作数压入栈中,执行完后,栈顶指针esp减4,ebp用来保存push执行后esp的值,执行完毕后,用ebp回复esp。同时,调用此函数上层函数也用ebp做同样的事情,先push入栈,返回之前弹出,避免ebp被我们改动。
xor eax, eax
ret
xor eax,eax常用来代替mov eax,0。清零操作。在windows中,函数返回值都是放在eax中然后返回,外部从eax中得到返回值。这就代表return 0操作。
lea edi, [ebp-40h]
lea取得第二个参数代表的地址放入第一个参数代表的寄存器中。
mov ecx, 10h
mov eax, 0CCCCCCCh
rep stos dword ptr es:[edi]
stos是串存储指令,它的功能是将eax中的数据放入 [edi] 所指的地址中,同时 [edi] 会增加4个字节。Rep使指令重复执行ecx中填写的次数。
由于CPU的寄存器有限,而且操作寄存器会影响标志值,push作用是压栈,pop是退栈。即保存寄存器标志的值和寄存器本身的值,以便在函数调用完毕后恢复原有的标志值。这也是为什么我们见到在调用某个函数或者运行一个程序时,入口总是push一堆寄存器的东东,返回函数再 pop 一堆寄存器,也就是这个原因。
如下,反汇编。
6: int main(int argc, char* argv[])
7: {
0040D6F0 55 push ebp
>>保存esp,返回之前弹出,避免ebp被我们改动,push操作使esp减小,esb不变
0040D6F1 8B EC mov ebp,esp
>>用来保存函数执行前的esp的值,执行完毕后,用ebp恢复esp,原ebp值已经被压栈(位于栈顶),而新的ebp又恰恰指向栈顶。此时ebp寄存器就已经处于一个非常重要的地位,该寄存器中存储着栈中的一个地址(原ebp入栈后的栈顶),从该地址为基准,向上(栈底方向)能获取返回地址、参数值(假如main中有参数,“获取参数值“会比较容易理解);向下(栈顶方向)能获取函数局部变量值,而该地址又存储着上一层函数调用时ebp的值
0040D6F3 83 EC 40 sub esp,40h
>>把esp向上移动一个范围,等于在栈中开辟一片空间存储main函数的局部变量;由于冯诺依曼机是小端模式,所以sub操作可以理解为将esp栈顶减去,实际上是为栈增加空间
0040D6F6 53 push ebx
0040D6F7 56 push esi
0040D6F8 57 push edi
>>保存三个寄存器的值,待main结束恢复,原来的值被破坏有可能引起系统崩溃
0040D6F9 8D 7D C0 lea edi,[ebp-40h]
>>把ebp-040h加载到edi中,目的是保存局部变量的区域
0040D6FC B9 10 00 00 00 mov ecx,10h
0040D701 B8 CC CC CC CC mov eax,0CCCCCCCCh
0040D706 F3 AB rep stos dword ptr [edi]
>>从ebp-040h开始的局部变量空间区域加载初始化全部变成0CCCCCCCCh(也就是int 3中断指令的机器码);局部变量不可能被执行,执行了就会出错,这样发生意外时执行堆栈里面的内容会引发调试中断提示开发者return 0
8: printf("Hello World!\n");
0040D708 68 1C 20 42 00 push offset string "%d" (0042201c)
0040D70D E8 4E 39 FF FF call printf (00401060)
0040D712 83 C4 04 add esp,4
9:
10: return 0;
0040D715 33 C0 xor eax,eax
>>返回值将放在eax返回(因为eax是可以更改的,所以这就是很多如阿健给藐视报批的原因)
11: }
0040D717 5F pop edi
0040D718 5E pop esi
0040D719 5B pop ebx
>>恢复原来寄存器的值,怎么push进去的,倒序pop出来
0040D71A 83 C4 40 add esp,40h
>>将esp回到程序开始执行mov ebp, esp时的状态,相当于销毁函数最开始分配的用于保存局部变量的栈空间
0040D71D 3B EC cmp ebp,esp
>>比较ebp和esp的值,确定是否相等
0040D71F E8 BC 39 FF FF call __chkesp (004010e0)
>>调用__chkesp()函数,检查esp与函数之前实现开始时状态是否一致
0040D724 8B E5 mov esp,ebp
>>由于调用__chkesp()函数后,esp的值发生了变化,因此需要mov恢复esp栈顶指针
0040D726 5D pop ebp
>>恢复ebp,也就是恢复调用函数之前各个寄存器的状态
0040D727 C3 ret
>>将返回地址存入eip,退出主函数