I am attempting to use a version script while building a cdylib
Rust crate, however I am running into issues due to the anonymous version script created by the Rust compiler. I followed this forum post on how to add a version script, but they never mentioned this issue.
I'm using cargo-make
to build my project. In my Makefile.toml
I have this task:
[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"]
Upon running cargo make build
, that task executes this build command.
rustup run nightly cargo rustc --release -p my_crate -- -C link-args=-Wl,--version-script=versions.map
However, it keeps producing this error. From what I can tell, my version script (shown below) conflicts with an anonymous version script that gets generated by Rust (/tmp/rustcyXUHTy/list
in the error). Unfortunately, the version script Rust generates is deleted immediately after creation so I don't actually know what it looks like. I attempted to follow this answer to view the other version script, but it was deleted too quickly and I was unable to see the output.
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() {}
I'm not very experienced writing version scripts so this is the simple test script I came up with. The final product will use a similar version script from an existing C project.
Project_1.0 {
global:
foo;
};
Solution provided by itamarst on the Rust forums.
As shown in the question, ld
does not support multiple version scripts. However, lld
does so we can use that instead. (Can be installed with sudo apt install lld
on ubuntu). To use lld
instead of ld
, pass -Clink-arg=-fuse-ld=lld
to rustc
.
However, this is not enough on its own. The version script Rust generates will take precedence and the version node will not be applied as specified in our version script. To get around this, functions can be given a temporary name and a new symbol can be linked to it via linker args (--defsym
). In the version script the new symbol can be freely used and the original function name can be marked as local to prevent a duplicate symbols from being uploaded.
// 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;
};
In a cdylib
all of the rust/linker arguments can be configured in the 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);
}
Alternatively, all of these arguments can be passed on the command line.
cargo rustc -- -Clink-arg=-fuse-ld=lld -Clink-args=-Wl,--defsym=foo=foo_impl,--version-script=mapfile