易语言教程_易语言源码_易语言视频教程_易语言论坛

 找回密码
 点击注册

Vip新手入门区
新手学习指南  学员作品展示 Vip课程总纲  Vip绝密课程系列

Vip相关下载区
Vip模块下载   Vip模块绑定   Vip模块例子 魔鬼插件下载  魔鬼插件例子  教程工具下载

Vip论坛服务区
教程问题提问区   模块问题提问区 技术交流区   魔鬼插件建议   忘记密码找回

VIP会员办理QQ: 8643245   
【请先加好友,然后到好友列表双击联系客服,办理VIP会员。】
【基础篇】易语言辅助入门基础教程
VIP模块办理QQ: 7189694 办理正版魔鬼作坊VIP模块 【基础篇】OD与CE入门基础教程
办理【终身VIP会员】“秒杀价” 仅需 RMB278.00元… 【基础篇】零基础绝密汇编语言入门课程 (共26课已完成)…
办理VIP详情…猛击这里查看详情 【基础篇】VIP辅助入门基础教程-新手必学 已发布10课 ……
VIP教程免费试看章节…猛击下载 【第1款】制作“辅助挂”教程目录查看(共107+16_x64下更新课已完成)…
亲爱的VIP学员,请到此写下你学习的感受与发布作品截图… 【第2款】制作“任务挂”教程目录查看(共77+1_x64下更新课已完成)…
卍解吧!不用bp send类封包断点找CALL的各种通杀思路 【第3款】驱动过保护技术课程(共38课已完成)…
【绝密教程】VIP绝密教程系列---注意:随时会更新! 【第4款】VIP邪恶二叉树辅助课程 (共31+17_x64下更新课已完成)…
【精品第13款】3D射击游戏与页游透视 智辅课程 已完成17课… 【第5款】零基础易语言按键辅助教程 (30课已完成)…
【精品第14款】变态功能辅助是如何炼成的 已完成36课… 【第6款】从零开始学习封包辅助技术教程(20课已完成) …
【精品第15款】DNF商业变态辅助的修炼之路 已完成27课… 【第7款】大杀特杀分析来源与CALL吸血鬼课程 (56课已完成)
【精品第16款】中控台多线程多开自动化商业辅助课程 已完成66课… 【第8款】完全零基础网页辅助课程(40课已完成)
【全新精品第17款】检测原理与过游戏内存检测技术课程 已发布9课… 【第9款】自动登录与操控LUA技术课程 (共46+8_x64下更新课已完成)…
【全新精品第18款】手游全自动化任务脚本辅助课程 已发布25课…… 【第10款】网页辅助封包脱机进阶课程 已完成30课…
【全新精品第19款】D3D方框骨骼透视与自瞄辅助课程进阶篇 已发布34课…… 【第11款】VC++ Lua脚本辅助课程 已完成112课…
【全新精品第20款】 X64模拟器吃鸡游戏方框透视自瞄辅助课程 发布中... 【第12款】网游脱机封包智辅课程 已完成35课…
查看: 2036|回复: 2

原创:从一个最简递归函数模型来看递归的本质

[复制链接]

54

主题

98

回帖

130

积分

终身VIP会员

花钱是让你服务的,不是叫你大哥 ...

Rank: 7Rank: 7Rank: 7

魔鬼币
10632
发表于 2011-8-28 15:10:38 | 显示全部楼层 |阅读模式
*第一次写代码分析的文章,所以第一次会详尽一点。
问题原型:
有5个人坐在一起,问第5个人多少岁?答, 比第4个人大2岁。 第4个人说他比第3个人大2岁, 第3个人说比第2个人大2岁, 第2个人比第1个人大2岁, 问第1个人时回答是10岁, 那第5个人到底多大?
// 问题原型是5个人,我们简化为3个人,虽然性质是完全一样的。
C源码:
#include <stdio.h>
int age(int n)
{
        int c;
        if(n == 1) c = 10;
        else c = age(n-1) + 2;
        return c;
}
int main(void)
{
        printf("%d", age(3));
        return 0;
}
*这个子程序, 实参为3,age函数共调用了3次。(n=5~1之间调用了3次)


