http://blog.csdn.net/wishfly/article/details/5022008
简单的函数调用,通过简单的函数调用反汇编可以清楚了解如下
1.栈到底是什么,如何操纵栈的?
2.参数和临时变量是以什么形式在哪存放?
3.如何传递返回值?
测试代码如下(这是一个简单的通过调用函数计算两数之和的程序):
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <stdio.h> int add(int a, int b) { c = a + b; } int main(void) { x = add(y, z); } |
解释如下
ASM Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
add函数 { ;把main函数的ebp压栈,ebp=1000,esp=896 ;得到“新”栈基址,这里的新的意思是每个函数访问属于自己的一块栈区域,其实是相邻的内存区域,或者说栈只有一个 ;ebp=896,esp=828 0040D757 push esi 0040D758 push edi ;ebp=896,esp=816 0040D75C mov ecx, 11h 0040D761 mov eax, 0CCCCCCCCh 0040D766 rep stos dword ptr [edi] ;初始化内部变量区 ;c放入“新”栈基址 0040D772 add eax, dword ptr [ebp + 0Ch] ;运算结果放入c中 ;用寄存器eax返回结果 ;恢复寄存器的值,ebp=896,esp=828 0040D77B pop edi 0040D77C pop esi 0040D77D pop ebx ;恢复“旧”栈顶地址,ebp=896,esp=896,此函数堆栈被释放! ;恢复“旧”栈基地址,ebp=1000,esp=900,此时恢复到调用前的栈基地址和顶地址 ;返回调用点,ebp=1000,esp=904,调用点地址被弹出,返回到调用点 main函数 { 0040D791 mov ebp, esp ;esp下移,开辟出0x4C字节的空间,这个空间是留给内部参数用的,这个空间的大小随内部变量多少由编译器决定。 ;保存 ebx,esi,edi的值,ebp=1000,esp=924-12=912 0040D797 push esi 0040D798 push edi ;把内部参数占用的空间每个字节都初始化为0xCC,这个是为了在DUBUG程序的方便,编译器加入的 0040D79C mov ecx, 13h 0040D7A1 mov eax, 0CCCCCCCCh 0040D7A6 rep stos dword ptr [edi] ;内部变量放入刚才被初始化为0xCC的内部变量区,x占用四字节在地址9996-9999,y,z一次类推 ;把参数按照stdcall方式从右到左压栈,ebp=1000,esp=912-8=904,z首先入栈在908-911,y在904-907 0040D7C0 push eax 0040D7C1 mov ecx, dword ptr [ebp - 8] 0040D7C4 push ecx ;把返回下一行代码即 0040D7CA [add esp,8] 的地址压栈 ;ebp=1000,esp=912,恢复到压入参数前栈基地址和顶地址,这个步骤叫做堆栈修正 ;返回的变量放到x中 16: return 0; |
main函数堆栈变化示意图:
add函数堆栈变化示意图:
现在来总结开始提出的三个问题
1.栈到底是什么,如何操纵栈的?
栈是操作系统分配给程序运行的一块内存区域,有以下特点:
1.1、改变堆栈用push, pop,用的esp栈顶指针,而读指针则用ebp栈基指针灵活访问
1.2、每当一个函数跳转到另一个函数时,会在上一个函数用到的栈空间下方开辟空间
2.参数和临时变量是以什么形式在哪存放?
2.1、参数放在旧栈的返回地址和旧栈基地址的上方,而临时变量则在新栈的最上方处,变量名会被编译器连接一个地址,程序在被编译成汇编以后,变量名就是虚无了。
3.如何传递返回值?
3.1、传递一个值的情况下,通过eax传递
可以看出,栈溢出是由于编译器没有检查栈是否还有空间。