[SOLVED] Link against a static library when your crate defines a symbol which is used by the static library


#1

Hi!

I’ve got a linker related problem. I’d like to create Rust bindings for a C program so people can write modules in Rust for it. A module is just an .so file. I need to link against a static library. It declares the function rust_parser_proxy_new() and my crate defines it. The problem is, that after the .so file is produced by cargo I see the rust_parser_proxy_new() function both as undefined and in the .text section. I’d like to see it only in the .text section.

I reproduced this problem with a very simple library which can be found here: https://github.com/ihrwein/link-repro

Here, the liba.a static library defines the foo() function and declares the bar() function. I link against this library and my crate defines the bar() function. If I run cargo build and check the .so file I see the bar symbol both as undefined and in the .text section. I think this is because the -l parameters are after the object file produced by rustc. This is good for almost every case but not for my one. Do you know a solution for this problem?

$ rustc src/lib.rs --crate-name link_repro --crate-type dylib -g --out-dir /home/tibi/workspace/link-repro/target/debug --emit=dep-info,link -L dependency=/home/tibi/workspace/link-repro/target/debug -L dependency=/home/tibi/workspace/link-repro/target/debug/deps -L native=/home/tibi/workspace/link-repro -l static=a -Z print-link-args
src/lib.rs:2:1: 5:2 warning: function bar is marked #[no_mangle], but not exported, #[warn(private_no_mangle_fns)] on by default
src/lib.rs:2 extern fn bar() -> i32 {
src/lib.rs:3     println!("bar");
src/lib.rs:4     1 as i32
src/lib.rs:5 }
src/lib.rs:2:1: 5:2 warning: function is never used: `bar`, #[warn(dead_code)] on by default
src/lib.rs:2 extern fn bar() -> i32 {
src/lib.rs:3     println!("bar");
src/lib.rs:4     1 as i32
src/lib.rs:5 }
"cc" "-Wl,--as-needed" "-m64" "-L" "/home/tibi/.multirust/toolchains/stable/lib/rustlib/x86_64-unknown-linux-gnu/lib" "/home/tibi/workspace/link-repro/target/debug/link_repro.0.o" "-o" "/home/tibi/workspace/link-repro/target/debug/liblink_repro.so" "-Wl,--whole-archive" "-l" "morestack" "-Wl,--no-whole-archive" "/home/tibi/workspace/link-repro/target/debug/link_repro.metadata.o" "-nodefaultlibs" "-Wl,--whole-archive" "/tmp/rustc.Kar3vNl88nIY/libstd-198068b3.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.Kar3vNl88nIY/libcollections-198068b3.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.Kar3vNl88nIY/librustc_unicode-198068b3.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.Kar3vNl88nIY/librand-198068b3.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.Kar3vNl88nIY/liballoc-198068b3.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.Kar3vNl88nIY/liblibc-198068b3.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.Kar3vNl88nIY/libcore-198068b3.rlib" "-Wl,--no-whole-archive" "-L" "/home/tibi/workspace/link-repro/target/debug" "-L" "/home/tibi/workspace/link-repro/target/debug/deps" "-L" "/home/tibi/workspace/link-repro" "-L" "/home/tibi/.multirust/toolchains/stable/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-L" "/home/tibi/workspace/link-repro/.rust/lib/x86_64-unknown-linux-gnu" "-L" "/home/tibi/workspace/link-repro/lib/x86_64-unknown-linux-gnu" "-Wl,-Bstatic" "-Wl,--whole-archive" "-l" "a" "-Wl,--no-whole-archive" "-Wl,-Bdynamic" "-l" "dl" "-l" "pthread" "-l" "rt" "-l" "gcc_s" "-l" "pthread" "-l" "c" "-l" "m" "-shared" "-Wl,-rpath,$ORIGIN/../../../../.multirust/toolchains/stable/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-Wl,-rpath,/usr/local/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-l" "compiler-rt"

#2

[quote=“ihrwein, post:1, topic:3403”]
my crate defines it.
[/quote]If I understand the first warning correctly, it doesn’t. Is there a reason the function isn’t pub?


#3

It defines it:

$ nm target/debug/liblink_repro.so | grep bar
00000000000a7240 t bar
                 U bar
00000000000a7270 t _ZN3bar10__rust_abiE
00000000003c3880 d _ZN3bar15__STATIC_FMTSTR20h9c2f034e5985ab08qaaE
00000000000ddf90 T _ZN4sync7barrier17BarrierWaitResult9is_leader20h01c317323ccc7697drpE
00000000000dd860 T _ZN4sync7barrier7Barrier3new20h5aea1a92fb55f912IppE
00000000000dd950 T _ZN4sync7barrier7Barrier4wait20hc64d9d40caad24c7YppE

It is intentionally not public. This will be a some sort of entry point to reach the Rust code. If its public then only one Rust module can be used at a time: the linker would resolve the function at the first time and the other module would be “unreachable” because every bar() call is mapped to the first module. Does this makes sense? :smile:


#4

Aren’t the modules loaded dynamically? Allowing each to export the same set of symbols?


#5

It sounds like you need a linker script or something like gcc’s -fvisibility=hidden. The function bar needs to be pub to be linked between objects, otherwise it’s like a C static that only the current compilation unit can access. But -fvisibility is a way to control the final ELF symbol visibility in the .so.

See https://gcc.gnu.org/wiki/Visibility


#6

Yep, they are dynamically loaded and the module loader will call foo() in every case. The problem is, that the the foo() function in real life is a generated grammar and is common between all Rust modules (a static library). Every module should implement the bar() function which instantiates the module specific objects. So foo() (in C) calls into bar() (in Rust). If bar() is public then the first module’s bar() will shadow all the other ones.


#7

Your solution solved this problem, thank you very much! :smile:

For the record, I leave here this example:

C:

__attribute__((visibility("hidden"))) int32_t bar();

Rust:

pub extern fn bar() -> i32 { ... }

And everything is fine :smile: Thanks for @gkoz as well!


#8

I would expect each module to have its own copy of the static library.

To reproduce the shadowing I’ve built two versions of the module (both with pub extern fn bar), one prints “bar” and the other prints “baz”, loaded them both with

#include <dlfcn.h>

void run_foo(void *handle) {
	void (*f)() = dlsym(handle, "foo");
	f();
}

void main(int arc, char **argv) {
	run_foo(dlopen("./m1.so", RTLD_NOW));
	run_foo(dlopen("./m2.so", RTLD_NOW));
}

and…

>./run
bar
baz
>

#9

Thank you! It’s very good to see how helpful the Rust community is!