VC++6.0 自带反汇编编译源码:
10:   void main()
11:   {
00401090   push        ebp  //保存ebp的寄存器变量
00401091   mov         ebp,esp  //将当前指向栈顶指针给ebp,是开辟内存空间的前奏
00401093   sub         esp,40h //开辟 40h大小的内存空间
00401096   push        ebx  //基地址保护(待确认)
00401097   push        esi  //保存记录源地址的寄存器变量, 其中的s可以看做是source
00401098   push        edi //同上, d 可以看做是destination
00401099   lea         edi,[ebp-40h]  //edi指向所开辟内存空间的顶端, 目的是为了要对开辟的内存空间实行保护操作;
0040109C   mov         ecx,10h    // 将循环次数保存到ecx中, 10h = 40h / 4
004010A1   mov         eax,0CCCCCCCCh  // eax中写入0cccccccch
004010A6   rep stos    dword ptr [edi]   //循环执行10次,向所开辟的空间写入int 3
12:       printf("%d", age(3));
004010A8   push        3  //age函数实参
004010AA   call        @ILT+0(age) (00401005)  //age函数,接收一个参数3
004010AF   add         esp,4
004010B2   push        eax  //printf参数1
004010B3   push        offset string "%d" (0042201c)  //参数2,字符串偏移
004010B8   call        printf (00401130)  //调用库函数printf
004010BD   add         esp,8    //明显的__cdecl调用约定格式;
13:   }
// 恢复环境变量
004010C0   pop         edi   
004010C1   pop         esi
004010C2   pop         ebx
004010C3   add         esp,40h
004010C6   cmp         ebp,esp
004010C8   call        __chkesp (004010f0) //堆栈检测,无需深究
004010CD   mov         esp,ebp
004010CF   pop         ebp
004010D0   ret

//age函数反汇编
2:    int age(int n)
3:    {
00401020   push        ebp  //执行3次递归调用
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]
4:        int c;
5:        if(n == 1) c = 10;
00401038   cmp         dword ptr [ebp+8],1  //ebp+8显然就是形参c了,这是一个明显的if指令
0040103C   jne         age+27h (00401047)  //n!=1则跳转
// n = 1 则执行下述指令;
0040103E   mov         dword ptr [ebp-4],0Ah
6:        else c = age(n-1) + 2;
00401045   jmp         age+3Ch (0040105c)
// n != 1 则执行下述指令;
00401047   mov         eax,dword ptr [ebp+8]
0040104A   sub         eax,1  //sub eax, 1 就是 n -1
0040104D   push        eax
0040104E   call        @ILT+0(age) (00401005) //在age函数内部再次调用age函数
00401053   add         esp,4
00401056   add         eax,2
00401059   mov         dword ptr [ebp-4],eax  //执行完3次调用age函数后,eax=10,ebp-4为变量c
7:        return c;
0040105C   mov         eax,dword ptr [ebp-4]
8:    }
0040105F   pop         edi
00401060   pop         esi
00401061   pop         ebx
00401062   add         esp,44h
00401065   cmp         ebp,esp
00401067   call        __chkesp (004010f0)
0040106C   mov         esp,ebp
0040106E   pop         ebp
0040106F   ret
&#61548;        假设我们传递到age函数里面的实参为3;
&#61548;        1. 首先走到004010AA   call        @ILT+0(age) (00401005) 处, f11进入age函数内部 ,观察并记录进入内部后的esp栈顶指向的值; 此时esp=0x0012ff2c , 0x0012ff2c地址指向的数据为0x004010af ,即执行完当前call后的eip的值; 这也验证了一个执行call的经典规则: push eip & jmp call;
&#61548;        进入call内部后,第一条指令 00401005   jmp         age (00401020) ,继续F10正式进入call内部,接着往下走,或者ctrl+F10走到 一个递归自调用age函数前, F11进入后,记录下当前的esp中的数据为: ss:[esp] = 0x00401053;
&#61548;        递归调用3次age函数后,执行到ret指令(第三次调用age函数中)处,此时esp=0012fe74,[esp] = 00401053 (显然是 第三次调用age函数完成后的eip值) ,恢复栈平衡后, add eax, 2 即 age(n-1)+2 。此时变量c = 10 + 2 ,反汇编代码即为: mov         dword ptr [ebp-4],eax  //ebp-4 为变量c , 0040105C   mov    eax,dword ptr [ebp-4]在这里, 说明age(2) = 12 ;我们ret处返回;
&#61548;        继续返回到 00401053 处 ,继续上一步的操作后,变量c = 10 + 2 + 2 ,age(3) = 14
&#61548;        然后我们返回到 0x004010af 处,自此走出了age函数;
&#61548;        不难发现, ret指令的运行机制就是:pop eip
&#61548;        重要:再补充一点,递归函数写起来简单,理解起来有难度,其中问题的关键在于,多次调用call之后,没有ret。 这个例子中,是连续3次 push eip ,但是都没有ret,调用后第三次后 连续ret3次, 这也就是递归的本质, 先多次递推调用函数,然后多次回推!
*ret的本质(属于段内转移):
Pop eip //将esp指向的栈顶元素弹出堆栈 存放到 eip寄存器中去;
执行完pop eip后,以一条指令执行的位置即eip执行的内存地址处开始执行;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
od反汇编源码:
*我们跳过启动函数。
*这里我用得是吾爱破解版的原版OD,出于不同的名称修饰约定,所以事先说明下。

