-
Notifications
You must be signed in to change notification settings - Fork 13.8k
Open
Labels
A-linkageArea: linking into static, shared libraries and binariesArea: linking into static, shared libraries and binariesC-bugCategory: This is a bug.Category: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.Relevant to the compiler team, which will review and decide on the PR/issue.
Description
If you build your Rust project as a static library to link it into a C codebase, you usually want to crank up the optimizations, as also indicated by the librsvg blogpost (towards the end):
https://people.gnome.org/~federico/blog/librsvg-build-infrastructure.html
This is all fine, until you want to link in another library built with Rust, which is very likely if you want to incrementally introduce Rust into your codebase. At that point the linker will start complaining about collisions caused by symbols defined by the standard library:
./liblib2.a(lib2-91c10be1e28e3b68.0.o): In function `std::heap::__default_lib_allocator::__rdl_alloc':
/checkout/src/libstd/heap.rs:31: multiple definition of `__rdl_alloc'
./liblib1.a(lib1-25b44f601e0ef586.0.o):/checkout/src/libstd/heap.rs:31: first defined here
./liblib2.a(lib2-91c10be1e28e3b68.0.o): In function `__rdl_alloc_excess':
lib2.cgu-0.rs:(.text.__rdl_alloc_excess+0x0): multiple definition of `__rdl_alloc_excess'
./liblib1.a(lib1-25b44f601e0ef586.0.o):lib1.cgu-0.rs:(.text.__rdl_alloc_excess+0x0): first defined here
./liblib2.a(lib2-91c10be1e28e3b68.0.o): In function `__rdl_alloc_zeroed':
lib2.cgu-0.rs:(.text.__rdl_alloc_zeroed+0x0): multiple definition of `__rdl_alloc_zeroed'
./liblib1.a(lib1-25b44f601e0ef586.0.o):lib1.cgu-0.rs:(.text.__rdl_alloc_zeroed+0x0): first defined here
./liblib2.a(lib2-91c10be1e28e3b68.0.o): In function `__rdl_dealloc':
lib2.cgu-0.rs:(.text.__rdl_dealloc+0x0): multiple definition of `__rdl_dealloc'
./liblib1.a(lib1-25b44f601e0ef586.0.o):lib1.cgu-0.rs:(.text.__rdl_dealloc+0x0): first defined here
./liblib2.a(lib2-91c10be1e28e3b68.0.o): In function `__rdl_grow_in_place':
lib2.cgu-0.rs:(.text.__rdl_grow_in_place+0x0): multiple definition of `__rdl_grow_in_place'
./liblib1.a(lib1-25b44f601e0ef586.0.o):lib1.cgu-0.rs:(.text.__rdl_grow_in_place+0x0): first defined here
./liblib2.a(lib2-91c10be1e28e3b68.0.o): In function `__rdl_oom':
lib2.cgu-0.rs:(.text.__rdl_oom+0x0): multiple definition of `__rdl_oom'
./liblib1.a(lib1-25b44f601e0ef586.0.o):lib1.cgu-0.rs:(.text.__rdl_oom+0x0): first defined here
./liblib2.a(lib2-91c10be1e28e3b68.0.o): In function `__rdl_realloc':
lib2.cgu-0.rs:(.text.__rdl_realloc+0x0): multiple definition of `__rdl_realloc'
./liblib1.a(lib1-25b44f601e0ef586.0.o):lib1.cgu-0.rs:(.text.__rdl_realloc+0x0): first defined here
./liblib2.a(lib2-91c10be1e28e3b68.0.o): In function `__rdl_realloc_excess':
lib2.cgu-0.rs:(.text.__rdl_realloc_excess+0x0): multiple definition of `__rdl_realloc_excess'
./liblib1.a(lib1-25b44f601e0ef586.0.o):lib1.cgu-0.rs:(.text.__rdl_realloc_excess+0x0): first defined here
./liblib2.a(lib2-91c10be1e28e3b68.0.o): In function `__rdl_shrink_in_place':
lib2.cgu-0.rs:(.text.__rdl_shrink_in_place+0x0): multiple definition of `__rdl_shrink_in_place'
./liblib1.a(lib1-25b44f601e0ef586.0.o):lib1.cgu-0.rs:(.text.__rdl_shrink_in_place+0x0): first defined here
./liblib2.a(lib2-91c10be1e28e3b68.0.o): In function `__rdl_usable_size':
lib2.cgu-0.rs:(.text.__rdl_usable_size+0x0): multiple definition of `__rdl_usable_size'
./liblib1.a(lib1-25b44f601e0ef586.0.o):lib1.cgu-0.rs:(.text.__rdl_usable_size+0x0): first defined here
./liblib2.a(lib2-91c10be1e28e3b68.0.o): In function `rust_eh_personality':
lib2.cgu-0.rs:(.text.rust_eh_personality+0x0): multiple definition of `rust_eh_personality'
./liblib1.a(lib1-25b44f601e0ef586.0.o):lib1.cgu-0.rs:(.text.rust_eh_personality+0x0): first defined here
This only happens if you set lto = true
.
zerolfx, cerisier, eerii, breezewish, emesare and 1 more
Metadata
Metadata
Assignees
Labels
A-linkageArea: linking into static, shared libraries and binariesArea: linking into static, shared libraries and binariesC-bugCategory: This is a bug.Category: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.Relevant to the compiler team, which will review and decide on the PR/issue.
Type
Projects
Milestone
Relationships
Development
Select code repository
Activity
TimNN commentedon Sep 17, 2017
I don't think linking two rust static libraries into the same binary is actively supported today, see also @alexcrichton's #44283 (comment) in another issue.
Still marking as a bug for the moment since it works without LTO.
alexcrichton commentedon Sep 17, 2017
@CryZe can you provide a script or repository to reproduce this? In theory linking together two staticlibs with LTO should work, although there may be bugs preventing it from doing so. In the long run you probably don't want to do this, though, as each Rust library you link in will bring in a new copy of the standard library, which ideally everyone would share.
steveklabnik commentedon Sep 12, 2019
Triage: Hey @CryZe , are you still seeing this? Alex asked for a reproduction a while ago, if we can't reproduce, then we can't fix this :)
CryZe commentedon Sep 12, 2019
Alright I made a repository here: https://github.com/CryZe/multi-lib-lto-rust-bug-repro
Just run make and it should fail:
awakecoding commentedon Apr 10, 2020
I am also investigating this issue. I did a bunch of tests, and it looks like lto = true is the only way to avoid having the __rdl_alloc symbols defined without name mangling. lto = false or lto = "off" both get them defined and cause the conflict. While my primary concern is with iOS, I did notice that the multi-lib-lto-rust-bug-repro doesn't link on macOS because of a symbol conflict on _rust_eh_personality. This symbol specifically doesn't seem to be affected by the lto option :/
awakecoding commentedon Apr 14, 2020
@CryZe did you get any further on this? We are desperate for a workaround to make it work on iOS, where using shared libraries is not an option :(
awakecoding commentedon Apr 14, 2020
I came up with a "solution" that is wrong on so many levels, but at least makes the @CryZe reproduction sample link:
This effectively builds the first library, extracts the object files, does a search & replace on "rust_eh_personality" to rename it to "rust_eh_personaliti", and rebuilds the static library from the object files. By forcing the symbol change on one of the two libraries, the symbol conflict is avoided.
Now this is obviously a very ugly hack, and I don't know the impact of forcing a symbol name change on rust_eh_personality. Technically, does it need to keep this name, or could it be name mangled like the rest?
awakecoding commentedon Apr 15, 2020
After digging through some older tickets, I found #43415 and it mentioned a different result when building in debug vs release. I modified the reproduction sample to build in debug, and there is no linker issue. It does significantly change the layout of internal object files and where the symbols are (extracting the .a with 'ar' to see where symbols are).
One thing I noticed is that the linker doesn't complain about duplicated symbols if they are identical, and inside an object file of the same name (and possibly identical in totality I'm guessing). When building in debug, rust_eh_personality is defined in the same object file for both lib1 and lib2:
panic_unwind-bf192f19d9586a96.panic_unwind.67c9yyht-cgu.0.rcgu.o
When built in release, rust_eh_personality is defined in lib1-93605d7bc2291864.lib1.1hx46jnd-cgu.0.rcgu.o and lib2-3edec0329c3e4919.lib2.64tkgp8c-cgu.0.rcgu.o respectively. We decompiled the symbols to see if they were identical, and they are in our case (empty function stubs) but the linker sees them as different. I'm pretty sure that if rust_eh_personality was compiled inside an object file of the same name like what happens when you build in debug, the linker problem would go away.
This would still mean that in order to get things to build and link correctly, one should use exactly the same version of Rust to avoid issues, but it would still be a much better option that being unable to link multiple static libraries together.
awakecoding commentedon Apr 15, 2020
Nevermind about building as debug as a way to get it working, as it fails the same way as in release mode when using panic="abort":
awakecoding commentedon Apr 15, 2020
@alexcrichton @TimNN any idea what affects the compilation of the rust_eh_personality symbol specifically? There is this: https://github.com/rust-lang/rust/blob/master/src/librustc_codegen_ssa/back/symbol_export.rs#L118
I'm trying to figure out if there is a way to get this symbol definition built as part of a separate object file, like what happens when it gets built as part of panic_unwind.o (+ the name mangling). I am pretty sure this would get rid of the duplicated symbol issue once and for all.
It may seem like a hack, but it would actually work quite well. The recommendation would be to build with the same version of Rust to avoid potential issues with symbols that have slight differences, but this is acceptable in my mind. It is very similar to building static libraries against a compatible MSVC static runtime on Windows, except we're talking about the Rust core libraries here.
This type of issue needs to be fixed for cases where shared libraries are not a suitable option, like iOS. It is only going to become more common as more C libraries get rewritten to use Rust under the hood, causing conflicts when two of such libraries get linked together.
kleisauke commentedon Jan 1, 2021
I also experience this bug when linking against two Rust staticlibs (i.e. librsvg and rav1e) which was built with "fat" LTO enabled. I could workaround this with:
LDFLAGS="-Wl,--allow-multiple-definition"
But it's something I'd rather avoid.
fwiw, the above mentioned link is now 404. It was probably linking to:
rust/compiler/rustc_codegen_ssa/src/back/symbol_export.rs
Line 117 in 18d27b2
10 remaining items
bjorn3 commentedon Jun 26, 2023
See also #104707. If you are able to use dynamically linked libraries, the cdylib crate type doesn't have this issue.
Lupus commentedon Jun 26, 2023
Unfortunately OCaml supports only C stubs as
.a
archives for compilation to native code,.so
options is used for bytecode compilation, which is not really pratcial for production use as it's slow.Actually after setting
lto
tooff
, I still see the same linking conflicts. Probably I'm running into the same issues, as described here: #73632 (comment)bjorn3 commentedon Jun 26, 2023
I see. It is really unfortunate that
.a
archives don't have a way to hide symbols.nirbheek commentedon Nov 30, 2023
This issue seems to be taking a while to get fixed, so I thought I'd share an ugly+hacky workaround that worked for us at GStreamer: rename the symbols. The proper way to do that involves using something like
goblin
, but in our case we just used ...sed
hack to fix clash with boringtun
bowemi commentedon Sep 3, 2025
I don't think this is true. Take a look at this article https://alanwu.space/post/symbol-hygiene/
In a post build step, you can crack open the .a file with
ar x ...
Then link all the .o files back into a single .o file with
ld -r
and on mac you can control the exported symbols. This is almost ideal except the mac ld64 seems to drop the dwarf symbols whenld -r
is used though there are still STABS debug symbols. The STABS information points back to the original .o files (instead of the final master .o file), so those original files are required to do any debugging which is a pain.If the symbols are marked as local instead of global, then users won't have this problem. It would be nice if rust supported this out of the box. So if I could add some configuration like...
Then rust automatically does a step to combine all the .o files into a single object which only has the global symbols
my_c_api_1
andmy_c_api_2
bjorn3 commentedon Sep 3, 2025
Partial linking is not supported on Windows.
bowemi commentedon Sep 4, 2025
I don't understand this comment, I'm going to make the assumption that you're making the statement "It is not possible to do partial linking on Windows with the COFF/PE file format"
On windows, the static library (.lib) file format is COFF/PE. As a file format, COFF/PE does support partial linking and the
ld
linker supports partial linking.Given the following files...
If I naively do
I get the expected linker error
multiple definition of `inner';
If I compile the test*.c files into .o files with
gcc -c
, localize theinner
symbol withobjcopy.exe
, do a partial link withld -r
, and finally use gcc to compile the partially linked COFF/PE file + main.c. So commands...Then everything works as expected WRT linking and printing.
Ultimately though, I personally don't care about Windows support. The case I care the most about is Mach-O file formats which is what my comment was about
bjorn3 commentedon Sep 4, 2025
MSVC's link.exe has no
-r
flag.amyspark commentedon Sep 4, 2025
I asked for such a flag here: https://developercommunity.visualstudio.com/t/Allow-merging-object-files-aka-Single-O/10688830
You can use GNU ld to prelink MSVC object files, however their PE-COFF support does not include controlling the exported symbols list, nor it is possible to make Rust not mark its COMDAT sections as "deduplicate", see #143931 . (Admittedly there is no documentation on whether LINK.EXE supports COMDAT deduplicating at all.)
At GStreamer I made a tool to deduplicate a workspace's worth of Rust libraries, creating a single monolithic "rsworkspace" lib and then keeping only the crate specific objects in each library: https://gitlab.freedesktop.org/gstreamer/cerbero/-/merge_requests/1916, tool is here: https://gitlab.freedesktop.org/amyspark/dragonfire
bowemi commentedon Sep 4, 2025
This might be a naive statement, but this problem seems like it would be generally better if it was solved by Rust itself via an option for static libs like I mentioned above.
#44322 (comment)
ld64 seems to do this the best by combining the export symbols and -r functionality, but even ld64 still removes the dwarf debug symbols which isn't great