Skip to content
Draft
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
20 changes: 16 additions & 4 deletions ddprof-lib/src/main/cpp/counters.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +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") \
/* Subset of the above: recoveries that landed in Lookup::resolveMethod(), \
* i.e. faults while symbolicating at dump time rather than while walking a \
* stack in a signal handler. Counted separately because the two have \
* different root causes (stale jmethodID / class unload vs. a bad frame \
* pointer) and would otherwise be indistinguishable. */ \
X(METHOD_RESOLVE_LONGJMP_RECOVERED, "method_resolve_longjmp_recovered") \
/* Lookup::resolveMethod() calls that ran without siglongjmp protection \
* 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_RESOLVE_UNPROTECTED, "method_resolve_unprotected") \
/* 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 \
* other reason -- separately increments SAMPLES_DROPPED_THREAD_LOCAL too. \
* So every pool-exhaustion drop bumps both counters together; the two \
* should be subtracted (thread_local_pool_exhausted from \
* samples_dropped_thread_local) to isolate non-pool priming drops, never \
* summed. */ \
* So every pool-exhaustion drop bumps both counters together; the two \
* should be subtracted (thread_local_pool_exhausted from \
* samples_dropped_thread_local) to isolate non-pool priming drops, never \
* summed. */ \
X(SAMPLES_DROPPED_TLS_POOL_EXHAUSTED, "thread_local_pool_exhausted") \
/* writeElement() guards against a corrupted/dangling JfrMetadata tree. \
* Root cause is still unconfirmed, so these counters are the durable \
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