您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 市场营销 > 从汇编的角度看函数调用的过程
从汇编的角度看函数调用的过程有时候,我们需要深入了解编程语言的一些细节性问题,比如,编程语言结构--函数是如何实现的,函数的执行会是怎么样的一个过程。下面我们举一个例子,看看函数调用的时候,堆栈会发生怎么样的变化。#includestdio.hlongtest(inta,intb){a=a+1;b=b+100;returna+b;}voidmain(){printf(%d,test(1000,2000));}写成32位汇编就是这样:;//////////////////////////////////////////////////////////////////////////////////////////////////////.386.modelflat,stdcall;这里我们用stdcall就是函数参数压栈的时候从最后一个开始压,和被调用函数负责清栈optioncasemap:none;区分大小写includelibmsvcrt.lib;这里是引入类库相当于#includestdio.h了printfPROTOC:DWORD,:VARARG;这个就是声明一下我们要用的函数头,到时候汇编程序会自动到msvcrt.lib里面找的了;:VARARG表后面的参数不确定因为C就是这样的printf(constchar*,...);;这样的函数要注意不是被调用函数负责清栈因为它本身不知道有多少个参数;而是有调用者负责清栈下面会详细说明.dataszTextFmtBYTE'%d',0;这个是用来类型转换的,跟C的一样,字符用字节类型adword1000;假设bdword2000;处理数值都用双字没有int跟long的区别;/////////////////////////////////////////////////////////////////////////////////////////.code_testproc;A:DWORD,B:DWORDpushebpmovebp,espmoveax,dwordptrss:[ebp+8]addeax,1movedx,dwordptrss:[ebp+0Ch]addedx,100addeax,edxpopebpretn8_testendp_mainprocpushdwordptrds:b;反汇编我们看到的b就不是b了而是一个[*****]数字dwordptr就是我们在ds(数据段)把[*****];开始的一个双字长数值取出来pushdwordptrds:a;跟她对应的还有byteptr****就是取一个字节出来比如这样moval,byteptrds:szTextFmt;就把%取出来而不包括dcall_testpusheax;假设pusheax的地址是×××××pushoffsetszTextFmtcallprintfaddesp,8ret_mainendpend_main;//////////////////////////////////////////////////////////////下面介绍堆栈的变化/stdio.h首先要明白的是操作堆栈段,ss只能用esp或ebp寄存器其他的寄存器eaxebxedx等都不能够用。而esp永远指向堆栈栈顶,ebp用来在堆栈段里面寻址。push指令是压栈ESP=ESP-4,pop指令是出栈ESP=ESP+4。我们假设main函数一开始堆栈定是ESP=400。pushdwordptrds:b;ESP-4=396-里面的值就是2000就是b的数值pushdwordptrds:a;ESP-4=392-里面的值就是1000就是a的数值calltest;ESP-4=388-里面的数值是什么?这个太重要了就是我们用来找游戏函数的原理所在。里面的数值就是calltest指令下一条指令的地址-即pusheax的地址×××××到了test函数里面pushebp;ESP-4=384-里面保存了当前ebp的值而不是把ebp清零movebp,esp;这里ESP=384就没变化了,但是ebp=esp=384,为什么要这样做呢因为我们要用ebp到堆栈里面找参数moveax,dwordptrss:[ebp+8];反汇编是这样的想想为什么a就是[ebp+8]呢;我们往上看看堆栈里地址392处就保存着a的值这里ebp=384加上8正好就是392了;这样就把传递过来的1000拿了出来eax=1000addeax,1;相当于a+1了eax=1001movedx,dwordptrss:[ebp+0Ch];0Ch=12一样道理这里指向堆栈的地址是384+12=396就是2000了edx=2000addedx,100;相当于b+100edx=2100addeax,edx;eax=eax+edx=1001+2100=3101这里eax已经保存了最终的结果了;因为win32汇编一般用eax返回结果所以如果最终结果不是在eax里面的话还要把它放到eax;比如假设我的结果保存在变量nRet里面最后还是要这样moveax,dwordptrnRetpopebp;ESP=384+4=388而保存在栈顶384的值保存到ebp中即恢复ebp原来的值;因为一开始我们就把ebp的值压栈了,movebp,esp已经改变了ebp的值,这里恢复就是保证了堆栈平衡retn8;ESP+8-396这里retn是由系统调用的我们不用管系统会自动把EIP指针指向原来的call的下一条指令;由于是系统自动恢复了call那里的压栈所以真正返回到的时候ESP+4就是恢复了call压栈的堆栈;到了这个时候ESP=400就是函数调用开始的堆栈,就是说函数调用前跟函数调用后的堆栈是一样的;这就是堆栈平衡由于我们用stdcall上面retn8就是被调用者负责恢复堆栈的意思了,函数test是被调用者,所以负责把堆栈加8,call那里是系统自动恢复的pusheax;ESP-4=396-里面保存了eax的值3101;上面已经看到了eax保存着返回值,我们要把它传给printf也是通过堆栈传递pushoffsetszTextFmt;ESP-4=392-里面保存了szTextFmt的地址也就是C里面的指针实际上没有什么把字符串传递的,我们传的都是地址;无论是在汇编或C所以在汇编里没有什么字符串类型用最多的就是DWORD。嘿嘿游戏里面传递参数简单多了callprintf;ESP-4=388-里面保存了下一条指令的地址addesp,8;ESP+8=400恢复了调用printf前的堆栈状态;上面说了由于printf后面参数是:VARARG这样的类型是有调用者恢复堆栈的所以printf里面没有retn8之类的指令;这是由调用者负责清栈main是调用者所以下面一句就是addesp,8把堆栈恢复到调用printf之前;而callprintf那里的压栈是由系统做的恢复的工作也是系统完成我们不用理只是知道里面保存是返回地址就够;了ret;main函数返回其他的事情是系统自动搞定我们不用理任务完成
本文标题:从汇编的角度看函数调用的过程
链接地址:https://www.777doc.com/doc-7167110 .html