How to use linker version scripts in rust 1.54+

This is essentially the same as the question asked by @alexanderlinne back in 2019.

Linker version scripts no longer appear to be working in the current version of rust. This appears to be due to Rust providing its own version script which conflicts with the one I want to use.

Example

src/lib.rs

pub unsafe extern "system" fn foo() {}

Cargo.toml

[package]
name = "version_script_test"
version = "0.1.0"
edition = "2018"

[lib]
name = "testing"
crate-type = ["cdylib"]

[dependencies]

mapfile

testing1.0 {
       global:
                foo;
};

Building

$ rustc --version
rustc 1.54.0 (a178d0322 2021-07-26)
$ cargo rustc -- -C link-args=-Wl,--version-script=mapfile
   Compiling version_script_test v0.1.0 (/mnt/c/Users/Jasper/CLionProjects/version_script_test)
error: linking with `cc` failed: exit status: 1
  |
  = note: "cc" "-Wl,--version-script=/tmp/rustcrti1pz/list" [truncated]  "-Wl,--version-script=mapfile"
  = note: /usr/bin/ld: anonymous version tag cannot be combined with other version tags
          collect2: error: ld returned 1 exit status


error: aborting due to previous error

error: could not compile `version_script_test`

To learn more, run the command again with --verbose.

How can I use my own version script?

GNU ld can't do multiple version scripts... but lld can. So:

  1. Use lld.
  2. If you want to hide symbols from rustc's version script, you can mark them as private in yours by setting them as local.

export RUSTFLAGS="-Clinker=clang -Clink-arg=-fuse-ld=lld"

The build.rs:

fn main() -> Result<(), std::io::Error> {
    let cur_dir = std::env::current_dir()?;

    // Use a versionscript to limit symbol visibility.
    println!(
        "cargo:rustc-cdylib-link-arg=-Wl,--version-script={}/versionscript.txt",
        cur_dir.to_string_lossy()
    );
}

And the version script:

{
  global:
    yourlibrary_symbol;
    yourlibrary_symbol2;
  local:
    rust_eh_personality;
    *;
};
2 Likes

This helped resolve the error from the compiler, however when I check it using objdump -TC target/debug/libtesting.so it did not attach the version node to foo. I can tell the version script is being used though, since I can remove rust_eh_personality by making it local.

$ objdump -TC target/debug/lib
testing.so

target/debug/libtesting.so:     file format elf64-x86-64

DYNAMIC SYMBOL TABLE:
0000000000000000  w   D  *UND*  0000000000000000  Base        __gmon_start__
0000000000000000  w   D  *UND*  0000000000000000  Base        _ITM_deregisterTMCloneTable
0000000000000000  w   D  *UND*  0000000000000000  Base        _ITM_registerTMCloneTable
0000000000000000  w   DF *UND*  0000000000000000  Base        __cxa_finalize
0000000000000000      DF *UND*  0000000000000000  GCC_3.0     _Unwind_GetDataRelBase
0000000000000000      DF *UND*  0000000000000000  GCC_4.2.0   _Unwind_GetIPInfo
0000000000000000      DF *UND*  0000000000000000  GCC_3.0     _Unwind_GetLanguageSpecificData
0000000000000000      DF *UND*  0000000000000000  GCC_3.0     _Unwind_GetRegionStart
0000000000000000      DF *UND*  0000000000000000  GCC_3.0     _Unwind_GetTextRelBase
0000000000000000      DF *UND*  0000000000000000  GCC_3.0     _Unwind_SetGR
0000000000000000      DF *UND*  0000000000000000  GCC_3.0     _Unwind_SetIP
0000000000003a40 g    DF .text  0000000000000001  Base        foo
0000000000003c50 g    DF .text  00000000000002e5  Base        rust_eh_personality

In the objdump output, I would expect it to say testing1.0 instead of Base for foo.

I suspect the anonymous version script generated by Rust is taking precedence. Do you know how I might be able to fix this?

Maybe give the Rust function and the exported symbols different names? You can add a thing to your build.rs to add new names to symbols. So in Rust you e.g. expose foo_impl, and then in build.rs:

    println!("cargo:rustc-cdylib-link-arg=-Wl,--defsym=foo=foo_impl");
1 Like

And then your version script versions the newly created foo that rustc's version script didn't touch.

Thanks! --defsym with lld did the trick to fix the issue.

Changes

#[no_mangle]
pub unsafe extern "system" fn foo_impl() {}
testing1.0 {
       global:
                foo;
       local:
                foo_impl;
};

Output

$ cargo rustc -- -C link-args=-Wl,--defsym=foo=foo_impl,--version-script=mapfile -Clinker=clang -Clink-arg=-fuse-ld=lld
   Compiling version_script_test v0.1.0 (/home/tests/projects/version_script_test)
    Finished dev [unoptimized + debuginfo] target(s) in 15.09s
$ objdump -TC target/debug/lib
testing.so

target/debug/libtesting.so:     file format elf64-x86-64

DYNAMIC SYMBOL TABLE:
0000000000000000  w   D  *UND*  0000000000000000  Base        __gmon_start__
0000000000000000  w   D  *UND*  0000000000000000  Base        _ITM_deregisterTMCloneTable
0000000000000000  w   D  *UND*  0000000000000000  Base        _ITM_registerTMCloneTable
0000000000000000  w   DF *UND*  0000000000000000  Base        __cxa_finalize
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 pthread_mutex_lock
0000000000000000      DF *UND*  0000000000000000  GLIBC_2.2.5 pthread_mutex_unlock
0000000000000000      DF *UND*  0000000000000000  GCC_3.0     _Unwind_GetDataRelBase
0000000000000000      DF *UND*  0000000000000000  GCC_4.2.0   _Unwind_GetIPInfo
0000000000000000      DF *UND*  0000000000000000  GCC_3.0     _Unwind_GetLanguageSpecificData
0000000000000000      DF *UND*  0000000000000000  GCC_3.0     _Unwind_GetRegionStart
0000000000000000      DF *UND*  0000000000000000  GCC_3.0     _Unwind_GetTextRelBase
0000000000000000      DF *UND*  0000000000000000  GCC_3.0     _Unwind_SetGR
0000000000000000      DF *UND*  0000000000000000  GCC_3.0     _Unwind_SetIP
0000000000003c40 g    DF .text  00000000000002e1  Base        rust_eh_personality
0000000000003a10 g    D  .text  0000000000000000  testing1.0  foo

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.