00401286  |.  52            push edx
00401287  |.  A1 847C4200   mov eax,dword ptr ds:[__argv]
0040128C  |.  50            push eax
0040128D  |.  8B0D 807C4200 mov ecx,dword ptr ds:[__argc]
00401293  |.  51            push ecx
//这里就是我们写的main函数的入口地址了,上面都是一些启动函数,暂时无须深究,进入;
00401294  |.  E8 71FDFFFF   call 1.0040100A
00401299  |.  83C4 0C       add esp,0C
0040129C  |.  8945 E4       mov [local.7],eax

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
(OD)main函数反汇编代码:
//显然是比vc++6.0显示的要简洁明了多了,这里我们不进行深入分析了;
00401090 >/> \55            push ebp
00401091  |.  8BEC          mov ebp,esp
00401093  |.  83EC 40       sub esp,40
00401096  |.  53            push ebx
00401097  |.  56            push esi
00401098  |.  57            push edi
00401099  |.  8D7D C0       lea edi,[local.16]
0040109C  |.  B9 10000000   mov ecx,10
004010A1  |.  B8 CCCCCCCC   mov eax,CCCCCCCC
004010A6  |.  F3:AB         rep stos dword ptr es:[edi]
004010A8  |.  6A 03         push 3
//走到004010aa处,我们看堆栈,显示0012FF30   00000003  \Arg1 = 00000003 (arg1就是形参1,这里只有1个形参),进入:
004010AA  |.  E8 56FFFFFF   call 1.00401005
004010AF  |.  83C4 04       add esp,4
004010B2  |.  50            push eax                                 ; /<%d>
004010B3  |.  68 1C204200   push 1.0042201C                          ; |format = "%d"
004010B8  |.  E8 73000000   call 1.printf                            ; \printf
004010BD  |.  83C4 08       add esp,8
004010C0  |.  5F            pop edi
004010C1  |.  5E            pop esi
004010C2  |.  5B            pop ebx
004010C3  |.  83C4 40       add esp,40
004010C6  |.  3BEC          cmp ebp,esp
004010C8  |.  E8 23000000   call 1._chkesp
004010CD  |.  8BE5          mov esp,ebp
004010CF  |.  5D            pop ebp
004010D0  \.  C3            retn

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
(OD)age函数反汇编代码:
//反汇编代码略长,但除却必要的环境保护和恢复之外,代码量已经不大了,VC++6.0的ebp看的头痛,这里有种眼前一亮的感觉!
00401020 >/> \55            push ebp
00401021  |.  8BEC          mov ebp,esp
00401023  |.  83EC 44       sub esp,44
00401026  |.  53            push ebx
00401027  |.  56            push esi
00401028  |.  57            push edi
00401029  |.  8D7D BC       lea edi,[local.17]
0040102C  |.  B9 11000000   mov ecx,11
00401031  |.  B8 CCCCCCCC   mov eax,CCCCCCCC
00401036  |.  F3:AB         rep stos dword ptr es:[edi]
//arg.1 是 形参1
00401038  |.  837D 08 01    cmp [arg.1],1
0040103C  |.  75 09         jnz short 1.00401047
//local.1 是 局部变量1
0040103E  |.  C745 FC 0A000>mov [local.1],0A
00401045  |.  EB 15         jmp short 1.0040105C
00401047  |>  8B45 08       mov eax,[arg.1]
0040104A  |.  83E8 01       sub eax,1
0040104D  |.  50            push eax
0040104E  |.  E8 B2FFFFFF   call 1.00401005
00401053  |.  83C4 04       add esp,4
00401056  |.  83C0 02       add eax,2
00401059  |.  8945 FC       mov [local.1],eax
0040105C  |>  8B45 FC       mov eax,[local.1]
0040105F  |.  5F            pop edi
00401060  |.  5E            pop esi
00401061  |.  5B            pop ebx
00401062  |.  83C4 44       add esp,44
00401065  |.  3BEC          cmp ebp,esp
00401067  |.  E8 84000000   call 1._chkesp
0040106C  |.  8BE5          mov esp,ebp
0040106E  |.  5D            pop ebp
0040106F  \.  C3            retn

*用od分析,显然难度急骤降低. 以后我们会逐渐从简单的开始, 然后会用其他工具,比如ida, 难度梯进式的反汇编分析一些更有挑战性的例子。比如在游戏中会常用到的链表、二叉树等等。
*递归的本质 就是
I.        回推阶段
II.        递推阶段

评分

参与人数 1魔鬼币 +30 收起 理由
魔鬼作坊内部组 + 30 原创内容

查看全部评分

3

主题

3

回帖

6

积分

编程入门

Rank: 1

魔鬼币
165
发表于 2011-8-28 21:27:34 | 显示全部楼层
沙画 呵呵

30

主题

220

回帖

155

积分

终身VIP会员

Rank: 7Rank: 7Rank: 7

魔鬼币
72790
发表于 2011-8-29 00:12:11 | 显示全部楼层
膜拜菊花哥
您需要登录后才可以回帖 登录 | 点击注册

本版积分规则

魔鬼作坊|易语言教程|易语言源码|易语言论坛|易语言视频教程| 论坛导航|免责申明|手机版||网站地图
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表魔鬼作坊立场!
任何人不得以任何方式翻录、盗版或出售本站视频,一经发现我们将追究其相关责任!
我们一直在努力成为最好的编程论坛!
Copyright© 2010-2019 All Right Reserved.
快速回复 返回顶部 返回列表