Skip to content

Select rows and symbols in walkVM by attribution address - #813

Draft
rkennke wants to merge 1 commit into
mainfrom
sphinx/fix-jira-PROF-15955
Draft

rkennke wants to merge 1 commit into
mainfrom
sphinx/fix-jira-PROF-15955

Conversation

@rkennke

@rkennke rkennke commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Targets main directly. #786 has merged (squashed as 0d06533), so attributionPC() is on main and the earlier stacking is gone — this is now a single commit on top of main. The ASAN chain-buffer fix that #786 needed is also on main, so it is not part of this diff.

Addresses PROF-15955.

Summary

HotspotSupport::walkVM fed the raw walking pc to findLibraryByAddress, findFrameDesc and the DW_REG_PLT stub-offset test. Past the leaf that pc is a return address, so a call that is the last instruction of its caller selected the following function's CFA row, derived a sender sp from it, and could miss a MARK_THREAD_ENTRY sitting on the caller. Same defect #786 fixed in StackWalker::walkFP/walkDwarf — except this is the path CSTACK_DEFAULT actually resolves to whenever the JVM exposes VMStructs on Linux, so it is the common production configuration rather than a fallback.

walkVM now tracks per-frame whether the pc came out of a return-address slot and routes the range-based lookups through attributionPC().

Which pcs get adjusted, and which deliberately do not

Consumer Address used Why
findLibraryByAddress, findFrameDesc attribution range lookups; the whole point of the fix
DW_REG_PLT stub-offset test attribution must be decided on the same address the row was selected with
isContReturnBarrier, isContEntryReturnPc, isEntryFrame raw exact-equality comparisons against return addresses
DW_PC_OFFSET arithmetic raw DW_OP_breg<PC> names the pc register value
no-progress guard, inDeadZone raw needs the exact address
isFrameCompleteAt, findScopeOffset (PcDesc) raw see below

A signal-frame CIE suppresses the adjustment exactly as it does in walkDwarf (pc_is_ra = !f.isSignalFrame()).

Every site in walkVM that writes pc — 16 of them — sets the flag. Two were missing from the original enumeration in the ticket: both in-loop anchor->getFrame(pc, sp, fp) calls silently rewrite pc to lastJavaPC(), which is a return address.

The wire format does not move

resolveNativeFrameForWalkVM was using one address for two jobs: range lookups and the emitted remote-symbolication pc_offset. It now takes the attribution address for findLibraryByAddress/binarySearch while pc_offset keeps deriving from the raw pc, so the emitted wire value is byte-identical.

This matters for review: it means this PR does not depend on the unresolved cross-team question about pc_offset semantics raised in #786. It is effectively option (b) from that PR's own list of options, applied locally.

Two findings that correct the ticket's premises

The suspected PcDesc double-subtraction bug does not exist. PROF-15955 flagged that x64's ad hoc -1 being re-fed into isFrameCompleteAt/findScopeOffset might be a second latent bug. It is not: findScopeOffset (vmStructs.cpp:798) is a ceiling search — on an exact miss it returns the first PcDesc with _pc >= pc_offset — and HotSpot records PcDescs at return addresses, so RA-1 and RA resolve to the same entry. isFrameCompleteAt is a monotone threshold. Both absorb the decrement, so the PcDesc sites are safe on the raw pc.

There is a different, real x86_64 bug — filed as PROF-16033, not fixed here. unwindPrologue/unwindEpilogue/unwindStub fold -1 into the pc they return on x86_64 but not on aarch64, so on x86_64 the three exact-equality comparisons above always fail for pcs arriving through those helpers: continuation-boundary and entry-frame detection silently do not fire. The adjustment is also branch-inconsistent (unwindPrologue's isFrameComplete branch at hotspotStackFrame_x64.cpp:155 omits it), which is why a correct flag cannot be threaded without changing those helpers' contract.

Those three call sites are therefore flagged pc_is_ra = false here — conservative, cannot double-adjust, and no behaviour change on either arch. The arch files are untouched. Fixing them properly means touching x86_64/aarch64 pattern-matching code shared with the legacy getJavaTraceAsync/AsyncGetCallTrace path, which is a different blast radius and belongs in its own change.

Deliberately left untouched

Per the ticket's step 5, calling these out so they do not read as oversights:

  • JVM-internal metadata lookups (CodeHeap::findNMethod, isFrameCompleteAt, findScopeOffset → PcDesc) stay on the raw pc — correct by HotSpot's own convention, confirmed above.
  • unwindPrologue/unwindEpilogue/unwindStub — PROF-16033.
  • unwindCompiled — same inconsistency, but only reachable from the AGCT path, not walkVM. Covered by PROF-16033.
  • PerfEvents::walkKernel — carries a related defect but is outside PROF-15955's scope; it is a pure consumer of the kernel's ring buffer and would be a much smaller separate change.

Test plan

walkVM's affected paths only fire inside a live JVM, and resolveNativeFrameForWalkVM resolves through the Libraries singleton whose _native_libs is private with no injection seam. Adding a test that exercises adjacent code rather than the changed code would be false confidence, so none was added. The passing suite establishes no regression, not correctness. Real coverage needs the harness described in step 4 of the ticket — a synthetic CodeCache/Libraries seam, or a live-JVM fixture forcing a noreturn call as the last instruction of a native function reached from Java — and that is its own piece of work.

Reviewers: the judgement calls worth pushing back on are (a) the raw-vs-attribution split in the table above, and (b) whether shipping this without behavioural coverage is acceptable given walkVM is the default path.

🤖 Generated with Claude Code

@datadog-datadog-prod-us1

This comment has been minimized.

@dd-octo-sts

dd-octo-sts Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

CI Test Results

Run: #35868811509 | Commit: 62aa779 | Duration: 15m 53s (longest job)

All 32 test jobs passed

Status Overview

JDK glibc-aarch64/debug glibc-amd64/debug musl-aarch64/debug musl-amd64/debug
8 - - -
8-ibm - - -
8-j9 - -
8-librca - -
8-orcl - - -
11 - - -
11-j9 - -
11-librca - -
17 - -
17-graal - -
17-j9 - -
17-librca - -
21 - -
21-graal - -
21-librca - -
25 - -
25-graal - -
25-librca - -

Legend: ✅ passed | ❌ failed | ⚪ skipped | 🚫 cancelled

Summary: Total: 32 | Passed: 32 | Failed: 0


Updated: 2026-09-23 14:01:26 UTC

@rkennke
rkennke force-pushed the sphinx/fix-jira-PROF-15955 branch from a78aa75 to 36ff97c Compare September 22, 2026 16:48
@dd-octo-sts

dd-octo-sts Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

All 40 integration tests passed

📊 Dashboard · 👷 Pipeline · 📦 09d5f7d2

Base automatically changed from sphinx/fix-jira-PROF-15934 to main September 22, 2026 18:42
walkVM fed the raw walking pc to findLibraryByAddress, findFrameDesc and the
DW_REG_PLT stub-offset test. Past the leaf that pc is a return address, so a
call that is the last instruction of its caller selected the following
function's CFA row, derived a sender sp from it, and could miss a
MARK_THREAD_ENTRY sitting on the caller -- the same defect already fixed in
StackWalker::walkFP/walkDwarf, on the path CSTACK_DEFAULT actually resolves to.

Track per-frame whether the pc came out of a return-address slot and route the
range-based lookups through attributionPC(). Exact-address consumers
(isContReturnBarrier, isContEntryReturnPc, isEntryFrame), the DW_PC_OFFSET
arithmetic and the no-progress guard keep the raw pc, and a signal-frame CIE
suppresses the adjustment exactly as it does in walkDwarf.

resolveNativeFrameForWalkVM was using one address for two jobs. It now takes
the attribution address for findLibraryByAddress/binarySearch while the emitted
pc_offset keeps deriving from the raw pc, so the remote-symbolication wire
value is unchanged and no cross-team contract moves.

unwindPrologue/unwindEpilogue/unwindStub are left alone: x86_64 already folds
the adjustment into the pc they return and does so inconsistently (the
isFrameComplete branch omits it) while aarch64 returns it raw, so their results
are flagged as non-return-addresses and cannot be adjusted twice. Unifying that
contract also fixes the exact-address comparisons those helpers currently break
on x86_64, and is left to its own change.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
@rkennke
rkennke force-pushed the sphinx/fix-jira-PROF-15955 branch from 36ff97c to 09d5f7d Compare September 23, 2026 13:41

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant