https://sourceware.org/bugzilla/show_bug.cgi?id=34942
Bug ID: 34942
Summary: ld writes pc_begin 0 into kept FDEs with --rosegment
--gc-sections, ".eh_frame_hdr refers to overlapping
FDEs
Product: binutils
Version: 2.47
Status: UNCONFIRMED
Severity: normal
Priority: P2
Component: ld
Assignee: unassigned at sourceware dot org
Reporter: daan.j.demeyer at gmail dot com
Target Milestone: ---
Created attachment 17241
--> https://sourceware.org/bugzilla/attachment.cgi?id=17241&action=edit
reproducer
Description
===========
With --rosegment, ld places .eh_frame below .text. In some layouts it then
writes a pc_begin of 0
into FDEs of functions that it keeps. If two FDEs get pc_begin 0 in the same
link, ld fails:
/usr/bin/ld: .eh_frame_hdr refers to overlapping FDEs
/usr/bin/ld: final link failed: bad value
If one FDE gets pc_begin 0, or the link uses --no-eh-frame-hdr, the link
succeeds and the output
contains wrong unwind info. Nothing reports an error.
The failure depends only on addresses. An FDE gets pc_begin 0 when this sum
equals the address of
the function that the FDE covers:
output address of the input .eh_frame section
+ offset of the FDE's pc_begin field in the input .eh_frame section
The offset is the one before ld removes the FDEs of sections that --gc-sections
discarded. The sum
can only reach a function address when .eh_frame is below .text, and when
--gc-sections removed a
large run of FDEs in front of the kept FDE.
The bug reproduces with the 2.47 release (Arch Linux build) and with the
2.47.50.20261008 snapshot
in Fedora rawhide. Fedora configures binutils with --enable-rosegment=yes, so
plain non-LTO builds
there hit it without any special option. It was found as a link failure of a
systemd test program.
I haven't tested current master.
Testcase
========
The testcase is one C file without includes, attached as
binutils-ld-rosegment-fde.c. The
preprocessed file from -save-temps is attached as binutils-ld-rosegment-fde.i.
It contains 100 functions that nothing calls, then used1() and used2(), which
main() calls.
--gc-sections removes the 100 functions. Each function has a 20-byte FDE, and
used2() starts 80
bytes after used1() in .text and in .eh_frame. With equal spacing, both FDEs
get pc_begin 0 in the
same layout. The PAD macro sets the size of a .rodata array. It moves
.eh_frame, while .text stays
at its page-aligned address. PAD defaults to 784, which fails with both
toolchains below. With
other crt files, try other multiples of 8.
Command line and output, binutils 2.47 release
===============================================
$ gcc -v
...
Configured with: ../gcc/configure
--enable-languages=ada,c,c++,d,fortran,go,lto,m2,objc,obj-c++,rust,cobol
--enable-bootstrap --prefix=/usr --libdir=/usr/lib --libexecdir=/usr/lib
--mandir=/usr/share/man --infodir=/usr/share/info
--with-bugurl=https://gitlab.archlinux.org/archlinux/packaging/packages/gcc/-/issues
--with-build-config=bootstrap-lto --with-gcc-major-version-only
--with-linker-hash-style=gnu --with-system-zlib --enable-cet=auto
--enable-checking=release --enable-clocale=gnu --enable-default-pie
--enable-default-ssp --enable-gnu-indirect-function --enable-gnu-unique-object
--enable-libstdcxx-backtrace --enable-link-serialization=1
--enable-linker-build-id --enable-lto --enable-multilib --enable-plugin
--enable-shared --enable-threads=posix --disable-fixincludes --disable-libssp
--disable-libstdcxx-pch --disable-werror
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 16.2.1 20260810 (GCC)
$ ld -v
GNU ld (GNU Binutils) 2.47
$ gcc -O2 -ffunction-sections -pie -fPIE -o pr binutils-ld-rosegment-fde.c
-Wl,--gc-sections -Wl,--rosegment
/usr/bin/ld: .eh_frame_hdr refers to overlapping FDEs
/usr/bin/ld: final link failed: bad value
collect2: error: ld returned 1 exit status
$ gcc -O2 -ffunction-sections -pie -fPIE -o pr binutils-ld-rosegment-fde.c
-Wl,--gc-sections -Wl,--no-rosegment
$
The link with --no-rosegment succeeds.
Command line and output, Fedora rawhide
=======================================
$ gcc -v
...
Target: x86_64-redhat-linux
Configured with: ../configure --enable-bootstrap
--enable-languages=c,c++,fortran,objc,obj-c++,ada,go,d,m2,cobol,algol68,lto
--prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info
--with-bugurl=https://bugzilla.redhat.com/ --enable-shared
--enable-threads=posix --enable-checking=release --with-system-zlib
--enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object
--enable-linker-build-id --with-gcc-major-version-only --enable-plugin
--enable-initfini-array --enable-libstdcxx-backtrace
--with-libstdcxx-zoneinfo=/usr/share/zoneinfo
--with-isl=/builddir/build/BUILD/gcc-16.2.1-build/gcc-16.2.1-20260819/obj-x86_64-redhat-linux/isl-install
--enable-gnu-indirect-function --enable-multilib --with-linker-hash-style=gnu
--enable-offload-targets=nvptx-none,amdgcn-amdhsa --enable-offload-defaulted
--without-cuda-driver --enable-cet --with-tune=generic --with-tls=gnu2
--with-arch_32=i686 --build=x86_64-redhat-linux
--with-build-config=bootstrap-lto --enable-link-serialization=1
--disable-libssp
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 16.2.1 20260819 (Red Hat 16.2.1-2) (GCC)
$ ld -v
GNU ld version 2.47.50.20261008
$ gcc -O2 -ffunction-sections -pie -fPIE -save-temps -o pr
binutils-ld-rosegment-fde.c -Wl,--gc-sections -Wl,--rosegment
/usr/bin/ld.bfd: .eh_frame_hdr refers to overlapping FDEs
/usr/bin/ld.bfd: final link failed: bad value
collect2: error: ld returned 1 exit status
The binutils package is binutils-2.47.50-20.fc46. It is configured with
--enable-separate-code=yes
and --enable-rosegment=yes.
Wrong output without an error
=============================
The FDE offsets in .eh_frame don't depend on PAD. In the input object, the
pc_begin relocations of
used1() and used2() are at these offsets:
00000000000007f0 0000006600000002 R_X86_64_PC32 0000000000000000
.text.used1 + 0
0000000000000840 0000006a00000002 R_X86_64_PC32 0000000000000000
.text.used2 + 0
With --no-eh-frame-hdr, .eh_frame moves, and PAD=904 hits the condition on
Fedora rawhide. ld
reports nothing:
$ gcc -O2 -ffunction-sections -pie -fPIE -DPAD=904 -o pr904
binutils-ld-rosegment-fde.c -Wl,--gc-sections -Wl,--rosegment
-Wl,--no-eh-frame-hdr
$ nm pr904 | grep -w -e used1 -e used2
0000000000001150 T used1
00000000000011a0 T used2
$ readelf -SW pr904 | grep -w -e .eh_frame -e .text
[11] .eh_frame PROGBITS 0000000000000908 000908 00009c 00
A 0 0 8
[16] .text PROGBITS 0000000000001040 001040 000163 00
AX 0 0 16
$ readelf --debug-dump=frames pr904 | grep FDE
00000018 0000000000000024 0000001c FDE cie=00000000
pc=0000000000001020..0000000000001040
00000040 0000000000000014 00000044 FDE cie=00000000
pc=0000000000001060..0000000000001086
00000058 0000000000000010 0000005c FDE cie=00000000
pc=0000000000000968..00000000000009b1
00000070 0000000000000010 00000070 FDE cie=00000000
pc=000000000000097c..000000000000097f
00000080 0000000000000014 00000084 FDE cie=00000000
pc=0000000000001040..000000000000105a
The FDEs at 0x58 and 0x6c have the ranges of used1() (0x49 bytes) and used2()
(3 bytes), but they
start inside .eh_frame. Their pc_begin field is 0, and readelf adds the field's
own address. The
FDEs of the input section start at output address 0x908 + 0x58 = 0x960. The
sums from the
description:
0x960 + 0x7f0 = 0x1150 = used1
0x960 + 0x840 = 0x11a0 = used2
Both pc-relative relocations therefore evaluate to 0 at the input offset. ld
then leaves the field
at 0 instead of adjusting it to the new offset of the FDE.
Additional notes
================
- --no-rosegment avoids the bug in every layout I tried, because .eh_frame is
then above .text and
the sum stays above every function address.
- -no-pie fails the same way in a different layout. On Fedora rawhide that is
with 100 unused
functions and PAD=1080 (an assembler version of the same testcase).
- -z pack-relative-relocs and the rpath length only move the layout. A link
without DT_RELR fails
too, once the layout hits the condition.
- Linker-generated unwind info isn't involved. --no-ld-generated-unwind-info
only moves the
failing layout.
- The x86 layout change for --rosegment is commit 99600a478791 ("elf/x86: Place
code after
read-only data for --rosegment"), first released in 2.47.
--
You are receiving this mail because:
You are on the CC list for the bug.