Skip to content
Closed
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
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,4 @@ constexpr size_t KNUTH_MULTIPLICATIVE_CONSTANT = 0x9e3779b97f4a7c15ULL;
fprintf(stderr, "[ddprof] [WARN] " fmt "\n", ##__VA_ARGS__); \
} while (0)

#endif // _COMMON_H
#endif // _COMMON_H
24 changes: 24 additions & 0 deletions ddprof-lib/src/main/cpp/counters.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,27 @@
X(SAMPLES_DROPPED_THREAD_LOCAL, "samples_dropped_thread_local") \
X(SAFECOPY_FAILED, "safecopy_failed") \
X(SAFEFETCH_FAILED, "safefetch_failed") \
/* Every siglongjmp recovery, from any protected window, counted centrally \
* in Profiler::checkFault(). */ \
X(STACKWALK_LONGJMP_RECOVERED, "stackwalk_longjmp_recovered") \
X(METHOD_RESOLUTION_FAILED, "method_resolution_failed") \
/* Dump-time raw-Method* resolution (HotspotSupport::resolve, reached only \
* for cstack=vm + fjmethodid=false frames). NOT additive with \
* STACKWALK_LONGJMP_RECOVERED: checkFault() bumps that one unconditionally \
* before every siglongjmp, so each fault counted here is counted there too. \
* Subtract, never sum. Non-zero means stale HotSpot metadata (GC or class \
* unloading) reached the dump thread; the frame serializes as "unknown". */ \
X(METHOD_RESOLVE_FAULT_RECOVERED, "method_resolve_fault_recovered") \
/* Subset of the above: recoveries that landed in Lookup::resolveMethod() \
* (flightRecorder.cpp), i.e. faults while symbolicating a Java frame at \
* dump time via fillMethod() rather than during HotspotSupport::resolve()'s\
* raw-Method* walk. Counted separately because the two protected windows \
* cover different code and have different root causes. */ \
X(METHOD_RESOLVE_LONGJMP_RECOVERED, "method_resolve_longjmp_recovered") \
/* Symbol length/body rejected during the same resolution: unreadable, \
* empty, or longer than the fixed dump-time buffers in hotspotSupport.cpp. \
* The frame serializes as "unknown". */ \
X(METHOD_RESOLVE_SYMBOL_UNREADABLE, "method_resolve_symbol_unreadable") \
/* Strict subset of SAMPLES_DROPPED_THREAD_LOCAL, not an independent count: \
* ThreadLocalDataPool::claim() increments this on capacity exhaustion, and \
* every acquireCurrent() caller that gets nullptr back -- for this or any \
Expand All @@ -143,6 +163,10 @@
* samples_dropped_thread_local) to isolate non-pool priming drops, never \
* summed. */ \
X(SAMPLES_DROPPED_TLS_POOL_EXHAUSTED, "thread_local_pool_exhausted") \
/* Lookup::resolveMethod() calls that dropped method resolution, \
* because no ProfiledThread could be allocated for the dump thread (OOM): \
* there is nowhere to publish a landing pad. Expected to stay at 0. */ \
X(METHOD_RESOLUTION_DROPPED_TLS, "method_resolution_dropped_tls") \
/* writeElement() guards against a corrupted/dangling JfrMetadata tree. \
* Root cause is still unconfirmed, so these counters are the durable \
* signal for spotting a recurrence. */ \
Expand Down
9 changes: 9 additions & 0 deletions ddprof-lib/src/main/cpp/faultInjection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@

#include "faultInjection.h"

#include <stdint.h>

void crashNow() {
volatile uintptr_t* p = (volatile uintptr_t*)nullptr;
*p = 0xBAD;
__builtin_unreachable(); // the store above never returns.
}


// The whole translation unit is empty unless fault injection is enabled, so a
// normal build links a no-op object file.
#ifdef __FAULT_INJECTION__
Expand Down
55 changes: 55 additions & 0 deletions ddprof-lib/src/main/cpp/faultInjection.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@
//
// return INJECT_FAULT_BOOL_LIKELY(dlopen(name, flags) != nullptr);
//
// INJECT_CRASH_* goes at the same kind of site as INJECT_FAULT_ADDRESS_*, but it
// is a statement rather than an expression wrapper: it takes no argument and
// yields no value. Instead of substituting a poison address for the caller to
// dereference -- which a downstream recovery path (SafeAccess safefetch, a
// sigsetjmp/siglongjmp window) may absorb without a signal ever being raised --
// it raises the SIGSEGV itself, right at the call site. Use it to exercise the
// sigsetjmp/siglongjmp window enclosing the call site, or the top-level crash
// handler where there is no such window:
//
// INJECT_CRASH_LIKELY();
//

