Linux 和 Windows x86 程序集调用约定

Linux and Windows x86 assembly calling conventions

提问人:Tim 提问时间:11/5/2023 最后编辑:Sep RolandTim 更新时间:11/6/2023 访问量:91

问:

据我所知,在 Linux 和 Windows 之间有两种常见的调用约定:函数调用的参数要么加载到寄存器中,要么放在堆栈上。

例如,对于函数,据说 Linux 加载了带有所需参数的寄存器。对于在 Windows 上,我已经看到使用了这两种约定。在我的课堂上,他们让我们将参数放在堆栈上,但是在在线代码示例中,参数被放置在寄存器中。printfprintfprintf

Basile Starynkevitch 的这个回答让我认为 Windows 只将堆栈用于参数,而 Linux 使用寄存器。Jester 的这个答案显示了 Windows 使用寄存器作为参数。printf

我为我的类编写的代码的下一个小部分显示,在 Windows 上,我一直在使用堆栈进行参数:

section .data
msg:  db "my message", 0ah, 0

section .text
_main:
        
    push   msg
    call   _printf
    add    esp, 4
  • 那么 Windows 是否能够同时使用这两种调用约定呢?
  • 你怎么知道应该使用哪个?
  • 32 位和 64 位 x86 程序有什么不同吗?

我还看到人们可以在没有下划线的情况下在 Windows 程序集中使用。我知道这对于 Linux 汇编代码来说是正常的,但在 Windows 上,如果我不使用下划线,我会收到错误。Windows 上的其他人似乎没有这个问题。printf

在 Jester 回答的原始问题(上面的链接)中,提出问题的用户似乎没有使用任何特殊的 NASM 指令来允许这样做,除非它是他们正在使用的链接器命令中的内容。
C. K. Young 只解释了如果您使用某个 NASM 选项,如何在没有下划线的情况下使用。
printf

  • 那么,为什么某些 Windows 程序集程序在不使用 NASM 选项时可以使用不带下划线呢?printf

我搜索了很多,但没有找到这些奇怪案例的任何解释。

Linux Windows 程序集 x86 NASM

评论

3赞 fuz 11/5/2023
调用约定在 32 位和 64 位之间有所不同,是的。符号装饰(前导下划线的存在)也不同。确保不要混淆这些。
3赞 Peter Cordes 11/5/2023
agner.org/optimize 有一个调用约定指南,涵盖了 Windows/非 Windows 和 32 位与 64 位 x86 之间的差异。
0赞 Carlo Arenas 11/5/2023
ABI 还以不同的方式处理没有原型或具有可变数量原型(如 printf)的函数,并且不仅会因操作系统而变化,还会因 CPU 类型而变化,并且根据所使用的语言进行配置。有关详细信息,应查阅您感兴趣的特定配置的调用约定的文档
1赞 Carlo Arenas 11/6/2023
扩展@fuz评论,我认为“调用约定”在您的问题中没有正确使用,这可能是您获得令人困惑的搜索结果的部分原因。“printf vs _printf”标识函数在导出时可以具有的不同名称,虽然在 Windows 中通常有“cdecl 调用约定”函数在其名称中使用前导下划线,但这只是一种约定,要确定它如何期望其参数,您应该检查标题和文档。

答:

3赞 Carlo Arenas 11/6/2023 #1

32 位 x86 CPU 没有那么多寄存器可供使用,因此最初为它们设计的大多数调用约定主要使用堆栈。

这显然不如使用寄存器快,因此一旦 64 位 x86 CPU 变得更加普遍,参数就会尽可能多地存储在寄存器中。

要生成一个完全可用的应用程序,您需要将代码链接到“libc”;Windows 提供了至少 2 个 SDK ,并且还有独立的实现,例如 MinGW-w64 使用的实现;它们中的每一个都将使用您应该匹配的特定调用约定导出其函数,并且在名称中使用前导“_”通常是防止冲突调用成功链接的方式。

评论

0赞 Tim 11/7/2023
感谢 Carlo 和大家的澄清和文档。也感谢 Sep 的语法修复。