eyra icon indicating copy to clipboard operation
eyra copied to clipboard

Building a `no_std` program without explicitly using `eyra` now fails with "no global memory allocator found but one is required"

Open polarathene opened this issue 8 months ago • 5 comments

This might not be Eyra specifically, and technically it's a non-issue, I only observed this when building a basic program that only needs the rustix dep, but having eyra in the Cargo.toml deps introduced a build failure with nightly in the past month. My actual project had two separate sources in src/bin/*.rs, one was for Eyra, another for Rustix.

Since it's highly likely one of the crates that @sunfishcode maintains, I thought I'd open an issue to raise awareness of the observation just in case there was a mistaken assumption made 🤔 I assume because of the eyra dep during compilation, even though it's not used/required, it now messes with the global allocator in some manner?

Reproduction

Cargo.toml:

[package]
name = "example"
version = "0.1.0"
edition = "2024"

[dependencies]
eyra = { version = "0.21.0", default-features = false }
rustix = { version = "1.0.0", default-features = false, features = ["runtime", "stdio"] }

# Requirements to build (unrelated to reported build failure with eyra)
# `no_std` usage in `src/main.rs` requires `panic="abort"`
# `lto = true` is a workaround required in nightly, alternatively `-Z build-std`
# More details covered in collapsed segment of:
# https://github.com/sunfishcode/eyra/issues/60#issuecomment-2892735792
[profile.release]
lto = true
panic = "abort"

src/main.rs:

#![no_std]
#![no_main]

#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
  hello_world();
  exit();
}

fn exit() -> ! { unsafe { rustix::runtime::exit_thread(42) } }

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

#[inline(always)]
fn hello_world() {
  rustix::io::write(
    unsafe { rustix::stdio::stdout() },
    "Hello, world!\n".as_bytes()
  ).unwrap();
}

Nightly May

rust-toolchain.toml:

[toolchain]
profile = "minimal"
channel = "nightly-2025-05-18"
components = ["rust-src"]
targets = ["x86_64-unknown-linux-gnu"]
$ RUSTFLAGS="-C target-feature=+crt-static -C relocation-model=static -C link-arg=-nostartfiles" cargo build --release --target x86_64-unknown-linux-gnu

# ...

error: no global memory allocator found but one is required; link to std or add `#[global_allocator]` to a static item that implements the GlobalAlloc trait

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

Nightly April

An earlier nightly is required with older crate versions to compile approx a month ago:

[toolchain]
profile = "minimal"
channel = "nightly-2025-04-14"
components = ["rust-src"]
targets = ["x86_64-unknown-linux-gnu"]

This requires a few changes to be compatible with the earlier nightly, but now it builds without the global allocator requirement:

# Rollback eyra one release:
$ cargo add [email protected]

# Pin `unwinding` for nightly compatibility:
# (related to newer nightly change making the naked attribute safe)
$ cargo update unwinding --precise 0.2.5

$ RUSTFLAGS="-C target-feature=+crt-static -C relocation-model=static -C link-arg=-nostartfiles" cargo +nightly-2025-04-14 build --release --target x86_64-unknown-linux-gnu

# ...

   Compiling example v0.1.0 (/example)
    Finished `release` profile [optimized] target(s) in 54.42s

For context, the eyra rollback made the following lock file adjustments:

Downgrading c-gull v0.22.1 -> v0.21.0
Downgrading c-scape v0.22.1 -> v0.21.0
Downgrading eyra v0.21.0 -> v0.20.0 (available: v0.22.0)
Downgrading getrandom v0.3.3 -> v0.2.16
    Adding itoa v1.0.15
    Adding linux-raw-sys v0.4.15
Downgrading origin v0.26.2 -> v0.24.0
Downgrading rand v0.9.1 -> v0.8.5
Downgrading rand_core v0.9.3 -> v0.6.4
Downgrading rand_pcg v0.9.0 -> v0.3.1
    Adding rustix v0.38.44
Downgrading rustix-dlmalloc v0.2.2 -> v0.1.7
Downgrading rustix-futex-sync v0.4.0 -> v0.2.3
Downgrading rustix-openpty v0.2.0 -> v0.1.1
Downgrading wasi v0.14.2+wasi-0.2.4 -> v0.11.0+wasi-snapshot-preview1

polarathene avatar May 19 '25 22:05 polarathene

It is expected that eyra isn't actually linked in if you don't reference it from your program in some way. (even use eyra as _; should be enough to link it) Rustc just ignores dependencies passed in by cargo when the source code doesn't actually reference the dependency in question.

bjorn3 avatar May 19 '25 22:05 bjorn3

Rustc just ignores dependencies passed in by cargo when the source code doesn't actually reference the dependency in question.

I don't mind the compilation of the crates that occurs when I run the command, but the point was that Cargo.toml just had eyra added there, I had no actual use for it in the program compiled. Commenting it out will build successfully, but when the dep is declared in Cargo.toml it causes the global allocator fail.

If you remove eyra from both nightly examples, the rustix program builds successfully. So I am not sure what changed, but I assume something is now being detected at build even though I am not opting into using eyra explicitly.

Perhaps I could better word the issue title? 😅

polarathene avatar May 19 '25 22:05 polarathene

The error might be due to c-scape enabling the "fde-registry" feature of the unwinding crate. That feature causes the unwinding crate to use alloc::boxed::Box, which I theorize is what's prompting that error. That said, I don't know why this didn't occur in older versions. If this is the problem, then the fix will probably be to chance c-scape to not enable fde-registry by default.

sunfishcode avatar May 20 '25 00:05 sunfishcode

The error might be due to c-scape enabling the "fde-registry" feature of the unwinding crate. That feature causes the unwinding crate to use alloc::boxed::Box, which I theorize is what's prompting that error.

