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

Document compatible target? #27

Closed
polarathene opened this issue Nov 20, 2023 · 4 comments
Closed

Document compatible target? #27

polarathene opened this issue Nov 20, 2023 · 4 comments

Comments

@polarathene
Copy link
Contributor

polarathene commented Nov 20, 2023

I am used to building with the *-musl target, but noticed that eyra is not compatible (default hello world example generated from cargo init).

--target :

  • ✔️ x86_64-unknown-linux-gnu
  • x86_64-unknown-linux-musl

I know the README clarifies the support constraints to linux, but doesn't highlight the -gnu vs -musl target compatibility? I didn't think it would matter with eyra?

Might be worthwhile to convey that incompatibility in the README too?


Reproduction

$ cargo init
$ cargo add eyra
# Prepend to `src/main.rs`:
$ echo 'extern crate eyra;' | cat - src/main.rs | sponge src/main.rs
# Build with `*-musl` target (fail):
$ RUSTFLAGS="-C link-arg=-nostartfiles -C relocation-model=static -C target-feature=+crt-static" cargo +nightly build --target x86_64-unknown-linux-musl --release
# Build with `*-gnu` target (success):
$ RUSTFLAGS="-C link-arg=-nostartfiles -C relocation-model=static -C target-feature=+crt-static" cargo +nightly build --target x86_64-unknown-linux-gnu --release

Some error messages from -musl target attempt:

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
   --> /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/c-scape-0.15.23/src/use_libc.rs:22:17
    |
22  |                 core::mem::transmute(crate::use_libc::Pad::new(core::ptr::read(src_ptr)));
    |                 ^^^^^^^^^^^^^^^^^^^^
    |
   ::: /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/c-scape-0.15.23/src/thread/mod.rs:653:44
    |
653 |     libc!(libc::pthread_rwlockattr_destroy(checked_cast!(_attr)));
    |                                            -------------------- in this macro invocation
    |
    = note: source type: `Pad<PthreadRwlockattrT>` (128 bits)
    = note: target type: `Pad<pthread_rwlockattr_t>` (96 bits)
    = note: this error originates in the macro `checked_cast` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
   --> /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/c-scape-0.15.23/src/time/mod.rs:104:33
    |
104 |     libc!(libc::gettimeofday(t, _tz));
    |           ------------------    ^^^ expected `*mut c_void`, found `*mut timezone`
    |           |
    |           arguments to this function are incorrect
    |
    = note: expected raw pointer `*mut c_void`
               found raw pointer `*mut timezone`
note: function defined here
   --> /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/libc-0.2.150/src/unix/linux_like/linux/musl/mod.rs:740:12
    |
740 |     pub fn gettimeofday(tp: *mut ::timeval, tz: *mut ::c_void) -> ::c_int;

Potentially related to?:

@polarathene
Copy link
Contributor Author

polarathene commented Nov 20, 2023

Below is unrelated to issue addressed above (-musl target support with eyra).

Slightly off-topic, but might have some relevance. I noticed eyra made it possible for the -gnu target to successfully build statically linked that'd otherwise fail without eyra (that or I'm just too inexperienced to know better).

eyra helps enable no_std static builds for -gnu target

I have noticed that:

// Ref: https://lifthrasiir.github.io/rustlog/why-is-a-rust-executable-large.html
// Ref: https://darkcoding.net/software/a-very-small-rust-binary-indeed/
#![no_std]
#![no_main]

// cargo add libc --no-default-features
extern crate libc;

#[no_mangle]
pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
    // since we are passing a C string, the final null character is mandatory:
    const HELLO: &'static str = "Hello, world!\n\0";
    unsafe { libc::printf(HELLO.as_ptr() as *const _); }
    0
}

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

Will build for -gnu target dynamically fine (with glibc), but fail attempting a static build. Some changes with eyra fixes that (add extern + drop panic handler):

#![no_std]
#![no_main]

extern crate eyra;
extern crate libc;

