汇编相关 -by codesnail

来源:互联网 发布:vb调用打印机页面设置 编辑:程序博客网 时间:2024/05/17 21:38

 

 

简单的函数调用,通过简单的函数调用反汇编可以清楚了解如下

1.栈到底是什么,如何操纵栈的?

2.参数和临时变量是以什么形式在哪存放?

3.如何传递返回值?


举例:

#include <stdio.h>


int add(int a,int b)
{
     int c=0;
     c=a+b;
     return c;
}

int main(void)
{
     int x=0;
     int y=3;
     int z=4;
     x=add(y,z);
     return 0;
}

这是一个简单的通过调用函数计算两数之和的程序

VC6.0生成的汇编代码如下:

add函数

{


0040D750   push        ebp

//把main函数的ebp压栈,ebp=1000,esp=896
0040D751   mov         ebp,esp

//得到“新”栈基址,这里的新的意思是每个函数访问属于自己的一块栈区域,其实是相邻的内存区域,或者说栈只有一个。ebp=896,esp=896
0040D753   sub         esp,44h

//ebp=896,esp=828
0040D756   push        ebx
0040D757   push        esi
0040D758   push        edi

//ebp=896,esp=816
0040D759   lea         edi,[ebp-44h]
0040D75C   mov         ecx,11h
0040D761   mov         eax,0CCCCCCCCh
0040D766   rep stos    dword ptr [edi]

//初始化内部变量区
5:        int c=0;
0040D768   mov         dword ptr [ebp-4],0

//c放入“新”栈基址
6:        c=a+b;
0040D76F   mov         eax,dword ptr [ebp+8]
0040D772   add         eax,dword ptr [ebp+0Ch]

//因为“新”栈基地址就是“旧”栈顶地址,所以通过ebp访问传过来的参数,ebp+8到ebp+c是因为ebp上方的8字节用于存储调用函数的调用地址和“旧”堆栈基地址了。
0040D775   mov         dword ptr [ebp-4],eax

//运算结果放入c中
7:        return c;
0040D778   mov         eax,dword ptr [ebp-4]

//用寄存器eax返回结果
8:    }
0040D77B   pop         edi
0040D77C   pop         esi
0040D77D   pop         ebx

//恢复寄存器的值,ebp=896,esp=828
0040D77E   mov         esp,ebp

//恢复“旧”栈顶地址,ebp=896,esp=896,此函数堆栈被释放!
0040D780   pop         ebp

//恢复“旧”栈基地址,ebp=1000,esp=900,此时恢复到调用前的栈基地址和顶地址

0040D781   ret

//返回调用点,ebp=1000,esp=904,调用点地址被弹出,返回到调用点

 

main函数

{
0040D790   push        ebp
0040D791   mov         ebp,esp

//用栈顶地址作为栈基地址,目的是不和调用前栈空间冲突,为了叙述方便假设此时的栈基址ebp=1000,esp=1000。
0040D793   sub         esp,4Ch

//esp下移,开辟出0x4C字节的空间,这个空间是留给内部参数用的,这个空间的大小随内部变量多少由编译器决定。ebp=1000,esp=1000-0x4C=924
0040D796   push        ebx
0040D797   push        esi
0040D798   push        edi

//保护 ebx,esi,edi的值,ebp=1000,esp=924-12=912
0040D799   lea         edi,[ebp-4Ch]
0040D79C   mov         ecx,13h
0040D7A1   mov         eax,0CCCCCCCCh

0040D7A6   rep stos    dword ptr [edi]

//把内部参数占用的空间每个字节都初始化为0xCC,这个是为了在DUBUG程序的方便,编译器加入的,如果不在DEBUG状态下,这个区域是没有被初始化的,也就是说是随机值。
12:       int x=0;
0040D7A8   mov         dword ptr [ebp-4],0
13:       int y=3;
0040D7AF   mov         dword ptr [ebp-8],3
14:       int z=4;
0040D7B6   mov         dword ptr [ebp-0Ch],4

//内部变量放入刚才被初始化为0xCC的内部变量区,x占用四字节在地址9996-9999,y,z一次类推
15:       x=add(y,z);
0040D7BD   mov         eax,dword ptr [ebp-0Ch]
0040D7C0   push        eax
0040D7C1   mov         ecx,dword ptr [ebp-8]
0040D7C4   push        ecx

//把参数按照stdcall方式从右到左压栈,ebp=1000,esp=912-8=904,z首先入栈在908-911,y在904-907
0040D7C5   call        @ILT+15(_add) (00401014)

//把返回下一行代码即 add esp,8 的地址压栈,转向add函数,ebp=1000,esp=900,看add函数
0040D7CA   add         esp,8

//ebp=1000,esp=912,恢复到压入参数前栈基地址和顶地址,这个步骤叫做堆栈修正
0040D7CD   mov         dword ptr [ebp-4],eax

//返回的变量放到x中
16:       return 0;
17:   }

 


现在来总结开始提出的三个问题

1.栈到底是什么,如何操纵栈的?

   栈是操作系统分配给程序运行的一块内存区域,有以下特点

   1.1、改变堆栈用push, pop,用的esp栈顶指针,而读指针则用ebp栈基指针灵活访问

   1.2、每当一个函数跳转到另一个函数时,会在上一个函数用到的栈空间下方开辟空间

2.参数和临时变量是以什么形式在哪存放?

   2.1、参数放在旧栈的返回地址和旧栈基地址的上方,而临时变量则在新栈的最上方处,变量名会被编译器连接一个地址,程序在被编译成汇编以后,变量名就是虚无了。

3.如何传递返回值?

   3.1、传递一个值的情况下,通过eax传递

可以看出,栈溢出是由于编译器没有检查栈是否还有空间。

 

原创粉丝点击