在 cdylib Rust crate 中使用 ld 版本脚本

Using an ld version script in a cdylib Rust crate

我在构建 cdylib Rust crate 时尝试使用版本脚本,但是由于 Rust 编译器创建的匿名版本脚本,我 运行 遇到了问题。我按照这个 forum post 了解如何添加版本脚本,但他们从未提到过这个问题。

执行

我正在使用 cargo-make 构建我的项目。在我的 Makefile.toml 中,我有这个任务:

[tasks.build]
toolchain = "nightly"  # Running with nightly-x86_64-unknown-linux-gnu
command = "cargo"
args = ["rustc", "--release", "-p", "my_crate", "--", "-C", "link-args=-Wl,--version-script=versions.map"]

在 运行 cargo make build 之后,该任务执行此构建命令。

rustup run nightly cargo rustc --release -p my_crate -- -C link-args=-Wl,--version-script=versions.map

错误

但是,它一直产生这个错误。据我所知,我的版本脚本(如下所示)与 Rust 生成的匿名版本脚本(错误中的 /tmp/rustcyXUHTy/list)冲突。不幸的是,Rust 生成的版本脚本在创建后立即被删除,所以我实际上不知道它是什么样子。我试图跟随this answer查看其他版本的脚本,但是它被删除得太快了,我无法看到输出。

error: linking with `cc` failed: exit status: 1
  |
  = note: "cc" "-Wl,--version-script=/tmp/rustcyXUHTy/list" ... "-Wl,--version-script=versions.map"
  = note: /usr/bin/ld: anonymous version tag cannot be combined with other version tags
          collect2: error: ld returned 1 exit status

生锈

// I'm not completely sure which tags should be used and so far they have had no effect on the error
// #[no_mangle]
// #[export_name = "foo"]
pub unsafe extern "system" fn foo() {}

// The crate also contains other functions which are not covered by my version script
// I tried removing all of the other #[no_mangle] functions, but it had no effect
#[no_mangle]
pub unsafe extern "system" fn bar() {}

版本脚本

我在编写版本脚本方面不是很有经验,所以这是我想出的简单测试脚本。最终产品将使用现有 C 项目中的类似版本脚本。

Project_1.0 {
    global:
        foo;
};

itamarst在Rust forums上提供的解决方案。

说明

如问题所示,ld不支持多版本脚本。但是,lld 这样做了,所以我们可以用它来代替。 (可以在 ubuntu 上使用 sudo apt install lld 安装)。要使用 lld 而不是 ld,请将 -Clink-arg=-fuse-ld=lld 传递给 rustc

然而,这本身是不够的。 Rust 生成的版本脚本将优先,版本节点将不会按照我们的版本脚本中指定的那样应用。为了解决这个问题,可以给函数一个临时名称,并且可以通过链接器参数 (--defsym) 将一个新符号链接到它。在版本脚本中可以自由使用新的符号,并且可以将原函数名标记为本地,以防止上传重复的符号。

Rust 代码

// Name function foo_impl and rename it on the command line
#[no_mangle]
pub unsafe extern "system" fn foo_impl() {}

版本脚本

Project_1.0 {
    global:
        foo;
    local:
        foo_inner;
};

建筑

cdylib 中,所有 rust/linker 参数都可以在 build.rs.

中配置
// Tell Rust to use lld instead of ld
println!("cargo:rustc-cdylib-link-arg=-fuse-ld=lld");

// Set version script path
println!("cargo:rustc-cdylib-link-arg=-Wl,--version-script=mapfile");

// Rename symbols to get around the anonymous version script
for symbol in &["foo"] {
    println!("cargo:rustc-cdylib-link-arg=-Wl,--defsym={}={}_impl", symbol, symbol);
}

或者,所有这些参数都可以在命令行上传递。

cargo rustc -- -Clink-arg=-fuse-ld=lld -Clink-args=-Wl,--defsym=foo=foo_impl,--version-script=mapfile