#[no_mangle]
pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
    // since we are passing a C string, the final null character is mandatory:
    const HELLO: &'static str = "Hello, world!\n\0";
    unsafe { libc::printf(HELLO.as_ptr() as *const _); }
    0
}

// Must remove panic handler, as eyra provides one
# ...

# Ref: https://github.com/johnthagen/min-sized-rust
[profile.release]
codegen-units = 1
lto = true
opt-level = "z"
panic = "abort"
strip = true
# Ensure `--target` is specified when using `RUSTFLAGS`:
# https://msfjarvis.dev/posts/building-static-rust-binaries-for-linux/
$ 14K dynamically linked glibc (no_std):
$ RUSTFLAGS="-Zlocation-detail=none -C relocation-model=static" cargo +nightly build --target x86_64-unknown-linux-gnu --release

# 414K static linked with eyra (despite no_std)
$ RUSTFLAGS="-C link-arg=-nostartfiles -Zlocation-detail=none -C relocation-model=static -Ctarget-feature=+crt-static" cargo +nightly build --target x86_64-unknown-linux-gnu --release

# 49K (with -Z build-std):
$ RUSTFLAGS="-C link-arg=-nostartfiles -Zlocation-detail=none -C relocation-model=static -Ctarget-feature=+crt-static" cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --target x86_64-unknown-linux-gnu --release

I found that a bit interesting since the standard cargo init hello world example didn't have a problem (31K -gnu dynamic vs 945K -gnu static vs 37K eyra).

UPDATE: The above example will compile for -musl target if removing libc crate from Cargo.toml and giving the extern an attribute (compile error communicates this if not building with -Z build-std):

#![feature(rustc_private)]
extern crate libc;

Similar example (but compatible with -musl target) without libc crate

That no_std example above would fail to build on -musl targets too (Workaround provided in update above). Not sure if eyra could likewise fix that, might have something to do with the libc crate used? (possibly due to what is discussed here)

Here's a similar hello world no_std example without reliance on the libc crate. This builds fine with -musl targets, but fails with -gnu like the earlier example. Applying the same changes to build with eyra also enabled -gnu static builds to work 👍

// Ref: https://www.reddit.com/r/rust/comments/bf8l2b/comment/elbzd5h/
#![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 {} }

With -Z build-std command shared earlier, this will output a 13.1K binary for both -musl and -gnu (with eyra), the eyra build being slightly smaller.


Not sure if the issues with -gnu static linking glibc were related to caveats like this:

Note the standard limitation of static glibc:
If you do anything using NSS (notably domain name resolution), glibc will require matching system NSS libraries.
glibc is working on improving that.

@sunfishcode
Copy link
Owner

Yes, Eyra works by implementing the existing C ABIs. Currently it's only compatible with "-gnu" ABIs. There's no fundamental reason "-musl" ABIs couldn't be supported, it just hasn't been implemented yet. It's an interesting question whether that's worth implementing; Eyra doesn't use any of the glibc or musl libraries itself, so the choice of target doesn't matter that much to Eyra.

And yes, thanks for pointing out that that's not documented in the README.md; I've now posted #28.

I have noticed that:
[...]
Will build for -gnu target dynamically fine (with glibc), but fail attempting a static build. Some changes with eyra fixes that (add extern + drop panic handler):

I needed to add #![feature(rustc_private)], but otherwise, that example build for me without Eyra, with RUSTFLAGS="-C link-arg=-nostartfiles -Zlocation-detail=none -C relocation-model=static -Ctarget-feature=+crt-static" cargo +nightly build --target x86_64-unknown-linux-gnu --release.

// Must remove panic handler, as eyra provides one

Eyra itself doesn't provide a panic handler; Eyra depends on libraries that depend on std, which provides a panic handler.

Eyra previously didn't support no_std applications. With #29 I've now added support for no_std. In this mode, there is no std, so there's no panic handler, though keep in mind that there's also no global allocator or eh_personality provided either. I've added a no-std example to show how to make these work.

That said, this no-std mode doesn't currently support printf.

Not sure if the issues with -gnu static linking glibc were related to caveats like this:

Note the standard limitation of static glibc:
If you do anything using NSS (notably domain name resolution), glibc will require matching system NSS libraries.
glibc is working on improving that.

Eyra doesn't have this limitation. It can statically link without depending on system NSS libraries. It resolves NSS queries by invoking the getent command, so it respects the system NSS config without needing to link to the libraries itself.

@polarathene
Copy link
Contributor Author

Thanks for the great response and PRs to address feedback! ❤️

No pressure to read what follows and reply, it is mostly additional information to benefit myself and any future readers.


I needed to add #![feature(rustc_private)]

Ah ok, so nightly either way. I had found the same fix last night but for the -musl target to build that example, but forgot to submit the updated edit 😅 Good to know it works for -gnu too!

  • 797K static build for -gnu without Eyra, or 49K static build with Eyra (after -Z build-std, which I didn't think would matter with no_std).
  • The rustc_private feature attribute is not compatible with -Z build-std. To get the smaller 49K build with Eyra, that example still requires adding the libc crate to Cargo.toml.

that example built for me without Eyra, with RUSTFLAGS="-C link-arg=-nostartfiles -Zlocation-detail=none -C relocation-model=static -Ctarget-feature=+crt-static" cargo +nightly build --target x86_64-unknown-linux-gnu --release.

Might have been a typo on your end, but -C link-arg=-nostartfiles (without Eyra) builds a 4.4K binary that segfaults.

It will build a functional binary without that arg present though 👍


eyra and panic handler (resolved: see next section)

Original response before realizing the newly added `no_std` support

Eyra itself doesn't provide a panic handler; Eyra depends on libraries that depend on std, which provides a panic handler.

Ok, that was just an observation as I had to provide a panic handler when building without Eyra. I would get this error when adapting to build with Eyra:

error[E0152]: found duplicate lang item `panic_impl`
  --> src/main.rs:18:1
   |
18 | fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: the lang item is first defined in crate `std` (which `eyra` depends on)
   = note: first definition in `std` loaded from /home/polarathene/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-b9470aab25f77fbc.so, /home/polarathene/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-b9470aab25f77fbc.rlib
   = note: second definition in the local crate (`hello_world_no_std_libc_eyra`)

I suppose the std dep is why without -Z build-std it produces 414K static build?

Whereas with the other no_std example (that doesn't use libc calls), that duplicate lang item error doesn't occur when building with Eyra if you try without -Z build-std (using the cargo add eyra --rename=std approach), however it outputs 496 bytes binary that won't run (Which is expected since std isn't meant to be included).

[dependencies]
eyra = "0.16.4"
# Not compatible:
#std = { version = "0.16.4", package = "eyra" }

# Min size:
[profile.release]
strip = true
panic = "abort"
lto = true
opt-level = "z"
codegen-units = 1
#![no_std]
#![no_main]

extern crate eyra;

#[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;
}

// Eyra depends on std which provides this during build:
//#[panic_handler]
//fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }
# 370K:
$ RUSTFLAGS="-C link-arg=-nostartfiles -Zlocation-detail=none -C relocation-model=static -Ctarget-feature=+crt-static" cargo +nightly build --target x86_64-unknown-linux-gnu --release

# 13K (with `-Z build-std`):
$ RUSTFLAGS="-C link-arg=-nostartfiles -Zlocation-detail=none -C relocation-model=static -Ctarget-feature=+crt-static" cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --target x86_64-unknown-linux-gnu --release

