Skip to content

Commit f2a3a22

Browse files
jbachorikclaude
andcommitted
Guard JavaThread-specific VMThread accessors for non-JavaThread safety
Resolve CSTACK_DEFAULT dynamically: CSTACK_VM on HotSpot+Linux, CSTACK_DWARF on non-HotSpot JVMs (J9, Zing). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 87063c3 commit f2a3a22

7 files changed

Lines changed: 62 additions & 6 deletions

File tree

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ int Profiler::getJavaTraceAsync(void *ucontext, ASGCT_CallFrame *frames,
477477
// handler since JDK 9, so we do it only for threads already registered in
478478
// ThreadLocalStorage
479479
VMThread *vm_thread = VMThread::current();
480-
if (vm_thread == NULL) {
480+
if (vm_thread == NULL || !vm_thread->isThreadAccessible()) {
481481
Counters::increment(AGCT_NOT_REGISTERED_IN_TLS);
482482
return 0;
483483
}
@@ -1385,6 +1385,13 @@ Error Profiler::start(Arguments &args, bool reset) {
13851385
_cpu_engine = selectCpuEngine(args);
13861386
_wall_engine = selectWallEngine(args);
13871387
_cstack = args._cstack;
1388+
if (_cstack == CSTACK_DEFAULT) {
1389+
if (VMStructs::hasStackStructs() && OS::isLinux()) {
1390+
_cstack = CSTACK_VM;
1391+
} else if (DWARF_SUPPORTED) {
1392+
_cstack = CSTACK_DWARF;
1393+
}
1394+
}
13881395
if (_cstack == CSTACK_DWARF && !DWARF_SUPPORTED) {
13891396
_cstack = CSTACK_NO;
13901397
Log::warn("DWARF unwinding is not supported on this platform. Defaulting "

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,9 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
276276

277277
jmp_buf crash_protection_ctx;
278278
VMThread* vm_thread = VMThread::current();
279+
if (vm_thread != NULL && !vm_thread->isThreadAccessible()) {
280+
vm_thread = NULL;
281+
}
279282
void* saved_exception = vm_thread != NULL ? vm_thread->exception() : NULL;
280283

281284
// Should be preserved across setjmp/longjmp
@@ -547,7 +550,6 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
547550
// already used the anchor; disable it
548551
anchor = NULL;
549552
if (sp < prev_sp || sp >= bottom || !aligned(sp)) {
550-
fillFrame(frames[depth++], BCI_ERROR, "break_no_anchor");
551553
break;
552554
}
553555
// we restored from Java frame; clean the prev_native_pc
@@ -572,7 +574,6 @@ __attribute__((no_sanitize("address"))) int StackWalker::walkVM(void* ucontext,
572574
}
573575
}
574576
}
575-
fillFrame(frames[depth++], BCI_ERROR, "break_no_anchor");
576577
break;
577578
}
578579
fillFrame(frames[depth++], frame_bci, (void*)method_name);
@@ -681,7 +682,7 @@ void StackWalker::checkFault(ProfiledThread* thrd) {
681682
}
682683

683684
VMThread* vm_thread = VMThread::current();
684-
if (vm_thread != NULL && sameStack(vm_thread->exception(), &vm_thread)) {
685+
if (vm_thread != NULL && vm_thread->isThreadAccessible() && sameStack(vm_thread->exception(), &vm_thread)) {
685686
if (thrd) {
686687
// going to longjmp out of the signal handler, reset the crash handler depth counter
687688
thrd->resetCrashHandler();

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class ProfiledThread : public ThreadLocalData {
3636

3737
// Misc flags
3838
static constexpr u32 FLAG_JAVA_THREAD = 0x01;
39+
static constexpr u32 FLAG_JAVA_THREAD_KNOWN = 0x02;
3940

4041
// Free slot recycling - lock-free stack of available buffer slots
4142
// Note: Using plain int with GCC atomic builtins instead of std::atomic
@@ -200,11 +201,19 @@ class ProfiledThread : public ThreadLocalData {
200201

201202
// Flags
202203
inline void setJavaThread() {
203-
_misc_flags |= FLAG_JAVA_THREAD;
204+
_misc_flags |= (FLAG_JAVA_THREAD | FLAG_JAVA_THREAD_KNOWN);
204205
}
205206

206207
inline bool isJavaThread() const {
207-
return (_misc_flags & FLAG_JAVA_THREAD) == FLAG_JAVA_THREAD;
208+
return (_misc_flags & FLAG_JAVA_THREAD) != 0;
209+
}
210+
211+
inline bool isJavaThreadKnown() const {
212+
return (_misc_flags & FLAG_JAVA_THREAD_KNOWN) != 0;
213+
}
214+
215+
inline void cacheJavaThread(bool isJava) {
216+
_misc_flags |= FLAG_JAVA_THREAD_KNOWN | (isJava ? FLAG_JAVA_THREAD : 0);
208217
}
209218

210219
private:

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,7 @@ OSThreadState VMThread::osThreadState() {
10821082
}
10831083

10841084
int VMThread::state() {
1085+
if (!cachedIsJavaThread()) return 0;
10851086
int offset = VMStructs::thread_state_offset();
10861087
if (offset >= 0) {
10871088
int* state = (int*)at(offset);

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <type_traits>
1414
#include "codeCache.h"
1515
#include "safeAccess.h"
16+
#include "thread.h"
1617
#include "threadState.h"
1718
#include "vmEntry.h"
1819

@@ -539,6 +540,21 @@ DECLARE(VMThread)
539540
(vtbl[5] == _java_thread_vtbl[5]) >= 2;
540541
}
541542

543+
// Cached version of isJavaThread(). On first call per thread, computes the
544+
// vtable check and caches the result in ProfiledThread for O(1) subsequent
545+
// lookups. This is needed because JVMTI ThreadStart only fires for application
546+
// threads, not for JVM-internal JavaThread subclasses (CompilerThread, etc.).
547+
bool cachedIsJavaThread() {
548+
ProfiledThread* pt = ProfiledThread::currentSignalSafe();
549+
if (pt != NULL) {
550+
if (!pt->isJavaThreadKnown()) {
551+
pt->cacheJavaThread(isJavaThread());
552+
}
553+
return pt->isJavaThread();
554+
}
555+
return isJavaThread();
556+
}
557+
542558
OSThreadState osThreadState();
543559

544560
int state();
@@ -548,16 +564,34 @@ DECLARE(VMThread)
548564
}
549565

550566
bool inDeopt() {
567+
if (!cachedIsJavaThread()) return false;
551568
assert(_thread_vframe_offset >= 0);
552569
return SafeAccess::loadPtr((void**) at(_thread_vframe_offset), nullptr) != NULL;
553570
}
554571

572+
// Check if the thread object memory is readable up to the largest used
573+
// offset. On some JVMs (e.g. GraalVM 25 aarch64), a wall-clock signal
574+
// can hit a thread whose memory is only partially mapped — the vtable
575+
// at offset 0 may be readable while fields deeper in the object are not.
576+
// On non-HotSpot JVMs (J9, Zing) offsets stay at -1; skip the check.
577+
bool isThreadAccessible() {
578+
int max_offset = -1;
579+
if (_thread_exception_offset > max_offset) max_offset = _thread_exception_offset;
580+
if (_thread_state_offset > max_offset) max_offset = _thread_state_offset;
581+
if (_thread_osthread_offset > max_offset) max_offset = _thread_osthread_offset;
582+
if (_thread_anchor_offset > max_offset) max_offset = _thread_anchor_offset;
583+
if (_thread_vframe_offset > max_offset) max_offset = _thread_vframe_offset;
584+
if (max_offset < 0) return true;
585+
return SafeAccess::isReadableRange(this, max_offset + sizeof(void*));
586+
}
587+
555588
void*& exception() {
556589
assert(_thread_exception_offset >= 0);
557590
return *(void**) at(_thread_exception_offset);
558591
}
559592

560593
VMJavaFrameAnchor* anchor() {
594+
if (!cachedIsJavaThread()) return NULL;
561595
assert(_thread_anchor_offset >= 0);
562596
return VMJavaFrameAnchor::cast(at(_thread_anchor_offset));
563597
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ VMNMethod* VMMethod::code() {
1313
}
1414

1515
VMMethod* VMThread::compiledMethod() {
16+
if (!cachedIsJavaThread()) return NULL;
1617
assert(_comp_method_offset >= 0);
1718
assert(_comp_env_offset >= 0);
1819
assert(_comp_task_offset >= 0);

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ void WallClockASGCT::signalHandler(int signo, siginfo_t *siginfo, void *ucontext
8080

8181
ExecutionEvent event;
8282
VMThread *vm_thread = VMThread::current();
83+
if (vm_thread != NULL && !vm_thread->isThreadAccessible()) {
84+
vm_thread = NULL;
85+
}
8386
int raw_thread_state = vm_thread ? vm_thread->state() : 0;
8487
bool is_java_thread = raw_thread_state >= 4 && raw_thread_state < 12;
8588
bool is_initialized = is_java_thread;

0 commit comments

Comments
 (0)