PERRY_STACKMAP_WALKER=unwind segfaults on Linux/ELF: 3 of the 8
statepoint-compilable probes die with SIGSEGV, on a build where the default
(fast x29-chain) walker runs all 8 clean against the pinned Node oracle.
Why this is worse than it looks
On aarch64 this is a bisection control going wrong, because the fast chain walk
is the default and it works. On x86-64 the unwinder IS the walker — fp_chain
is compiled only for any(macos, linux) + aarch64 and stubbed to None
everywhere else, so WalkerMode::Fast falls through to unwind::visit. #7328
already stated the general form of this hazard: "on any host where the x29 chain
walk is unavailable the unwinder is the walker, so a configuration that finds no
roots enumerates through the same path and forced evacuation stays quiet."
So this is very likely the next blocker for statepoints on x86-64, sitting
directly behind the compact-map defect (#7321 / #7331) — and it is one that
fails by crashing or scanning wrong memory, not by refusing.
Evidence
Linux/aarch64 (aarch64-unknown-linux-gnu, ELF), perry built in a Debian
container, probes compiled with PERRY_STATEPOINTS=1, run against Node 26.5.1:
baseline (fast walker): exit=0
forced evac only: exit=0 (PERRY_GC_FORCE_EVACUATE + VERIFY_EVACUATION)
trace only: exit=0 (PERRY_GC_TRACE=1)
unwind only: exit=139 <-- SIGSEGV
unwind + forced evac: exit=139
verify only: exit=139 (verify runs the unwinder too)
PERRY_STACKMAP_WALKER=unwind alone is sufficient; neither the trace flag
nor forced evacuation is involved. Failing probes: 02_survivor_promotion,
03_cross_gen_writes, 08_map_set_sidetables. Passing: 01, 04, 05, 06,
07. (09_try_catch_roots cannot compile under the explicit bridge at all
since #7330.)
The full matrix under the default walker on the same binaries is 8/8, output
byte-identical to the oracle, with .perry_gcmap present and .llvm_stackmaps
absent asserted per probe — so the maps themselves decode correctly and the
roots are right. It is specifically the unwinder's idea of where the frame is.
Suspected mechanism, not yet proven
Every root in a Perry aarch64 module is SP-relative (DWARF 31; measured 57
of 57 on one module, 92 of 92 on another). The walker resolves those with
_Unwind_GetGR(context, dwarf_reg).
For the stack-pointer column, an unwinder is entitled to return the frame's
CFA rather than the in-body SP — they differ by the frame size, and the
DWARF model defines the SP after unwinding as the CFA. fp_chain does not use
_Unwind_GetGR at all: it decodes the prologue's add x29, sp, #imm to
recover the body SP (#7173, refined by #7329). If libgcc's _Unwind_GetGR
returns the CFA where Apple's libunwind returns the body SP, that exactly
explains "clean on macOS/aarch64, wrong on Linux/aarch64, and wrong on any
x86-64" while leaving the fast path correct.
That is a hypothesis consistent with all the above; it has not been confirmed by
reading back the register value.
Reproducing
Needs a Linux host (or container). aarch64 Linux additionally needs
#7331, without which
perry-runtime does not link and the compact-map rewriter refuses every module.
PERRY_STATEPOINTS=1 perry benchmarks/gc_ratchet/probes/02_survivor_promotion.ts -o /tmp/p02
PERRY_STATEPOINTS=1 PERRY_STACKMAP_WALKER=unwind /tmp/p02 # SIGSEGV
PERRY_STACKMAP_WALKER=unwindsegfaults on Linux/ELF: 3 of the 8statepoint-compilable probes die with SIGSEGV, on a build where the default
(fast x29-chain) walker runs all 8 clean against the pinned Node oracle.
Why this is worse than it looks
On aarch64 this is a bisection control going wrong, because the fast chain walk
is the default and it works. On x86-64 the unwinder IS the walker —
fp_chainis compiled only for
any(macos, linux) + aarch64and stubbed toNoneeverywhere else, so
WalkerMode::Fastfalls through tounwind::visit. #7328already stated the general form of this hazard: "on any host where the x29 chain
walk is unavailable the unwinder is the walker, so a configuration that finds no
roots enumerates through the same path and forced evacuation stays quiet."
So this is very likely the next blocker for statepoints on x86-64, sitting
directly behind the compact-map defect (#7321 / #7331) — and it is one that
fails by crashing or scanning wrong memory, not by refusing.
Evidence
Linux/aarch64 (
aarch64-unknown-linux-gnu, ELF), perry built in a Debiancontainer, probes compiled with
PERRY_STATEPOINTS=1, run against Node 26.5.1:PERRY_STACKMAP_WALKER=unwindalone is sufficient; neither the trace flagnor forced evacuation is involved. Failing probes:
02_survivor_promotion,03_cross_gen_writes,08_map_set_sidetables. Passing:01,04,05,06,07. (09_try_catch_rootscannot compile under the explicit bridge at allsince #7330.)
The full matrix under the default walker on the same binaries is 8/8, output
byte-identical to the oracle, with
.perry_gcmappresent and.llvm_stackmapsabsent asserted per probe — so the maps themselves decode correctly and the
roots are right. It is specifically the unwinder's idea of where the frame is.
Suspected mechanism, not yet proven
Every root in a Perry aarch64 module is SP-relative (DWARF 31; measured 57
of 57 on one module, 92 of 92 on another). The walker resolves those with
_Unwind_GetGR(context, dwarf_reg).For the stack-pointer column, an unwinder is entitled to return the frame's
CFA rather than the in-body SP — they differ by the frame size, and the
DWARF model defines the SP after unwinding as the CFA.
fp_chaindoes not use_Unwind_GetGRat all: it decodes the prologue'sadd x29, sp, #immtorecover the body SP (#7173, refined by #7329). If libgcc's
_Unwind_GetGRreturns the CFA where Apple's libunwind returns the body SP, that exactly
explains "clean on macOS/aarch64, wrong on Linux/aarch64, and wrong on any
x86-64" while leaving the fast path correct.
That is a hypothesis consistent with all the above; it has not been confirmed by
reading back the register value.
Reproducing
Needs a Linux host (or container). aarch64 Linux additionally needs
#7331, without which
perry-runtimedoes not link and the compact-map rewriter refuses every module.