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.

Reply via email to