Branch: refs/heads/bbiiggppiigg/springboard
Home: https://github.com/dyninst/dyninst
Commit: 12f0d35c676fbee576347c56e323c94c3412bef9
https://github.com/dyninst/dyninst/commit/12f0d35c676fbee576347c56e323c94c3412bef9
Author: wuxx1279 <bbiiggppiigg@xxxxxxxxx>
Date: 2026-07-05 (Sun, 05 Jul 2026)
Changed paths:
M dyninstAPI/src/Relocation/CodeMover.C
Log Message:
-----------
Relocation: classify springboards on a complete graph (issue #2155)
Test case / binary
------------------
Reproduced with dyninst's code_coverage example instrumenting the SPEC
CPU2017 602.gcc_s benchmark binary ("sgcc"), built -O2 + LTO. code_coverage
inserts a counter at every function entry, so every function is relocated
and every jump-table target block needs a springboard. gcc's source is
full of large switches, and at -O2/LTO these compile to dense PIC jump
tables whose case blocks are tiny landing pads -- sometimes only a few
bytes long and packed immediately adjacent. In function build_unary_op the
crash site has two such case-target blocks 3 bytes apart:
4eb514: 48 89 eb mov %rbp,%rbx ; jump-table target (3 bytes)
4eb517: 8b 43 38 mov 0x38(%rbx),%eax ; the next jump-table target
Both are reached by indirect jumps (0x4eb353 -> 0x4eb514, and
0x4eb57c/0x4eb748 -> 0x4eb517). All addresses below are original vaddrs in
that sgcc binary. A dyninst springboard is a 5-byte jmp rel32, so a
springboard at 0x4eb514 spans 0x4eb514..0x4eb519 and overwrites 0x4eb517.
Bug
---
CodeMover::finalizeRelocBlocks walked the RelocGraph once, calling
linkRelocBlocks() and determineSpringboards() back-to-back per block. But
an in-edge whose source is another RelocBlock is deferred by
getPredecessors() and only materialized when that source block is later
linked (via its getSuccessors -> makeEdge). So a block was classified
against a half-built graph: determineSpringboards()'s IndirBlockEntry
upgrade requires the block's INDIRECT in-edge to already exist, but for a
jump-table target whose dispatch block appears later in the list (a
backward indirect jump: dispatch addr > target addr) that edge is not
present yet, so the target is mis-classified as Suggested.
Here 0x4eb514's dispatch (0x4eb353) precedes it -> correctly IndirBlockEntry;
0x4eb517's dispatches (0x4eb57c, 0x4eb748) follow it -> mis-classified
Suggested. Springboards install per priority pass (FuncEntry >
IndirBlockEntry > Suggested), high addr to low within a pass. So 0x4eb514
(IndirBlockEntry) is installed first and lays its 5-byte jump; 0x4eb517
(Suggested, later pass) then cannot fit any springboard in the bytes
0x4eb514 already claimed and gets none. At runtime the switch dispatches to
0x4eb517, which is now 3 bytes into 0x4eb514's jump, executing its
displacement bytes as an instruction -> SIGSEGV.
Fix
---
Split finalizeRelocBlocks into two passes -- link all RelocBlocks first,
then classify all of them -- so determineSpringboards always reads a
complete graph. 0x4eb517 is then correctly IndirBlockEntry, installs its
springboard first (higher addr, same pass), and 0x4eb514 falls back to a
trap instead of clobbering it. Edge-set preserving; only the read timing
changes. Verified: instrumented sgcc runs to completion (exit 0); deepsjeng
unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@xxxxxxxxxxxxx>
To unsubscribe from these emails, change your notification settings at https://github.com/dyninst/dyninst/settings/notifications
|