Bug 2427891 - GCC 16 breaks QEMU static PIE build with libatomic.a relocation errors
Summary: GCC 16 breaks QEMU static PIE build with libatomic.a relocation errors
Keywords:
Status: NEW
Alias: None
Product: Fedora
Classification: Fedora
Component: gcc
Version: 45
Hardware: Unspecified
OS: Linux
unspecified
high
Target Milestone: ---
Assignee: Jakub Jelinek
QA Contact: Fedora Extras Quality Assurance
URL:
Whiteboard:
Depends On:
Blocks: GCC16-Mass-Prebuild
TreeView+ depends on / blocked
 
Reported: 2026-01-08 11:48 UTC by Daniel Berrangé
Modified: 2026-08-17 14:04 UTC (History)
16 users (show)

Fixed In Version:
Clone Of:
Environment:
Last Closed:
Type: ---
Embargoed:


Attachments (Terms of Use)

Description Daniel Berrangé 2026-01-08 11:48:17 UTC
The GCC 16 update has created a FTBFS problem in QEMU's static PIE builds related to libatomic.a

This example build log shows it

https://kojipkgs.fedoraproject.org//work/tasks/5317/140855317/build.log

    /usr/bin/ld: /usr/lib/gcc/x86_64-redhat-linux/16/libatomic.a(cas_16_.o): relocation R_X86_64_32 against hidden symbol `libat_compare_exchange_16_i1' can not be used when making a PIE object
    /usr/bin/ld: failed to set dynamic section sizes: bad value
collect2: error: ld returned 1 exit status

At first I thought this was caused by glib2 introducing an explicit '-latomic' in its pkg-config file Libs.private as that in turn added /usr/lib/gcc/x86_64-redhat-linux/16/libatomic.a to the gcc command line QEMU invokes, but I think that's a red herring.

Bisecting the command line I got it reduced to this fairly minimal reproducer:

gcc  -o qemu-aarch64    libuser.a.p/accel_tcg_user-exec.c.o   -static-pie
/usr/bin/ld: /usr/lib/gcc/x86_64-redhat-linux/16/libatomic.a(cas_16_.o): relocation R_X86_64_32 against hidden symbol `libat_compare_exchange_16_i1' can not be used when making a PIE object
/usr/bin/ld: failed to set dynamic section sizes: bad value
collect2: error: ld returned 1 exit status

Adding '--verbose' to GCC I see GCC itself adding  -latomic to COLLECT_GCC_OPTIONS.

I'm still unclear why there's a link issue though. The existence of libat_compare_exchange_16_i1 doesn't seem to change in libatomic.a between GCC 15 and 16 and I don't see a reference to 'compare_exchange' in the dissasembly of  libuser.a.p/accel_tcg_user-exec.c.o   - it seems like the mere addition of libatomic.a by GCC is perhaps causing the PIE relocation problem .

Reproducible: Always

Steps to Reproduce:
1. Attempt to build a static QEMU user binary as done by qemu.spec