// The four tiers name their firing frequency: RARE 0.01%, UNLIKELY 0.1%,
// LIKELY 1%, HIGH 10%. See faultInjection.cpp for the poison-address and PRNG
// details.
Expand All @@ -43,6 +55,12 @@

#include <cassert>

// Deliberately dereferences nullptr to raise a real SIGSEGV right now,
// unconditionally (no probability gate, no shouldFire() draw). For exercising
// crash-handler / recovery paths on demand (e.g. from a test), never from a
// production code path.
[[noreturn]] void crashNow();

#ifdef __FAULT_INJECTION__

#include "arch.h" // u64
Expand Down Expand Up @@ -88,6 +106,22 @@ inline T injectAddress(T ptr, u64 threshold, const char* fn) {
return ptr;
}

// Like injectAddress(), but instead of substituting a poison pointer into the
// expression (leaving recovery to whatever the caller does with it downstream
// -- SafeAccess safefetch, walkVM's sigsetjmp/siglongjmp), this crashes right
// here, right now, when the tier fires. Whatever encloses the call site is what
// gets exercised: the nearest sigsetjmp/siglongjmp window if there is one, the
// top-level crash handler otherwise.
//
// Unlike injectAddress() this wraps no expression -- it takes no pointer and
// returns nothing, so it is a statement, not a drop-in for an
// INJECT_FAULT_ADDRESS_* site. It does nothing when the tier does not fire.
inline void injectCrash(u64 threshold, const char* fn) {
if (__builtin_expect(shouldFire(threshold, fn), 0)) {
crashNow();
}
}

// Returns orig unchanged, or `faulty` when the tier fires. Unlike
// injectAddress() (which fakes an input about to be dereferenced), this fakes
// the *outcome* of a call that already ran for real — e.g. making a
Expand Down Expand Up @@ -120,18 +154,39 @@ inline T injectValue(T orig, T faulty, u64 threshold, const char* fn) {
#define INJECT_FAULT_BOOL_HIGH(v) \
::faultinj::injectValue((v), false, ::faultinj::PROB_HIGH, __func__)

#define INJECT_CRASH_RARE() \
::faultinj::injectCrash(::faultinj::PROB_RARE, __func__)
#define INJECT_CRASH_UNLIKELY() \
::faultinj::injectCrash(::faultinj::PROB_UNLIKELY, __func__)
#define INJECT_CRASH_LIKELY() \
::faultinj::injectCrash(::faultinj::PROB_LIKELY, __func__)
#define INJECT_CRASH_HIGH() \
::faultinj::injectCrash(::faultinj::PROB_HIGH, __func__)
#define INJECT_CRASH_ALWAYS() crashNow()


#else // __FAULT_INJECTION__ not defined — strict identity, zero cost.

#define INJECT_FAULT_ADDRESS_RARE(ptr) (ptr)
#define INJECT_FAULT_ADDRESS_UNLIKELY(ptr) (ptr)
#define INJECT_FAULT_ADDRESS_LIKELY(ptr) (ptr)
#define INJECT_FAULT_ADDRESS_HIGH(ptr) (ptr)
#define INJECT_FAULT_ADDRESS_HIGH(ptr) (ptr)

#define INJECT_FAULT_BOOL_RARE(v) (v)
#define INJECT_FAULT_BOOL_UNLIKELY(v) (v)
#define INJECT_FAULT_BOOL_LIKELY(v) (v)
#define INJECT_FAULT_BOOL_HIGH(v) (v)

// ((void)0) rather than nothing, so `INJECT_CRASH_LIKELY();` stays a
// well-formed expression statement in every context (e.g. as the sole body of
// an unbraced if/else) instead of collapsing to a stray semicolon.
#define INJECT_CRASH_RARE() ((void)0)
#define INJECT_CRASH_UNLIKELY() ((void)0)
#define INJECT_CRASH_LIKELY() ((void)0)
#define INJECT_CRASH_HIGH() ((void)0)
#define INJECT_CRASH_ALWAYS() ((void)0)

#define NO_INJECTION_ASSERT(a) (assert(a))

#endif // __FAULT_INJECTION__
Expand Down
Loading
Loading