`#[used]` forces `ELFOSABI_GNU` on produced binaries
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
I have an embedded ebpf target that consumes ELF files and expects a ELFOSABI_NONE in the produced ELF header. This usually works as expected and the compiled binaries tend to end up with this exact value in the osabi field. However as soon as #[used] or #[used(linker)] is applied, LLVM codegen and its lld linker default this field to ELFOSABI_GNU even when the target specifies non-gnu ABI. This affects every ELF-producing non-GNU target.
This is because LLVM will default to, on ELF producing targets and in absence of an override for solaris, using SHF_GNU_RETAIN as an implementation of llvm.used. This then also automatically sets the GNU osabi: https://github.com/anza-xyz/llvm-project/blob/02ddb3249520bee9512e029317f631b106337572/llvm/lib/MC/ELFObjectWriter.cpp#L305-L307
There are similar cases in the implementation of lld. On bintools side of things at least the initial implementation of the flag in bfd linker seems to have errored rather than implying the GNU osabi.
SHF_GNU_RETAIN is a GNUism and most other “OSABI”s don't really have an equivalent mechanism that could be used here, so if we were to fix this in Rust, the best Rust could do is to fall back onto #[used(compiler)] like it was doing before https://github.com/rust-lang/rust/pull/140872 for any non-GNU/Solaris target. Or emit an error.
All that said, I'm not sure if there is anything else than my obscure use-case that actually cares about the value of the osabi field at all. For most intents and purposes the value of this field might be entirely cosmetic. In that case closing this issue as informational can be an appropriate option as well.
To reproduce:
// src/main.rs
#![no_std]
#![no_main]
#[cfg_attr(make_it_gnu, used)]
#[unsafe(no_mangle)]
pub static FOO: u8 = 42;
#[unsafe(no_mangle)]
pub fn _start() {
}
#[panic_handler]
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
loop {}
}
$ cargo +nightly rustc --target=x86_64-unknown-none -Z build-std=core
$ readelf -h target/x86_64-unknown-none/debug/banana
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
...
OS/ABI: UNIX - System V (actually NONE, which is "Same as ELFOSABI_SYSV.")
$ cargo +nightly rustc --target=x86_64-unknown-none -Z build-std=core -- --cfg=make_it_gnu
$ readelf -h target/x86_64-unknown-none/debug/banana
ELF Header:
Magic: 7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00
...
OS/ABI: UNIX - GNU
though you should be able to reproduce this with any other ELF target as well.
Meta
rustc --version --verbose:
rustc 1.98.0-nightly (df6ee909e 2026-06-28)
binary: rustc
commit-hash: df6ee909ef35c75aa58aa45af6ac071a9b8285c2
commit-date: 2026-06-28
host: x86_64-unknown-linux-gnu
release: 1.98.0-nightly
LLVM version: 22.1.8
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce with the provided no_std example using cargo +nightly rustc, the x86_64-unknown-none target, and readelf -h, comparing builds with and without #[used]. Read the referenced LLVM TargetLoweringObjectFileImpl.cpp and ELFObjectWriter.cpp paths, then trace rustc's #[used] handling and lld behavior. Done means establishing the expected OS/ABI behavior for non-GNU ELF targets and adding or identifying regression coverage for that decision.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100