[DynInst_API:] [dyninst/dyninst] 33cccc: Classify springboards after reloc graph finalized ...


Date: Thu, 09 Jul 2026 14:18:19 -0700
From: bbiiggppiigg <noreply@xxxxxxxxxx>
Subject: [DynInst_API:] [dyninst/dyninst] 33cccc: Classify springboards after reloc graph finalized ...
  Branch: refs/heads/master
  Home:   https://github.com/dyninst/dyninst
  Commit: 33ccccd6188c4c64ca06d432a66d676e2a929623
      https://github.com/dyninst/dyninst/commit/33ccccd6188c4c64ca06d432a66d676e2a929623
  Author: bbiiggppiigg <bbiiggppiigg@xxxxxxxxx>
  Date:   2026-07-09 (Thu, 09 Jul 2026)

  Changed paths:
    M dyninstAPI/src/Relocation/CodeMover.C

  Log Message:
  -----------
  Classify springboards after reloc graph finalized (issue #2155) (#2312)

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] 33cccc: Classify springboards after reloc graph finalized ..., bbiiggppiigg <=