rust-lang / cargo

The Rust package manager
https://doc.rust-lang.org/cargo
Apache License 2.0
12.63k stars 2.4k forks source link

Exports symbols of cdylib dependencies #13978

Open ensc opened 4 months ago

ensc commented 4 months ago

Problem

When a cdylib has another cdylib as a dependency, the resulting library contains the symbols of both packages.

E.g. when building a project (see https://github.com/ensc/cargo-13978) like

|-- ext/foo
|   |-- Cargo.toml
|   |   [lib]
|   |   crate-type = ["cdylib", "rlib"]
|   |
|   `-- src/lib.rs
|       #[no_mangle]
|       extern "C" fn foo_func() -> u32 { func() }
|
|-- Cargo.toml       
|   [lib]
|   crate-type = ["cdylib", "rlib"]
|
|   [dependencies]
|   foo = { path = "ext/foo" }
|
`-- src/lib.rs
    #[no_mangle]
    extern "C" fn bar_func() -> u32 { func() }

the resulting (outer) library contains

$ nm -D libbar.so | grep ' T '
0000000000011920 T bar_func
00000000000119b0 T foo_func

The inner library (which can be installed separately) contains

$ nm -D libfoo.so | grep ' T '
0000000000011800 T foo_func

foo_func should be only in libfoo.so.

Version

cargo 1.80.0-nightly (a8d72c675 2024-05-24)
release: 1.80.0-nightly
commit-hash: a8d72c675ee52dd57f0d8f2bae6655913c15b2fb
commit-date: 2024-05-24
host: x86_64-unknown-linux-gnu
libgit2: 1.7.2 (sys:0.18.3 vendored)
libcurl: 8.6.0-DEV (sys:0.4.72+curl-8.6.0 vendored ssl:OpenSSL/1.1.1w)
ssl: OpenSSL 1.1.1w  11 Sep 2023
os: Fedora 40.0.0 (Forty) [64-bit]
ehuss commented 4 months ago

I believe this is expected behavior. The bar package refers to foo::func(), and thus must pull in the foo rlib as a static library. Rust does not support dynamically loading cdylibs automatically.

You can use dylib to dynamically link Rust projects, or use a library like libloading to dynamically load a cdylib. (I think artifact dependencies can also do something similar, but it's more advanced and requires an extern block to set up the bindings.)

ensc commented 4 months ago

I do not think this is related to loading cdylib from rust. I want to use it like

graph LR;
  c_app[C application]
  libfoo[libfoo.so]
  libbar[libbar.so]
  libbaz[libbaz.so]
  c_app -- foo_func() --> libfoo;
  c_app -- bar_func() --> libbar;
  c_app -- baz_func() --> libbaz;
  libbar -. foo::func() .-> libfoo;
  libbaz -. foo::func() .-> libfoo;

(to avoid an easy solution like "use only libbar.so", I added a libbaz.so which is similarly to libbar.so)

It would be nice when the rust internal linking between libbar.so and libfoo.so would work (I tried dylib but this breaks somehow when building tests; perhaps LTO related and another issue). For now, it is ok when libbar.so contains the statically linked rust code of libfoo.so.

But the problem is that the C library libbar.so (and libbaz.so) contains the symbols of libfoo.so.

cdylib libraries should be created in a way that extern "C" symbols of dependent cdylib are exported as hidden (in C __attribute__((__visibility__(hidden))) or the corresponding link flag). When dependency is a pure rlib, they should be public.