从c语言到汇编语言的详细解说

从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总是动态的指向当前程序运行到的地址!它是随着程序运行时不断变化而变化的

 

 

你可能感兴趣的:(c,工作,汇编,开发工具,Borland)