前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C语言函数调用约定

C语言函数调用约定

作者头像
babyctfer
修改2023-12-20 15:16:21
1800
修改2023-12-20 15:16:21
举报
文章被收录于专栏:CTF学习笔记
代码语言:c
复制
__attribute__((cdecl)) int a1(int a,int b,int c,int d){
    return a + b + c + d;
}

__attribute__((fastcall)) int a2(int a,int b,int c,int d){
    return a + b + 2*c + d;
}

__attribute__((stdcall)) int a3(int a,int b,int c,int d){
    return 3*a + 2*b + 2*c + d;
} 

int main(){
    int a,b,c,d;
    a = 10;
    b = 20;
    c = 30;
    d = 40;
    a1(a,b,c,d);
    a2(a,b,c,d);
    a3(a,b,c,d);
}

gcc -m32 -g -o0 func.c -o funcdemo

-m32 强制编译为32位,-g带debug信息,-o0 编译器不进行优化, -o输出文件名

在Window下和wsl下编译都报错了,Ubuntu下成功编译,环境问题头痛啊!

objdump -S -M intel funcdemo

代码语言:c
复制
000011ad <a1>:
// #include  <stdio.h> 
__attribute__((cdecl)) int a1(int a,int b,int c,int d){
    11ad:       f3 0f 1e fb             endbr32 
    11b1:       55                      push   ebp   //保存ebp寄存器的栈顶指针,
                                                    //可以在函数退出时恢复,该寄存器将用来保存堆栈
    11b2:       89 e5                   mov    ebp,esp     //保存堆栈指针
    11b4:       e8 eb 00 00 00          call   12a4 <__x86.get_pc_thunk.ax>
    11b9:       05 23 2e 00 00          add    eax,0x2e23
    return a + b + c + d;
    11be:       8b 55 08                mov    edx,DWORD PTR [ebp+0x8]
    11c1:       8b 45 0c                mov    eax,DWORD PTR [ebp+0xc]
    11c4:       01 c2                   add    edx,eax
    11c6:       8b 45 10                mov    eax,DWORD PTR [ebp+0x10]
    11c9:       01 c2                   add    edx,eax
    11cb:       8b 45 14                mov    eax,DWORD PTR [ebp+0x14]
    11ce:       01 d0                   add    eax,edx
}
    11d0:       5d                      pop    ebp
    11d1:       c3                      ret        //被调用函数直接rentun ,注意,这里没有修改堆栈
000011d2 <a2>:

__attribute__((fastcall)) int a2(int a,int b,int c,int d){
    11d2:       f3 0f 1e fb             endbr32 
    11d6:       55                      push   ebp
    11d7:       89 e5                   mov    ebp,esp
    11d9:       83 ec 08                sub    esp,0x8
    11dc:       e8 c3 00 00 00          call   12a4 <__x86.get_pc_thunk.ax>
    11e1:       05 fb 2d 00 00          add    eax,0x2dfb
    11e6:       89 4d fc                mov    DWORD PTR [ebp-0x4],ecx
    11e9:       89 55 f8                mov    DWORD PTR [ebp-0x8],edx
    return a + b + 2*c + d;
    11ec:       8b 55 fc                mov    edx,DWORD PTR [ebp-0x4]
    11ef:       8b 45 f8                mov    eax,DWORD PTR [ebp-0x8]
    11f2:       01 c2                   add    edx,eax
    11f4:       8b 45 08                mov    eax,DWORD PTR [ebp+0x8]
    11f7:       01 c0                   add    eax,eax
    11f9:       01 c2                   add    edx,eax
    11fb:       8b 45 0c                mov    eax,DWORD PTR [ebp+0xc]
    11fe:       01 d0                   add    eax,edx
}
    1200:       c9                      leave  
    1201:       c2 08 00                ret    0x8         // 表示清理8个字节的堆栈,2个参数在栈上
                                                           //函数自己恢复了堆栈
00001204 <a3>:

__attribute__((stdcall)) int a3(int a,int b,int c,int d){
    1204:       f3 0f 1e fb             endbr32 
    1208:       55                      push   ebp
    1209:       89 e5                   mov    ebp,esp
    120b:       e8 94 00 00 00          call   12a4 <__x86.get_pc_thunk.ax>
    1210:       05 cc 2d 00 00          add    eax,0x2dcc
    return 3*a + 2*b + 2*c + d;
    1215:       8b 55 08                mov    edx,DWORD PTR [ebp+0x8]     //a -> edx
    1218:       89 d0                   mov    eax,edx                     // edx -> eax   
    121a:       01 c0                   add    eax,eax                     // a + a -> eax   
    121c:       01 c2                   add    edx,eax                     // a+a+a -> edx
    121e:       8b 45 0c                mov    eax,DWORD PTR [ebp+0xc]     //b -> eax       
    1221:       01 c0                   add    eax,eax                    // b+b -> eax
    1223:       01 c2                   add    edx,eax                     //3*a + 2*b ->edx   
    1225:       8b 45 10                mov    eax,DWORD PTR [ebp+0x10]       //c ->eax 
    1228:       01 c0                   add    eax,eax                        // c+c ->eax
    122a:       01 c2                   add    edx,eax                        //3*a+2*b+2*c->edx  
    122c:       8b 45 14                mov    eax,DWORD PTR [ebp+0x14]
    122f:       01 d0                   add    eax,edx
} 
    1231:       5d                      pop    ebp
    1232:       c2 10 00                ret    0x10       // 表示清理16个字节的堆栈,4个参数
                                                           //函数自己恢复了堆栈
代码语言:c
复制
00001235 <main>:

int main(){
    1235:       f3 0f 1e fb             endbr32 
    1239:       55                      push   ebp
    123a:       89 e5                   mov    ebp,esp
    123c:       83 ec 10                sub    esp,0x10
    123f:       e8 60 00 00 00          call   12a4 <__x86.get_pc_thunk.ax>
    1244:       05 98 2d 00 00          add    eax,0x2d98
    int a,b,c,d;
    a = 10;
    1249:       c7 45 f0 0a 00 00 00    mov    DWORD PTR [ebp-0x10],0xa      //a
    b = 20;
    1250:       c7 45 f4 14 00 00 00    mov    DWORD PTR [ebp-0xc],0x14       //b 
    c = 30;
    1257:       c7 45 f8 1e 00 00 00    mov    DWORD PTR [ebp-0x8],0x1e        //c
    d = 40;
    125e:       c7 45 fc 28 00 00 00    mov    DWORD PTR [ebp-0x4],0x28      //d
    a1(a,b,c,d);
    // cdecl ,C语言默认调用约定,参数通过从右向左的顺序压栈,调用者函数恢复堆栈
    1265:       ff 75 fc                push   DWORD PTR [ebp-0x4]            //d
    1268:       ff 75 f8                push   DWORD PTR [ebp-0x8]             //c   
    126b:       ff 75 f4                push   DWORD PTR [ebp-0xc]            //b
    126e:       ff 75 f0                push   DWORD PTR [ebp-0x10]            //a
    1271:       e8 37 ff ff ff          call   11ad <a1>                     //调用a1
    1276:       83 c4 10                add    esp,0x10            //注意:这里调用者在函数恢复堆栈
    a2(a,b,c,d);
    // fastcall ,函数的第一个和第二个DWORD参数通过ecx和edx传递(a->ecx,b->edx),
    //其他参数通过从右向左的顺序压栈,被调用函数清理堆栈
    1279:       8b 55 f4                mov    edx,DWORD PTR [ebp-0xc]         //b
    127c:       8b 45 f0                mov    eax,DWORD PTR [ebp-0x10]        //a
    127f:       ff 75 fc                push   DWORD PTR [ebp-0x4]            //d
    1282:       ff 75 f8                push   DWORD PTR [ebp-0x8]            //c
    1285:       89 c1                   mov    ecx,eax                        //a
    1287:       e8 46 ff ff ff          call   11d2 <a2>    // 调用后没有恢复堆栈操作,被调用函数恢复
    a3(a,b,c,d);
    //stdcall ,参数从右向左的顺序压栈,调用者函数清理堆栈
    128c:       ff 75 fc                push   DWORD PTR [ebp-0x4]            //d
    128f:       ff 75 f8                push   DWORD PTR [ebp-0x8]            //c
    1292:       ff 75 f4                push   DWORD PTR [ebp-0xc]            //b
    1295:       ff 75 f0                push   DWORD PTR [ebp-0x10]           //a
    1298:       e8 67 ff ff ff          call   1204 <a3>
    129d:       b8 00 00 00 00          mov    eax,0x0
}

一个程序由若干个函数组成,程序的执行实际上就是函数之间的相互调用。

