查看“Calling Conventions”的源代码
←
Calling Conventions
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
在C中调用外部函数,并从其他语言调用C函数,是OS编程中的常见问题,尤其是在其他语言是汇编的情况下。(译者注:本页其实讨论了汇编和C语言的互操作问题,但是对其它不同语言间的互操作问题也有一些启发,) 本页将主要关注后一种情况,但也会考虑其他语言。 这里描述的一些内容是由x86架构强加的,有些是GNU[[GCC]]工具链所特有的。 有些是可配置的,你可以制定自己的GCC目标来支持不同的调用约定。 目前,本页面没有区分对待这些问题。 ==基础知识== 通常,可以将遵循C调用约定并在C标头中适当声明 (见下文) 的函数称为普通C函数。 遵循调用规则的大部分负担落在汇编程序上。 == 备忘单 == 下面是常见调用约定的快速概述。 请注意,调用约定通常比这里表示的更复杂 (例如,大型结构如何返回? 一个包含两个寄存器的结构怎么办? 那如果是多个变量列表呢?)。 如果要确定,请查找各自的规格说明。 编写测试函数并使用gcc-S查看编译器如何生成代码可能会很有用,这可能会提示如何解释调用约定规范。 {| {{wikitable}} ! 平台 ! 返回值 ! 参数寄存器 ! 其他参数 ! 栈对齐 ! Scratch寄存器 ! Preserved寄存器 ! Call List |- | System V i386 || eax, edx || none || stack (right to left)<sup>[[#Note1|1]]</sup> || || eax, ecx, edx || ebx, esi, edi, ebp, esp || ebp |- | System V X86_64<sup>[[#Note2|2]]</sup> || rax, rdx || rdi, rsi, rdx, rcx, r8, r9 || stack (right to left)<sup>[[#Note1|1]]</sup> || 16-byte at call<sup>[[#Note3|3]]</sup> || rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11 || rbx, rsp, rbp, r12, r13, r14, r15 || rbp |- | Microsoft x64 || rax || rcx, rdx, r8, r9 || stack (right to left)<sup>[[#Note1|1]]</sup> || 16-byte at call<sup>[[#Note3|3]]</sup> || rax, rcx, rdx, r8, r9, r10, r11 || rbx, rdi, rsi, rsp, rbp, r12, r13, r14, r15 || rbp |- | ARM || r0, r1 || r0, r1, r2, r3 || stack || 8 byte<sup>[[#Note4|4]]</sup> || r0, r1, r2, r3, r12 || r4, r5, r6, r7, r8, r9, r10, r11, r13, r14 || |} <small id="Note2">注1: 被调用的函数允许修改堆栈上的参数,并且调用者不得假定堆栈参数被保留。 调用方应该清理堆栈。</small> <small id="Note2">注2: 堆栈下方有一个128字节区域,称为 “红色区域(red zone)”,leaf函数可以在不增加 %rsp的情况下使用。 这需要内核根据用户空间中的信号将%rsp额外增加128字节。 这<em>不是</em>由CPU完成的 - 如果中断使用当前堆栈 (与内核代码一样),并且启用了红色区域 (默认),则中断将静默破坏堆栈。 如果中断不遵守红色区域,请始终将-mno-red-zone传递给内核代码(甚至支持嵌入内核的libc库)。</small> <small id="Note3">注3:调用时栈是16字节对齐的。该调用推送%rip,因此如果被调用方推送%rbp,堆栈将再次以16字节对齐。</small> <small id="Note4">注4: 栈在函数的prologue/epilogue之外的任何时候都是8字节对齐的。</small> == System V ABI == {{Main|System V ABI}} SystemV ABI是当今使用的主要ABI之一,在Unix系统中几乎是通用的。 这是<tt>i686-elf-gcc</tt> and <tt>x86_64-elf-gcc</tt>等工具链使用的调用约定。 ==外部引用== 为了从C调用外函数,它必须具有正确的C原型。 因此,如果函数<tt>fee()</tt>以C调用顺序接受参数fie、foe和fum,并返回一个整数值,那么相应的头文件应该具有以下原型: <syntaxhighlight lang="c"> int fee(int fie, char foe, double fum); </syntaxhighlight> 同样,汇编代码中的全局变量必须声明为<tt>extern</tt>: <syntaxhighlight lang="c"> extern int frotz; </syntaxhighlight> 汇编语言或其他语言中的C函数必须声明为适用于该语言。 例如,在NASM中,C函数 <syntaxhighlight lang="c"> int foo(int bar, char baz, double quux); </syntaxhighlight> 会被声明为 <syntaxhighlight lang="c"> extern foo </syntaxhighlight> 此外,在大多数汇编语言中,要导出的函数或变量必须声明为全局的: <syntaxhighlight lang="asm"> global foo global frotz </syntaxhighlight> ==Name Mangling== 在某些目标格式([[a.out]])中,C函数的名称通过在其前面加上下划线(“_”)来automagically mangled。(译者注Name Mangling-名称打乱,是一种链接时防止重名的机制) 因此,要以这样的格式在汇编中调用C函数 <tt>foo()</tt>,你必须将其定义为 <tt>extern _foo</tt>,而不是 <tt>extern foo</tt>。 此要求不适用于大多数现代格式,如[[COFF]、[[PE]]和[[ELF]]。 C++的name mangling 要复杂得多,同时C++编译器也将参数列表中的类型信息编码到符号中。 (这是实现C++中的函数重载的首要基础。) Binutils包包含工具<tt>c++filt</tt>,可用于确定正确的损坏(mangled)名称。 ==寄存器== 通用寄存器<tt>EBX</tt>, <tt>ESI</tt>, <tt>EDI</tt>, <tt>EBP</tt>, <tt>DS</tt>, <tt>ES</tt>, and <tt>SS</tt>, 必须由被调用的函数保存。 如果使用它们,必须先保存它们,然后再恢复。 相反, <tt>EAX</tt> and <tt>EDX</tt> 用于返回值,因此不应保留。 被调用函数不需要保存其他寄存器,但如果调用函数使用到它们,则调用函数应在调用之前保存它们,并在调用之后恢复。 ==传递函数参数== GCC/x86在堆栈上传递函数参数。 这些参数的推送顺序与参数列表中的顺序相反。 此外,由于x86保护模式堆栈操作是对32位值进行操作,因此即使实际值小于完整的32位值,这些值也始终作为32位值推送。 因此,对于函数 <tt>foo()</tt>,首先将 <tt>quux</tt> (48位FP值) 的值作为两个32位值 (低32位值) 推送 ; <tt>baz</tt>的值被推送到32位值中的第一个字节;最后,<tt>bar</tt>被作为32位值推送。 要将参数传递给C函数,调用函数必须推送如上所述参数值。 因此,要从 [[NASM]] 汇编程序调用foo(),你可以做这样的事情 <syntaxhighlight lang="asm"> push eax ; low 32-bit of quux push edx ; high 32-bit of quux push bl ; baz push ecx ; bar call foo </syntaxhighlight> ==访问函数参数== 在GCC/x86 C调用约定中,任何接受形参的函数都应该做的第一件事是推送 <tt>EBP</tt>的值(调用函数的帧基指针),然后将<tt>ESP</tt>的值复制到<tt>EBP</TT>。 这将设置函数自己的框架指针,该指针用于跟踪参数和 (在C中或在任何适当可重入(reentrant)的汇编代码中) 局部变量。 要访问C函数传递的参数,需要使用<tt>EBP</tt>一个等于4*(n+2)的偏移量,其中n是参数列表中参数的编号(不是按其推送顺序的编号),索引为零。 +2是调用函数保存的帧指针和返回指针的附加偏移量(由 <tt>CALL</tt>自动推送,由 <tt>RET</tt>弹出)。 因此,在函数 <tt>fee</tt> 中,将 <tt>fie</tt> 移动到 <tt>EAX</tt>,<tt>foe</tt> 移动到 <tt>BL</tt>,然后 <tt>fum</tt> 进入 <tt>EAX</tt> 和 <tt>EDX</tt>,你将在NASM中这样写: <syntaxhighlight lang="asm"> mov ecx, [ebp + 8] ; fie mov bl, [ebp + 12] ; foe mov edx, [ebp + 16] ; low 32-bit of fum mov eax, [ebp + 20] ; high 32-bit of fum </syntaxhighlight> 如前所述,GCC中的返回值是使用<tt>EAX</tt>和<tt>EDX</tt>传递的。 如果值超过64位,则必须将其作为指针传递。 == 另见 == ===外部链接=== *[http://www.delorie.com/djgpp/doc/ug/asm/calling.html DJGPP FAQ: GCC calling conventions] *[http://gul.ime.usp.br/Docs/docs/howto/other-formats/html/HOWTO-INDEX-html/Assembly-HOWTO-5.html Linux Assembly Language HOWTO chapter 5] *http://files.osdev.org/mirrors/geezer/osd/libc/index.htm [[Category:ABI]] [[Category:C]] [[de:Aufrufkonventionen]]
本页使用的模板:
模板:Main
(
查看源代码
)
模板:Wikitable
(
查看源代码
)
返回至“
Calling Conventions
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
变体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息