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: rawhide
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-02-04 20:33 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.


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