Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[rust] Image needs to ship the x86_64-unknown-linux-musl target #272

Open
eddiezane opened this issue Feb 14, 2023 · 4 comments
Open

[rust] Image needs to ship the x86_64-unknown-linux-musl target #272

eddiezane opened this issue Feb 14, 2023 · 4 comments

Comments

@eddiezane
Copy link
Contributor

As far as I can tell, the only way to build a fully statically linked rust binary for use with multi stage build and static is to use the build target of x86_64-unknown-linux-musl. I think this is a binary. I had to install it locally with rustup target install x86_64-unknown-linux-musl.

If I try to build this target right now I get errors.

Step 4/7 : RUN cargo build --release --target x86_64-unknown-linux-musl
 ---> Running in f4cf7365cf0d
    Updating crates.io index
 Downloading crates ...
  Downloaded futures-task v0.3.26
...
   Compiling pin-utils v0.1.0
error[E0463]: can't find crate for `core`
  |
  = note: the `x86_64-unknown-linux-musl` target may not be installed
  = help: consider downloading the target with `rustup target add x86_64-unknown-linux-musl`

error[E0463]: can't find crate for `compiler_builtins`

For more information about this error, try `rustc --explain E0463`.
@kaniini
Copy link
Contributor

kaniini commented Feb 14, 2023

Building the musl target requires building musl, and having it available at runtime. For this reason, I don't think it is a good idea. Additionally, the musl developers (including myself) pushed back on the Rust community for the whole "musl == static linking" thing they did, so that particular default is likely to be deprecated in the future.

It is possible to build a glibc static binary with -C target-feature=+crt-static in RUSTFLAGS. This is probably what we should recommend instead.

@eddiezane
Copy link
Contributor Author

Ack!

Though I'm not sure about "and having it available at runtime". Otherwise, the built binary wouldn't work in scratch which it does.

@patflynn
Copy link
Contributor

is there an action we need to take here? @kaniini

developer-guy added a commit to Dentrax/images that referenced this issue Feb 12, 2024
Signed-off-by: Batuhan Apaydin <batuhan.apaydin@chainguard.dev>
@polarathene
Copy link

is there an action we need to take here?

