【逆向基础】五、函数调用约定(__cdecl,__stdcall,__fastcall,__thiscall)详解

前言

函数调用约定指的是函数调用过程中,针对函数传参,返回值和内存释放等进行制定的统一标准。
下面小编将从函数,函数调用,函数调用约定等三个维度进行深入解析;

一,函数

将函数调用拆分,我们则需要了解什么是函数;在编程实现需求的过程中,不同的编程语言都提供了封装代码的方式,它可以提供某个子需求的具体实现,对外开放清晰简洁的接口定义;这个实现的过程在 Java中被称为方法,而在C/C++中被成为函数; 下面以C语言32位程序为例,详解介绍函数调用的相关内容;

二,函数调用

学习函数调用前需要先理解如下名词
:一种为函数使得参数先进后出的内存管理机制(提供了内存的分配与回收的机制);
栈帧:当函数执行所需要的存储空间超出寄存器能否存放的大小时,就会使用栈上的存储空间;我们把这部分存储空间称为函数的栈帧
EIP寄存器:程序计数寄存器,用于存放程序下一条执行指令的地址;它决定了程序的流程;
函数返回地址:执行完当前函数所有指令之后,当前函数的下一条指令或下一个函数的地址;

1,传递控制

首先,函数调用通过汇编指令call来完成;具体操作如下:

1,call指令将函数首地址(第一条指令的地址)写入EIP寄存器,以此实现函数调用;
2,call指令将函数的返回地址压入(push)栈中;
在这里插入图片描述

然后,函数执行完成后通过指令ret来结束;具体操作如下:

1,将函数返回地址通栈中弹出(pop)栈
2,将返回地址写入EIP寄存器,以此实现函数执行完成后下一条指令的调用;
在这里插入图片描述

总结:通过call指令调用函数后,ret指令将地址重新拉回调用函数后的地址;这样就是实现了函数的调用与返回;

2,传递参数

(一)传递参数的介质
(1)64位(x64)的程序

函数传递参数根据参数的数量来决定传递的介质; 参数数量 1-6
:使用寄存器传递参数,不需要内存对齐;(参数对应寄存器顺序:rdi,rsi,rdx,rcx,r8,r9) 参数数量 <= 7
:使用栈帧传递,需要对齐内存

(2)32位(x86)的程序

函数传递参数使用栈帧传递;

(3),arm的程序

函数传递参数根据参数的数量来决定传递的介质; 参数数量 1-4 :使用寄存器传递参数,不需要内存对齐;(参数对应寄存器顺序:R0 R1 R2
R3) 参数数量 <= 5 :使用栈帧传递,需要对齐内存

(二)传递参数的方式
(1),值传递

基本类型使用值传递时,会拷贝一份真实的数据压入栈,即深拷贝;会消耗更多的栈内存;

(2),引用传递

引用类型进行传递时,因为是间接只向对象,所以只会拷贝对象的地址;即浅拷贝;消耗较少的栈内存;

(3),指针传递

指针类型进行传递时,因为是间接只向对象,所以只会拷贝对象的地址;即浅拷贝;消耗较少的栈内存;

3,内存分配与释放

寄存器:在程序执行过程中,一种被函数共享的资源; 为避免寄存器上的数据覆盖,处理器处理函数时需遵循寄存器的使用惯例;即在通用寄存器RSP外;
其余15个寄存器被分别定义了调用者保存和被调用者保存;具体如下
被调用者保存寄存器(非易失性寄存器):rbx,rbp,r12,r13,r14,r15
调用者保存寄存器(易失性寄存器):rdi,rsi,rdx,rcx,r8,r9,rax,r10,r11

区别
若函数A调用函数B的过程中,若函数A中寄存器rbx的值在函数B中被修改,则为了让函数A的数据不被覆盖,需要根据有两种方式解决:
1,使用调用者保存寄存器,在函数A中将寄存器的值保存起来,执行完函数B后,在函数A恢复寄存器的值;
2,使用被调用这保存寄存器,在函数B中将寄存器值保存起来,执行修改后,在函数A中恢复寄存器值的值;

三,函数调用约定

根据上述对于函数调用的讲解,相信大家对函数调用的过程有了一定理解,但缺没有讲到传递参数的顺序,也没有讲到谁去释放内存吧?嘻嘻,那下面我们置入正题,讲讲这个约定吧。

1,约定事件1

参数传递顺序
1.从右到左依次入栈:__stdcall,__cdecl,__thiscall,__fastcall
2.从左到右依次入栈:__pascal

2,约定事件2

调用堆栈清理
1.调用者清除栈。
2.被调用函数返回后清除栈。

3,约定分类

__cdecl (C默认缺省函数调用约定)

1、参数是从右向左传递的,也是放在堆栈中。
2、堆栈平衡是由调用函数来执行的(函数具有可变参数时,调用完才知道使用字节数,才能清理)。 3、函数的前面会加一个前缀"_“修饰,
举例:函数"add"使用约定__cdecl修饰后的汇编语句函数名称变为为”_add"

__stdcall

1、参数是从右往左传递的,也是放在堆栈中。 2、函数的堆栈平衡操作是由被调用函数执行的。
3、在函数名的前面加一个前缀"_“修饰,在函数名的后面由@来修饰并加上栈需要的字节数的空间。
举例:函数"add"使用约定__cdecl修饰后的汇编语句函数名称变为为”_add@x"(x表示字节数)

__fastcall

1、参数是从右往左传递的,也是放在堆栈中。
__fastcall见名知其意,其特点就是快。__fastcall函数调用约定表明了参数应该放在寄存器中,而不是在栈中,VC编译器采用调用约定传递参数时,最左边的两个不大于4个字节(DWORD)的参数分别放在ecx和edx寄存器。当寄存器用完的时候,其余参数仍然从右到左的顺序压入堆栈。像浮点值、远指针和__int64类型总是通过堆栈来传递的。(使用__fastcall方式无法用作跨编译器的接口。)

__thiscall(不能直接修饰,C++默认缺省函数调用约定)

1、参数是从右往左传递的,也是放在堆栈中。 2、函数的堆栈平衡操作对于参数确定的函数是由被调用函数执行的。
函数的堆栈平衡操作对于不参数确定的函数是由调用函数执行的。
__thiscall是C++成员函数的默认调用约定,__thiscall不是关键字,不能进行显示指定。参数是从右向左压栈,由被调用的函数清理堆栈。而且使用ecx寄存器来传递this指针。(注意:并不是所有的成员函数调用都是通过ecx来实现的,得看具体的编译器)

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

修道-0323

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值