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
3 changes: 3 additions & 0 deletions ddprof-lib/src/main/cpp/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@
constexpr size_t KNUTH_MULTIPLICATIVE_CONSTANT = 0x9e3779b97f4a7c15ULL;

#ifdef DEBUG
#define DEBUG_ONLY(s) s

#define TEST_LOG(fmt, ...) do { \
fprintf(stdout, "[TEST::INFO] " fmt "\n", ##__VA_ARGS__); \
fflush(stdout); \
} while (0)
#else
#define DEBUG_ONLY(s)
#define TEST_LOG(fmt, ...) // No-op in non-debug mode
#endif

Expand Down
12 changes: 12 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,19 @@
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") \
/* 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
66 changes: 64 additions & 2 deletions ddprof-lib/src/main/cpp/faultInjection.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,33 @@
//
// return INJECT_FAULT_BOOL_LIKELY(dlopen(name, flags) != nullptr);
//
// The three tiers name their firing frequency: RARE 0.01%, UNLIKELY 0.1%,
// LIKELY 1%. See faultInjection.cpp for the poison-address and PRNG details.
// 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.

#ifndef _FAULT_INJECTION_H
#define _FAULT_INJECTION_H

#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 All @@ -56,6 +75,7 @@ namespace faultinj {
constexpr u64 PROB_RARE = 1844674407370955ULL; // 1e-4 (0.01%)
constexpr u64 PROB_UNLIKELY = 18446744073709552ULL; // 1e-3 (0.1%)
constexpr u64 PROB_LIKELY = 184467440737095520ULL; // 1e-2 (1%)
constexpr u64 PROB_HIGH = 1844674407370955162ULL; // 1e-1 (10%)

// Called once at profiler startup (off the signal path) to mmap the PROT_NONE
// guard region used by poisonAddress(). Safe to call before any injection.
Expand Down Expand Up @@ -86,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 All @@ -106,19 +142,35 @@ inline T injectValue(T orig, T faulty, u64 threshold, const char* fn) {
::faultinj::injectAddress((ptr), ::faultinj::PROB_UNLIKELY, __func__)
#define INJECT_FAULT_ADDRESS_LIKELY(ptr) \
::faultinj::injectAddress((ptr), ::faultinj::PROB_LIKELY, __func__)
#define INJECT_FAULT_ADDRESS_HIGH(ptr) \
::faultinj::injectAddress((ptr), ::faultinj::PROB_HIGH, __func__)

#define INJECT_FAULT_BOOL_RARE(v) \
::faultinj::injectValue((v), false, ::faultinj::PROB_RARE, __func__)
#define INJECT_FAULT_BOOL_UNLIKELY(v) \
::faultinj::injectValue((v), false, ::faultinj::PROB_UNLIKELY, __func__)
#define INJECT_FAULT_BOOL_LIKELY(v) \
::faultinj::injectValue((v), false, ::faultinj::PROB_LIKELY, __func__)
#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_INT_RARE(v) (v)
#define INJECT_FAULT_INT_UNLIKELY(v) (v)
Expand All @@ -131,6 +183,16 @@ inline T injectValue(T orig, T faulty, u64 threshold, const char* fn) {
#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))

Expand Down
Loading
Loading