Probably just add a mention / example on the Rust image overview page?

  • If the user is familiar with Chainguard/Wolfi images then they'd probably be aware that glibc is used for the majority of images.
  • For the final build stage, there might be some confusion for users with static:latest vs static:latest-glibc and using apk (although this isn't relevant for the Chainguard Rust image like it is with docker.io/rust:alpine).
  • The Chainguard image requires custom build via apko and melange AFAIK if they need to add the equivalent openssl-libs-static Alpine package.
    • This can be a common issue to run into for static builds when a project has a direct or implicit openssl crate dependency, while larger projects like Vector require a few more packages (depending on features enabled for a custom build).
    • Perhaps these native deps is a bit more hassle with Chainguard Rust image, and the Wolfi base image may still not be sufficient, so a static build may be better suited to docker.io/rust:alpine?

It is possible to build a glibc static binary with -C target-feature=+crt-static in RUSTFLAGS.

You'll usually need to provide an explicit --target to go with that. Builds can fail when proc macros are introduced by a crate otherwise.


This is a no_std hello world example that will compile without issue via rust:alpine (presently Rust 1.76 + Alpine 3.19) but not the Debian 12 Bookworm rust:latest (also Rust 1.76) when you use the suggestion for static build:

$ cargo init /example && cd /example
$ echo -e '[profile.release]\npanic = "abort"' >> Cargo.toml

# After swapping `src/main.rs` for the snippet below, build it:
RUSTFLAGS="-C target-feature=+crt-static" cargo run --release --target x86_64-unknown-linux-gnu
#![no_std]
#![no_main]

#[no_mangle]
pub extern "C" fn main() -> isize {
    const HELLO: &'static str = "Hello, world!\n";
    unsafe { write(1, HELLO.as_ptr() as *const i8, HELLO.len()) };
    0
}

#[link(name = "c")]
extern "C" {
    fn write(fd: i32, buf: *const i8, count: usize) -> isize;
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }

You'll get a lovely error like this:

Error output
error: linking with `cc` failed: exit status: 1
  |
  = note: LC_ALL="C" PATH="/usr/local/rustup/toolchains/1.76.0-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/bin:/usr/local/cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" VSLANG="1033" "cc" "-m64" "/tmp/rustcJGpmNm/symbols.o" "/example/target/x86_64-unknown-linux-gnu/release/deps/example-9c48775fb1fd88c0.example.ab096a222c763aa7-cgu.0.rcgu.o" "-Wl,--as-needed" "-L" "/example/target/x86_64-unknown-linux-gnu/release/deps" "-L" "/example/target/release/deps" "-L" "/usr/local/rustup/toolchains/1.76.0-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-Wl,-Bstatic" "-lc" "/usr/local/rustup/toolchains/1.76.0-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/librustc_std_workspace_core-5af394d9b1f07bdc.rlib" "/usr/local/rustup/toolchains/1.76.0-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-693a8f23970c5917.rlib" "/usr/local/rustup/toolchains/1.76.0-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcompiler_builtins-13fc9d1ed9c7a2bc.rlib" "-Wl,-Bdynamic" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "/usr/local/rustup/toolchains/1.76.0-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "/example/target/x86_64-unknown-linux-gnu/release/deps/example-9c48775fb1fd88c0" "-Wl,--gc-sections" "-static-pie" "-Wl,-z,relro,-z,now" "-Wl,-O1" "-nodefaultlibs"
  = note: /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/libc.a(iofclose.o): in function `_IO_new_fclose.cold':
          (.text.unlikely+0x30): undefined reference to `_Unwind_Resume'
          /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/libc.a(iofclose.o):(.data.rel.local.DW.ref.__gcc_personality_v0[DW.ref.__gcc_personality_v0]+0x0): undefined reference to `__gcc_personality_v0'
          /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/libc.a(iofflush.o): in function `_IO_fflush.cold':
          (.text.unlikely+0x30): undefined reference to `_Unwind_Resume'
          /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/libc.a(wfileops.o): in function `_IO_wfile_underflow.cold':
          (.text.unlikely+0x3c): undefined reference to `_Unwind_Resume'
          /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/libc.a(fileops.o): in function `_IO_new_file_underflow.cold':
          (.text.unlikely+0x31): undefined reference to `_Unwind_Resume'
          /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/libc.a(iofputs.o): in function `_IO_fputs.cold':
          (.text.unlikely+0x30): undefined reference to `_Unwind_Resume'
          /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/libc.a(iofwrite.o): in function `_IO_fwrite.cold':
          (.text.unlikely+0x30): undefined reference to `_Unwind_Resume'
          /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/libc.a(iogetdelim.o):(.text.unlikely+0x30): more undefined references to `_Unwind_Resume' follow
          /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/libc.a(printf_fp.o): in function `__printf_fp_l':
          (.text+0x4fd): undefined reference to `__unordtf2'
          /usr/bin/ld: (.text+0x52a): undefined reference to `__unordtf2'
          /usr/bin/ld: (.text+0x548): undefined reference to `__letf2'
          /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/12/../../../x86_64-linux-gnu/libc.a(printf_fphex.o): in function `__printf_fphex':
          (.text+0x9c): undefined reference to `__unordtf2'
          /usr/bin/ld: (.text+0xce): undefined reference to `__unordtf2'
          /usr/bin/ld: (.text+0xea): undefined reference to `__letf2'
          collect2: error: ld returned 1 exit status

  = note: some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified
  = note: use the `-l` flag to specify native libraries to link
  = note: use the `cargo:rustc-link-lib` directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-link-libkindname)

error: could not compile `example` (bin "example") due to 1 previous error

This can be fixed by adding near the top of the src/main.rs file (however this is a workaround that is discouraged and relies on a nightly-only toolchain feature attribute):

#![feature(rustc_private)]

extern crate libc;

Still you will notice something is up due the static build size difference between glibc and musl targets:

# rust:latest
# 699K if processed with `strip` command:
$ du -bh target/x86_64-unknown-linux-gnu/release/example
775K    target/x86_64-unknown-linux-gnu/release/example

$ ldd target/x86_64-unknown-linux-gnu/release/example
statically linked

# rust:alpine
# Much smaller:
$ du -bh target/x86_64-unknown-linux-musl/release/example
25.7K   target/x86_64-unknown-linux-musl/release/example

# musl ldd is a little different, this is equivalent to "statically linked":
$ ldd target/x86_64-unknown-linux-musl/release/example
/lib/ld-musl-x86_64.so.1: target/x86_64-unknown-linux-musl/release/example: Not a valid dynamic program

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants