摸鱼玩玩汇编递归,咸鱼使我快乐{:301_978:}
汇编递归
这次通过简单的递归例子来分析递归的对应汇编
递归求前n项和
描述
求序列的前n项和。输入一个正整数n,输出1+2+3+4+5+6+...+n的前n项和
输入输出示例
输入
5
输出
15(1+2+3+4+5)
代码
int sum(int n){ if(n==1)return n; else{ return n+sum(n-1); }}int main(int argc, char* argv[]){ int result=sum(5); printf("%d\n",result); return 0;}运行结果
反汇编代码
函数外部
19: int result=sum(5);0040D768 push 50040D76A call @ILT+10(fib) (0040100f)0040D76F add esp,40040D772 mov dword ptr [ebp-4],eax20: printf("%d\n",result);函数内部
8: int sum(int n){0040D6F0 push ebp0040D6F1 mov ebp,esp0040D6F3 sub esp,40h0040D6F6 push ebx0040D6F7 push esi0040D6F8 push edi0040D6F9 lea edi,[ebp-40h]0040D6FC mov ecx,10h0040D701 mov eax,0CCCCCCCCh0040D706 rep stos dword ptr [edi]9: if(n==1)return n;0040D708 cmp dword ptr [ebp+8],10040D70C jne sum+23h (0040d713)0040D70E mov eax,dword ptr [ebp+8]0040D711 jmp sum+37h (0040d727)10: else{11: return n+sum(n-1);0040D713 mov eax,dword ptr [ebp+8]0040D716 sub eax,10040D719 push eax0040D71A call @ILT+10(fib) (0040100f)0040D71F add esp,40040D722 mov ecx,dword ptr [ebp+8]0040D725 add eax,ecx12: }13: }0040D727 pop edi0040D728 pop esi0040D729 pop ebx0040D72A add esp,40h0040D72D cmp ebp,esp0040D72F call __chkesp (004010e0)0040D734 mov esp,ebp0040D736 pop ebp0040D737 ret反汇编分析
函数外部
19: int result=sum(5);0040D768 push 50040D76A call @ILT+10(fib) (0040100f)0040D76F add esp,40040D772 mov dword ptr [ebp-4],eax1.压入参数5
0040D768 push 52.调用函数
0040D76A call @ILT+10(fib) (0040100f)3.堆栈外均衡(默认采用cdecl调用协定)
0040D76F add esp,44.将返回值eax赋值给result
0040D772 mov dword ptr [ebp-4],eax函数内部
截取出关键代码
9: if(n==1)return n;0040D708 cmp dword ptr [ebp+8],10040D70C jne sum+23h (0040d713)0040D70E mov eax,dword ptr [ebp+8]0040D711 jmp sum+37h (0040d727)10: else{11: return n+sum(n-1);0040D713 mov eax,dword ptr [ebp+8]0040D716 sub eax,10040D719 push eax0040D71A call @ILT+10(fib) (0040100f)0040D71F add esp,40040D722 mov ecx,dword ptr [ebp+8]0040D725 add eax,ecx12: }13: }0040D727 pop edi1. cmp比较语句
比较参数和1,这里的[ebp+8]=参数n
0040D708 cmp dword ptr [ebp+8],12.jcc判断跳转语句
jne:jump not equal,不相等则跳转
0040D70C jne sum+23h (0040d713)跳转地址:0040d713
10: else{11: return n+sum(n-1);0040D713 mov eax,dword ptr [ebp+8]即跳转到else
3.将返回值赋值给eax
留意这里是前面的跳转语句没执行才能运行的,也就是前面[ebp+8]=1
这里实在就是mov eax,1
0040D70E mov eax,dword ptr [ebp+8]4.绝对跳转语句
0040D711 jmp sum+37h (0040d727)跳转地址:0040d727
0040D727 pop edi跳转到要返回前的恢复现场的语句
5.将参数赋值给eax
这里属于else的代码,这里相当于eax=参数n
0040D713 mov eax,dword ptr [ebp+8]6.将eax减1
相当于eax=eax-1,结合前面的eax=参数n,此时eax=参数n - 1
0040D716 sub eax,17.将eax作为参数压入堆栈
0040D719 push eax8.再次调用函数自己,实现递归
0040D71A call @ILT+10(fib) (0040100f)9.call执行后进行堆栈外均衡
0040D71F add esp,410.将参数n赋值给ecx
相当于ecx=n
0040D722 mov ecx,dword ptr [ebp+8]11.将前面的ecx加给eax
函数返回值存储在eax中,相当于eax=eax+ecx,结合前面的赋值语句,相当于eax=n+sum(n-1)
0040D725 add eax,ecx自写汇编实现
仅仅是分析汇编显然不够interesting(>人<;),自己用汇编安排它(ง •_•)ง
代码
#include "stdafx.h"int sum(int n){ int address=(int)sum; int result; __asm{_if: cmp n,1 //比较参数n和1 jne _else //如果参数n不等于1则跳转到_else段_match: mov eax,1 //将1赋值给eax mov result,eax //将eax赋值给result,结合上面相当于mov result,1 jmp _ret //绝对跳转到_ret段_else: mov eax,n //将参数n赋值给eax dec eax //让eax自减1,相当于eax=eax-1,结合上面相当于eax=参数n-1 push eax //将eax作为参数压入堆栈 call address //调用函数自身 add esp,4 //堆栈外均衡 add eax,n //相当于eax=eax+n mov result,eax //将eax赋值给result_ret: } return result;}int main(int argc, char* argv[]){ int result=sum(5); printf("%d\n",result); return 0;}代码分析
这次的自写的函数没有使用裸函数,而是直接在代码中插入汇编代码
裸函数干系可前往:逆向基础笔记九 C语言内联汇编和调用协定
代码流程如下:
1.获取函数的地址
int address=(int)sum;由于在汇编中需要调用函数自己,所以需要函数的地址
2.声明一个变量用于存储返回值
int result;3.声明四个代码段
- _if段:对应if(n==1)的代码段
- _match段:对应n==1后执行的代码段
- _else段:对应else的代码段
- _ret段:对应返回后的代码段,这里为空,采用了c和asm混编的写法,背面直接就是return result
代码段的详细解释已在上面给出,这里不再赘述
总结
递归函数和普通函数的反汇编实在并没用什么特别大的出入,只不过在函数内部调用的函数是自身
函数调用的优先级较其它运算更高(调用函数也可以看作是一种运算),在汇编语句中可以看到是先调用了函数然后再进行的加法
递归函数如果没有正确返回就会不停向堆栈中压入参数,就会导致所谓的递归栈溢出
来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |