[DynInst_API:] [dyninst/dyninst] 12f0d3: Relocation: classify springboards on a complete gr...


Date: Sun, 05 Jul 2026 02:09:18 -0700
From: bbiiggppiigg <noreply@xxxxxxxxxx>
Subject: [DynInst_API:] [dyninst/dyninst] 12f0d3: Relocation: classify springboards on a complete gr...
  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
[← Prev in Thread] Current Thread [Next in Thread→]
  • [DynInst_API:] [dyninst/dyninst] 12f0d3: Relocation: classify springboards on a complete gr..., bbiiggppiigg <=