As mentioned earlier, the -gnu target does not successfully produce a static build of that example without Eyra.

  • The rustc_private feature attribute (with added extern crate libc;) will enable a -gnu static build (without Eyra), producing a 797K binary.
  • Compared to the -musl target that builds a 13.1K binary(_ regardless of -Z build-std, and doesn't need the libc workaround_).

eyra with no_std support

Eyra previously didn't support no_std applications.

  • With Add no_std support with a "std" feature. #29 I've now added support for no_std. In this mode, there is no std, so there's no panic handler, though keep in mind that there's also no global allocator or eh_personality provided either.
  • I've added a no-std example to show how to make these work.

Oh... I should really read the full response before typing the above 😆

The following doesn't appear needed if using the -Z build-std approach (which still produces same 13 304 bytes / 13K binary as before):

Snippets
#![feature(lang_items)]
#![feature(rustc_private)]

extern crate libc;

#[lang = "eh_personality"]
extern "C" fn eh_personality() {}

Which leaves only the addition of the global allocator (in addition to bringing back the panic handler):

#[global_allocator]
static GLOBAL_ALLOCATOR: rustix_dlmalloc::GlobalDlmalloc = rustix_dlmalloc::GlobalDlmalloc;
# 13K (with `-Z build-std`):
$ RUSTFLAGS="-C link-arg=-nostartfiles -Zlocation-detail=none -C relocation-model=static -Ctarget-feature=+crt-static" cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --target x86_64-unknown-linux-gnu --release

Global allocator

I guess compared to the prior example, the binary might differ by global allocator now? (that would have been provided from std?)

  • rustix-dlmalloc references the original C version which describes itself as widely used and default allocator on Linux? So perhaps they're roughly equivalent? 🤷‍♂️
  • I did try out of curiosity swapping it for mimalloc (no_std compatible), which works with both -gnu and -musl targets (without Eyra involved), but presumably from the build errors is not compatible with Eyra yet.
Eyra build errors when trying mimalloc
error: linking with `cc` failed: exit status: 1

...

  = note: /usr/bin/ld: /tmp/rustc5zEoNy/liblibmimalloc_sys-4ddef3846abe8639.rlib(static.o): in function `_mi_fputs':
          static.c:(.text._mi_fputs+0x1d): undefined reference to `stdout'
          /usr/bin/ld: static.c:(.text._mi_fputs+0x29): undefined reference to `stderr'
          /usr/bin/ld: /tmp/rustc5zEoNy/liblibmimalloc_sys-4ddef3846abe8639.rlib(static.o): in function `mi_vfprintf.part.0':
          static.c:(.text.mi_vfprintf.part.0+0x48): undefined reference to `__vsnprintf_chk'
          /usr/bin/ld: /tmp/rustc5zEoNy/liblibmimalloc_sys-4ddef3846abe8639.rlib(static.o): in function `mi_printf_amount.constprop.0':
          static.c:(.text.mi_printf_amount.constprop.0+0xa5): undefined reference to `__snprintf_chk'
          /usr/bin/ld: static.c:(.text.mi_printf_amount.constprop.0+0x142): undefined reference to `__snprintf_chk'
          /usr/bin/ld: static.c:(.text.mi_printf_amount.constprop.0+0x172): undefined reference to `__snprintf_chk'
          /usr/bin/ld: /tmp/rustc5zEoNy/liblibmimalloc_sys-4ddef3846abe8639.rlib(static.o): in function `mi_vfprintf_thread.constprop.0':
          static.c:(.text.mi_vfprintf_thread.constprop.0+0x6b): undefined reference to `__snprintf_chk'
          /usr/bin/ld: /tmp/rustc5zEoNy/liblibmimalloc_sys-4ddef3846abe8639.rlib(static.o): in function `_mi_prim_numa_node_count':
          static.c:(.text._mi_prim_numa_node_count+0x4d): undefined reference to `__snprintf_chk'
          /usr/bin/ld: /tmp/rustc5zEoNy/liblibmimalloc_sys-4ddef3846abe8639.rlib(static.o): in function `_mi_prim_process_info':
          static.c:(.text._mi_prim_process_info+0x28): undefined reference to `getrusage'
          /usr/bin/ld: /tmp/rustc5zEoNy/liblibmimalloc_sys-4ddef3846abe8639.rlib(static.o): in function `_mi_prim_out_stderr':
          static.c:(.text._mi_prim_out_stderr+0x7): undefined reference to `stderr'
          /usr/bin/ld: static.c:(.text._mi_prim_out_stderr+0xf): undefined reference to `fputs'
          /usr/bin/ld: /tmp/rustc5zEoNy/liblibmimalloc_sys-4ddef3846abe8639.rlib(static.o): in function `mi_option_get':
          static.c:(.text.mi_option_get+0x193): undefined reference to `strtol'
          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

eh_personality vs panic = "abort"

I was under the impression that eh_personality was not necessary when panic = "abort" is configured (even without related -Z build-std args?), but I guess that's something Rust implicitly handled with no_std on -gnu / -musl targets when building without Eyra? (EDIT: Yep, as referenced from Rust unstable book on lang-items)

This resource notes that panic = "abort" in Cargo.toml should disable unwinding and thus not require eh_personality? I can see from cargo tree that rustix is using the unwinding crate which provides a no_std compatible pure rust alternative, so perhaps this opt-out behaviour belongs upstream there?

no_std example (without -Z build-std)

Just adding this as reference to earlier examples shared. Adapted from your no_std example.

[dependencies]
eyra = { version = "0.16.4", default-features = false }
rustix-dlmalloc = { version = "0.1.0", features = ["global"] }

[profile.release]
strip = true
panic = "abort"
lto = true
opt-level = "z"
codegen-units = 1
#![no_std]
#![no_main]
#![feature(lang_items)]
#![feature(rustc_private)]

extern crate libc;
extern crate eyra;

#[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 {} }

// Additionally required for building with Eyra:
#[global_allocator]
static GLOBAL_ALLOCATOR: rustix_dlmalloc::GlobalDlmalloc = rustix_dlmalloc::GlobalDlmalloc;

#[lang = "eh_personality"]
extern "C" fn eh_personality() {}
$ RUSTFLAGS="-C link-arg=-nostartfiles -Z location-detail=none -C relocation-model=static -C target-feature=+crt-static" cargo +nightly build --target x86_64-unknown-linux-gnu --release

# 17K (17,400 bytes):
$ du -b target/x86_64-unknown-linux-gnu/release/hello_world_eyra_nostd

Very cool! 😎


Targets

There's no fundamental reason "-musl" ABIs couldn't be supported, it just hasn't been implemented yet. It's an interesting question whether that's worth implementing; Eyra doesn't use any of the glibc or musl libraries itself, so the choice of target doesn't matter that much to Eyra.

Not something I can comment much on, way beyond my expertise :) No point in implementing and maintaining support for a target if there is no other benefit. For users like myself just having that cleared up in the README is sufficient 👍

It didn't require the libc extern, so no rustc_private feature needed either, although I don't know if that'd carry over with Eyra support.

I'm only aware of how musl differs with it's allocator performing poorly, and it having a history of issues with DNS / NSS and such. None of which would apply to a build with Eyra.


Eyra doesn't have this limitation. It can statically link without depending on system NSS libraries. It resolves NSS queries by invoking the getent command, so it respects the system NSS config without needing to link to the libraries itself.

Good to know, not having to be aware of such gotchas is another positive for building with Eyra 💪

Whereas with the -musl target, users may not be aware of drawbacks that can be encountered like the default allocator (which they can change once they're aware of an actual perf issue introduced by the target).

sunfishcode added a commit that referenced this issue Nov 21, 2023
)

* Mention that Eyra currently requires a `*-gnu` target in README.md.

See #27.

* Update README.md

Co-authored-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com>

---------

Co-authored-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com>
@sunfishcode
Copy link
Owner

Thanks for the detailed report! The questions here seem answered, so I'll close this now, though feel free to reopen or file new issues if there's anything to add here.

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

2 participants