$ dnf builddep qemu
$ ./configure --cc=gcc --cxx=/bin/false --prefix=/usr --libdir=/usr/lib64 --datadir=/usr/share --sysconfdir=/etc --interp-prefix=/usr/qemu-%M --localstatedir=/var --docdir=/usr/share/doc --libexecdir=/usr/libexec '--extra-ldflags=-Wl,-z,relro -Wl,--as-needed  -Wl,-z,pack-relative-relocs -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -specs=/usr/lib/rpm/redhat/redhat-hardened-ld-errors -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1  -Wl,--build-id=sha1 -specs=/usr/lib/rpm/redhat/redhat-package-notes  ' '--extra-cflags=-O2  -fexceptions -g -grecord-gcc-switches -pipe -Wall -Wno-complain-wrong-lang -Werror=format-security -Wp,-U_FORTIFY_SOURCE,-D_FORTIFY_SOURCE=3 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1  -m64 -march=x86-64 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection -mtls-dialect=gnu2 -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer' --with-pkgversion=qemu-10.2.0-0.3.rc4.fc44 --with-suffix=qemu --firmwarepath=/usr/share/qemu-firmware:/usr/share/ipxe/qemu:/usr/share/seavgabios:/usr/share/seabios --enable-trace-backends=dtrace --with-coroutine=ucontext --tls-priority=@QEMU,SYSTEM --audio-drv-list= --disable-af-xdp --disable-alsa --disable-asan --disable-attr --disable-auth-pam --disable-blkio --disable-block-drv-whitelist-in-tools --disable-bochs --disable-bpf --disable-brlapi --disable-bsd-user --disable-bzip2 --disable-cap-ng --disable-capstone --disable-cfi --disable-cfi-debug --disable-cloop --disable-cocoa --disable-colo-proxy --disable-coreaudio --disable-coroutine-pool --disable-crypto-afalg --disable-curl --disable-curses --disable-dbus-display --disable-debug-graph-lock --disable-debug-info --disable-debug-mutex --disable-debug-remap --disable-debug-tcg --disable-dmg --disable-docs --disable-download --disable-dsound --disable-fdt --disable-fuse --disable-fuse-lseek --disable-gcrypt --disable-gettext --disable-gio --disable-glusterfs --disable-gnutls --disable-gtk --disable-gtk-clipboard --disable-guest-agent --disable-guest-agent-msi --disable-hv-balloon --disable-hvf --disable-iconv --disable-igvm --disable-jack --disable-kvm --disable-l2tpv3 --disable-libcbor --disable-libdaxctl --disable-libdw --disable-libkeyutils --disable-libiscsi --disable-libnfs --disable-libpmem --disable-libssh --disable-libudev --disable-libusb --disable-linux-aio --disable-linux-io-uring --disable-linux-user --disable-lto --disable-lzfse --disable-lzo --disable-malloc-trim --disable-membarrier --disable-modules --disable-module-upgrades --disable-mpath --disable-mshv --disable-multiprocess --disable-netmap --disable-nettle --disable-numa --disable-nvmm --disable-opengl --disable-oss --disable-pa --disable-parallels --disable-passt --disable-pie --disable-pipewire --disable-pixman --disable-plugins --disable-pvg --disable-qcow1 --disable-qed --disable-qom-cast-debug --disable-qpl --disable-rbd --disable-rdma --disable-relocatable --disable-replication --disable-rust --disable-rutabaga-gfx --disable-rng-none --disable-safe-stack --disable-sdl --disable-sdl-image --disable-seccomp --disable-selinux --disable-slirp --disable-slirp-smbd --disable-smartcard --disable-snappy --disable-sndio --disable-sparse --disable-spice --disable-spice-protocol --disable-strict-rust-lints --disable-strip --disable-system --disable-tcg --disable-tools --disable-tpm --disable-tsan --disable-uadk --disable-u2f --disable-ubsan --disable-usb-redir --disable-user --disable-valgrind --disable-vpc --disable-vde --disable-vdi --disable-vfio-user-server --disable-vhdx --disable-vhost-crypto --disable-vhost-kernel --disable-vhost-net --disable-vhost-user --disable-vhost-user-blk-server --disable-vhost-vdpa --disable-virglrenderer --disable-virtfs --disable-vnc --disable-vnc-jpeg --disable-png --disable-vnc-sasl --disable-vte --disable-vvfat --disable-werror --disable-whpx --disable-xen --disable-xen-pci-passthrough --disable-xkbcommon --disable-zstd --without-default-devices --enable-attr --enable-linux-user --enable-pie --enable-tcg --disable-install-blobs --static
$ make -j 20

Actual Results:
Failure with

/usr/bin/ld: /usr/lib/gcc/x86_64-redhat-linux/16/libatomic.a(cas_16_.o): relocation R_X86_64_32 against hidden symbol `libat_compare_exchange_16_i1' can not be used when making a PIE object

Expected Results:
Build succeeds

Comment 1 Daniel Berrangé 2026-01-08 11:49:30 UTC
Koji log showing RPM versions - tldr: gcc-16.0.0-0.4.fc44

https://kojipkgs.fedoraproject.org//work/tasks/5317/140855317/root.log

Comment 2 Jakub Jelinek 2026-01-08 12:05:12 UTC
GCC since https://gcc.gnu.org/PR81358 indeed adds --push-state --as-needed -latomic --pop-state to the link line from the gcc/g++ etc. drivers, if -fno-link-libatomic option isn't used.
And indeed this causes some issues with libtool which ignores the --push-state --as-needed and --pop-state wrappers around the -latomic option and so in some cases can link -latomic
even when not really needed.
Does qemu actually need -latomic?  From the word about it previously buildrequiring libatomic-static I'd guess it does.
But in that case I have no idea what further things changed.
libatomic.a itself has been compiled with -fPIC both in GCC 15 and 16.

Comment 3 Daniel Berrangé 2026-01-08 12:11:07 UTC
QEMU added a BR on libatomic-static due to glib2 introducing a dep on libatomic - this was redundant in QEMU though, because glib2-static already had a Requires: libatomic-static  dep.

I'm unclear if QEMU explicitly needs -latomic or not - we've never directly added that ourselves in QEMU, but we use various atomic functions ?

Passing the new -fno-link-libatomic  flag to GCC avoids the QEMU build errors, but that still leaves the question of why linking to libatomic.a is broken to begin with in GCC 16 ?

With GCC 15 if I explicitly added /usr/lib/gcc/x86_64-redhat-linux/15/libatomic.a to QEMU's linker args, we saw no PIE failures.

Comment 4 Jakub Jelinek 2026-01-08 12:16:45 UTC
Actually without -fPIC for both GCC 15 and 16, only libatomic.so.6 has been built with -fPIC.

Comment 5 Jakub Jelinek 2026-01-08 12:22:13 UTC
And it seems it never worked even in GCC 15:
gcc --version
gcc (GCC) 15.2.1 20251022 (Red Hat 15.2.1-3)
Copyright (C) 2025 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
gcc -static-pie -fpie -o a a.c -Wl,--whole-archive -latomic -Wl,--no-whole-archive
/usr/bin/ld: /usr/lib/gcc/x86_64-redhat-linux/15/libatomic.a(gload.o): relocation R_X86_64_32S against `.rodata' can not be used when making a PIE object; recompile with -fPIE
/usr/bin/ld: failed to set dynamic section sizes: bad value
collect2: error: ld returned 1 exit status

