研究递归函数的堆栈表示

 

目的:研究使用递归函数时,所使用的堆栈的数据组织结构及其意义。

 

源代码:

 

#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 frameespebp的内容为:

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

espebp内存中的内容为:

 

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

说明:00401057call之后的指令地址。为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

espebp内存中的内容为:

 

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 frameebp

 

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

说明:00401057call之后的指令地址。为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

espebp内存中的内容为:

 

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 frameebp

 

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

说明:00401057call之后的指令地址。为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

espebp内存中的内容为:

 

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 frameebp

因为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指令。

Ebpjiechen(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 该处的内容为3jiechen(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 该处的内容为4mainjiechen(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) 调用过程中 整个堆栈的表示为

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值