目的:研究使用递归函数时,所使用的堆栈的数据组织结构及其意义。
源代码:
#include <stdio.h>
#include <windows.h>
int jiechen(int n)
{
if (n==0||n==1) return 1;
int j = n*jiechen(n-1);
return j;
}
void main()
{
int n =4;
int j = jiechen(n);
return;
}
编译环境 Visual c++ 6.0
所产生的汇编代码为:
int jiechen(int n)
{
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,44h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-44h]
0040102C mov ecx,11h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
if (n==0||n==1) return 1;
00401038 cmp dword ptr [ebp+8],0
0040103C je jiechen+24h (00401044)
0040103E cmp dword ptr [ebp+8],1
00401042 jne jiechen+2Bh (0040104b)
00401044 mov eax,1
00401049 jmp jiechen+46h (00401066)
int j = n*jiechen(n-1);
0040104B mov eax,dword ptr [ebp+8]
0040104E sub eax,1
00401051 push eax
00401052 call @ILT+10(fib_rec) (0040100f)
00401057 add esp,4
0040105A mov ecx,dword ptr [ebp+8]
0040105D imul ecx,eax
00401060 mov dword ptr [ebp-4],ecx
return j;
00401063 mov eax,dword ptr [ebp-4]
}
00401066 pop edi
00401067 pop esi
00401068 pop ebx
00401069 add esp,44h
0040106C cmp ebp,esp
0040106E call __chkesp (004010f0)
00401073 mov esp,ebp
00401075 pop ebp
00401076 ret
void main()
28: {
004010A0 push ebp
004010A1 mov ebp,esp
004010A3 sub esp,48h
004010A6 push ebx
004010A7 push esi
004010A8 push edi
004010A9 lea edi,[ebp-48h]
004010AC mov ecx,12h
004010B1 mov eax,0CCCCCCCCh
004010B6 rep stos dword ptr [edi]
29: int n =4;
004010B8 mov dword ptr [ebp-4],4
30:
31: int j = jiechen(n);
004010BF mov eax,dword ptr [ebp-4]
004010C2 push eax
004010C3 call @ILT+10(fib_rec) (0040100f)
004010C8 add esp,4
004010CB mov dword ptr [ebp-8],eax
32:
33: return;
34: }
004010CE pop edi
004010CF pop esi
004010D0 pop ebx
004010D1 add esp,48h
004010D4 cmp ebp,esp
004010D6 call __chkesp (004010f0)
004010DB mov esp,ebp
004010DD pop ebp
004010DE ret
分析程序执行的时候,其使用的堆栈动态变化。
分析第一步:
1、 main函数的stack frame
main函数004010C3 call @ILT+10(fib_rec) (0040100f)之前的stack frame表现如下:
其中寄存器ESP = 0012FF28 EBP = 0012FF80
观察内存中main函数stack frame从esp到ebp的内容为:
0012FF28 04 00 00 00 00 00 00 00 B8 F8 75 02 00 E0 FD 7F
0012FF38 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FF48 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FF58 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FF68 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FF78 CC CC CC CC 04 00 00 00 C0 FF 12 00
其中所做的工作包括;
将n变量的值保存在ebp-4地址处。
将变量n的值压入推栈0X0012ff28处,是调用下一个函数的参数。
分析第二步:
004010C3 call @ILT+10(fib_rec) (0040100f)
观察call 命令执行之后main函数的堆栈变化,和jichen(4)的stack frame堆栈变化:
1、 观察esp的值变为ESP = 0012FF24 观察esp处的内容为:
0012FF24 C8 10 40 00 其为main函数中
004010C3 call @ILT+10(fib_rec) (0040100f)
004010C8 add esp,4
call 之后add 的地址。
这一步说明将返回压入堆栈,为jichen函数ret作准备。
将ebp入栈:
0012FF20 80 FF 12 00
2、 观察jichen(4)函数的stack frame
int jiechen(int n)
{
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,44h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-44h]
0040102C mov ecx,11h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
以上代码执行后堆栈如文档最后附图:
寄存器:ESP = 0012FED0 EBP = 0012FF20
从esp到ebp内存中的内容为:
0012FED0 80 FF 12 00 B8 F8 75 02 00 E0 FD 7F CC CC CC CC .
0012FEE0 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FEF0 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FF00 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FF10 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FF20 80 FF 12 00
第三步:n= 4
int j = n*jiechen(n-1);
0040104B mov eax,dword ptr [ebp+8]
0040104E sub eax,1
00401051 push eax
对jiechen(4)堆栈的影响:
0012FECC 03 00 00 00
对寄存器的影响:ESP = 0012FECC
00401052 call @ILT+10(fib_rec) (0040100f)
之后 堆栈的变化:
0012FEC8 57 10 40 00
寄存器的变化:ESP = 0012FEC8
说明:00401057是call之后的指令地址。为ret作准备。
00401052 call @ILT+10(fib_rec) (0040100f)
00401057 add esp,4
0040105A mov ecx,dword ptr [ebp+8]
第四步:jichen(3)的堆栈变化:
int jiechen(int n)
{
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,44h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-44h]
0040102C mov ecx,11h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
以上代码执行后堆栈如文档最后附图:
寄存器:ESP = 0012FE74 EBP = 0012FEC4
从esp到ebp内存中的内容为:
0012FE74 20 FF 12 00 B8 F8 75 02 00 E0 FD 7F CC CC CC CC
0012FE84 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FE94 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FEA4 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FEB4 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FEC4 20 FF 12 00
说明:ebp = 0012FEC4 处的内容为 20 FF 12 00刚好是jiechen(4)的stack frame的ebp。
int j = n*jiechen(n-1);
0040104B mov eax,dword ptr [ebp+8]
0040104E sub eax,1
00401051 push eax
执行之后:ESP = 0012FE70 EBP = 0012FEC4
0012FE70 02 00 00 00 为 jiechen(2)作参数准备。
00401052 call @ILT+10(fib_rec) (0040100f)
之后 堆栈的变化:
0012FE6C 57 10 40 00
寄存器的变化:ESP = 0012FE6C EBP = 0012FEC
说明:00401057是call之后的指令地址。为ret作准备。
00401052 call @ILT+10(fib_rec) (0040100f)
00401057 add esp,4
0040105A mov ecx,dword ptr [ebp+8]
第五步:jiechen(2)的堆栈变化。
int jiechen(int n)
{
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,44h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-44h]
0040102C mov ecx,11h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
以上代码执行后堆栈如文档最后附图:
寄存器:ESP = 0012FE18 EBP = 0012FE68
从esp到ebp内存中的内容为:
0012FE18 C4 FE 12 00 B8 F8 75 02 00 E0 FD 7F CC CC CC CC
0012FE28 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FE38 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FE48 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FE58 CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FE68 C4 FE 12 00
说明:ebp = 0012FE68 处的内容为 C4 FE 12 00刚好是jiechen(3)的stack frame的ebp。
int j = n*jiechen(n-1);
0040104B mov eax,dword ptr [ebp+8]
0040104E sub eax,1
00401051 push eax
执行之后:ESP = 0012FE14 EBP = 0012FE68
0012FE14 01 00 00 00 为 jiechen(1)作参数准备。
00401052 call @ILT+10(fib_rec) (0040100f)
之后 堆栈的变化:
ESP = 0012FE10 57 10 40 00
寄存器的变化:ESP = 0012FE10 EBP = 0012FE68
说明:00401057是call之后的指令地址。为ret作准备。
00401052 call @ILT+10(fib_rec) (0040100f)
00401057 add esp,4
0040105A mov ecx,dword ptr [ebp+8]
第六步:jiechen(1)的堆栈变化。
int jiechen(int n)
{
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,44h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-44h]
0040102C mov ecx,11h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
以上代码执行后堆栈如文档最后附图:
寄存器:ESP = 0012FDBC EBP = 0012FE0C
从esp到ebp内存中的内容为:
0012FDBC 68 FE 12 00 B8 F8 75 02 00 E0 FD 7F CC CC CC CC
0012FDCC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FDDC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FDEC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FDFC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
0012FE0C 68 FE 12 00
说明:ebp = 0012FE0C 处的内容为 68 FE 12 00刚好是jiechen(2)的stack frame的ebp。
因为n =1,现在的执行流程有些不一样:
00401044 mov eax,1
00401049 jmp jiechen+46h (00401066)
00401066 pop edi
00401067 pop esi
00401068 pop ebx
00401069 add esp,44h
0040106C cmp ebp,esp
0040106E call __chkesp (004010f0)
00401073 mov esp,ebp
00401075 pop ebp
00401076 ret
此时,活动的堆栈帧为:jiechen(1)
以上指令的作用为:去掉jiechen(1)的堆栈帧,将jiechen(2)的堆栈帧设为活动的堆栈帧,
通过代码mov esp,ebp 和pop ebp做到。
分析ret指令的作用:
pop函数执行之后,相关寄存器的值为:
EIP = 00401076 ESP = 0012FE10
EBP = 0012FE68
其中eip即为将要执行的ret指令。
Ebp为jiechen(2)的栈帧基址。
Esp 为jiechen(2)的栈指针所指的地方。
Ret执行之后:
EIP = 00401057 ESP = 0012FE14
EBP = 0012FE68
其中esp增大4字节,由jiechen(2)的堆栈分析可知0012FE10地方保存的值为00401057
可以推断,ret指令的作用为:将ESP = 0012FE10处的内容赋值给eip。
现在函数在jiechen(2)中执行:
0040105A mov ecx,dword ptr [ebp+8]
0040105D imul ecx,eax
00401060 mov dword ptr [ebp-4],ecx
ebp+8 值为0x0012fe70 该处的内容为2 在jiechen(3)中为jiechen(2)准备。
然后将2*jiechen(1)的值保存在jiechen(2)的ebp-4地址处。
为下一步计算作准备。
7: return j;
00401063 mov eax,dword ptr [ebp-4]
将计算结果赋值给exa为下一步计算作准备。
00401066 pop edi
00401067 pop esi
00401068 pop ebx
00401069 add esp,44h
0040106C cmp ebp,esp
0040106E call __chkesp (004010f0)
00401073 mov esp,ebp
00401075 pop ebp
00401076 ret
上面指令的作用为:
将jiechen(2)堆栈摧毁,设当前活动堆栈为jeichen(3),
函数在jiechen(3)中执行
0040105A mov ecx,dword ptr [ebp+8]
0040105D imul ecx,eax
00401060 mov dword ptr [ebp-4],ecx
ebp+8 值为0x0012FEC4 该处的内容为3在jiechen(4)为jiechen(3)准备。
然后将3*jiechen(2)的值保存在jiechen(3)的ebp-4地址处。
为下一步计算作准备。
7: return j;
00401063 mov eax,dword ptr [ebp-4]
将计算结果赋值给exa = 6为下一步计算作准备。
00401066 pop edi
00401067 pop esi
00401068 pop ebx
00401069 add esp,44h
0040106C cmp ebp,esp
0040106E call __chkesp (004010f0)
00401073 mov esp,ebp
00401075 pop ebp
00401076 ret
上面指令的作用为:
将jiechen(3)堆栈摧毁,设当前活动堆栈为jeichen(4),
函数在jiechen(4)中执行
0040105A mov ecx,dword ptr [ebp+8]
0040105D imul ecx,eax
00401060 mov dword ptr [ebp-4],ecx
ebp+8 值为0x0012FF28 该处的内容为4在main为jiechen(4)准备。
然后将4*jiechen(3)的值保存在jiechen(4)的ebp-4地址处。
为下一步计算作准备。
7: return j;
00401063 mov eax,dword ptr [ebp-4]
将计算结果赋值给exa = 24为下一步计算作准备。
00401066 pop edi
00401067 pop esi
00401068 pop ebx
00401069 add esp,44h
0040106C cmp ebp,esp
0040106E call __chkesp (004010f0)
00401073 mov esp,ebp
00401075 pop ebp
00401076 ret
上面代码的作用是返回到main函数中。计算的值由exa保存。
从main函数到jiechen(1) 调用过程中 整个堆栈的表示为