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.