UPDATE: Given the information I cited below regarding c-scape + unwinding, that sounds quite likely?

I see that because the rustix example is rather simple, a global allocator should only be required when using alloc. Thus the change you mention is probably introducing a failure as crates are redundantly compiled?

Since I don't actually use eyra in the example program (only in Cargo.toml to trigger the failure), I assume this is due to cargo building crates that won't actually be needed for building src/main.rs 🤔 Cargo just doesn't know any better?


That said, I don't know why this didn't occur in older versions.

~~I think my reproduction might be a little inaccurate 😬~~ (EDIT: I've now added the two settings to Cargo.toml)

I had been using LTO with panic=abort (which as noted here in the "Additional Context" section, the failure noted on nightly with rustix shouldn't really occur due to panic=abort removing the need for an unwinder? And the follow-up comment at that issue further notes no_std should be skipping any requirement for an unwinder, but c-scape enforced it regardless).

So it may be related to the unwinding dep in c-scape not being no_std/panic=abort aware?


Settings required in `Cargo.toml` for building `no_std`

To build the Rustix example with eyra removed from Cargo.toml still seems to require enabling lto = true + panic = "abort" or adding the equivalent RUSTFLAGS='-C lto=true -C embed-bitcode=yes -C panic=abort' (_**EDIT:** panic=abortwith-Z build-std` works fine too_).

Not having that addition will fail to build on stable or nightly with (EDIT: stable channel only requires -C panic=abort due to panic handler):

$ RUSTFLAGS="-C target-feature=+crt-static -C relocation-model=static -C link-arg=-nostartfiles" cargo +stable build --release --target x86_64-unknown-linux-gnu

   Compiling rustix v1.0.7
   Compiling bitflags v2.9.1
   Compiling linux-raw-sys v0.9.4
   Compiling example-rustix v0.1.0 (/example-rustix)

error: unwinding panics are not supported without std
  |
  = help: using nightly cargo, use -Zbuild-std with panic="abort" to avoid unwinding
  = note: since the core library is usually precompiled with panic="unwind", rebuilding your crate with panic="abort" may not be enough to fix the problem

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

Additional Context (specific to rustix / no_std)

-C embed-bitcode=yes is implicit when using lto=true in Cargo.toml I think, as without that when using the RUSTFLAGS alternative, linux-raw-sys fails to build:

error: options `-C embed-bitcode=no` and `-C lto` are incompatible

error: could not compile `linux-raw-sys` (lib) due to 1 previous error

I also noticed that LTO is only necessary with nightly due to this failure:

$ RUSTFLAGS="-C target-feature=+crt-static -C relocation-model=static -C link-arg=-nostartfiles -C panic=abort" cargo +nightly build --release --target x86_64-unknown-linux-gnu

   Compiling rustix v1.0.7
   Compiling bitflags v2.9.1
   Compiling linux-raw-sys v0.9.4
   Compiling example-rustix v0.1.0 (/example-rustix)
error: linking with `cc` failed: exit status: 1
  |
  = note:  "cc" "-m64" "/tmp/rustcDYJFhr/symbols.o" "<1 object files omitted>" "-Wl,--as-needed" "-Wl,-Bstatic" "/example-rustix/target/x86_64-unknown-linux-gnu/release/deps/{librustix-1fb835665caf31e2.rlib,libbitflags-0ea7d025780f884c.rlib,liblinux_raw_sys-e40375fdc0e695e8.rlib}.rlib" "<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/lib/{librustc_std_workspace_core-*,libcore-*,libcompiler_builtins-*}.rlib" "-L" "/tmp/rustcDYJFhr/raw-dylibs" "-Wl,-Bdynamic" "-B<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/bin/gcc-ld" "-fuse-ld=lld" "-Wl,-znostart-stop-gc" "-Wl,--eh-frame-hdr" "-Wl,-z,noexecstack" "-L" "<sysroot>/lib/rustlib/x86_64-unknown-linux-gnu/lib" "-o" "/example-rustix/target/x86_64-unknown-linux-gnu/release/deps/example_rustix-366d0ee80c64ecb2" "-Wl,--gc-sections" "-static" "-no-pie" "-Wl,-z,relro,-z,now" "-Wl,-O1" "-Wl,--strip-debug" "-nodefaultlibs" "-nostartfiles"

  = note: some arguments are omitted. use `--verbose` to show all linker arguments
  = note: rust-lld: error: undefined symbol: rust_eh_personality
          >>> referenced by core.71dbd4a95fb1a922-cgu.0
          >>>               core-a295b978c7e899d2.core.71dbd4a95fb1a922-cgu.0.rcgu.o:(DW.ref.rust_eh_personality) in archive /root/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libcore-a295b978c7e899d2.rlib
          collect2: error: ld returned 1 exit status


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

I have noticed that nightly bundles it's own lld (rust-lld), I can use the system one via -C link-self-contained=no (error will replace rust-lld with ld-lld on fedora when lld package is installed, no need for adding -C link-arg=-fuse-ld=lld).

  • I have notes related to this difference for the same rustix example in this comments "April 2025 update" section, which when updating the eyra example I needed to pin nightly at the time due to a change being incompatible with unwinding 0.2.5 (which would now need to pin unwinding to that version too, or use a newer nightly with [email protected]).
  • A non-issue regardless when adding -Z build-std (despite not using std). Enabling LTO instead presumably optimizes the rust_eh_personality symbol away? I'm not entirely sure of the difference between stable and nightly channel regarding why only nightly needs one of these as a workaround.

polarathene avatar May 20 '25 02:05 polarathene

Even with panic=abort we still need to provide an unwinder in case non-Rust code attempts to unwind. As well as for backtraces.

bjorn3 avatar May 20 '25 06:05 bjorn3