Skip to content

Commit 18a5706

Browse files
jbachorikclaude
andcommitted
Apply remote symbolication to VM/VMX stack walkers
VMX and VM stack walkers were bypassing remote symbolication by directly returning resolved symbol names. This caused frames to show as 'burn_cpu_recursive' instead of '<build-id>.<remote>(0x<offset>)' format. Extracted resolveNativeFrame() as shared function and added applyRemoteSymbolicationToVMFrames() to post-process VM walker output, converting resolved symbols back to RemoteFrameInfo structures when libraries have build-ids. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent de74290 commit 18a5706

2 files changed

Lines changed: 146 additions & 59 deletions

File tree

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

Lines changed: 136 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -323,76 +323,141 @@ int Profiler::getNativeTrace(void *ucontext, ASGCT_CallFrame *frames,
323323
return convertNativeTrace(native_frames, callchain, frames);
324324
}
325325

326-
int Profiler::convertNativeTrace(int native_frames, const void **callchain,
327-
ASGCT_CallFrame *frames) {
328-
int depth = 0;
329-
jmethodID prev_method = NULL;
326+
void Profiler::applyRemoteSymbolicationToVMFrames(ASGCT_CallFrame *frames, int num_frames) {
327+
for (int i = 0; i < num_frames; i++) {
328+
// Only process native frames (not Java frames or special frames)
329+
if (frames[i].bci != BCI_NATIVE_FRAME) {
330+
continue;
331+
}
330332

331-
for (int i = 0; i < native_frames; i++) {
332-
uintptr_t pc = (uintptr_t)callchain[i];
333+
// method_id contains the resolved symbol name (const char*)
334+
const char* symbol_name = (const char*)frames[i].method_id;
335+
if (symbol_name == nullptr) {
336+
continue;
337+
}
333338

334-
jmethodID current_method;
335-
int current_bci;
336-
337-
if (_remote_symbolication) {
338-
// Remote symbolication mode: store build-id and PC offset
339-
CodeCache* lib = _libs->findLibraryByAddress((void*)pc);
340-
TEST_LOG("Remote symbolication: pc=0x%lx, lib=%p, hasBuildId=%d",
341-
pc, lib, lib != nullptr ? lib->hasBuildId() : -1);
342-
if (lib != nullptr && lib->hasBuildId()) {
343-
TEST_LOG("Using remote symbolication for lib=%s, build-id=%s",
344-
lib->name(), lib->buildId());
345-
// Check if this is a marked C++ interpreter frame before using remote format
346-
const char *method_name = nullptr;
347-
lib->binarySearch(callchain[i], &method_name);
348-
if (method_name != nullptr && NativeFunc::isMarked(method_name)) {
349-
// This is C++ interpreter frame, this and later frames should be reported
350-
// as Java frames returned by AGCT. Terminate the scan here.
351-
return depth;
352-
}
339+
// Find the library containing this symbol to get the PC
340+
// We search all libraries for this symbol
341+
uintptr_t pc = 0;
342+
CodeCache* lib = nullptr;
343+
const CodeCacheArray& native_libs = _libs->native_libs();
344+
int lib_count = native_libs.count();
345+
for (int lib_idx = 0; lib_idx < lib_count; lib_idx++) {
346+
CodeCache* candidate_lib = native_libs[lib_idx];
347+
if (candidate_lib == nullptr) {
348+
continue;
349+
}
353350

354-
// Calculate PC offset within the library
355-
uintptr_t offset = pc - (uintptr_t)lib->imageBase();
351+
// Search for the symbol in this library
352+
const void* symbol_addr = candidate_lib->findSymbol(symbol_name);
353+
if (symbol_addr != nullptr) {
354+
lib = candidate_lib;
355+
pc = (uintptr_t)symbol_addr;
356+
TEST_LOG("Found symbol %s in lib %s at pc=0x%lx", symbol_name, lib->name(), pc);
357+
break;
358+
}
359+
}
356360

357-
// Allocate RemoteFrameInfo (TODO: optimize with LinearAllocator)
358-
RemoteFrameInfo* rfi = static_cast<RemoteFrameInfo*>(malloc(sizeof(RemoteFrameInfo)));
359-
if (rfi != nullptr) {
360-
rfi->build_id = lib->buildId();
361-
rfi->pc_offset = offset;
362-
rfi->lib_index = lib->libIndex();
361+
// If we found the PC and the library has a build-id, apply remote symbolication
362+
if (pc != 0 && lib != nullptr && lib->hasBuildId()) {
363+
TEST_LOG("Applying remote symbolication to VM frame: symbol=%s, lib=%s, build-id=%s",
364+
symbol_name, lib->name(), lib->buildId());
365+
366+
// Calculate PC offset within the library
367+
uintptr_t offset = pc - (uintptr_t)lib->imageBase();
368+
369+
// Allocate RemoteFrameInfo
370+
RemoteFrameInfo* rfi = static_cast<RemoteFrameInfo*>(malloc(sizeof(RemoteFrameInfo)));
371+
if (rfi != nullptr) {
372+
rfi->build_id = lib->buildId();
373+
rfi->pc_offset = offset;
374+
rfi->lib_index = lib->libIndex();
375+
376+
// Replace the frame with remote symbolication format
377+
frames[i].method_id = (jmethodID)rfi;
378+
frames[i].bci = BCI_NATIVE_FRAME_REMOTE;
379+
TEST_LOG("Converted VM frame to remote format: build-id=%s, offset=0x%lx", rfi->build_id, rfi->pc_offset);
380+
}
381+
}
382+
}
383+
}
363384

364-
current_method = (jmethodID)rfi;
365-
current_bci = BCI_NATIVE_FRAME_REMOTE;
366-
} else {
367-
// Fallback to resolved symbol if allocation failed
368-
// Need to resolve the symbol now since we didn't do it earlier
369-
const char *fallback_name = nullptr;
370-
lib->binarySearch(callchain[i], &fallback_name);
371-
current_method = (jmethodID)fallback_name;
372-
current_bci = BCI_NATIVE_FRAME;
373-
}
385+
Profiler::NativeFrameResolution Profiler::resolveNativeFrame(uintptr_t pc) {
386+
if (_remote_symbolication) {
387+
// Remote symbolication mode: store build-id and PC offset
388+
CodeCache* lib = _libs->findLibraryByAddress((void*)pc);
389+
TEST_LOG("Remote symbolication: pc=0x%lx, lib=%p, hasBuildId=%d",
390+
pc, lib, lib != nullptr ? lib->hasBuildId() : -1);
391+
if (lib != nullptr && lib->hasBuildId()) {
392+
TEST_LOG("Using remote symbolication for lib=%s, build-id=%s",
393+
lib->name(), lib->buildId());
394+
// Check if this is a marked C++ interpreter frame before using remote format
395+
const char *method_name = nullptr;
396+
lib->binarySearch((void*)pc, &method_name);
397+
if (method_name != nullptr && NativeFunc::isMarked(method_name)) {
398+
// This is C++ interpreter frame, this and later frames should be reported
399+
// as Java frames returned by AGCT. Terminate the scan here.
400+
return {nullptr, BCI_NATIVE_FRAME, true};
401+
}
402+
403+
// Calculate PC offset within the library
404+
uintptr_t offset = pc - (uintptr_t)lib->imageBase();
405+
406+
// Allocate RemoteFrameInfo (TODO: optimize with LinearAllocator)
407+
RemoteFrameInfo* rfi = static_cast<RemoteFrameInfo*>(malloc(sizeof(RemoteFrameInfo)));
408+
if (rfi != nullptr) {
409+
rfi->build_id = lib->buildId();
410+
rfi->pc_offset = offset;
411+
rfi->lib_index = lib->libIndex();
412+
413+
return {(jmethodID)rfi, BCI_NATIVE_FRAME_REMOTE, false};
374414
} else {
375-
// Library not found or no build-id, fallback to resolved symbol
376-
const char *method_name = findNativeMethod(callchain[i]);
377-
if (method_name != nullptr && NativeFunc::isMarked(method_name)) {
378-
// This is C++ interpreter frame, this and later frames should be reported
379-
// as Java frames returned by AGCT. Terminate the scan here.
380-
return depth;
381-
}
382-
current_method = (jmethodID)method_name;
383-
current_bci = BCI_NATIVE_FRAME;
415+
// Fallback to resolved symbol if allocation failed
416+
// Need to resolve the symbol now since we didn't do it earlier
417+
const char *fallback_name = nullptr;
418+
lib->binarySearch((void*)pc, &fallback_name);
419+
return {(jmethodID)fallback_name, BCI_NATIVE_FRAME, false};
384420
}
385421
} else {
386-
// Traditional mode: resolve and store symbol name
387-
const char *method_name = findNativeMethod(callchain[i]);
422+
// Library not found or no build-id, fallback to resolved symbol
423+
const char *method_name = findNativeMethod((void*)pc);
388424
if (method_name != nullptr && NativeFunc::isMarked(method_name)) {
389425
// This is C++ interpreter frame, this and later frames should be reported
390426
// as Java frames returned by AGCT. Terminate the scan here.
391-
return depth;
427+
return {nullptr, BCI_NATIVE_FRAME, true};
392428
}
393-
current_method = (jmethodID)method_name;
394-
current_bci = BCI_NATIVE_FRAME;
429+
return {(jmethodID)method_name, BCI_NATIVE_FRAME, false};
430+
}
431+
} else {
432+
// Traditional mode: resolve and store symbol name
433+
const char *method_name = findNativeMethod((void*)pc);
434+
if (method_name != nullptr && NativeFunc::isMarked(method_name)) {
435+
// This is C++ interpreter frame, this and later frames should be reported
436+
// as Java frames returned by AGCT. Terminate the scan here.
437+
return {nullptr, BCI_NATIVE_FRAME, true};
395438
}
439+
return {(jmethodID)method_name, BCI_NATIVE_FRAME, false};
440+
}
441+
}
442+
443+
int Profiler::convertNativeTrace(int native_frames, const void **callchain,
444+
ASGCT_CallFrame *frames) {
445+
int depth = 0;
446+
jmethodID prev_method = NULL;
447+
448+
for (int i = 0; i < native_frames; i++) {
449+
uintptr_t pc = (uintptr_t)callchain[i];
450+
451+
// Resolve native frame using shared logic
452+
NativeFrameResolution resolution = resolveNativeFrame(pc);
453+
454+
// Check if this is a marked frame (terminate scan)
455+
if (resolution.is_marked) {
456+
return depth;
457+
}
458+
459+
jmethodID current_method = resolution.method_id;
460+
int current_bci = resolution.bci;
396461

397462
// Skip duplicates in LBR stack
398463
if (current_method == prev_method && _cstack == CSTACK_LBR) {
@@ -770,10 +835,22 @@ void Profiler::recordSample(void *ucontext, u64 counter, int tid,
770835
if (num_frames < _max_stack_depth) {
771836
int max_remaining = _max_stack_depth - num_frames;
772837
if (_features.mixed) {
773-
num_frames += ddprof::StackWalker::walkVM(ucontext, frames + num_frames, max_remaining, _features, eventTypeFromBCI(event_type), &truncated);
838+
int vm_start = num_frames;
839+
int vm_frames = ddprof::StackWalker::walkVM(ucontext, frames + vm_start, max_remaining, _features, eventTypeFromBCI(event_type), &truncated);
840+
num_frames += vm_frames;
841+
// Apply remote symbolication to VM frames if enabled
842+
if (_remote_symbolication) {
843+
applyRemoteSymbolicationToVMFrames(frames + vm_start, vm_frames);
844+
}
774845
} else if (event_type == BCI_CPU || event_type == BCI_WALL) {
775846
if (_cstack >= CSTACK_VM) {
776-
num_frames += ddprof::StackWalker::walkVM(ucontext, frames + num_frames, max_remaining, _features, eventTypeFromBCI(event_type), &truncated);
847+
int vm_start = num_frames;
848+
int vm_frames = ddprof::StackWalker::walkVM(ucontext, frames + vm_start, max_remaining, _features, eventTypeFromBCI(event_type), &truncated);
849+
num_frames += vm_frames;
850+
// Apply remote symbolication to VM frames if enabled
851+
if (_remote_symbolication) {
852+
applyRemoteSymbolicationToVMFrames(frames + vm_start, vm_frames);
853+
}
777854
} else {
778855
// Async events
779856
AsyncSampleMutex mutex(ProfiledThread::currentSignalSafe());

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,16 @@ class alignas(alignof(SpinLock)) Profiler {
279279
Error dump(const char *path, const int length);
280280
void logStats();
281281
void switchThreadEvents(jvmtiEventMode mode);
282+
283+
// Result of resolving a native frame for symbolication
284+
struct NativeFrameResolution {
285+
jmethodID method_id; // RemoteFrameInfo* or const char* symbol name, or nullptr if marked
286+
int bci; // BCI_NATIVE_FRAME_REMOTE or BCI_NATIVE_FRAME
287+
bool is_marked; // true if this is a marked C++ interpreter frame (stop processing)
288+
};
289+
290+
NativeFrameResolution resolveNativeFrame(uintptr_t pc);
291+
void applyRemoteSymbolicationToVMFrames(ASGCT_CallFrame *frames, int num_frames);
282292
int convertNativeTrace(int native_frames, const void **callchain,
283293
ASGCT_CallFrame *frames);
284294
void recordSample(void *ucontext, u64 weight, int tid, jint event_type,

0 commit comments

Comments
 (0)