Skip to content

Commit 6070109

Browse files
committed
fix(walkvm): detect Path A via cont_entry_return_pc exact-PC match
- Replace heuristic type_size()==0 && hasActiveContinuation() check with isContEntryReturnPc(pc) exact-PC match before nmethod dispatch - Add isContEntryReturnPc() to VMStructs mirroring isContReturnBarrier() - Remove hasActiveContinuation() from VMThread (no longer needed) - Update enterSpecial comment: RuntimeBlob on JDK 21-26, nmethod on JDK 27+ - Add assertFalse(vt.isAlive()) timeout guard to blocking VT test The old condition fired only when ContinuationEntry was absent from vmStructs (musl/minimal JDK 21-26 builds). On JDK 21-26 glibc, ContinuationEntry IS in vmStructs so type_size()!=0, but enterSpecial is still a RuntimeBlob whose findNMethod() returns NULL. Path A was silently skipped, showing truncated traces instead of carrier frames. This commit made by [/dd:git:commit:atomic](https://github.com/DataDog/claude-marketplace/tree/main/dd/commands/git/commit/atomic.md)
1 parent 8c331b3 commit 6070109

File tree

4 files changed

+32
-44
lines changed

4 files changed

+32
-44
lines changed

ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -475,11 +475,10 @@ void VMStructs::resolveOffsets() {
475475
_interpreter_nm = CodeHeap::findNMethod(_interpreter_start);
476476
}
477477
if (_enter_special_nm == NULL && _cont_entry_return_pc != NULL) {
478-
// enterSpecial is a generated nmethod. If findNMethod returns NULL the
479-
// CodeHeap may not yet contain it at VM-ready time; Path A (enterSpecial
480-
// detection) will silently not trigger for those samples, which is safe —
481-
// the stack will simply be truncated at the continuation boundary rather
482-
// than showing carrier frames.
478+
// On JDK 27+, enterSpecial is a proper nmethod; findNMethod succeeds.
479+
// On JDK 21-26, it is a RuntimeBlob; findNMethod returns NULL and
480+
// _enter_special_nm stays NULL. Path A is then detected via
481+
// isContEntryReturnPc() in the walk loop rather than nmethod identity.
483482
_enter_special_nm = CodeHeap::findNMethod(_cont_entry_return_pc);
484483
}
485484
}

ddprof-lib/src/main/cpp/hotspot/vmStructs.h

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -475,9 +475,11 @@ class VMStructs {
475475
return _cont_return_barrier != nullptr && pc == _cont_return_barrier;
476476
}
477477

478-
// Returns true if cont_returnBarrier was successfully resolved (JDK 21+).
479-
static bool hasContReturnBarrier() {
480-
return _cont_return_barrier != nullptr;
478+
// Path A: bottom VT frame's return address when the continuation is fully
479+
// thawed (CPU-bound or fully resumed). Mirrors isContReturnBarrier (Path B).
480+
// Available on JDK 21+ via vmStructs or symbol fallback.
481+
static bool isContEntryReturnPc(const void* pc) {
482+
return _cont_entry_return_pc != nullptr && pc == _cont_entry_return_pc;
481483
}
482484

483485
static VMNMethod* enterSpecialNMethod() {
@@ -860,14 +862,6 @@ DECLARE(VMThread)
860862
return ptr != nullptr ? VMContinuationEntry::cast(ptr) : nullptr;
861863
}
862864

863-
// Returns true if a continuation (virtual thread) is currently mounted on
864-
// this carrier thread, without requiring VMContinuationEntry::type_size() > 0.
865-
// Used on JDK 21-26 where type_size() == 0 but _cont_entry_offset is valid.
866-
bool hasActiveContinuation() {
867-
if (_cont_entry_offset < 0) return false;
868-
return SafeAccess::loadPtr((void**) at(_cont_entry_offset), nullptr) != nullptr;
869-
}
870-
871865
inline VMMethod* compiledMethod();
872866
private:
873867
static inline int nativeThreadId(JNIEnv* jni, jthread thread);

ddprof-lib/src/main/cpp/stackWalker.cpp

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,10 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
381381
uintptr_t entry_fp;
382382

383383
if (VMContinuationEntry::type_size() > 0) {
384-
// JDK 27+: full ContinuationEntry support via vmStructs.
384+
// ContinuationEntry is known via vmStructs (JDK 27+, and JDK 21-26
385+
// on distros that expose it). Walk the linked list of entries for
386+
// nested-continuation support and derive the enterSpecial frame FP
387+
// from the struct layout (entry + type_size).
385388
cont_entry = (cont_entry != nullptr) ? cont_entry->parent() : vm_thread->contEntry();
386389
if (cont_entry == nullptr) {
387390
Counters::increment(WALKVM_CONT_ENTRY_NULL);
@@ -390,12 +393,11 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
390393
}
391394
entry_fp = cont_entry->entryFP();
392395
} else {
393-
// JDK 21-26: ContinuationEntry type size absent from vmStructs.
394-
// Derive the enterSpecial frame FP directly from the current fp:
395-
// Path A (nm == enterSpecial): fp IS the enterSpecial frame
396-
// Path B (pc == cont_returnBarrier): the saved caller FP at *fp
397-
// is the enterSpecial frame (the VT's bottom frame's FP chain
398-
// leads directly to the enterSpecial frame on the carrier stack).
396+
// ContinuationEntry absent from vmStructs (musl/minimal JDK 21-26).
397+
// Derive the enterSpecial frame FP from the current fp:
398+
// Path A (pc == cont_entry_return_pc): fp IS the enterSpecial frame
399+
// Path B (pc == cont_returnBarrier): the saved caller FP at *fp
400+
// leads to the enterSpecial frame on the carrier stack.
399401
// Nested continuation tracking is unavailable without type_size().
400402
entry_fp = path_a ? fp : (uintptr_t)SafeAccess::load((void**)fp);
401403
}
@@ -453,35 +455,26 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
453455
}
454456
prev_native_pc = NULL; // we are in JVM code, no previous 'native' PC
455457

