如何在Rust中调用汇编函数?

5

我知道在Rust中调用C函数的方法之一是使用以下方式:

extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("abs(-3) = {}", abs(-3));
    }
}

但是如何在Rust代码中调用一些汇编函数(位于与其他Rust文件相同目录中的.s文件内)呢?


1
如果您编写的汇编函数遵循C调用约定/ABI,对于调用函数来说,无论是您还是编译器编写了该汇编代码都没有关系。 - Peter Cordes
2个回答

15

示例

这是在macOS上使用C x86_64调用约定的汇编函数。它不会崩溃,所以一定是正确的 :-)

Cargo.toml

[build-dependencies]
cc = "1.0"

build.rs

fn main() {
    cc::Build::new()
        .file("add.S")
        .compile("my-asm-lib");
}

add.S

.text
.globl _my_adder
_my_adder:
        addq %rdi, %rsi
        movq %rsi, %rax
        ret

src/main.rs

extern "C" {
    fn my_adder(a: u64, b: u64) -> u64;
}

fn main() {
    let sum = unsafe { my_adder(1, 2) };
    println!("{}", sum);
}

Explanation

要使用汇编函数,有两个主要步骤:

  1. 编译和链接函数
  2. 调用函数

编译和链接

Rust 并没有内置编译汇编文件的方式。相反,你需要使用机器上已经存在的 C 编译器进行编译。最常用的方法是在 构建脚本 中使用 cc crate

构建脚本将会把汇编文件编译成静态库,并且告诉 Cargo 去链接这个库。

你必须确保你的汇编语法对使用的 C 编译器是有效的。例如,MSVC 和 GCC 使用不同的汇编风格。你可能需要有多份相同的汇编函数来处理不同的平台。

调用

调用函数高度依赖于调用约定。主要的调用约定是C 调用约定,通过 extern "C" 或者只使用 extern 表示。你可能还需要使用 #[link_name = "..." ] 或者 #[no_mangle] 属性。

你还可以使用 #[naked] 函数创建自定义调用约定(参见 RFC 1201)。然后,根据其调用约定,在调用方代码中在调用前/后使用汇编代码。这在稳定的 Rust 中不可用

Rust 1.21.1 还支持许多其他的调用约定:

cdecl、stdcall、fastcall、vectorcall、thiscall、aapcs、win64、sysv64、ptx-kernel、msp430-interrupt、x86-interrupt、Rust、C、system、rust-intrinsic、rust-call、platform-intrinsic、unadjusted


谢谢回答。我需要用 Rust 将汇编文件链接起来,这样它就知道函数来自哪里,还是如果汇编文件与 Rust 文件在同一目录中,Rust 可以自动解决? - typos
1
你能详细说明一下Rust的调用约定吗? - fuz
2
@fuz:这是未指定的。Rust在任何目标上都不承诺任何特定的调用约定。实际上,目前它通常与C约定相匹配;但是在将来的任何时候都可能发生变化,例如如果在特定架构上找到了更快的调用约定。 - Matthieu M.
当交叉编译不同的目标时,这个怎么工作呢?你的代码示例似乎仍然调用gcc而不是$TARGET-gcc。我该如何更改呢? - piegames
1
@piegames:这是一个非常好的问题,但很遗憾我没有答案。如果你在 Stack Overflow 上找不到答案,我建议你将其作为一个独立的问题提出来,希望更有经验的人能够加入讨论。 - Matthieu M.
显示剩余5条评论

3
完成另一个答案

如果您使用夜间版的Rust,则可以通过使用global_asm!宏避免使用自定义构建脚本。

A simple usage looks like this:

#[feature(global_asm)]

...

global_asm!(include_str!("something_neato.s"));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接