函数调用约定

·492 字·3 分钟· loading · loading ·
reverse asm 学习笔记
spoula
作者
spoula

​ 函数调用约定,是指当一个函数被调用时,函数的 参数会被传递给被调用的函数和 返回值会被返回给调用函数。函数的调用约定就是描述参数是怎么传递和由谁平衡 堆栈的,当然还有返回值。

windows平台下主要又三种函数调用约定

分别是:

_cdecl , _stdcall 和 _fastcall

__cdecl
#

__cdecl函数约定由四个特点:

  1. 使用栈空间传递参数
  2. 函数参数按从左往右的方向传递
  3. 调用者负责释放参数空间
  4. 返回值在寄存器中

下面对一段代码进行分析,代码是:

#include <stdio.h>

int __cdecl sum(int a,int b)
{
    return a+b;
}

int main()
{
	int m = 1 , n = 2;
    int y = sum(1,2);
}

看看这段代码的汇编

_a$ = 8                                       ; size = 4
_b$ = 12                                                ; size = 4
int sum(int,int) PROC                                  ; sum
        push    ebp
        mov     ebp, esp
        mov     eax, DWORD PTR _a$[ebp]
        add     eax, DWORD PTR _b$[ebp]
        pop     ebp
        ret     0
int sum(int,int) ENDP                                  ; sum

_y$ = -12                                         ; size = 4
_n$ = -8                                                ; size = 4
_m$ = -4                                                ; size = 4
_main   PROC
        push    ebp
        mov     ebp, esp
        sub     esp, 12                             ; 0000000cH
        mov     DWORD PTR _m$[ebp], 1
        mov     DWORD PTR _n$[ebp], 2
        push    2
        push    1
        call    int sum(int,int)                     ; sum
        add     esp, 8
        mov     DWORD PTR _y$[ebp], eax
        xor     eax, eax
        mov     esp, ebp
        pop     ebp
        ret     0
_main   ENDP

之前提到__cdecl是用栈传递参数,且传参方向是从右往左的

对应下图两个push

image-20231123221205057

image-20231123221259790

反回值被存在寄存器eax里

image-20231123221403694

调用者(这里是main函数)负责释放参数空间

将栈顶指针加8以达到释放目的

image-20231123221445568

__stdcall
#

_stdcall函数调用约定方式于 _cdecl 基本一直,唯一不同的是释放参数空间是被调用者

  1. 使用栈空间传递参数
  2. 函数参数按从左往右的方向传递
  3. 被调用者负责释放参数空间
  4. 返回值在寄存器中

还用刚刚那段代码做例子

#include <stdio.h>

int __stdcall sum(int a,int b)
{
    return a+b;
}

int main()
{
	int m = 1 , n = 2;
    int y = sum(1,2);
}

对应的汇编是

_a$ = 8                                       ; size = 4
_b$ = 12                                                ; size = 4
int sum(int,int) PROC                                  ; sum
        push    ebp
        mov     ebp, esp
        mov     eax, DWORD PTR _a$[ebp]
        add     eax, DWORD PTR _b$[ebp]
        pop     ebp
        ret     8
int sum(int,int) ENDP                                  ; sum

_y$ = -12                                         ; size = 4
_n$ = -8                                                ; size = 4
_m$ = -4                                                ; size = 4
_main   PROC
        push    ebp
        mov     ebp, esp
        sub     esp, 12                             ; 0000000cH
        mov     DWORD PTR _m$[ebp], 1
        mov     DWORD PTR _n$[ebp], 2
        push    2
        push    1
        call    int sum(int,int)                     ; sum
        mov     DWORD PTR _y$[ebp], eax
        xor     eax, eax
        mov     esp, ebp
        pop     ebp
        ret     0
_main   ENDP

这边是被调用者(也就是sum函数)示范参数空间

可以看的这边sum函数返回的ret指令后面跟了个8

代表栈顶指针值加8,以释放参数空间

image-20231123222041031

__fastcall
#

_fastcall 顾名思义更fast

为什么更快呢,因为他前两个参数是用寄存器进行传递的

在计算机中,cpu访问寄存器内容的速度是远远高于访问内存的

如果有三个及以上个参数的话,剩下的参数用栈进行传递

  1. 前两个参数使用寄存器传递
  2. 其余参数使用栈从右往左的方向传递
  3. 被调用函数负责释放参数空间
  4. 返回值在寄存器中

这边把刚刚代码改一下,多加2个参数

#include <stdio.h>

int __fastcall sum(int a,int b, int c, int d)
{
    return a+b+c+d;
}

int main()
{
    int y = sum(1,2,3,4);
}

对应的汇编是

_b$ = -8                                                ; size = 4
_a$ = -4                                                ; size = 4
_c$ = 8                                       ; size = 4
_d$ = 12                                                ; size = 4
int sum(int,int,int,int) PROC                                    ; sum
        push    ebp
        mov     ebp, esp
        sub     esp, 8
        mov     DWORD PTR _b$[ebp], edx
        mov     DWORD PTR _a$[ebp], ecx
        mov     eax, DWORD PTR _a$[ebp]
        add     eax, DWORD PTR _b$[ebp]
        add     eax, DWORD PTR _c$[ebp]
        add     eax, DWORD PTR _d$[ebp]
        mov     esp, ebp
        pop     ebp
        ret     8
int sum(int,int,int,int) ENDP                                    ; sum

_y$ = -4                                                ; size = 4
_main   PROC
        push    ebp
        mov     ebp, esp
        push    ecx
        push    4
        push    3
        mov     edx, 2
        mov     ecx, 1
        call    int sum(int,int,int,int)               ; sum
        mov     DWORD PTR _y$[ebp], eax
        xor     eax, eax
        mov     esp, ebp
        pop     ebp
        ret     0
_main   ENDP

这边传了四个参数,可以看的前两个参数1和2分别放入ecx和edx寄存器里传递的

然后其余参数从右往左压入栈内传参

image-20231123223030637

也是被调用者释放参数空间

image-20231123223119122

返回值也是被存入寄存器eax中返回的

image-20231123223146985