本帖最后由 gclome 于 2020-3-14 20:14 编辑
汇编学习--函数
函数是一个程序模块,用来实现一个特定的功能。一个函数包括函数名、入口参数、返回值、函数功能等功能。
一、函数的识别 程序通过调用程序来调用函数,在函数执行后又返回调用程序继续执行。调用函数的代码中保存了一个返回地址,该地址会与参数一起传递给被调用的函数。有多种方法可以实现这个功能,在绝大多数情况下,编译器会使用call和ret指令来调用函数及返回调用函数。 call指令:call指令与跳转指令功能相似,不同的是,call指令保存返回信息,即将其之后的指令地址压入栈的顶部,当遇到ret指令时返回这个地址。 因此,可以通过定位call机器指令或利用ret指令结束的标志来识别函数。call的指令的操作数就是所调用函数的首地址。 可以先看一个示例:
file:///C:\Users\asus\AppData\Roaming\Tencent\Users\2594738584\QQ\WinTemp\RichOle\165I}QD8V3H]SJG1)V1OVFL.png
这是函数直接调用的方式,大部分情况也是这样;不过也有调用函数是间接调用的,即通过寄存器函数地址或动态计算函数地址调用。例如: 二、函数的参数
函数传参有三种方式:分别是栈方式、寄存器以及全局变量进行隐含参数传递的方式。若是栈传递的,就需要定义参数在栈中的顺序,并约定函数被调用后由谁来平衡栈。若是通过寄存器传递,就要确定参数放在那个寄存器中。
(1)利用栈传参 首先看一个示例:
- // t1.cpp : Defines the entry point for the console application.
- //
- #include "stdafx.h"
- #include "windows.h"
- int WINAPI test1(int a,int b)
- {
- return a+b;
- }
- int __fastcall test2(int a,int b)
- {
- return a+b;
- }
- int PASCAL test3(int a,int b)
- {
- return a+b;
- }
- int __stdcall test4(int a,int b)
- {
-
- return a+b;
- }
- int test5(int a,int b)
- {
- return a+b;
- }
- int main(int argc, char* argv[])
- {
- test1(3,5);
- test2(3,5);
- test3(3,5);
- test4(3,5);
- test5(3,5);
- return 0;
- }
复制代码 切入到反汇编可以看到函数的调用过程,接下来我们重点看一看每一种方式去调用函数时,函数内部是按照什么顺序将参数压栈,以及函数结束后,由谁来平衡栈?
1.WINAPI
参数传递时,先传递3,然后在与5相加;在子程序执行时最后一句 ret 8,用于平衡堆栈。
2.pascal 此处参数传递时,先将3给eax,然后与5相加,同样有个 ret 8 ,也是由子程序平衡堆栈。
3._stdcall
此处参数传递时,先将3给eax,然后与5相加,同样有个 ret 8 ,也是由子程序平衡堆栈。
4._cdecl(c规范)
看到函数的调用过程都是先参数压栈,在调用函数时,多了一句 add esp,8 ,说明_cdecl需要调用者平衡堆栈。
小结: 经过自己的实践,与书上做对比,平衡栈者是吻合了, 不过参数传递顺序却发现都是先push 5,然后push 3,按照这样的话,那都是从右到左,这与书上并不符合,这是让我困惑的一个点。
,
可是看了这张图之后,发现在VC里面pascal方式就是stdcall方式,也就是winapi方式,那我之前的疑惑在这里也算是解决了。
(2)利用寄存器传递参数
这里主要涉及fastcall规范和thiscall规范
1.fastcall规范:顾名思义,特点就是快,因为它是靠寄存器来传递参数的。
因为可以看到这里的调用方式是push 9,push 7, mov edx,5,mov ecx,3,也就是把左边两个参数分别放在了ecx和edx寄存器中,然后后两个参数按照从右向左的顺序放入了栈中,所以这里的参数传递时,使用寄存器和栈,在子程序执行时,先把寄存器的两个数放入栈中,然后进行计算,最后是 ret 8,用于平衡堆栈。
file:///C:\Users\asus\AppData\Roaming\Tencent\Users\2594738584\QQ\WinTemp\RichOle\838}WPVF0C537IAACH2(KHD.png
2.thiscall规范:thiscall是c++的非静态类成员函数的默认调用约定,对象的每个函数隐含接收this函数。采用thiscall函数时,函数约定的参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,仅通过ecx寄存器传递一个额外的参数-----this指针。
三、函数的返回值
(1)用return操作符返回值
在一般情况下,函数的返回值放在eax寄存器中返回,如果处理结果的大小超过eax寄存器的容量,其高32位就会放到edx寄存器里。
(2)通过参数按传引用方式返回值
给函数传参有两种,分别是传值和传引用。
传值调用时,会建立参数的一份复本,并把它传给调用函数,在调用函数中修改参数值的复本不会影响参数的原始值。
传引用调用允许调用函数修改原始变量的值.调用某个函数,当把变量的地址传递给函数时,可以在函数中用间接引用运算符修改调用函数内存单元中该变量的值。
|