Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions ddprof-lib/src/main/cpp/counters.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/faultInjection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/faultInjection.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions ddprof-lib/src/main/cpp/guards.h
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
24 changes: 12 additions & 12 deletions ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,19 +248,19 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex
int bcp_offset = InterpreterFrame::bcp_offset();


jmp_buf crash_protection_ctx;
// Chaining jmp_buf
sigjmp_buf crash_protection_ctx;
Comment thread
zhengyu123 marked this conversation as resolved.
// 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.
jmp_buf* prev_jmp_buf = prof_thread->getJmpCtx();
// Should be preserved across setjmp/longjmp
sigjmp_buf* prev_jmp_buf = prof_thread->getJmpCtx();
// 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);
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -984,14 +984,14 @@ void HotspotSupport::checkFault(ProfiledThread* thrd) {
return;
}

// Check if longjmp is setup for this thread
// Check if siglongjmp is set up for this thread
if (!thrd->isProtected()) {
return;
}

thrd->resetCrashHandler();
Counters::increment(WALKVM_LONGJMP_RECOVERED);
longjmp(*thrd->getJmpCtx(), 1);
siglongjmp(*thrd->getJmpCtx(), 1);
}

int HotspotSupport::getJavaTraceAsync(void *ucontext, ASGCT_CallFrame *frames,
Expand Down
4 changes: 2 additions & 2 deletions ddprof-lib/src/main/cpp/hotspot/vmStructs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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-
Expand Down
14 changes: 7 additions & 7 deletions ddprof-lib/src/main/cpp/profiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -927,23 +927,23 @@ 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();
if (crashHandlerInternal(signo, siginfo, 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);
Expand All @@ -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
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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);

Expand Down
4 changes: 2 additions & 2 deletions ddprof-lib/src/main/cpp/safeAccess.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
10 changes: 5 additions & 5 deletions ddprof-lib/src/main/cpp/threadLocalData.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@ class ProfiledThread : public ThreadLocalData {
static void freeValue(void* value);

static ThreadLocal<ProfiledThread*, nullptr, freeValue> _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
// _crash_depth is hardened below.
std::atomic<jmp_buf*> _jmp_buf;
std::atomic<sigjmp_buf*> _jmp_buf;

u64 _pc;
u64 _sp;
Expand Down Expand Up @@ -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) {
_jmp_buf = buf;
inline void setJmpCtx(sigjmp_buf* buf) {
_jmp_buf = buf;
}
Comment thread
Copilot marked this conversation as resolved.

inline jmp_buf* getJmpCtx() const {
inline sigjmp_buf* getJmpCtx() const {
Comment thread
zhengyu123 marked this conversation as resolved.
return _jmp_buf;
}

Expand Down
18 changes: 9 additions & 9 deletions ddprof-lib/src/test/cpp/faultInjection_ut.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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*);
Expand All @@ -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()); // siglongjmp if protected
// Not protected and not a safefetch fault — real crash.
if (signo == SIGBUS && orig_busHandler != nullptr) {
orig_busHandler(signo, siginfo, context);
Expand Down Expand Up @@ -144,8 +144,8 @@ 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.
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);

Expand All @@ -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, 1) != 0) {
recovered = true; // returned here via checkFault -> siglongjmp
Comment thread
zhengyu123 marked this conversation as resolved.
faults++;
}
t->setJmpCtx(&ctx);
Expand All @@ -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();
}
Expand Down
Loading
Loading