Note: I wasn't sure whether to place this under language design or compiler, since it's a bit of both.
We've been working on packaging and distributing Rust for the musl-native Alpine Linux distribution. Our perspective as such is mostly influenced by this. Thanks to the wonderful crt-static RFC, Rust now has the possibility to switch between a static and dynamic C runtime at... runtime.
Recently, a pull request has emerged to add support for dynamically linking musl target binaries. We based our work on packaging Rust to Alpine partially off this PR, as we as a distribution want to simply distribute dynamically-linked binaries. When working further off these changes to solve some bugs, issues and errors in the compilation and usage of Rust and Cargo, we ran into some issues that challenge the definition of the
crt-static target feature, especially given its main other usage: Windows MSVC targets.
CRT in one process
Between Windows and the *nix systems that I know of, there is one relevant fundamental difference in their approach to C runtimes: in Windows, dynamic libraries can have their own statically linked C runtimes. On *nix, this is not possible: there should be a single C runtime within the entire process.
What does this mean with regards to
crt-static? It means, for musl targets, that
crt-static implies that the the entire final output is linked statically: there can be no dynamic libraries with their own private C runtime as a result of the situation above. However, this conflicts with the situation for MSVC, where this is perfectly valid and I'd presume sometimes even appropriate approach. Thus, this is a difference in
crt-static semantics that needs to be resolved.
I have thought a bit about this, and in my opinion there seem to be two reasonable solutions to this:
- Accept the difference in
crt-static semantics between platforms (it implying a complete static link on *nix targets, and not on Windows targets);
- Add a new target feature (maybe simply
full-static?), which on *nix targets would be implied by
The result of this fully static linkage, whether target-specific or implied by a new target feature, would be that all
unknown-marked libraries (no specific linkage preference) would be linked statically instead of the default, dynamically, and that (implementation detail)
-static be added to the linker command line on the GNU linker through a newly added
Linker::static_executable(). Static linking binaries with musl works as well as per the current situation (of course, since that is currently the main usage for the musl target pre-PR above), but this is more out of luck with the final linker arguments and seems fragile.
Our own patches currently assume the former for simplicitly, but as our aim is to get all relevant patches upstream, resolving this in the way the community feels best has our preference.
In the situation above, dylibs can obviously not work with
crt-static. As I understood from @retep998, they do not always work properly on MSVC with
crt-static either, but are needed nonetheless:
<WindowsBunny> Shiz: One interesting consequence of the static CRT thing on windows is that rust dylibs are horrible. If you ever write any function in a rust dylib which relies on CRT state, you have to make sure it never gets inlined or monomorphized, or it will be using CRT state from the wrong DLL
<WindowsBunny> Moral of the story, never use rust dylibs
<WindowsBunny> cdylib is fine though
<WindowsBunny> Shiz: Note that msvc dylibs work fine with crt-static
<WindowsBunny> Shiz: It's only when you rely on CRT state which Rust normally does not do
<WindowsBunny> Shiz: using crt-static with dylibs is actually an import thing for rustc, since it is split into a bunch of dylibs and having it not rely on the VC++ redistributable is important
dylib builds on
crt-static targets where it absolutely will not work, such as on *nix, may be an interesting avenue to explore, as it may just irritate users with very confusing link-time errors as below, and possibly break Cargo-powered packages that specify a
Compiling foo v0.0.1 (file:///home/builder/alpine-aports/testing/cargo/src/cargo-0.17.0/target/cit/t69/foo)
Running `rustc --crate-name foo src/lib.rs --crate-type dylib --crate-type rlib --emit=dep-info,link -C debuginfo=2 -C metadata=5c5882d5b8d32bd0 --out-dir /home/builder/alpine-aports/testing/cargo/src/cargo-0.17.0/target/cit/t69/foo/target/debug/deps -L dependency=/home/builder/alpine-aports/testing/cargo/src/cargo-0.17.0/target/cit/t69/foo/target/debug/deps`
error: linking with `cc` failed: exit code: 1
= note: "cc" "-Wl,--as-needed" "-Wl,-z,noexecstack" "-Wl,--eh-frame-hdr" "-m64" "-L" "/usr/lib/rustlib/x86_64-unknown-linux-musl/lib" "/home/builder/alpine-aports/testing/cargo/src/cargo-0.17.0/target/cit/t69/foo/target/debug/deps/foo.0.o" "-o" "/home/builder/alpine-aports/testing/cargo/src/cargo-0.17.0/target/cit/t69/foo/target/debug/deps/libfoo.so" "/home/builder/alpine-aports/testing/cargo/src/cargo-0.17.0/target/cit/t69/foo/target/debug/deps/foo.metadata.o" "-nodefaultlibs" "-Wl,-(" "-L" "/home/builder/alpine-aports/testing/cargo/src/cargo-0.17.0/target/cit/t69/foo/target/debug/deps" "-L" "/usr/lib/rustlib/x86_64-unknown-linux-musl/lib" "-Wl,-Bstatic" "-Wl,--whole-archive" "/tmp/rustc.eou9jVOLyku3/libstd-9767eb92afceb88d.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.eou9jVOLyku3/librand-3942a5633368c16e.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.eou9jVOLyku3/libcollections-ae24cbc916c89b63.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.eou9jVOLyku3/libstd_unicode-e03fe48e8667e45a.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.eou9jVOLyku3/libpanic_unwind-291a6fecc34131a5.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.eou9jVOLyku3/libunwind-8caeecdfbe801a4a.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.eou9jVOLyku3/liballoc-ff4a0ee7aa04ad06.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.eou9jVOLyku3/liballoc_system-ebd1d2bcdd8dd0ed.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.eou9jVOLyku3/liblibc-675e4236817c419c.rlib" "-Wl,--no-whole-archive" "-Wl,--whole-archive" "/tmp/rustc.eou9jVOLyku3/libcore-ed12715d20c4c8c1.rlib" "-Wl,--no-whole-archive" "/tmp/rustc.eou9jVOLyku3/libcompiler_builtins-c882c2ddbd3077dc.rlib" "-l" "unwind" "-l" "c" "-Wl,-)" "-shared"
= note: /usr/lib/gcc/x86_64-alpine-linux-musl/6.3.0/../../../../x86_64-alpine-linux-musl/bin/ld: /usr/lib/gcc/x86_64-alpine-linux-musl/6.3.0/../../../../lib/libc.a(sigaction.lo): relocation R_X86_64_PC32 against protected symbol `__restore_rt' can not be used when making a shared object
/usr/lib/gcc/x86_64-alpine-linux-musl/6.3.0/../../../../x86_64-alpine-linux-musl/bin/ld: final link failed: Bad value
collect2: error: ld returned 1 exit status
One final inquiry I would like to make if it's the intention for the
crt-static target feature (and a possible second feature as listed above) to be stabilised. Evidently, we feel that being able to flip between being able to compile static and dynamic objects at run-time is an important feature for any compiler, and would like to see it stabilised so we do not have to patch the feature gate out ourselves and possibly create confusion among end-users.
Addendum: overview of our patches
This part is mostly an overview of our current patches and what they do to make both dynamic and static linking on a musl-native system work, and is added more for those who are interested in reading it than a direct contribution the discussion. Our patches can be found at https://github.com/alpinelinux/aports/tree/master/testing/rust, and I'll be describing the functional changes in them file-by-file, for the patches that are relevant to Rust upstream:
support-dynamically-linked-musl.patch: A modified version of PR #40133, fixing more compilation errors and bugs we ran into:
crt-static value to be described during bootstrap, so a non-
crt-static rustc can be compiled using a default
crt-static bootstrap rustc, and vice-versa;
mips opt-out of default-static linking for musl, now that it can be toggled easily;
- Remove imposed dynamic linking restrictions (
position_independent_executables) from the musl target;
- Infer a default musl root of
/usr for musl-native hosts;
- (change from the version in the PR): no longer always statically link
libunwind on musl: instead, do not state a linking preference and let the final linking step decide whether to link statically or dynamically;
musl-root support for MIPS targets;
libunix, remove the different
#[link] statements for deciding how to link
libc and simply do not state any preference as above with
static-pie.patch: A restoration patch after the above patch that restores static linking ability, and (relevant to us) introduces static-PIE compatibility:
static_position_independent_executables target feature, indicating support for static PIE binaries, and enable it for
- Create fully static binaries in the final executable linking step if the
crt-static target feature is set;
- Link libraries with no linkage preference set as static if the
crt-static target feature is set;
- Do final link using group linking (using
-Wl,-) on the GNU linker, unneeded on the MSVC linker) to prevent dependency issues during a static link (was done solely on the musl target before, but it's a useful general feature);
- Do not link dylibs when the
crt-static target feature is set;
fix-linux_musl_base.patch: Makes modifications to the musl base target as a result of the changes in the previous two patches:
- Do not copy over and manually link the C runtime libraries and startup files for musl: let the compiler (
musl-gcc on non-musl-native targets) handle this by also removing the passed
-nostdlib flag, as it's far better at this, and may differ in what it wants to add then what Rust thinks is appropriate, leading to breakage if Rust decides this for itself;
- Remove the manual
-Wl,-) addition to the linker flags in favour of the group linking feature added in
fix-jemalloc-musl.patch: Fix jemalloc compilation on musl, as an unprefixed jemalloc will conflict with the system allocator in dynamically linked musl binaries (already upstreamed and merged in PR #41168)
allow-crt-static-on-stable.patch: Un-gate the
crt-static target feature in our stable release so users can switch between static and dynamic compilation at run-time;
force-rpath-on-prefer-dynamic.patch: Not important, but possibly interesting: our build removes the Rust libraries in
/usr/lib and only refers to the ones in
/usr/lib/rustlib/$arch/lib to save space, as they are otherwise identical: this patch makes sure the compiler adds the appropriate
RPATH directives to this directory when linking dynamically.
Or, this post in a few bullet points:
- With support for dynamically linked musl upcoming,
crt-static has possibly different semantics between Windows and *nix platforms with regards to implications if the entire result should be linked statically;
dylib support seems (architecturally) broken on *nix targets with
crt-static, it may be a good idea to reject or ignore
dylib compilations when this target feature is enabled;
- We'd like to propose stabilisation of the
crt-static feature and a possible extra feature that may arise as a result of bullet point #1, when this issue is resolved;
- With our patches, we now have a working Rust compiler on a musl-native target OS that can compile both working static and dynamic binaries, with PIE support for static binaries!
Edited 00:24 CET to correct insights on the necessity of dylibs with
crt-static on MSVC.