C和汇编的对应—main之前之后的汇编到底在干什么
Posted on 2010年07月21日 by sstompkins
下面以一个非常简洁的C来进行讲解ASM的实现方式
查看汇编语言实现方法,把程序设置为debug,在int main()行首添加断点打开IDE集成开发工具的CUP视图,进行逐行的运行,和查看你的代码
用这种方法我们可以非常方便的弄清楚C,C++的语句在汇编下是怎么实现的这种方法可以非常方便的让我们学懂汇编指令
1.用C实现一个简单的函数调用功能
//filename: demo.c
//0x11=17, 0x21=33,17+23=50
//run value add(9,20) is :50
#include
int add(int x,int y){return (x+y);}
int main()
{
int value;
value=add(0x11,0x21);
printf("add(9,20) is :%d",value);
return 0;
}
2.编译器自动实现的汇编代码如下:
代码左边 C函数体的运行位置
//int main(){
push ebp
mov ebp,esp
//value=add(0x11,0x21)
push 0x11
push 0x21
//call add(int,int)
//int add(int x,int y){
push ebp
mov ebp,esp
//return (x+y);
mov eax,[ebp+ox08]
add eax,[ebp+ox0c]
//}
pop ebp
ret
//value=add(0x11,0x21)
add esp,0x08
push eax
push 0x0040a0e8
//call printf(const ….)
add esp,0x08
//return 0;
xor eax,eax
//}
pop ebp
ret
3.汇编按行一一讲解明细
ebp基地址指针寄存器,常用于指向程序段的入口地址!
esp堆栈指针寄存器,常用于指向堆栈顶元素,也就是每条指令的操作对象,还记得我们之前用C++实现的堆栈吗?
esp简单的讲就是每次指令操作对象的内存地址,每执行一个指令它都会变化的
————————————————————————————————————–
//此代码未运行之前:ebp=0012FFB8,esp=0012FF90,堆栈数据未知
//int main(){
push ebp //是ebp的地址压入堆栈保存起来,esp跟踪堆栈顶元素,由于数据在内存是先从高地址往低地址存放数据
//esp减4位(0012FF90-4=0012FF8C)esp=0012FF8C
mov ebp,esp //esp的地址复制到ebp结果ebp,esp是存放同一地址
程序说明:从C的int main(){开始计算
//此代码未运行之后:ebp=0012FF8C,esp=0012FF8C,堆栈数据是0012FFB8
(push ebp),(mov ebp,esp)这两句是保存当前程序现场,以方便用pop ebp,ret来恢复现场
堆栈压入0012FFB8,它是执行这个int main()之前的程序入口地址
————————————————————————————————————-
//value=add(0x11,0x21)
push 0x11 //把0x11(17)压入堆栈,esp减4位(0012FF8C-4=0012FF88)esp=0012FF88
//0x11存入地址:0012FF88
push 0x21 //把0x21(33)压入堆栈,esp减4位(0012FF88-4=0012FF84)esp=0012FF84
//0x21存入地址:0012FF84
程序说明:从C的value=add(0x11,0x21)开始计算
//以上两行读入参数顺序是先右边-后左,先读取0x11,再读取0x21
//就是著名的函数的参数调用是用堆栈来传递的实事证明
//此代码运行之前:ebp=0012FF8C,esp=0012FF8C,堆栈数据是0012FFB8
//此代码运行之后:ebp=0012FF8C,esp=0012FF84,堆栈数据是0012FFB8
————————————————————————————————————-
//call add(int,int) //esp自然减4位(0012FF84-4=0012FF80)esp=0012FF80
//int add(int x,int y){
push ebp //ebp的地址压入堆栈,esp自然减4位(0012FF80-4=0012FF7C)esp=0012FF7C
mov ebp,esp //esp的地址复制到ebp结果ebp,esp是存放同一地址
程序说明:从C的int add(int x,int y){开始计算
//此代码运行之前:ebp=0012FF7C,esp=0012FF90,堆栈数据是0012FFB8
//此代码运行之后:ebp=0012FF7C,esp=0012FF7C,堆栈数据是0012FF8C,0012FFB8
(push ebp),(mov ebp,esp)这两句是保存当前程序现场,以方便用pop ebp,ret来恢复现场
堆栈压入0012FF8C,它是执行这个int add(int x,int y)之前的程序入口地址
————————————————————————————————————-
//用EAX进行两个数据的加法运算
//return (x+y);
mov eax,[ebp+ox08] //把数据0x21,移入eax
add eax,[ebp+ox0c] //把数据0x11,与eax原来数据进行累加之后再送入eax,结果eax=0x32也就是50
//[ebp+0x08]怎么得到0x21,计算如下[ebp+ox08]=0012FF7C+8=0012FF84,而0012FF84存放的是0x21,由之间的push 0x21
//[ebp+0x0c]怎么得到0x11,计算如下[ebp+ox0c]=0012FF7C+c=0012FF88,而0012FF88存放的是0x11,由之间的push 0x11
//因计算是针对eax,所以ebp,esp都没有变化
//此代码运行之前:ebp=0012FF7C,esp=0012FF7C,堆栈数据是0012FFB8
//此代码运行之后:ebp=0012FF7C,esp=0012FF7C,堆栈数据是0012FF8C,0012FFB8
————————————————————————————————————-
//}
pop ebp //把堆栈顶数据弹出到ebp,堆栈数据是0012FF8C,结果ebp=0012FF8C,
//esp+4位(0012FF7C+4=0012FF80)esp=0012FF80
ret //退出函数调用,返回到int main() 代码中来ret,
//value=add(0x11,0x21)
add esp,0x08 //esp+8位(0012FF80+8=0012FF8C)esp=0012FF8C,现在ebp=esp=0012FF8C
//退出函数调用之后的收尾动作
//此代码运行之前:ebp=0012FF7C,esp=0012FF7C,堆栈数据是0012FFB8
//此代码运行之后:ebp=0012FF8C,esp=0012FF8C,堆栈数据是0012FFB8
————————————————————————————————————-
push eax //把eax地址保存起来
push 0x0040a0e8 //把50的内存地址压入堆栈,让printf函数去调用并显示出来
//call printf(const ….) //进行下面的printf函数调用,实现明细如上
add esp,0x08 //把esp的内存地址和ebp对齐到一起esp=ebp
//return 0;
xor eax,eax //把eax复位为0,没有保存任何地址
//此代码运行之前:ebp=0012FF7C,esp=0012FF7C,堆栈数据是0012FFB8
//此代码运行之后:ebp=0012FF8C,esp=0012FF8C,堆栈数据是0012FFB8
————————————————————————————————————-
//} //在执行完int main(){}最后一个}后,把原来的地址复原
pop ebp //把堆栈顶数据弹出到ebp,堆栈数据是0012FFB8,结果ebp=0012FFB8,
//esp+4位(0012FF8C+4=0012FF90)esp=0012FF90
ret //退出int main()函数调用
//最后此代码运行之后:ebp=0012FFB8,esp=0012FF90,堆栈数据未知
//首行此代码未运行之前:ebp=0012FFB8,esp=0012FF90,堆栈数据未知
调用完main()函数后,ebp,esp又得到原来的地址,它就可以继续执行原来未运行完的其它程序
调用各个函数时,ebp,esp的工作总结
1.ebp的工作方式调用一个程序的每一个函数时,都是调用之前用堆栈保存好ebp,调用之后,又恢复ebp
2.esp的工作方式也是跟着ebp的变化相互配合,都是调用之前用esp取函数指令地址,调用之后,又恢复到原来
esp总是动态的指向当前程序运行到的地址!它是随着程序运行时不断变化而变化的