Skip to content

Commit 3869f88

Browse files
zhengyu123Copilotkaahos
authored
Use ThreadLocal to setup protection longjmp buffer and fix jmp_buf chaining (#615)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Paul Fournillon <112829622+kaahos@users.noreply.github.com>
1 parent 2731696 commit 3869f88

46 files changed

Lines changed: 1290 additions & 494 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99
#include "log.h"
1010
#include "os.h"
1111
#include "common.h"
12-
#include "thread.h"
1312
#include "vmEntry.h" // For BCI_ERROR constant
1413
#include "arch.h" // For LP64_ONLY macro and COMMA macro
1514
#include "guards.h" // For table swap critical sections
16-
#include "thread.h"
15+
#include "threadLocalData.h"
1716
#include <string.h>
1817
#include <atomic>
1918

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
#include "guards.h"
2020
#include "otel_context.h"
2121
#include "profiler.h"
22-
#include "thread.h"
22+
#include "threadLocalData.h"
2323
#include <cstring>
2424

2525
/**

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@
121121
/* Delegated stacks dropped at slot-lock. Rec-lock drops from all recording \
122122
* paths (delegated and direct) go into SAMPLES_DROPPED_REC_LOCK. */ \
123123
X(JVMTI_STACKS_DROPPED_LOCK, "jvmti_stacks_dropped_lock") \
124-
X(SAMPLES_DROPPED_REC_LOCK, "samples_dropped_rec_lock")
124+
X(SAMPLES_DROPPED_REC_LOCK, "samples_dropped_rec_lock") \
125+
X(SAMPLES_DROPPED_THREAD_LOCAL, "samples_dropped_thread_local")
125126
#define X_ENUM(a, b) a,
126127
typedef enum CounterId : int {
127128
DD_COUNTER_TABLE(X_ENUM) DD_NUM_COUNTERS

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ void CTimerJvmti::signalHandler(int signo, siginfo_t *siginfo, void *ucontext) {
227227
int tid = 0;
228228
ProfiledThread *current = ProfiledThread::currentSignalSafe();
229229
assert(current == nullptr || !current->isDeepCrashHandler());
230-
if (current != nullptr && JVMThread::isInitialized() && JVMThread::current() == nullptr
230+
if (current != nullptr && JVMThread::current() == nullptr
231231
&& current->inInitWindow()) {
232232
current->tickInitWindow();
233233
errno = saved_errno;
@@ -287,7 +287,7 @@ void CTimer::signalHandler(int signo, siginfo_t *siginfo, void *ucontext) {
287287
// thread_native_entry setting JVM TLS (PROF-13072): skip at most one signal
288288
// per thread. Pure native threads (where JVMThread::current() is always null)
289289
// are allowed through once the one-shot window expires.
290-
if (current != nullptr && JVMThread::isInitialized() && JVMThread::current() == nullptr
290+
if (current != nullptr && JVMThread::current() == nullptr
291291
&& current->inInitWindow()) {
292292
current->tickInitWindow();
293293
errno = saved_errno;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#include "guards.h"
1818
#include "common.h"
1919
#include "os.h"
20-
#include "thread.h"
20+
#include "threadLocalData.h"
2121

2222
// Signal-context tracking — backed by ProfiledThread::_signal_depth; see
2323
// the comment block in guards.h for the rationale (initial-exec TLS was

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

Lines changed: 45 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,21 @@
66

77
#include <climits>
88
#include <cstdlib>
9-
#include <setjmp.h>
109
#include "asyncSampleMutex.h"
10+
#include "frames.h"
11+
#include "guards.h"
1112
#include "hotspot/hotspotSupport.h"
1213
#include "hotspot/jitCodeCache.h"
1314
#include "hotspot/vmStructs.inline.h"
1415
#include "jvmSupport.inline.h"
15-
#include "guards.h"
16+
#include "jvmThread.h"
17+
#include "profiler.h"
1618
#include "stackWalker.inline.h"
17-
#include "frames.h"
19+
#include "threadLocal.h"
1820

1921
using StackWalkValidation::inDeadZone;
2022
using StackWalkValidation::aligned;
2123
using StackWalkValidation::MAX_FRAME_SIZE;
22-
using StackWalkValidation::sameStack;
2324

2425
// Initialize once, they survive on profiler restart
2526
static jobject JAVA_PLATFORM_CLASSLOADER = nullptr;
@@ -227,15 +228,46 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex
227228
__attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontext, ASGCT_CallFrame* frames, int max_depth,
228229
StackWalkFeatures features, EventType event_type,
229230
const void* pc, uintptr_t sp, uintptr_t fp, int lock_index, bool* truncated) {
231+
230232
// VMStructs is only available for hotspot JVM
231233
assert(VM::isHotspot());
234+
235+
ProfiledThread* prof_thread = ProfiledThread::currentSignalSafe();
236+
if (prof_thread == nullptr) {
237+
Counters::increment(SAMPLES_DROPPED_THREAD_LOCAL);
238+
return 0;
239+
}
240+
232241
HotspotStackFrame frame(ucontext);
233242
uintptr_t bottom = (uintptr_t)&frame + MAX_WALK_SIZE;
234243

235244
Profiler* profiler = Profiler::instance();
236245
int bcp_offset = InterpreterFrame::bcp_offset();
237246

247+
238248
jmp_buf crash_protection_ctx;
249+
// Chaining jmp_buf
250+
// A non-signal-based-sampler can be interrupted by signal based sampler,
251+
// then we end up with multiple HotspotSupport::walkVM() calls on stack,
252+
// each one sets up jmp_buf, they need to be chained to jump back to
253+
// correct location.
254+
jmp_buf* prev_jmp_buf = prof_thread->getJmpCtx();
255+
// Should be preserved across setjmp/longjmp
256+
volatile int depth = 0;
257+
int actual_max_depth = truncated ? max_depth + 1 : max_depth;
258+
259+
if (setjmp(crash_protection_ctx) != 0) {
260+
// checkFault() does a longjmp from inside segvHandler, bypassing
261+
// segvHandler's SignalHandlerScope destructor. Compensate.
262+
SIGNAL_HANDLER_UNWIND_AFTER_LONGJMP();
263+
prof_thread->setJmpCtx(prev_jmp_buf);
264+
if (depth < max_depth) {
265+
fillFrame(frames[depth++], BCI_ERROR, "break_not_walkable");
266+
}
267+
return depth;
268+
}
269+
270+
prof_thread->setJmpCtx(&crash_protection_ctx);
239271
VMThread* vm_thread = VMThread::current();
240272
if (vm_thread != NULL && !vm_thread->isThreadAccessible()) {
241273
Counters::increment(WALKVM_THREAD_INACCESSIBLE);
@@ -246,39 +278,16 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex
246278
} else {
247279
Counters::increment(WALKVM_VMTHREAD_OK);
248280
}
249-
void* saved_exception = vm_thread != NULL ? vm_thread->exception() : NULL;
250281

251-
// Should be preserved across setjmp/longjmp
252-
volatile int depth = 0;
253-
int actual_max_depth = truncated ? max_depth + 1 : max_depth;
254282
bool fp_chain_fallback = false;
255283
int fp_chain_depth = 0;
256284

257-
ProfiledThread* profiled_thread = ProfiledThread::currentSignalSafe();
258-
259285
VMJavaFrameAnchor* anchor = NULL;
260286
if (vm_thread != NULL) {
261287
anchor = vm_thread->anchor();
262288
if (anchor == NULL) {
263289
Counters::increment(WALKVM_ANCHOR_NULL);
264290
}
265-
vm_thread->exception() = &crash_protection_ctx;
266-
if (profiled_thread != nullptr) {
267-
profiled_thread->setCrashProtectionActive(true);
268-
}
269-
if (setjmp(crash_protection_ctx) != 0) {
270-
// checkFault() does a longjmp from inside segvHandler, bypassing
271-
// segvHandler's SignalHandlerScope destructor. Compensate.
272-
SIGNAL_HANDLER_UNWIND_AFTER_LONGJMP();
273-
if (profiled_thread != nullptr) {
274-
profiled_thread->setCrashProtectionActive(false);
275-
}
276-
vm_thread->exception() = saved_exception;
277-
if (depth < max_depth) {
278-
fillFrame(frames[depth++], BCI_ERROR, "break_not_walkable");
279-
}
280-
return depth;
281-
}
282291
}
283292

284293
const void* prev_native_pc = NULL;
@@ -616,7 +625,8 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex
616625
if (features.vtable_target && nm->isVTableStub() && depth == 0) {
617626
uintptr_t receiver = frame.jarg0();
618627
if (receiver != 0) {
619-
VMSymbol* symbol = VMKlass::fromOop(receiver)->name();
628+
VMKlass* klass = VMKlass::fromOop(receiver);
629+
VMSymbol* symbol = klass != nullptr ? klass->name() : nullptr;
620630
// Store the raw VMSymbol* in the frame's method_id
621631
// slot. BCI_VTABLE_RECEIVER (vmEntry.h) repurposes
622632
// method_id for this pointer — same precedent as
@@ -922,12 +932,7 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex
922932
}
923933

924934
done:
925-
if (profiled_thread != nullptr) {
926-
profiled_thread->setCrashProtectionActive(false);
927-
}
928-
if (vm_thread != NULL) {
929-
vm_thread->exception() = saved_exception;
930-
}
935+
prof_thread->setJmpCtx(prev_jmp_buf);
931936

932937
// Drop unknown leaf frame - it provides no useful information and breaks
933938
// aggregation by lumping unrelated samples under a single "unknown" entry
@@ -953,33 +958,20 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex
953958
}
954959

955960
void HotspotSupport::checkFault(ProfiledThread* thrd) {
956-
if (!JVMThread::isInitialized()) {
957-
// JVM has not been loaded or has not been initialized yet
958-
return;
959-
}
960-
961-
VMThread* vm_thread = VMThread::current();
962-
if (vm_thread == NULL || !vm_thread->isThreadAccessible()) {
961+
// Should not get to here (?)
962+
if (thrd == nullptr) {
963963
return;
964964
}
965965

966-
// Prefer the semantic crash protection flag (reliable regardless of stack frame sizes).
967-
// Fall back to sameStack heuristic when ProfiledThread TLS is unavailable (e.g. during
968-
// early init or in crash recovery tests). sameStack uses a fixed 8KB threshold which
969-
// can fail with ASAN-inflated frames, but the crashProtectionActive path handles that.
970-
bool protected_walk = (thrd != nullptr && thrd->isCrashProtectionActive())
971-
|| sameStack(vm_thread->exception(), &vm_thread);
972-
if (!protected_walk) {
966+
// Check if longjmp is setup for this thread
967+
if (!thrd->isProtected()) {
973968
return;
974969
}
975970

976-
if (thrd != nullptr) {
977-
thrd->resetCrashHandler();
978-
}
979-
longjmp(*(jmp_buf*)vm_thread->exception(), 1);
971+
thrd->resetCrashHandler();
972+
longjmp(*thrd->getJmpCtx(), 1);
980973
}
981974

982-
983975
int HotspotSupport::getJavaTraceAsync(void *ucontext, ASGCT_CallFrame *frames,
984976
int max_depth, StackContext *java_ctx,
985977
bool *truncated) {
@@ -1189,7 +1181,6 @@ int HotspotSupport::getJavaTraceAsync(void *ucontext, ASGCT_CallFrame *frames,
11891181
return trace.frames - frames + 1;
11901182
}
11911183

1192-
11931184
int HotspotSupport::walkJavaStack(StackWalkRequest& request) {
11941185
CStack cstack = Profiler::instance()->cstackMode();
11951186
StackWalkFeatures features = Profiler::instance()->stackWalkFeatures();

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
#include "hotspot/hotspotStackFrame.h"
1111
#include "hotspot/jitCodeCache.h"
12-
#include "profiler.h"
1312
#include "stackFrame.h"
1413
#include "stackWalker.h"
1514

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "jvmThread.h"
1616
#include "safeAccess.h"
1717
#include "spinLock.h"
18+
#include "threadLocalData.h"
1819
#include "threadState.h"
1920

2021
CodeCache* VMStructs::_libjvm = nullptr;

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

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
#include "counters.h"
1717
#include "jvmThread.h"
1818
#include "safeAccess.h"
19-
#include "thread.h"
2019
#include "threadState.h"
2120
#include "vmEntry.h"
2221

@@ -45,12 +44,8 @@ class VMNMethod;
4544
inline bool crashProtectionActive();
4645

4746
template <typename T>
48-
inline T* cast_to(const void* ptr) {
49-
assert(VM::isHotspot()); // This should only be used in HotSpot-specific code
50-
assert(T::type_size() > 0); // Ensure type size has been initialized
51-
assert(crashProtectionActive() || ptr == nullptr || SafeAccess::isReadableRange(ptr, T::type_size()));
52-
return reinterpret_cast<T*>(const_cast<void*>(ptr));
53-
}
47+
inline T* cast_to(const void* ptr);
48+
5449

5550
template <typename T>
5651
T* cast_or_null(const void* ptr) {
@@ -677,7 +672,24 @@ DECLARE(VMKlass)
677672
if (_compact_object_headers) {
678673
uintptr_t mark = *(uintptr_t*)oop;
679674
if (mark & MONITOR_BIT) {
680-
mark = *(uintptr_t*)(mark ^ MONITOR_BIT);
675+
// TOCTOU: MonitorDeflationThread may free the ObjectMonitor between
676+
// reading the mark word and dereferencing the monitor pointer. Use
677+
// safeFetch64 so a concurrent deflation/free does not crash here.
678+
// Two reads with different error values disambiguate a genuine fault
679+
// from a real header word that happens to equal one sentinel value
680+
// (mirrors SafeAccess::isReadable()'s double-read trick).
681+
int64_t* monitor_addr = (int64_t*)(mark ^ MONITOR_BIT);
682+
uintptr_t tmp = (uintptr_t)SafeAccess::safeFetch64(monitor_addr, 1);
683+
if (tmp != 1) {
684+
mark = tmp;
685+
} else {
686+
tmp = (uintptr_t)SafeAccess::safeFetch64(monitor_addr, 2);
687+
if (tmp != 2) {
688+
mark = tmp;
689+
} else {
690+
return nullptr;
691+
}
692+
}
681693
}
682694
narrow_klass = mark >> _markWord_klass_shift;
683695
} else {
@@ -843,17 +855,6 @@ DECLARE(VMThread)
843855
return *(void**) at(_thread_exception_offset);
844856
}
845857

846-
// Returns true if setjmp crash protection is currently active for this thread.
847-
// Reads the exception field via direct pointer arithmetic, deliberately bypassing
848-
// at() and its crashProtectionActive() assertion to avoid infinite recursion.
849-
// Safe because 'this' is the current live thread (we are in its signal handler).
850-
static bool isExceptionActive() {
851-
if (_thread_exception_offset < 0) return false;
852-
void* vt = JVMThread::current();
853-
if (vt == nullptr) return false;
854-
return *(const void* const*)((const char*)vt + _thread_exception_offset) != nullptr;
855-
}
856-
857858
NOADDRSANITIZE VMJavaFrameAnchor* anchor() {
858859
if (!isJavaThread(this)) return NULL;
859860
assert(_thread_anchor_offset >= 0);
@@ -1221,18 +1222,4 @@ class InterpreterFrame : VMStructs {
12211222
}
12221223
};
12231224

1224-
// Defined here (after VMThread) so the VMThread::isExceptionActive() fallback
1225-
// is accessible. The forward declaration at the top of this file allows cast_to()
1226-
// to reference it before VMThread is declared.
1227-
inline bool crashProtectionActive() {
1228-
ProfiledThread* pt = ProfiledThread::currentSignalSafe();
1229-
if (pt != nullptr && pt->isCrashProtectionActive()) return true;
1230-
// Fallback for threads without ProfiledThread TLS (e.g. JVM internal threads):
1231-
// if walkVM has set up setjmp protection via vm_thread->exception(), the assert
1232-
// is equally redundant — any bad read will be caught by the SIGSEGV handler.
1233-
// Uses VMThread::isExceptionActive() which reads the field directly without
1234-
// going through at() to avoid recursive assertion.
1235-
return JVMThread::key() != pthread_key_t(-1) && VMThread::isExceptionActive();
1236-
}
1237-
12381225
#endif // _HOTSPOT_VMSTRUCTS_H

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,26 @@
77
#ifndef _HOTSPOT_VMSTRUCTS_INLINE_H
88
#define _HOTSPOT_VMSTRUCTS_INLINE_H
99

10+
#include "hotspot/hotspotSupport.h"
1011
#include "hotspot/vmStructs.h"
1112
#include "jvmThread.h"
13+
#include "threadLocalData.h"
14+
15+
inline bool crashProtectionActive() {
16+
ProfiledThread* pt = ProfiledThread::currentSignalSafe();
17+
if (pt != nullptr) {
18+
return pt->isProtected();
19+
}
20+
return false;
21+
}
22+
23+
template <typename T>
24+
inline T* cast_to(const void* ptr) {
25+
assert(VM::isHotspot()); // This should only be used in HotSpot-specific code
26+
assert(T::type_size() > 0); // Ensure type size has been initialized
27+
assert(crashProtectionActive() || ptr == nullptr || SafeAccess::isReadableRange(ptr, T::type_size()));
28+
return reinterpret_cast<T*>(const_cast<void*>(ptr));
29+
}
1230

1331
VMThread* VMThread::current() {
1432
assert(VM::isHotspot());

0 commit comments

Comments
 (0)