函数调用方和被调用方必须遵守同样的约定,即调用约定(Calling Convention)。

一个调用惯例一般规定以下两方面的内容:

[函数参数的传递方式]:是通过栈传递还是通过寄存器传递;

[函数参数的传递顺序]:当参数个数多于一个时,按照什么顺序把参数压入栈?

是从左到右入栈还是从右到左入栈;

[参数弹出方式]:函数调用后,由谁来把栈恢复原状?

函数调用结束后需要将压入栈中的参数全部弹出,以使得栈在函数调用前后保持一致。这个弹出的工作可以由调用方来完成,也可以由被调用方来完成。

[函数名修饰方式]:函数名在编译时会被修改,调用惯例可以决定如何修改函数名。

函数调用惯例在函数声明和函数定义时都可以指定,语法格式为:

‌返回值类型 调用惯例 函数名(函数参数)

int __cdecl max(int m, int n); // __cdecl是C语言默认的调用约定,在平时编程中,我们并没有去指定调用约定,就使用默认的 __cdecl。

__cdecl 并不是标准关键字,是在 VC/VS 下有效,但在 GCC 下,要使用 __attribute__((cdecl))。 __attribute__ 是属性声明,告诉编译器此变量/函数需要检查或优化。

除了 cdecl,还有其他调用约定:

调用约定 参数传递方式 参数出栈方式 名字修饰(编译器重命名函数)

cdecl 从右到左的顺序入栈 调用方(caller) _+function

stdcall 从右到左的顺序入栈 被调用方(callee) _+function+@+参数的字节数

fastcall 部分参数放入寄存器,剩下的参数按照从右到左的顺序入栈 被调用方(callee) @+function+@+参数的字节数

pascal 从左到右的顺序入栈 被调用方(callee) \

调用约定

参数传递方式

参数出栈方式

名字修饰(编译器重命名函数)

cdecl

从右到左的顺序入栈

调用方(caller)

_+function

stdcall

被调用方(callee)

_+function+@+参数的字节数

fastcall

函数的第一个和第二个DWORD参数通过ecx和edx传递,剩下的参数按照从右到左的顺序入栈

cdecl: C语言默认,变参函数

由于每次函数调用都要由编译器产生还原栈的代码,所以使用 __cdecl 方式编译的程序比使用 __stdcall 方式编译的程序要大很多。

但是 __cdecl 调用方式是由函数调用者负责清除栈中的函数参数,所以这种方式支持可变参数,比如 printf()和 Windows 的 API wsprintf()就是 __cdecl调用方式。

stdcall:Windows API、内核驱动

fastcall:x64

以 fastcall 声明执行的函数,具有较快的执行速度,因为前俩个参数通过寄存器来进行传递的。

x64平台,还有一些扩展…

一个函数在调用时,前四个参数是从左至右依次存放于RCX、RDX、R8、R9寄存器里面,剩下的参数从右至左顺序入栈;栈的增长方向为从高地址到低地址。

浮点前4个参数传入XMM0、XMM1、XMM2 和 XMM3 中,其他参数传递到堆栈中。

调用者负责在栈上分配32字节的“shadow space”,用于存放那四个存放调用参数的寄存器的值(亦即前四个调用参数);小于64位(bit)的参数传递时高位并不填充零(例如只传递ecx),大于64位需要按照地址传递;

调用者负责栈平衡;

被调用函数的返回值是整数时,则返回值会被存放于RAX;浮点数返回在xmm0中

RAX,RCX,RDX,R8,R9,R10,R11是“易挥发”的,不用特别保护(所谓保护就是使用前要push备份),其余寄存器需要保护。(x86下只有eax, ecx, edx是易挥发的)

栈需要16字节对齐,“call”指令会入栈一个8字节的返回值(注:即函数调用前原来的RIP指令寄存器的值),这样一来,栈就对不齐了(因为RCX、RDX、R8、R9四个寄存器刚好是32个字节,是16字节对齐的,现在多出来了8个字节)。所以,所有非叶子结点调用的函数,都必须调整栈RSP的地址为16n+8,来使栈对齐。比如sub rsp,28h

对于 R8~R15 寄存器,我们可以使用 r8, r8d, r8w, r8b 分别代表 r8 寄存器的64位、低32位、低16位和低8位。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • cdecl: C语言默认,变参函数
  • stdcall:Windows API、内核驱动
  • fastcall:x64
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档