https://sourceware.org/bugzilla/show_bug.cgi?id=34979

            Bug ID: 34979
           Summary: ld: lang_relax_sections ignores need_layout on entry,
                    causing stale output section VMAs and false
                    R_RISCV_JAL truncation when SIZEOF_HEADERS grows
           Product: binutils
           Version: 2.48 (HEAD)
            Status: UNCONFIRMED
          Severity: normal
          Priority: P2
         Component: ld
          Assignee: unassigned at sourceware dot org
          Reporter: h.dwwwwww at gmail dot com
  Target Milestone: ---

Overview
========
When `ldelf_map_segments` invokes `lang_relax_sections (true)` for a second
pass because `bfd_elf_map_sections_to_segments` updated
`elf_program_header_size` (and therefore `SIZEOF_HEADERS`),
`lang_relax_sections` ignores the incoming `need_layout = true` flag when
`RELAXATION_ENABLED` is set.

Instead of recalculating output section VMAs before running relaxation,
`lang_relax_sections` immediately enters `lang_size_sections (&relax_again,
false)`. Because `lang_size_sections_1` walks the linker script
top-down—updating each output section's VMA and immediately calling
`bfd_relax_section` on its input sections before visiting later output
sections—earlier output sections (such as `.text`) see their new VMA (shifted
forward by the `SIZEOF_HEADERS` increase), while later output sections in the
same segment still have their stale VMA from the previous iteration.

On RISC-V, for any input section that did not have an `R_RISCV_ALIGN`
relocation (`sec->sec_flg0 == false`, which is the common case with
`-ffunction-sections`), this temporarily understates the positive PC-relative
distance (`foff = symval - pc`) from `.text` to any subsequent output section
by the exact amount of the `SIZEOF_HEADERS` increase (e.g., `2 *
sizeof(Elf64_Phdr) = 0x70` bytes for `PT_PHDR + PT_INTERP`, or `3 *
sizeof(Elf64_Phdr) = 0xa8` bytes when `.note.gnu.property` is also present). If
the true distance between a call site in `.text` and a callee in a subsequent
output section (for example, libc++'s `__lcxx_override` section containing
`operator new`) is within `(0xffffe - max_alignment, 0xffffe - max_alignment +
delta_headers]`, `_bfd_riscv_relax_call` / `_bfd_riscv_relax_jal` falsely sees
the target as in-range and irreversibly relaxes `auipc + jalr` (8 bytes) into
`jal` (4 bytes). Once `lang_size_sections_1` reaches the target output section
and updates its VMA by `+delta_headers`, the true distance is restored (`>
0xffffe`), and final relocation fails with:

  relocation truncated to fit: R_RISCV_JAL against symbol `...' defined in ...
section

(We hit this when linking `flatc` in Electron with `-ffunction-sections`
against LLVM libc++, which places `operator new` in section `__lcxx_override`.)


Steps to Reproduce
==================
1. Save the following reproducer as `repro.s`:

```s
.text
.globl _start
_start:
  call target
  .space 0xffffe

.section __lcxx_override,"ax",@progbits
.globl target
target:
  ret
```

2. Assemble and link as PIE:

```sh
riscv64-linux-gnu-as -march=rv64gc -mabi=lp64d -mrelax -o repro.o repro.s
riscv64-linux-gnu-ld -pie -o repro.elf repro.o
```

Actual Results
==============
```
repro.o: in function `_start':
(.text+0x0): relocation truncated to fit: R_RISCV_JAL against symbol `target'
defined in __lcxx_override section in repro.o
```

Expected Results
================
The link succeeds and keeps the cross-section call at `_start` as `auipc ra,
...; jalr ...(ra)` because the distance to `target` before relaxing the call is
`0x100006` bytes (and `0x100002` bytes if shortened by 4 bytes), which exceeds
the maximum positive reach `0xffffe` of `R_RISCV_JAL`.


Root Cause Analysis
===================
Two mechanisms interact to trigger this bug:

1. Why `SIZEOF_HEADERS` grows between Iteration 1 and Iteration 2 of
`ldelf_map_segments`:
   During `ldelf_before_allocation`, `strip_excluded_output_sections` runs
`one_lang_size_sections_pass (NULL, false)` before dynamic sections (such as
`.interp`) are sized, and before `one_lang_size_sections_pass` has visited
`.interp` or `.note.gnu.property`.
   When `SIZEOF_HEADERS` at the top of the default linker script calls
`_bfd_elf_sizeof_headers -> get_program_header_size` (`bfd/elf.c`),
`get_program_header_size` checks `s->size != 0` on `.interp` (`bfd/elf.c:4822`)
and `.note.gnu.property` (`bfd/elf.c:4863`). Because `s->size` is still 0 at
that point, `get_program_header_size` omits `PT_PHDR` and `PT_INTERP` (2 phdrs
= 0x70 bytes on ELF64), and `_bfd_elf_sizeof_headers` caches that underestimate
in `elf_program_header_size (abfd)`.
   At the end of the first iteration of `ldelf_map_segments`,
`bfd_elf_map_sections_to_segments` computes the actual program header size,
updates `elf_program_header_size (abfd)`, sets `need_layout = true`, and loops
for a second iteration of `lang_relax_sections (need_layout)`.

2. Why `lang_relax_sections (true)` corrupts relaxation on the second
iteration:
   In `repro.s`:
   - During Iteration 1 of `ldelf_map_segments`, `.text` is placed at VMA
`0x1d4` (size `0x100006`) and `__lcxx_override` at VMA `0x1001da`. The distance
from `_start` (`0x1d4`) to `target` (`0x1001da`) is `0x100006 > 0xffffe`, so
`_bfd_riscv_relax_call` rightly leaves `auipc + jalr` unrelaxed.
   - At the end of Iteration 1, `bfd_elf_map_sections_to_segments` adds
`PT_PHDR` and `PT_INTERP`, growing `SIZEOF_HEADERS` by `0x70` bytes and setting
`need_layout = true`.
   - Iteration 2 calls `lang_relax_sections (true)`. However,
`lang_relax_sections` in `ld/ldlang.c` ignores `need_layout` at the start of
`if (RELAXATION_ENABLED || link_info.enable_dt_relr)` and immediately starts
`relax_pass = 0, relax_trip = 0`.
   - `lang_size_sections_1` evaluates the new `SIZEOF_HEADERS`, updates
`.text->vma` from `0x1d4` to `0x244` (`+0x70`), and immediately invokes
`bfd_relax_section` on `.text`.
   - Inside `_bfd_riscv_relax_call`, `sec_addr (sec)` uses the **new**
`.text->vma` (`0x244`), whereas `symval` uses the **stale**
`__lcxx_override->vma` (`0x1001da`) because `lang_size_sections_1` has not
visited `__lcxx_override` yet.
   - Consequently, `foff = 0x1001da - 0x244 = 0xfff96`. Even after adding
`max_alignment` (`0x8`), `0xfff9e <= 0xffffe`, so `_bfd_riscv_relax_call`
relaxes the call to `jal` and deletes 4 bytes.
   - When `lang_size_sections_1` subsequently visits `__lcxx_override`, it
updates `__lcxx_override->vma` to `0x100246` (`+0x70 - 4`), making the actual
offset `0x100246 - 0x244 = 0x100002 > 0xffffe`, which fails at relocation time.

-- 
You are receiving this mail because:
You are on the CC list for the bug.

Reply via email to