Comment 6 Daniel Berrangé 2026-01-08 13:31:58 UTC
I've cut down the QEMU code until I found a minimal reproducer:

$ cat a.c
__int128_t demo(__int128_t cmpv, __int128_t newv)
{
    __int128_t *ptr = 0x0;
    (void)__atomic_compare_exchange_n(ptr, &cmpv, newv, false,
                              __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);

    return cmpv;
}

int main() {
    demo(1, 1);
    return 0;
}

Works GCC 16 with static build:

$ gcc --static  ../accel/tcg/a.c 

Fails GCC 16 with static PIE build:

$ gcc --static-pie  ../accel/tcg/a.c 
/usr/bin/ld: /usr/lib/gcc/x86_64-redhat-linux/16/libatomic.a(cas_16_.o): relocation R_X86_64_32 against hidden symbol `libat_compare_exchange_16_i1' can not be used when making a PIE object
/usr/bin/ld: failed to set dynamic section sizes: bad value
collect2: error: ld returned 1 exit status



This example also fails with GCC 15, so looks like a pre-existing bug in libatomic.a before GCC 16 too.

The reason why QEMU builds OK with GCC 15 is that it does NOT use __atomic_compare_exchange_n() with int128, as its configure script detects that as not supported, so QEMU did a fallback codepath using __sync_val_compare_and_swap_16 instead.

Comment 7 Jonathan Wakely 2026-01-08 13:39:55 UTC
Based on the comment in include/qemu/atomic128.h you need to change the configure checks, because GCC 16's new behaviour of linking to libatomic automatically makes the check misbehave. The check thinks it can use __atomic_compare_exchange_n without involving libatomic, but actually it's just getting a libatomic call that no longer causes a linker error.

Comment 8 Daniel Berrangé 2026-01-08 14:08:58 UTC
(In reply to Jonathan Wakely from comment #7)
> Based on the comment in include/qemu/atomic128.h you need to change the
> configure checks, because GCC 16's new behaviour of linking to libatomic
> automatically makes the check misbehave. The check thinks it can use
> __atomic_compare_exchange_n without involving libatomic, but actually it's
> just getting a libatomic call that no longer causes a linker error.

Yeah, so we're back at needing to modify qemu's  meson.buld script to always add -fno-link-libatomic, if GCC supports that arg.

I'm still surprised libatomic.a breaks with -static-pie for int128 though. If that's a valid bug for GCC to fix then this bug could track it, otherwise feel free to close this and I'll fix QEMU's check regardless

Comment 9 Jakub Jelinek 2026-01-08 14:12:03 UTC
The reason for __atomic_compare_exchange_16 not being supported inline is that while CAS works, for __atomic_* one needs also working atomic load and because the loaded address can be also in .rodata section, CAS loop can't be used for that.
And only recently Intel/AMD guaranteed in their docs that for AVX and higher vmovaps load is atomic if the address is 16-byte aligned, a few other chip makers did too but nobody from VIA said anything.
So libatomic is used instead and has an IFUNC which selects hw instructions for AVX on Intel/AMD.

Comment 10 Jakub Jelinek 2026-01-08 14:18:14 UTC
While libgcc.a is built with -fPIE, I think libstdc++.a is too, most other *.a libraries are not and so are not supported in -static-pie.

Comment 11 Daniel Berrangé 2026-01-08 15:28:20 UTC
For the record, I've sent the following fix upstream to QEMU

https://lists.nongnu.org/archive/html/qemu-devel/2026-01/msg01140.html

Comment 12 Paolo Bonzini 2026-01-08 16:20:16 UTC
> most other *.a libraries are not and so are not supported in -static-pie.

Should libatomic should be built with -fPIE, since it's added automatically (even if within as-needed)? For QEMU it's anyway the right thing to add -fno-link-libatomic, but still.

Comment 13 Aoife Moloney 2026-08-17 14:04:20 UTC
This bug appears to have been reported against 'rawhide' during the Fedora Linux 45 development cycle.
Changing version to 45.


Note You need to log in before you can comment on or make changes to this bug.