首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >C语言函数调用约定

C语言函数调用约定

作者头像
babyctfer
修改于 2023-12-20 07:16:21
修改于 2023-12-20 07:16:21
46100
代码可运行
举报
文章被收录于专栏:CTF学习笔记CTF学习笔记
运行总次数:0
代码可运行
代码语言:c
代码运行次数:0
运行
AI代码解释
复制
__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
代码运行次数:0
运行
AI代码解释
复制
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
代码运行次数:0
运行
AI代码解释
复制
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()和 WindowsAPI 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 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
你一定要搞明白的C函数调用方式与栈原理
这绝对不是标题党。而是C/C++开发中你必须要掌握的基础知识,也是高级技术岗位面试中高频题。我真的真的真的希望无论是学生还是广大C/C++开发者,都该掌握此文中介绍的知识。
范蠡
2018/07/25
3.4K0
你一定要搞明白的C函数调用方式与栈原理
用户层下API的逆向分析及重构
首发于奇安信攻防社区:https://forum.butian.net/share/1318
红队蓝军
2022/05/17
7780
用户层下API的逆向分析及重构
【C语言】汇编角度剖析函数调用的整个过程
压栈操作,他会改变esp所指向的位置,从而适应栈帧空间的扩大,操作方式就是将操作数直接压栈到栈帧空间
举杯邀明月
2023/04/12
1.8K0
【C语言】汇编角度剖析函数调用的整个过程
由一个简单的程序学习常见汇编指令
一直以来,内心有股焦急焦虑,急冲冲的学习,急冲冲的比赛,没有时间和心思回过头来静心总结。突然之间想安静下来回顾和记录一下pwn的知识点 其实是写web有点累了,想切换一下脑壳,看心情更新吧
Elapse
2020/08/17
8670
【C语言】函数——栈帧的创建和销毁
✨作者:@平凡的人1 ✨专栏:《C语言从0到1》 ✨一句话:凡是过往,皆为序章 ✨说明: 过去无可挽回, 未来可以改变 ---- 目录 前言😄 什么是栈🔑 什么是函数的栈帧🔑 认识相关寄存器和汇编指令🔑 寄存器🔥 相关的汇编指令:🔥 函数的调用堆栈🔑 函数栈帧的创建🔑 分析栈帧的创建:💧 为什么会出现“烫烫烫”:💧 分析main函数中的核心代码:💧 分析Add函数的传参💧 函数调用过程💧 函数栈帧的销毁下🔑 结语✍ ---- 前言😄 好的,各位,我们前面就已经学过函数的一些相关知识了
平凡的人1
2022/11/15
7390
【C语言】函数——栈帧的创建和销毁
关于函数参数入栈的思考(函数调用约定,入栈顺序)
首先,要实现函数调用,除了要知道函数的入口地址外,还要向函数传递合适的参数。向被调函数传递参数,可以有不同的方式实现。这些方式被称为“调用规范”或“调用约定”。C/C++中常见的调用规范有__cdecl、__stdcall、__fastcall和__thiscall。
恋喵大鲤鱼
2018/08/03
2.9K0
关于函数参数入栈的思考(函数调用约定,入栈顺序)
2020-09-04:函数调用约定了解么?
不同点是stdcall在被调用函数 (Callee) 返回前,由被调用函数 (Callee) 调整堆栈。cdecl在被调用函数 (Callee) 返回后,由调用方 (Caller) 调整堆栈,每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。
福大大架构师每日一题
2020/09/04
6590
C/C++ 反汇编:函数与结构体
反汇编即把目标二进制机器码转为汇编代码的过程,该技术常用于软件破解、外挂技术、病毒分析、逆向工程、软件汉化等领域,学习和理解反汇编对软件调试、系统漏洞挖掘、内核原理及理解高级语言代码都有相当大的帮助,软件一切神秘的运行机制全在反汇编代码里面。
王 瑞
2022/12/28
1.3K0
C/C++ 反汇编:函数与结构体
大神洗礼第四讲——函数相关及编程技巧
Author:bakari       Date:2012.11.2 1、参数传递问题: < 1 >、堆栈传参 < 2 >、寄存器传参(利用通用寄存器进行函数参数传递的方法) < 3 >、全局变量或静态变量传参 2、 Call Convention(函数调用约定) < 1 >、_cdecl a、 参数从右向左压入堆栈 b、 函数被调用者修改堆栈 c、 在win32应用程序里,宏APIENTRY,WINAPI,都表示_stdcall,非常常见. d、 C和C++程序的缺省调用方式。每一个调用它的函数都包含清空
Linux云计算网络
2018/01/10
7150
大神洗礼第四讲——函数相关及编程技巧
C/C++ 反汇编:分析类的实现原理
反汇编即把目标二进制机器码转为汇编代码的过程,该技术常用于软件破解、外挂技术、病毒分析、逆向工程、软件汉化等领域,学习和理解反汇编对软件调试、系统漏洞挖掘、内核原理及理解高级语言代码都有相当大的帮助,软件一切神秘的运行机制全在反汇编代码里面。下面将分析VS 2013 编译器产生C代码的格式与实现方法,研究一下编译器的编译特性。
王 瑞
2022/12/28
6530
C/C++ 反汇编:分析类的实现原理
C/C++ 反汇编:多维数组与指针
反汇编即把目标二进制机器码转为汇编代码的过程,该技术常用于软件破解、外挂技术、病毒分析、逆向工程、软件汉化等领域,学习和理解反汇编对软件调试、系统漏洞挖掘、内核原理及理解高级语言代码都有相当大的帮助,软件一切神秘的运行机制全在反汇编代码里面。
王 瑞
2022/12/28
8040
C/C++ 反汇编:多维数组与指针
菜鸟 学注册机编写之 “sha1”
2.将程序载入OD, 下MessageBoxA函数断点, F9运行程序, 程序运行后随便输入用户名与注册码,点"OK"后断下,F8一直走,就会看出如下的代码,我们在函数开头下好断点。(或者直接搜索字符串 "Thank you for registration!",也能快速定位到这里)
我是小三
2018/08/08
1.4K0
菜鸟 学注册机编写之 “sha1”
C/C++ 实现LyDebug动态PE调试器
LyDebug 是一款使用C/C++语言开发实现的命令行应用层动态反汇编调试器,通过运用Windows系统下的调试API函数并配合Capstone反汇编引擎,完美实现了x86与x64程序的调试功能,该调试器目前功能包括寄存器查看修改,软硬件断点的设置删除以及遍历,单步步进步过操作,堆栈检查,内存检查等。
王 瑞
2022/12/28
2820
C/C++ 实现LyDebug动态PE调试器
x86架构与x64架构在函数于栈中调用过程的不同之处
1、x86架构 x86架构是intel开发的一种32位的指令集。8个32位通用寄存器 eax,ebx,ecx,edx,ebp,esp,esi,edi。
Elapse
2020/08/17
2K0
从汇编角度来理解linux下多层函数调用堆栈运行状态
我们用下面的C代码来研究函数调用的过程。 int bar(int c, int d) {     int e = c + d;     return e; } int foo(int a, int 
s1mba
2017/12/28
1.6K0
从汇编角度来理解linux下多层函数调用堆栈运行状态
C/C++ 反汇编:数据类型与常量
反汇编即把目标二进制机器码转为汇编代码的过程,该技术常用于软件破解、外挂技术、病毒分析、逆向工程、软件汉化等领域,学习和理解反汇编对软件调试、系统漏洞挖掘、内核原理及理解高级语言代码都有相当大的帮助,软件一切神秘的运行机制全在反汇编代码里面。
王 瑞
2022/12/28
4530
C/C++ 反汇编:数据类型与常量
C语言与汇编的嵌入式编程:main中模拟函数的调用(两数交换)
注明:swap函数大致处理过程为:把下个地址压入堆栈,然后参数入栈,然后把所有寄存器压入堆栈,分配空间,空间清C然后变量赋值开始程序然后做堆栈平衡清理堆栈
墨文
2020/02/28
1.1K0
汇编角度看函数堆栈调用
带着以下一个问题来探索: (1)形参的内存空间的开辟和清理是由调用方还是由被调用方执行的? (2)主函数调用函数结束后,主函数从哪里开始执行?从头开始还是从调用之后开始? (3)返回值是如何带出来的?
lexingsen
2022/02/24
7700
汇编角度看函数堆栈调用
重学计算机组成原理(六)- 函数调用怎么突然Stack Overflow了!
从函数调用开始,在计算机指令层面函数间的相互调用是怎么实现的,以及什么情况下会发生栈溢出
JavaEdge
2019/08/15
8010
重学计算机组成原理(六)- 函数调用怎么突然Stack Overflow了!
5.5 汇编语言:函数调用约定
函数是任何一门高级语言中必须要存在的,使用函数式编程可以让程序可读性更高,充分发挥了模块化设计思想的精髓,今天我将带大家一起来探索函数的实现机理,探索编译器到底是如何对函数这个关键字进行实现的,并使用汇编语言模拟实现函数编程中的参数传递调用规范等。
王 瑞
2023/08/22
4350
推荐阅读
相关推荐
你一定要搞明白的C函数调用方式与栈原理
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档