汇编看C函数调用

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)
{
    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;
}
 
 
解释如下
 
 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函数
{
;ebp=1000,esp=900
    0040D750   push        ebp

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

;得到“新”栈基址,这里的新的意思是每个函数访问属于自己的一块栈区域,其实是相邻的内存区域,或者说栈只有一个
    ;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放入“新”栈基址
    ;因为“新”栈基地址就是“旧”栈顶地址,所以通过ebp访问传过来的参数
    ;ebp+8到ebp+c是因为ebp上方的8字节用于存储调用函数的调用地址和“旧”堆栈基地址了
    ;ebp-4则位于新栈中
6:  c = a + b;
    0040D76F   mov         eaxdword ptr [ebp + 8]
    0040D772   
add         eaxdword ptr [ebp + 0Ch]

;运算结果放入c中
    0040D775   mov         dword ptr [ebp - 4], eax

;用寄存器eax返回结果
7:  return c;
    0040D778   mov         eaxdword ptr [ebp - 4]

;恢复寄存器的值,ebp=896,esp=828
8:  }
0040D77B   
pop         edi
0040D77C   
pop         esi
0040D77D   
pop         ebx

;恢复“旧”栈顶地址,ebp=896,esp=896,此函数堆栈被释放!
0040D77E   mov         espebp

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

;返回调用点,ebp=1000,esp=904,调用点地址被弹出,返回到调用点
0040D781   ret



main函数

{
;用栈顶地址作为栈基地址,目的是不和调用前栈空间冲突
    ;为了叙述方便假设原 ebp=1004,esp=1004
    ;执行完下面两行汇编语句后 ebp=1000,esp=1000
    0040D790   push        ebp
    0040D791   
mov         ebpesp

;esp下移,开辟出0x4C字节的空间,这个空间是留给内部参数用的,这个空间的大小随内部变量多少由编译器决定。
    ;ebp=1000,esp=1000-0x4C=924
    0040D793   sub         esp, 4Ch

;保存 ebx,esi,edi的值,ebp=1000,esp=924-12=912
    0040D796   push        ebx
    0040D797   
push        esi
    0040D798   
push        edi

;把内部参数占用的空间每个字节都初始化为0xCC,这个是为了在DUBUG程序的方便,编译器加入的
    ;如果不在DEBUG状态下,这个区域是没有被初始化的,也就是说是随机值。
    0040D799   lea         edi, [ebp - 4Ch]
    0040D79C   
mov         ecx, 13h
    0040D7A1   
mov         eax, 0CCCCCCCCh

    0040D7A6   
rep stos    dword ptr [edi]

;内部变量放入刚才被初始化为0xCC的内部变量区,x占用四字节在地址9996-9999,y,z一次类推
12int x = 0;
    0040D7A8   mov         dword ptr [ebp - 4], 0
13int y = 3;
    0040D7AF   mov         dword ptr [ebp - 8], 3
14int z = 4;
    0040D7B6   mov         dword ptr [ebp - 0Ch], 4

;把参数按照stdcall方式从右到左压栈,ebp=1000,esp=912-8=904,z首先入栈在908-911,y在904-907
15: x = add(y, z);
    0040D7BD   mov         eaxdword ptr [ebp - 0Ch]
    0040D7C0   
push        eax
    0040D7C1   
mov         ecxdword ptr [ebp - 8]
    0040D7C4   
push        ecx

;把返回下一行代码即 0040D7CA [add esp,8] 的地址压栈
    ;转向add函数,ebp=1000,esp=900,看add函数
    0040D7C5   call        @ILT + 15(_add) (00401014)

;ebp=1000,esp=912,恢复到压入参数前栈基地址和顶地址,这个步骤叫做堆栈修正
    0040D7CA   add         esp8

;返回的变量放到x中
    0040D7CD   mov         dword ptr [ebp - 4], eax

16: return 0;
17: }
 
main函数堆栈变化示意图:
add函数堆栈变化示意图:

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

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

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

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

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

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

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

3.如何传递返回值?

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

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

 





附件列表

原文地址:https://www.cnblogs.com/fengkang1008/p/4732284.html