Skip to content

Commit 52107ce

Browse files
jbachorikclaude
andcommitted
feat(walkvm): wextend=vt_carrier opt-in for carrier frame walking
- Add wextend argument (string-parsed, comma-separated tokens) - vt_carrier token enables walking through VT continuation boundaries to carrier thread frames; default stops at synthetic "JVM Continuation" root frame (no truncation, no carrier noise) - Add carrier_frames bit to StackWalkFeatures (replaces _padding bit) - Use FRAME_PC_SLOT for architecture-portable carrier frame extraction - Split VMContinuationEntry into DECLARE_V21_TYPES_DO to prevent assert(_VMContinuationEntry_size > 0) on JDK <21 debug builds - Expand DECLARE_V21_TYPES_DO at all four declaration/init/read/verify sites in vmStructs.h and vmStructs.cpp - Enable wextend=vt_carrier in VirtualThreadWallClockTest Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4d21fbf commit 52107ce

6 files changed

Lines changed: 44 additions & 18 deletions

File tree

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,19 @@ Error Arguments::parse(const char *args) {
300300
}
301301
}
302302

303+
CASE("wextend")
304+
if (value != NULL) {
305+
const char* p = value;
306+
while (*p != '\0') {
307+
const char* comma = strchr(p, ',');
308+
size_t len = comma != NULL ? (size_t)(comma - p) : strlen(p);
309+
if (len == 10 && strncmp(p, "vt_carrier", 10) == 0) {
310+
_features.carrier_frames = 1;
311+
}
312+
p += len + (comma != NULL ? 1 : 0);
313+
}
314+
}
315+
303316
CASE("attributes")
304317
if (value != NULL) {
305318
std::string input(value);

ddprof-lib/src/main/cpp/arguments.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ struct StackWalkFeatures {
122122
unsigned short vtable_target : 1; // show receiver classes of vtable/itable stubs
123123
unsigned short comp_task : 1; // display current compilation task for JIT threads
124124
unsigned short pc_addr : 1; // record exact PC address for each sample
125-
unsigned short _padding : 3; // pad structure to 16 bits
125+
unsigned short carrier_frames: 1; // walk through VT continuation boundary to carrier frames (wextend=vt_carrier)
126+
unsigned short _padding : 2; // pad structure to 16 bits
126127
};
127128

128129
struct Multiplier {

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

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -350,40 +350,43 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
350350
anchor = vm_thread->anchor();
351351
}
352352

353-
// Advances through a continuation boundary to the carrier frame.
354-
// Walks cont_entry->parent() on repeated calls to handle nested continuations
355-
// (_parent is maintained by the JVM for its own bookkeeping; not triggered by
356-
// standard single-level VTs today, but required once any runtime layers
357-
// continuations on top of VTs). Returns true to continue the walk, false to break.
358-
// Synthetic root frame inserted when a continuation boundary is detected but
359-
// carrier frames cannot be captured (transient JVM state or invalid pointers).
360-
// Using BCI_NATIVE_FRAME rather than BCI_ERROR avoids marking the sample as
361-
// truncated — the virtual thread's own frames are complete; carrier internals
362-
// (ForkJoinWorkerThread pool mechanics) are not profiling-relevant anyway.
363353
static const char* CONT_ROOT_FRAME = "JVM Continuation";
364354

355+
// Advances through a continuation boundary to the carrier frame.
356+
// Without wextend=vt_carrier (default): always stops with a "JVM Continuation"
357+
// synthetic root frame — VT frames are complete, carrier internals are noise.
358+
// With wextend=carrier: attempts to walk through; failures emit BCI_ERROR
359+
// so the sample is truthfully marked truncated.
360+
// Walks cont_entry->parent() on repeated calls to handle nested continuations
361+
// (_parent not triggered by standard single-level VTs today, but required
362+
// once any runtime layers continuations on top of VTs).
363+
// Returns true to continue the walk, false to break.
365364
auto walkThroughContinuation = [&]() -> bool {
365+
if (!features.carrier_frames) {
366+
fillFrame(frames[depth++], BCI_NATIVE_FRAME, CONT_ROOT_FRAME);
367+
return false;
368+
}
366369
cont_entry = (cont_entry != nullptr) ? cont_entry->parent() : vm_thread->contEntry();
367370
if (cont_entry == nullptr) {
368371
Counters::increment(WALKVM_CONT_ENTRY_NULL);
369-
fillFrame(frames[depth++], BCI_NATIVE_FRAME, CONT_ROOT_FRAME);
372+
fillFrame(frames[depth++], BCI_ERROR, "break_cont_entry_null");
370373
return false;
371374
}
372375
uintptr_t entry_fp = cont_entry->entryFP();
373376
if (!StackWalkValidation::isValidFP(entry_fp)) {
374-
fillFrame(frames[depth++], BCI_NATIVE_FRAME, CONT_ROOT_FRAME);
377+
fillFrame(frames[depth++], BCI_ERROR, "break_cont_entry_fp");
375378
return false;
376379
}
377380
// entry_fp has been range-checked by isValidFP above; any remaining
378381
// SIGSEGV from a stale/concurrently-freed pointer is caught by the
379382
// setjmp crash protection in walkVM (checkFault -> longjmp).
380383
uintptr_t carrier_fp = *(uintptr_t*)entry_fp;
381-
const void* carrier_pc = ((const void**)entry_fp)[1];
382-
uintptr_t carrier_sp = entry_fp + 2 * sizeof(void*);
384+
const void* carrier_pc = ((const void**)entry_fp)[FRAME_PC_SLOT];
385+
uintptr_t carrier_sp = entry_fp + (FRAME_PC_SLOT + 1) * sizeof(void*);
383386
if (!StackWalkValidation::isValidFP(carrier_fp) ||
384387
StackWalkValidation::inDeadZone(carrier_pc) ||
385388
!StackWalkValidation::isValidSP(carrier_sp, sp, bottom)) {
386-
fillFrame(frames[depth++], BCI_NATIVE_FRAME, CONT_ROOT_FRAME);
389+
fillFrame(frames[depth++], BCI_ERROR, "break_cont_carrier_sp");
387390
return false;
388391
}
389392
sp = carrier_sp;

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const void* VMStructs::_interpreted_frame_valid_end = nullptr;
4646
// Initialize type size to 0
4747
#define INIT_TYPE_SIZE(name, names) uint64_t VMStructs::TYPE_SIZE_NAME(name) = 0;
4848
DECLARE_TYPES_DO(INIT_TYPE_SIZE)
49+
DECLARE_V21_TYPES_DO(INIT_TYPE_SIZE)
4950
#undef INIT_TYPE_SIZE
5051

5152
#define offset_value -1
@@ -211,6 +212,7 @@ void VMStructs::init_type_sizes() {
211212
}
212213

213214
DECLARE_TYPES_DO(READ_TYPE_SIZE)
215+
DECLARE_V21_TYPES_DO(READ_TYPE_SIZE)
214216

215217
#undef READ_TYPE_SIZE
216218

@@ -279,6 +281,9 @@ void VMStructs::verify_offsets() {
279281
// Verify type sizes
280282
#define VERIFY_TYPE_SIZE(name, names) assert(TYPE_SIZE_NAME(name) > 0);
281283
DECLARE_TYPES_DO(VERIFY_TYPE_SIZE);
284+
if (hotspot_version >= 21) {
285+
DECLARE_V21_TYPES_DO(VERIFY_TYPE_SIZE);
286+
}
282287
#undef VERIFY_TYPE_SIZE
283288

284289

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ inline T* cast_to(const void* ptr) {
117117
f(VMClassLoaderData, MATCH_SYMBOLS("ClassLoaderData")) \
118118
f(VMConstantPool, MATCH_SYMBOLS("ConstantPool")) \
119119
f(VMConstMethod, MATCH_SYMBOLS("ConstMethod")) \
120-
f(VMContinuationEntry, MATCH_SYMBOLS("ContinuationEntry")) \
121120
f(VMFlag, MATCH_SYMBOLS("JVMFlag", "Flag")) \
122121
f(VMJavaFrameAnchor, MATCH_SYMBOLS("JavaFrameAnchor")) \
123122
f(VMKlass, MATCH_SYMBOLS("Klass")) \
@@ -126,6 +125,10 @@ inline T* cast_to(const void* ptr) {
126125
f(VMSymbol, MATCH_SYMBOLS("Symbol")) \
127126
f(VMThread, MATCH_SYMBOLS("Thread"))
128127

128+
// Types only present in JDK 21+ (Project Loom); size is 0 on older JDKs
129+
#define DECLARE_V21_TYPES_DO(f) \
130+
f(VMContinuationEntry, MATCH_SYMBOLS("ContinuationEntry"))
131+
129132
/**
130133
* Following macros define field offsets, addresses or values of JVM classes that are exported by
131134
* vmStructs.
@@ -332,6 +335,7 @@ class VMStructs {
332335
static uint64_t TYPE_SIZE_NAME(name);
333336

334337
DECLARE_TYPES_DO(DECLARE_TYPE_SIZE_VAR)
338+
DECLARE_V21_TYPES_DO(DECLARE_TYPE_SIZE_VAR)
335339
#undef DECLARE_TYPE_SIZE_VAR
336340

337341
// Declare vmStructs' field offsets and addresses

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ protected boolean isPlatformSupported() {
5050

5151
@Override
5252
protected String getProfilerCommand() {
53-
return "wall=1ms,filter=";
53+
return "wall=1ms,filter=,wextend=vt_carrier";
5454
}
5555

5656
/**

0 commit comments

Comments
 (0)