从c语言到汇编语言的详细解说
author:chinayaosisr
blog:http://blog.csdn.net/chinayaosir
测试环境:Borland C++5.0,
测试时间:09/15/2007整理出来的汇编资料
能够及时的看懂我们的C源代码在汇编语言的实现方式,
就可以非常方便的优化我们的C,C++源代码提高代码的高效和明了系统的底层实现
下面以一个非常简洁的C来进行讲解ASM的实现方式
查看汇编语言实现方法,
把程序设置为debug,
在int main()行首添加断点打开IDE集成开发工具的CUP视图,
进行逐行的运行,和查看你的代码
用这种方法我们可以非常方便的弄清楚C的语句在汇编下是怎么实现的
这种方法可以非常方便的让我们学懂汇编指令
1.用C实现一个简单的函数调用功能
//filename: demo.c
//0x11=17, 0x21=33,17+23=50
//run value add(9,20) is :50
#include <stdio.h>
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堆栈指针寄存器,用于指向堆栈顶元素,也就是每条指令的操作对象
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()之前的程序入口地址
-------------------------------------------------------------------------------------------------------------
//操作系统程序运行堆栈操作细节
堆栈push内存地址的方向是从高地址向低地址入栈,压入一个数据,物理地址按数据长度自动减少N字节
堆栈pop 内存地址的方向是从低地址向高地址出栈,弹出一个数据,物理地址按数据长度自动增加N字节
//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原来数据进行累加add之后再送入eax,结果eax=0x32也就是50
//[ebp+ox08]怎么得到0x21,计算如下[ebp+ox08]=0012FF7C+8=0012FF84,而0012FF84存放的是0x21,由之间的push 0x21
//[ebp+ox0c]怎么得到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类似图灵机的读写头位置
esp总是动态的指向当前程序运行到的地址!它是随着程序运行时不断变化而变化的