From 6c453ea79174cecb795de4eefd1d575a7dd659a2 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Fri, 24 Jul 2026 20:07:21 +0000 Subject: [PATCH 1/8] Use sigsetjmp/siglongjmp instead of setjmp/longjmp --- ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp | 6 +++--- ddprof-lib/src/main/cpp/threadLocalData.h | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 9e4ad7283..387b4f015 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -248,13 +248,13 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex int bcp_offset = InterpreterFrame::bcp_offset(); - jmp_buf crash_protection_ctx; + sigjmp_buf crash_protection_ctx; // Chaining jmp_buf // A non-signal-based-sampler can be interrupted by signal based sampler, // then we end up with multiple HotspotSupport::walkVM() calls on stack, // each one sets up jmp_buf, they need to be chained to jump back to // correct location. - jmp_buf* prev_jmp_buf = prof_thread->getJmpCtx(); + sigjmp_buf* prev_jmp_buf = prof_thread->getJmpCtx(); // Should be preserved across setjmp/longjmp volatile int depth = 0; int actual_max_depth = truncated ? max_depth + 1 : max_depth; @@ -991,7 +991,7 @@ void HotspotSupport::checkFault(ProfiledThread* thrd) { thrd->resetCrashHandler(); Counters::increment(WALKVM_LONGJMP_RECOVERED); - longjmp(*thrd->getJmpCtx(), 1); + siglongjmp(*thrd->getJmpCtx(), 1); } int HotspotSupport::getJavaTraceAsync(void *ucontext, ASGCT_CallFrame *frames, diff --git a/ddprof-lib/src/main/cpp/threadLocalData.h b/ddprof-lib/src/main/cpp/threadLocalData.h index 3f143efa6..4a8da4a50 100644 --- a/ddprof-lib/src/main/cpp/threadLocalData.h +++ b/ddprof-lib/src/main/cpp/threadLocalData.h @@ -63,7 +63,7 @@ class ProfiledThread : public ThreadLocalData { // SEGV-handler context on the same thread; atomic makes the publish/observe // ordering explicit instead of relying on plain load/store, matching how // _crash_depth is hardened below. - std::atomic _jmp_buf; + std::atomic _jmp_buf; u64 _pc; u64 _sp; @@ -238,11 +238,11 @@ class ProfiledThread : public ThreadLocalData { return __atomic_load_n(&_crash_depth, __ATOMIC_RELAXED) > CRASH_HANDLER_NESTING_LIMIT; } - inline void setJmpCtx(jmp_buf* buf) { + inline void setJmpCtx(sigjmp_buf* buf) { _jmp_buf = buf; } - inline jmp_buf* getJmpCtx() const { + inline sigjmp_buf* getJmpCtx() const { return _jmp_buf; } From 7b54779fa85392954a36f50374a100d9258032ce Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Fri, 24 Jul 2026 21:56:17 +0000 Subject: [PATCH 2/8] Fix --- ddprof-lib/src/main/cpp/counters.h | 6 +++--- ddprof-lib/src/main/cpp/faultInjection.cpp | 2 +- ddprof-lib/src/main/cpp/faultInjection.h | 2 +- ddprof-lib/src/main/cpp/guards.h | 8 ++++---- .../src/main/cpp/hotspot/hotspotSupport.cpp | 18 +++++++++--------- ddprof-lib/src/main/cpp/hotspot/vmStructs.h | 4 ++-- ddprof-lib/src/main/cpp/profiler.cpp | 14 +++++++------- ddprof-lib/src/main/cpp/safeAccess.h | 4 ++-- ddprof-lib/src/main/cpp/threadLocalData.h | 2 +- ddprof-lib/src/test/cpp/faultInjection_ut.cpp | 4 ++-- .../test/cpp/hotspot_crash_protection_ut.cpp | 12 ++++++------ 11 files changed, 38 insertions(+), 38 deletions(-) diff --git a/ddprof-lib/src/main/cpp/counters.h b/ddprof-lib/src/main/cpp/counters.h index 623e2c95a..8817af541 100644 --- a/ddprof-lib/src/main/cpp/counters.h +++ b/ddprof-lib/src/main/cpp/counters.h @@ -149,10 +149,10 @@ #endif // Fault-injection + debug only: faults injected while the current thread was -// NOT inside a walkVM longjmp-protected region. Such a site relies solely on +// NOT inside a walkVM siglongjmp-protected region. Such a site relies solely on // safefetch (or would genuinely crash if the poisoned pointer is raw-dereferenced // outside any recovery), so a non-zero value flags injection sites that are not -// covered by longjmp protection. Compiled in only when both __FAULT_INJECTION__ +// covered by siglongjmp protection. Compiled in only when both __FAULT_INJECTION__ // and DEBUG are defined. #if defined(__FAULT_INJECTION__) && defined(DEBUG) #define DD_COUNTER_TABLE_FI_DEBUG(X) \ @@ -162,7 +162,7 @@ #endif // Debug-only counters: SafeAccess reads/copies issued while the thread is -// already inside a walkVM longjmp-protected region (redundant safefetch +// already inside a walkVM siglongjmp-protected region (redundant safefetch // overhead). Not compiled into release builds at all, so they occupy no enum // slot and add no storage there. #ifdef DEBUG diff --git a/ddprof-lib/src/main/cpp/faultInjection.cpp b/ddprof-lib/src/main/cpp/faultInjection.cpp index c42298ec2..d61be77b6 100644 --- a/ddprof-lib/src/main/cpp/faultInjection.cpp +++ b/ddprof-lib/src/main/cpp/faultInjection.cpp @@ -89,7 +89,7 @@ bool shouldFire(u64 threshold, const char* fn) { // place that counts an actually-injected fault. Counters::increment(FAULTS_INJECTED); #ifdef DEBUG - // Flag injections fired at a site with no walkVM longjmp protection active: + // Flag injections fired at a site with no walkVM siglongjmp protection active: // recovery there depends entirely on safefetch, and a raw deref would crash. ProfiledThread* t = ProfiledThread::current(); // never allocates if (t == nullptr || !t->isProtected()) { diff --git a/ddprof-lib/src/main/cpp/faultInjection.h b/ddprof-lib/src/main/cpp/faultInjection.h index f3e5ad1b6..e19f4bb1e 100644 --- a/ddprof-lib/src/main/cpp/faultInjection.h +++ b/ddprof-lib/src/main/cpp/faultInjection.h @@ -20,7 +20,7 @@ // site (VMStructs::at, walkVM, walkFP, walkDwarf). When __FAULT_INJECTION__ is // defined, each wrapped expression, with the tier's probability, is replaced by // a deliberately bad address (so the load faults and the profiler's recovery -// path — SafeAccess safefetch or walkVM's setjmp/longjmp — is exercised) or a +// path — SafeAccess safefetch or walkVM's sigsetjmp/siglongjmp — is exercised) or a // random int/long value. When the flag is NOT defined, every macro is a strict // identity: it expands to exactly the parenthesized original expression, with // unchanged type and value category and zero runtime cost. diff --git a/ddprof-lib/src/main/cpp/guards.h b/ddprof-lib/src/main/cpp/guards.h index 4addd8d39..18bc4fbed 100644 --- a/ddprof-lib/src/main/cpp/guards.h +++ b/ddprof-lib/src/main/cpp/guards.h @@ -90,14 +90,14 @@ class SignalHandlerScope { #define SIGNAL_HANDLER_GUARD() SignalHandlerScope _signal_handler_scope // Manually release the most recent SIGNAL_HANDLER_GUARD() before chaining to -// another handler that may longjmp through us (e.g. J9's SIGSEGV null-pointer +// another handler that may siglongjmp through us (e.g. J9's SIGSEGV null-pointer // check handler). After release(), depth has already been decremented; the // destructor becomes a no-op. #define SIGNAL_HANDLER_GUARD_RELEASE() _signal_handler_scope.release() -// Compensate for a longjmp that bypassed a SignalHandlerScope's destructor. -// Call at the setjmp landing point AFTER a known longjmp originated from -// within a signal handler frame (e.g. HotSpot's checkFault → longjmp recovery +// Compensate for a siglongjmp that bypassed a SignalHandlerScope's destructor. +// Call at the sigsetjmp landing point AFTER a known siglongjmp originated from +// within a signal handler frame (e.g. HotSpot's checkFault → siglongjmp recovery // in walkVM). void signalHandlerUnwindAfterLongjmp(); #define SIGNAL_HANDLER_UNWIND_AFTER_LONGJMP() signalHandlerUnwindAfterLongjmp() diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 387b4f015..3cfe00223 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -249,18 +249,18 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex sigjmp_buf crash_protection_ctx; - // Chaining jmp_buf + // Chaining sigjmp_buf // A non-signal-based-sampler can be interrupted by signal based sampler, // then we end up with multiple HotspotSupport::walkVM() calls on stack, - // each one sets up jmp_buf, they need to be chained to jump back to + // each one sets up sigjmp_buf, they need to be chained to jump back to // correct location. sigjmp_buf* prev_jmp_buf = prof_thread->getJmpCtx(); - // Should be preserved across setjmp/longjmp + // Should be preserved across sigsetjmp/siglongjmp volatile int depth = 0; int actual_max_depth = truncated ? max_depth + 1 : max_depth; - if (setjmp(crash_protection_ctx) != 0) { - // checkFault() does a longjmp from inside segvHandler, bypassing + if (sigsetjmp(crash_protection_ctx, 1) != 0) { + // checkFault() does a siglongjmp from inside segvHandler, bypassing // segvHandler's SignalHandlerScope destructor. Compensate. SIGNAL_HANDLER_UNWIND_AFTER_LONGJMP(); prof_thread->setJmpCtx(prev_jmp_buf); @@ -378,7 +378,7 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex } // entry_fp has been range-checked by isValidFP above; any remaining // SIGSEGV from a stale/concurrently-freed pointer is caught by the - // setjmp crash protection in walkVM (checkFault -> longjmp). + // sigsetjmp crash protection in walkVM (checkFault -> siglongjmp). uintptr_t* carrier_fp_addr = (uintptr_t*)INJECT_FAULT_ADDRESS_UNLIKELY(entry_fp); uintptr_t carrier_fp = *carrier_fp_addr; const void* carrier_pc = ((const void**)carrier_fp_addr)[FRAME_PC_SLOT]; @@ -414,8 +414,8 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex // while PC is still in JVM stubs (JavaCalls, method entry/exit), we see CodeHeap // code without VMThread context. // - // Without vm_thread, crash protection via setjmp/longjmp cannot work - // (checkFault() needs vm_thread->exception() to longjmp). Any memory dereference in interpreter + // Without vm_thread, crash protection via sigsetjmp/siglongjmp cannot work + // (checkFault() needs vm_thread->exception() to siglongjmp). Any memory dereference in interpreter // frame handling or NMethod validation would crash the process with unrecoverable SEGV. // // The missing VMThread is a timing issue during thread lifecycle. @@ -984,7 +984,7 @@ void HotspotSupport::checkFault(ProfiledThread* thrd) { return; } - // Check if longjmp is setup for this thread + // Check if siglongjmp is setup for this thread if (!thrd->isProtected()) { return; } diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h index 139d430a3..e09d035bb 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h @@ -33,11 +33,11 @@ class VMNMethod; // During stack walking in the profiler's signal handler, GC or class unloading // on another thread can free VMNMethod/VMMethod memory concurrently, making // pointers stale between the readability check and the actual dereference. -// In release builds the setjmp/longjmp crash protection in walkVM catches the +// In release builds the sigsetjmp/siglongjmp crash protection in walkVM catches the // resulting SIGSEGV. In debug builds the assert(isReadable) fires first, // sending SIGABRT which is uncatchable by crash protection. // When crash protection is active the assert is redundant — any bad read will -// be caught by the SIGSEGV handler and recovered via longjmp — so we skip it. +// be caught by the SIGSEGV handler and recovered via siglongjmp — so we skip it. // // Defined at the bottom of this file after VMThread is declared so that the // VMThread fallback path (isExceptionActive) is accessible without forward- diff --git a/ddprof-lib/src/main/cpp/profiler.cpp b/ddprof-lib/src/main/cpp/profiler.cpp index 51769089a..7c631b002 100644 --- a/ddprof-lib/src/main/cpp/profiler.cpp +++ b/ddprof-lib/src/main/cpp/profiler.cpp @@ -927,15 +927,15 @@ void Profiler::disableEngines() { void Profiler::segvHandler(int signo, siginfo_t *siginfo, void *ucontext) { // J9 installs a SIGSEGV handler that uses siglongjmp() to recover from // null-pointer-check faults during normal Java execution. When we chain to - // it, that longjmp unwinds past our stack frame and skips the RAII + // it, that siglongjmp unwinds past our stack frame and skips the RAII // destructor, permanently leaking depth on the thread. Release the guard // before chaining so depth is correct whether the chained handler returns - // or longjmps. + // or siglongjmps. // // Sanitizer-coverage note: this also means depth == 0 inside the chained // handler, so DEBUG_ASSERT_NOT_IN_SIGNAL() will NOT fire for AS-unsafe // code reachable from a chained handler that returns normally. This is - // the lesser of two evils — leaking depth on longjmp would silently + // the lesser of two evils — leaking depth on siglongjmp would silently // break the production deferred-refresh gate, while the sanitizer gap // is bounded to third-party signal handler code we don't own. SIGNAL_HANDLER_GUARD(); @@ -943,7 +943,7 @@ void Profiler::segvHandler(int signo, siginfo_t *siginfo, void *ucontext) { return; // Handled — destructor decrements depth } SIGNAL_HANDLER_GUARD_RELEASE(); - // Not handled, chain to next handler (may longjmp; never return through us) + // Not handled, chain to next handler (may siglongjmp; never return through us) SigAction chain = OS::getSegvChainTarget(); if (chain != nullptr) { chain(signo, siginfo, ucontext); @@ -954,7 +954,7 @@ void Profiler::segvHandler(int signo, siginfo_t *siginfo, void *ucontext) { void Profiler::busHandler(int signo, siginfo_t *siginfo, void *ucontext) { // See segvHandler: release before chaining in case the chained handler - // longjmps through us. + // siglongjmps through us. SIGNAL_HANDLER_GUARD(); if (crashHandlerInternal(signo, siginfo, ucontext)) { return; // Handled — destructor decrements depth @@ -982,7 +982,7 @@ int Profiler::crashHandlerInternal(int signo, siginfo_t *siginfo, void *ucontext // Reentrancy protection: use TLS-based tracking if available. // If TLS is not available, the thread is not protected by - // longjmp, so bail out. + // siglongjmp, so bail out. bool have_tls_protection = false; if (thrd != nullptr) { if (!thrd->enterCrashHandler()) { @@ -1015,7 +1015,7 @@ int Profiler::crashHandlerInternal(int signo, siginfo_t *siginfo, void *ucontext // the following checks require vmstructs and therefore HotSpot // HotspotSupport::checkFault has its own check if we're in a protected stack walk. - // If the fault is from our protected walk, it will longjmp and never return. + // If the fault is from our protected walk, it will siglongjmp and never return. // If it returns, the fault wasn't from our code. HotspotSupport::checkFault(thrd); diff --git a/ddprof-lib/src/main/cpp/safeAccess.h b/ddprof-lib/src/main/cpp/safeAccess.h index 7d78f2cc6..564743b15 100644 --- a/ddprof-lib/src/main/cpp/safeAccess.h +++ b/ddprof-lib/src/main/cpp/safeAccess.h @@ -119,9 +119,9 @@ class SafeAccess { #ifdef DEBUG private: // Debug diagnostic: bump a counter when a SafeAccess read/copy is issued while - // the current thread is already inside a walkVM longjmp-protected region, where + // the current thread is already inside a walkVM siglongjmp-protected region, where // the safefetch/safecopy overhead is redundant (a fault there is caught by the - // longjmp anyway). Defined out-of-line in safeAccess.cpp so this widely-included + // siglongjmp anyway). Defined out-of-line in safeAccess.cpp so this widely-included // header need not pull in threadLocalData.h / counters.h. isCopy selects the // SAFECOPY_WHILE_PROTECTED vs SAFEFETCH_WHILE_PROTECTED counter. static void countIfLongjmpProtected(bool isCopy); diff --git a/ddprof-lib/src/main/cpp/threadLocalData.h b/ddprof-lib/src/main/cpp/threadLocalData.h index 4a8da4a50..de070521d 100644 --- a/ddprof-lib/src/main/cpp/threadLocalData.h +++ b/ddprof-lib/src/main/cpp/threadLocalData.h @@ -58,7 +58,7 @@ class ProfiledThread : public ThreadLocalData { static void freeValue(void* value); static ThreadLocal _current_thread; - // longjmp buffer. Used by hotspot only at this moment. + // siglongjmp buffer. Used by hotspot only at this moment. // Published in walkVM() and consumed in checkFault() from an asynchronous // SEGV-handler context on the same thread; atomic makes the publish/observe // ordering explicit instead of relying on plain load/store, matching how diff --git a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp index abb81b8ed..f45745f92 100644 --- a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp +++ b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp @@ -60,7 +60,7 @@ static void fi_signal_wrapper(int signo, siginfo_t* siginfo, void* context) { if (SafeAccess::handle_safefetch(signo, context)) { return; // safefetch load recovered; PC already rewritten to _cont. } - HotspotSupport::checkFault(ProfiledThread::current()); // longjmp if protected + HotspotSupport::checkFault(ProfiledThread::current()); // setlongjmp if protected // Not protected and not a safefetch fault — real crash. if (signo == SIGBUS && orig_busHandler != nullptr) { orig_busHandler(signo, siginfo, context); @@ -144,7 +144,7 @@ TEST_F(FaultInjectionTest, SafeAccessRecoversFromInjectedFault) { } // (c2) walkVM path: a raw dereference of an injected poison pointer must be -// caught by the setjmp/longjmp crash protection, returning control to setjmp. +// caught by the sigsetjmp/siglongjmp crash protection, returning control to setjmp. TEST_F(FaultInjectionTest, WalkVmSetjmpRecoversFromInjectedFault) { ProfiledThread* t = ProfiledThread::current(); ASSERT_NE(t, nullptr); diff --git a/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp b/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp index 1f434aa4b..57bd3e0c7 100644 --- a/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp +++ b/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp @@ -10,7 +10,7 @@ * dereference JavaThread-only fields (anchor, vframe_top, …) on such threads. * VMThread::isJavaThread() provides the gate. * - * Crash recovery inside walkVM relies on setjmp/longjmp: + * Crash recovery inside walkVM relies on sigsetjmp/siglongjmp: * 1. walkVM stores a jmp_buf* on ProfiledThread (setJmpCtx/getJmpCtx), * chaining it with whatever context was already installed so a * signal-based sampler interrupting a non-signal-based sampler's own @@ -280,7 +280,7 @@ TEST_F(JmpCtxChainingTest, NestedFramesChainAndUnwindInOrder) { EXPECT_EQ(nullptr, _pt->getJmpCtx()); } -// End-to-end with real setjmp/longjmp: a fault inside the inner frame must +// End-to-end with real sigsetjmp/siglongjmp: a fault inside the inner frame must // land in the inner frame's own recovery branch — checkFault() always // longjmps through whatever is currently installed — and once the inner // frame has recovered and restored the outer's context, the outer frame must @@ -291,7 +291,7 @@ TEST_F(JmpCtxChainingTest, FaultInInnerFrameDoesNotDisturbOuterFrame) { int outer_landed = 0; int inner_landed = 0; - if (setjmp(outer_ctx) != 0) { + if (sigsetjmp(outer_ctx, 1) != 0) { outer_landed++; } else { _pt->setJmpCtx(&outer_ctx); @@ -301,14 +301,14 @@ TEST_F(JmpCtxChainingTest, FaultInInnerFrameDoesNotDisturbOuterFrame) { jmp_buf* inner_prev = _pt->getJmpCtx(); ASSERT_EQ(&outer_ctx, inner_prev); - if (setjmp(inner_ctx) != 0) { + if (sigsetjmp(inner_ctx, 1) != 0) { inner_landed++; _pt->setJmpCtx(inner_prev); } else { _pt->setJmpCtx(&inner_ctx); // Simulate checkFault(): longjmp through whatever is currently // installed — this must hit the inner frame, not the outer. - longjmp(*_pt->getJmpCtx(), 1); + siglongjmp(*_pt->getJmpCtx(), 1); FAIL() << "unreachable: longjmp does not return"; } // --- inner call has returned normally after recovering --- @@ -328,7 +328,7 @@ TEST_F(JmpCtxChainingTest, FaultInInnerFrameDoesNotDisturbOuterFrame) { // D. HotspotSupport::checkFault() guard clauses // // This gtest binary has no live JVM attached, so JVMThread is not initialized -// and the longjmp path can't be exercised end-to-end here. +// and the siglongjmp path can't be exercised end-to-end here. // These tests still call the real checkFault() (not a replica) to lock down // its early-return guard: a null ProfiledThread* // --------------------------------------------------------------------------- From 3c643bf655e7d1a2562e917e9418313fb0495921 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Fri, 24 Jul 2026 22:08:03 +0000 Subject: [PATCH 3/8] Fix --- ddprof-lib/src/test/cpp/faultInjection_ut.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp index f45745f92..997082cc1 100644 --- a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp +++ b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp @@ -51,7 +51,7 @@ TEST(FaultInjectionTest, DisabledValueMacrosAreIdentity) { #else // __FAULT_INJECTION__ enabled (built under -PenableFaultInjection) -// Chain: safefetch recovery first, then walkVM setjmp/longjmp recovery, then +// Chain: safefetch recovery first, then walkVM sigsetjmp/siglongjmp recovery, then // the crash handler as a last resort so a genuine bug still produces a report. static void (*orig_segvHandler)(int, siginfo_t*, void*); static void (*orig_busHandler)(int, siginfo_t*, void*); @@ -60,7 +60,7 @@ static void fi_signal_wrapper(int signo, siginfo_t* siginfo, void* context) { if (SafeAccess::handle_safefetch(signo, context)) { return; // safefetch load recovered; PC already rewritten to _cont. } - HotspotSupport::checkFault(ProfiledThread::current()); // setlongjmp if protected + HotspotSupport::checkFault(ProfiledThread::current()); // siglongjmp if protected // Not protected and not a safefetch fault — real crash. if (signo == SIGBUS && orig_busHandler != nullptr) { orig_busHandler(signo, siginfo, context); @@ -155,9 +155,9 @@ TEST_F(FaultInjectionTest, WalkVmSetjmpRecoversFromInjectedFault) { volatile size_t reads = 0; volatile size_t faults = 0; - jmp_buf ctx; - if (setjmp(ctx) != 0) { - recovered = true; // returned here via checkFault -> longjmp + sigjmp_buf ctx; + if (sigsetjmp(ctx) != 0) { + recovered = true; // returned here via checkFault -> siglongjmp faults++; } t->setJmpCtx(&ctx); From 36151d7217fb67712a07af970232834df7fc8a60 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Sat, 25 Jul 2026 00:15:06 +0200 Subject: [PATCH 4/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/threadLocalData.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddprof-lib/src/main/cpp/threadLocalData.h b/ddprof-lib/src/main/cpp/threadLocalData.h index de070521d..293a8c15a 100644 --- a/ddprof-lib/src/main/cpp/threadLocalData.h +++ b/ddprof-lib/src/main/cpp/threadLocalData.h @@ -239,7 +239,7 @@ class ProfiledThread : public ThreadLocalData { } inline void setJmpCtx(sigjmp_buf* buf) { - _jmp_buf = buf; + _jmp_buf = buf; } inline sigjmp_buf* getJmpCtx() const { From b914738923bb6592243b703579a25371cc227291 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Fri, 24 Jul 2026 22:16:41 +0000 Subject: [PATCH 5/8] Fix --- ddprof-lib/src/test/cpp/faultInjection_ut.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp index 997082cc1..f3cbf80b8 100644 --- a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp +++ b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp @@ -156,7 +156,7 @@ TEST_F(FaultInjectionTest, WalkVmSetjmpRecoversFromInjectedFault) { volatile size_t faults = 0; sigjmp_buf ctx; - if (sigsetjmp(ctx) != 0) { + if (sigsetjmp(ctx, 1) != 0) { recovered = true; // returned here via checkFault -> siglongjmp faults++; } From d2773a4fa0dd5c43ebd9975a2409a9c63d8bb57e Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Sat, 25 Jul 2026 00:56:29 +0000 Subject: [PATCH 6/8] Fix test --- .../src/test/cpp/hotspot_crash_protection_ut.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp b/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp index 57bd3e0c7..46b433214 100644 --- a/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp +++ b/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp @@ -286,8 +286,8 @@ TEST_F(JmpCtxChainingTest, NestedFramesChainAndUnwindInOrder) { // frame has recovered and restored the outer's context, the outer frame must // be left exactly as it was, never having been unwound itself. TEST_F(JmpCtxChainingTest, FaultInInnerFrameDoesNotDisturbOuterFrame) { - jmp_buf outer_ctx; - jmp_buf* outer_prev = _pt->getJmpCtx(); + sigjmp_buf outer_ctx; + sigjmp_buf* outer_prev = _pt->getJmpCtx(); int outer_landed = 0; int inner_landed = 0; @@ -297,8 +297,8 @@ TEST_F(JmpCtxChainingTest, FaultInInnerFrameDoesNotDisturbOuterFrame) { _pt->setJmpCtx(&outer_ctx); // --- inner "walkVM" call, interrupted mid-flight by a fault --- - jmp_buf inner_ctx; - jmp_buf* inner_prev = _pt->getJmpCtx(); + sigjmp_buf inner_ctx; + sigjmp_buf* inner_prev = _pt->getJmpCtx(); ASSERT_EQ(&outer_ctx, inner_prev); if (sigsetjmp(inner_ctx, 1) != 0) { @@ -309,7 +309,7 @@ TEST_F(JmpCtxChainingTest, FaultInInnerFrameDoesNotDisturbOuterFrame) { // Simulate checkFault(): longjmp through whatever is currently // installed — this must hit the inner frame, not the outer. siglongjmp(*_pt->getJmpCtx(), 1); - FAIL() << "unreachable: longjmp does not return"; + FAIL() << "unreachable: siglongjmp does not return"; } // --- inner call has returned normally after recovering --- From adad103c67ddfce3591070dd4855c0fb88df75fb Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Sat, 25 Jul 2026 23:08:44 +0200 Subject: [PATCH 7/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 3cfe00223..2b319b100 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -984,7 +984,7 @@ void HotspotSupport::checkFault(ProfiledThread* thrd) { return; } - // Check if siglongjmp is setup for this thread + // Check if siglongjmp is set up for this thread if (!thrd->isProtected()) { return; } From b4a0284218a2d682fa7deb11d9dc35d3accc56a3 Mon Sep 17 00:00:00 2001 From: Paul Fournillon Date: Mon, 27 Jul 2026 12:02:10 +0200 Subject: [PATCH 8/8] fix: address consistency suggestions --- ddprof-lib/src/test/cpp/faultInjection_ut.cpp | 8 ++--- .../test/cpp/hotspot_crash_protection_ut.cpp | 36 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp index f3cbf80b8..d1d25c250 100644 --- a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp +++ b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp @@ -144,8 +144,8 @@ TEST_F(FaultInjectionTest, SafeAccessRecoversFromInjectedFault) { } // (c2) walkVM path: a raw dereference of an injected poison pointer must be -// caught by the sigsetjmp/siglongjmp crash protection, returning control to setjmp. -TEST_F(FaultInjectionTest, WalkVmSetjmpRecoversFromInjectedFault) { +// caught by the sigsetjmp/siglongjmp crash protection, returning control to sigsetjmp. +TEST_F(FaultInjectionTest, WalkVmSigsetjmpRecoversFromInjectedFault) { ProfiledThread* t = ProfiledThread::current(); ASSERT_NE(t, nullptr); @@ -174,8 +174,8 @@ TEST_F(FaultInjectionTest, WalkVmSetjmpRecoversFromInjectedFault) { // We should have observed a recovered fault, or the loop completed cleanly. The // essential assertion is that the process did not die and, when a fault was - // injected, setjmp regained control. - EXPECT_GT(faults, 0u) << "expected at least one injected fault to longjmp-recover"; + // injected, sigsetjmp regained control. + EXPECT_GT(faults, 0u) << "expected at least one injected fault to siglongjmp-recover"; EXPECT_TRUE(recovered); SUCCEED(); } diff --git a/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp b/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp index 46b433214..be1dcab61 100644 --- a/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp +++ b/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp @@ -11,12 +11,12 @@ * VMThread::isJavaThread() provides the gate. * * Crash recovery inside walkVM relies on sigsetjmp/siglongjmp: - * 1. walkVM stores a jmp_buf* on ProfiledThread (setJmpCtx/getJmpCtx), + * 1. walkVM stores a sigjmp_buf* on ProfiledThread (setJmpCtx/getJmpCtx), * chaining it with whatever context was already installed so a * signal-based sampler interrupting a non-signal-based sampler's own * in-flight walkVM() call doesn't clobber the outer call's context. * 2. If a fault fires during the walk, checkFault() detects the live - * context via ProfiledThread::isProtected() and calls longjmp() to + * context via ProfiledThread::isProtected() and calls siglongjmp() to * unwind through whatever context is currently installed. * 3. ProfiledThread tracks nested crash-handler depth so recursive faults * (e.g. wall-clock signal inside a crash handler) are capped safely. @@ -24,7 +24,7 @@ * Tests cover: * A. ProfiledThread thread-type classification (isJavaThread fast path) * B. Crash-handler nesting depth (ProfiledThread crash handler state) - * C. jmp_buf chaining across nested/interrupted walkVM() calls + * C. sigjmp_buf chaining across nested/interrupted walkVM() calls */ #include @@ -115,7 +115,7 @@ TEST_F(ProfiledThreadTypeTest, FastPathReturnsFalseForNonJavaThread) { // Profiler::crashHandlerInternal calls: // enterCrashHandler() — on entry, returns false if limit reached // exitCrashHandler() — on normal exit -// resetCrashHandler() — from checkFault before longjmp to unwind all +// resetCrashHandler() — from checkFault before siglongjmp to unwind all // nesting at once // --------------------------------------------------------------------------- @@ -160,7 +160,7 @@ TEST_F(CrashHandlerNestingTest, LimitBlocksFurtherEntry) { } } -// resetCrashHandler() is called by checkFault() before longjmp so that the +// resetCrashHandler() is called by checkFault() before siglongjmp so that the // landing pad in walkVM starts with a clean nesting count. TEST_F(CrashHandlerNestingTest, ResetAllowsEntryAfterDeepNesting) { for (u32 i = 0; i < ProfiledThread::CRASH_HANDLER_NESTING_LIMIT; i++) { @@ -190,16 +190,16 @@ TEST_F(CrashHandlerNestingTest, IsDeepOnlyAboveLimit) { } // --------------------------------------------------------------------------- -// C. jmp_buf chaining (ProfiledThread::setJmpCtx/getJmpCtx/isProtected) +// C. sigjmp_buf chaining (ProfiledThread::setJmpCtx/getJmpCtx/isProtected) // // A non-signal-based sampler's walkVM() call can itself be interrupted by a // signal-based sampler, putting two walkVM() frames on the same thread's // stack. Each frame follows the same protocol: -// jmp_buf* prev = prof_thread->getJmpCtx(); // save whatever was there +// sigjmp_buf* prev = prof_thread->getJmpCtx(); // save whatever was there // prof_thread->setJmpCtx(&my_ctx); // install this frame's ctx // ... walk ... // prof_thread->setJmpCtx(prev); // restore on every exit path -// checkFault() always longjmps through whatever is currently installed +// checkFault() always siglongjmps through whatever is currently installed // (thrd->getJmpCtx()), so the inner frame must never leave the outer frame's // context installed while the inner frame is doing its own protected work, // and must always hand it back — via normal completion or fault recovery — @@ -227,7 +227,7 @@ TEST_F(JmpCtxChainingTest, InitiallyUnprotected) { } TEST_F(JmpCtxChainingTest, SetAndGetRoundTrip) { - jmp_buf ctx; + sigjmp_buf ctx; _pt->setJmpCtx(&ctx); EXPECT_TRUE(_pt->isProtected()); EXPECT_EQ(&ctx, _pt->getJmpCtx()); @@ -235,8 +235,8 @@ TEST_F(JmpCtxChainingTest, SetAndGetRoundTrip) { // Replicates a single walkVM() call's save/install/restore around its body. TEST_F(JmpCtxChainingTest, SingleFrameRestoresPreviousOnExit) { - jmp_buf outer; - jmp_buf* prev = _pt->getJmpCtx(); // nullptr: no enclosing walkVM() call + sigjmp_buf outer; + sigjmp_buf* prev = _pt->getJmpCtx(); // nullptr: no enclosing walkVM() call ASSERT_EQ(nullptr, prev); _pt->setJmpCtx(&outer); @@ -250,11 +250,11 @@ TEST_F(JmpCtxChainingTest, SingleFrameRestoresPreviousOnExit) { // Replicates two nested walkVM() calls: a signal-based sampler interrupting a // non-signal-based sampler's own in-flight walkVM(). The inner call must -// chain off the outer's jmp_buf*, install its own, and hand the outer's back +// chain off the outer's sigjmp_buf*, install its own, and hand the outer's back // on its way out — leaving the outer frame's context exactly as it left it. TEST_F(JmpCtxChainingTest, NestedFramesChainAndUnwindInOrder) { - jmp_buf outer_ctx; - jmp_buf* outer_prev = _pt->getJmpCtx(); + sigjmp_buf outer_ctx; + sigjmp_buf* outer_prev = _pt->getJmpCtx(); ASSERT_EQ(nullptr, outer_prev); _pt->setJmpCtx(&outer_ctx); EXPECT_EQ(&outer_ctx, _pt->getJmpCtx()); @@ -262,8 +262,8 @@ TEST_F(JmpCtxChainingTest, NestedFramesChainAndUnwindInOrder) { { // Inner walkVM() call, as if a signal fired while the outer one was // mid-walk. - jmp_buf inner_ctx; - jmp_buf* inner_prev = _pt->getJmpCtx(); + sigjmp_buf inner_ctx; + sigjmp_buf* inner_prev = _pt->getJmpCtx(); EXPECT_EQ(&outer_ctx, inner_prev); // chained off the outer frame _pt->setJmpCtx(&inner_ctx); @@ -282,7 +282,7 @@ TEST_F(JmpCtxChainingTest, NestedFramesChainAndUnwindInOrder) { // End-to-end with real sigsetjmp/siglongjmp: a fault inside the inner frame must // land in the inner frame's own recovery branch — checkFault() always -// longjmps through whatever is currently installed — and once the inner +// siglongjmps through whatever is currently installed — and once the inner // frame has recovered and restored the outer's context, the outer frame must // be left exactly as it was, never having been unwound itself. TEST_F(JmpCtxChainingTest, FaultInInnerFrameDoesNotDisturbOuterFrame) { @@ -306,7 +306,7 @@ TEST_F(JmpCtxChainingTest, FaultInInnerFrameDoesNotDisturbOuterFrame) { _pt->setJmpCtx(inner_prev); } else { _pt->setJmpCtx(&inner_ctx); - // Simulate checkFault(): longjmp through whatever is currently + // Simulate checkFault(): siglongjmp through whatever is currently // installed — this must hit the inner frame, not the outer. siglongjmp(*_pt->getJmpCtx(), 1); FAIL() << "unreachable: siglongjmp does not return";