【问题标题】:How to call Assembly Functions from C on x86 architecture?如何在 x86 架构上从 C 调用汇编函数?
【发布时间】:2020-03-29 06:34:00
【问题描述】:

不幸的是,互联网上缺乏关于如何在 C 程序中实现 x86 汇编的指南。此外,新手想知道不同的语法(IT&T 和英特尔)。

就我个人而言,我不确定如何处理不同的编译器/汇编器,如 NASM 或 GCC。

我创建了以下高度复杂的程序:

#include <stdio.h>

extern int add_imm(int,int);

int main(int argv, char** argc){
  int a = 2;
  int b = 3;
  int c = add_imm(a,b);
  printf("%d", c);
}

与其他高度复杂的程序一起使用:

SECTION .TEXT
      GLOBAL add_imm

add_imm:
    push rbp
    mov rsp, rbp
    mov rax, [rbp+24]
    mov rbx, [rbp+16]
    add rax, rbx
    pop rbp
    ret

编译:

nasm -f elf64 add_imm.S
gcc main.c add_imm.o -o tes

引发编译器错误:

Undefined symbols for architecture x86_64:
  "_add_imm", referenced from:
      _main in main-019ea6.o
ld: symbol(s) not found for architecture x86_64

所以我认为如果有人能够解释如何将汇编语言真正嵌入到 C 中,那将是值得的。 什么是重要的考虑?什么是通用调用约定?

注意:我使用的是 macOS,但我希望它也能在 Linux 上运行。

【问题讨论】:

  • 你在为什么操作系统编程?
  • 请注意,调用约定也很可能是错误的。但如果不知道您正在为什么操作系统编程,我无法确定。
  • @fuz:我认为 GCC 没有被移植到任何标准调用约定是堆栈参数的 x86-64 操作系统。 AFAIK 唯一支持的 3 个约定是 x86-64 SysV 和 Windows x64 fastcall 和 vectorcall(相同,但 __m128 在 XMM regs 中传递而不是通过引用传递)。
  • @PeterCordes 确实如此。这不正是我在原始评论中写的吗?顺便说一句,Go 在 amd64 上使用仅堆栈调用约定,所以就是这样。
  • MacOS Unix。您可能会争辩说,它比 Linux 更重要,因为它的传统可以追溯到 FreeBSD。 Linux 是 Unix 的重新实现,并且符合 POSIX,但有些人不喜欢称它为 Unix。或者您的意思是像 x86-64 Solaris 或原始 FreeBSD、OpenBSD 或 NetBSD 这样的 Unix?

标签: c macos assembly x86


【解决方案1】:

在汇编中编写函数需要做的关键事情是遵循您正在编程的平台的 ABI(应用程序二进制接口)。本文件规定:

  • C 语言(intlongfloat、...)和标准库中的类型宽度是多少
  • 结构在内存中的布局方式
  • 如何调用函数(函数调用顺序),具体来说
    • 如何将参数传递给函数
    • 函数需要保留哪些寄存器
    • 返回值如何返回给调用者
    • 如何实际调用函数
  • 如何将 C 标识符转换(修饰/修饰)为符号
  • 如何访问静态和线程局部变量和函数
  • 使用什么内存模型

每个操作系统和体系结构的 ABI 都不同,您不应对其进行假设。最重要的是,一个操作系统和架构的代码示例很可能无法在另一个上运行,即使它们具有相同的操作系统或架构。 x86_64-linux 与 x86_64-osx 是不同的平台!

对于 x86_64-osx,相关的 ABI 文档是 OS X ABI Function Call Guide,它指的是 x86_64 SysV ABI。主要区别在于标识符的修饰方式(带有前导下划线)以及有关共享对象的一些细节,这些细节现在不一定需要您关注。

对于特别讨厌的细节,编写一些 C 代码然后让编译器使用 -S 选项生成汇编代码通常是个好主意。要在汇编中做同样的事情,只需执行编译器所做的任何事情。

解决了这个问题,你需要做的是:

  • 为您的函数确定一个名称;我们就叫它foo
  • 在 C 源代码或要从中使用的头文件中使用适当的类型签名将函数声明为外部函数

    extern int foo(int, int);
    
  • 参考ABI文档,找到你的函数标识符对应的符号;对于 x86_64-osx,您需要在标识符前面加上下划线以获取符号(即 _foo

  • 使用您喜欢的汇编程序在汇编文件中编写您的函数。确保生成正确类型的目标文件。对于 x86_64-osx,正确的类型是macho64,所以你应该像这样组装:

    nasm -f macho64 foo.asm
    
  • 确保遵守调用约定。有关简短介绍,请参阅this article 或阅读上面链接的 ABI 文档。你真的应该阅读它。

  • 使用global 指令将符号标记为全局
  • 最后,将nasm生成的目标文件包含到链接中

以下是您做错的总结:

  • 您没有使用正确的符号名称,因为您忘记使用前导下划线来装饰它
  • 您没有正确遵循调用约定
  • 您生成的对象文件类型不正确(elf64 而不是 macho64
  • 您错误地命名了您的部分(请参阅 nasm 文档,了解它接受 macho64 的哪些部分名称)
  • 您犯了一些其他人指出的编程错误,导致您的函数无法正常工作

【讨论】:

  • 这是一个很棒的知识扩展答案。非常感谢!
【解决方案2】:

正如错误消息所暗示的,编译器希望找到名称中带有前导下划线的函数,即,您还需要从 NASM 代码中导出 _add_imm。我发现为此使用宏很方便。

该名称将解决编译器错误,但您的汇编代码也是错误的,例如,您正在用 rbp 覆盖 rsp 而它应该是相反的,即 mov rbp, rsp。 (Intel 和 AT&T 语法的操作数顺序颠倒了,也许这就是混乱的根源。)

在 64 位 x86 平台上,您的 C 调用约定也极有可能是错误的,即参数应该在寄存器中而不是在堆栈中。

section .text

%macro export_function 1
global %1, _%1
_%1:
%endmacro

export_function add_imm
add_imm:
    push rbp
    mov rbp, rsp
    mov rax, rdi  ; (calling convention assumed, check yours)
    add rax, rsi
    mov rsp, rbp
    pop rbp
    ret

rsprbp 的杂耍显然不需要此功能才能工作,但由于它们已包含在原始版本中,因此在此处进行了更正。)

【讨论】:

  • 您对 rbp、rsp 的看法是正确的。谢谢!不幸的是,您的答案的第一部分不正确,因为我犯了错误并在 macOS 中使用了 -f elf64
  • @Niclas 哪一部分不正确?我刚刚在 macOS 上尝试过,只是将 -f elf64 更改为 -f macho64 并且效果很好。如果我使用您的原始代码,它不适用于-f macho64,因为它缺少下划线并且使用了不正确的调用约定,就像我在回答中所说的那样。反正我这里有一些 macOS x86-64 组装实验:github.com/arkku/asm-x64-macos
  • 啊好吧,我误会你了。对不起,你是 100% 正确的!
猜你喜欢
  • 2018-10-13
  • 1970-01-01
  • 2013-04-21
  • 2012-11-06
  • 2023-03-25
  • 2012-12-03
  • 1970-01-01
  • 2020-12-17
相关资源
最近更新 更多