456-
// cont_returnBarrier is a JVM stub — CodeHeap::findNMethod() returns NULL for it,
457-
// so it must be handled before the nmethod dispatch below.
458+
// Both continuation boundary PCs are JVM stubs whose findNMethod()
459+
// returns NULL; detect them by exact-PC match before the nmethod
460+
// dispatch below.
461+
// Path B: bottom thawed frame returns to cont_returnBarrier when
462+
// frozen frames remain in the StackChunk (blocking VT).
463+
// Path A: bottom thawed frame returns to cont_entry_return_pc when
464+
// the continuation is fully thawed (CPU-bound VT).
458465
if (!CONT_UNWIND_DISABLED && VMStructs::isContReturnBarrier(pc)) {
459466
Counters::increment(WALKVM_CONT_BARRIER_HIT);
460467
if (walkThroughContinuation(false)) continue;
461468
break;
462469
}
470+
if (!CONT_UNWIND_DISABLED && VMStructs::isContEntryReturnPc(pc)) {
471+
Counters::increment(WALKVM_ENTER_SPECIAL_HIT);
472+
if (walkThroughContinuation(true)) continue;
473+
break;
474+
}
463475

464476
VMNMethod* nm = CodeHeap::findNMethod(pc);
465477
if (nm == NULL) {
466-
// On JDK 21-26, enterSpecial is a RuntimeBlob rather than an
467-
// nmethod, so findNMethod() returns NULL even though pc is in
468-
// the CodeHeap. When a continuation is active and the
469-
// cont_returnBarrier is known (JDK 21+), treat this as Path A:
470-
// the previous frame's fp already points to the enterSpecial
471-
// frame, so fp-based carrier-frame unwind is valid.
472-
if (VMContinuationEntry::type_size() == 0
473-
&& VMStructs::hasContReturnBarrier()
474-
&& vm_thread != NULL && vm_thread->hasActiveContinuation()) {
475-
if (!CONT_UNWIND_DISABLED) {
476-
Counters::increment(WALKVM_ENTER_SPECIAL_HIT);
477-
if (walkThroughContinuation(true)) continue;
478-
} else {
479-
// Continuation detected but unwind is disabled; emit
480-
// the synthetic boundary frame and stop cleanly.
481-
fillFrame(frames[depth++], BCI_NATIVE_FRAME, CONT_ROOT_FRAME);
482-
}
483-
break;
484-
}
485478
if (anchor == NULL) {
486479
// Add an error frame only if we cannot recover
487480
fillFrame(frames[depth++], BCI_ERROR, "unknown_nmethod");

ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/VirtualThreadWallClockTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.util.concurrent.CountDownLatch;
1717
import java.util.concurrent.locks.LockSupport;
1818

19+
import static org.junit.jupiter.api.Assertions.assertFalse;
1920
import static org.junit.jupiter.api.Assertions.assertTrue;
2021
import static org.junit.jupiter.api.Assumptions.assumeTrue;
2122

@@ -170,6 +171,7 @@ public void samplesCarrierFramesFromBlockingVT(@CStack String cstack) throws Exc
170171
LockSupport.unpark(vt);
171172
}
172173
vt.join(15_000);
174+
assertFalse(vt.isAlive(), "Virtual thread did not complete within timeout");
173175
stopProfiler();
174176

175177
verifyCStackSettings();

0 commit comments

Comments
 (0)