Skip to content
Open
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
5 changes: 5 additions & 0 deletions ddprof-lib/src/main/cpp/codeCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,11 @@ void CodeCache::addImport(void **entry, const char *name) {
saveImport(im_realloc, entry);
}
break;
case 's':
if (strcmp(name, "sigaction") == 0) {
saveImport(im_sigaction, entry);
}
break;
}
}

Expand Down
18 changes: 15 additions & 3 deletions ddprof-lib/src/main/cpp/codeCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
#ifndef _CODECACHE_H
#define _CODECACHE_H

#include "common.h"
#include "counters.h"
#include "utils.h"

#include <jvmti.h>
Expand All @@ -31,6 +33,7 @@ enum ImportId {
im_calloc,
im_realloc,
im_free,
im_sigaction,
NUM_IMPORTS
};

Expand Down Expand Up @@ -257,9 +260,10 @@ class CodeCacheArray {
volatile int _reserved; // next slot to reserve (CAS by writers)
volatile int _count; // published count (all indices < _count have non-NULL pointers)
volatile size_t _used_memory;
bool _overflow_reported;

public:
CodeCacheArray() : _reserved(0), _count(0), _used_memory(0) {
CodeCacheArray() : _reserved(0), _count(0), _used_memory(0), _overflow_reported(false) {
memset(_libs, 0, MAX_NATIVE_LIBS * sizeof(CodeCache *));
}

Expand All @@ -271,10 +275,17 @@ class CodeCacheArray {
// Pointer-first add: reserve a slot via CAS on _reserved, store the
// pointer with RELEASE, then advance _count. Readers see count() grow
// only after the pointer is visible, so indices < count() never yield NULL.
void add(CodeCache *lib) {
bool add(CodeCache *lib) {
int slot = __atomic_load_n(&_reserved, __ATOMIC_RELAXED);
do {
if (slot >= MAX_NATIVE_LIBS) return;
if (slot >= MAX_NATIVE_LIBS) {
Counters::increment(NATIVE_LIBS_DROPPED);
if (!_overflow_reported) {
_overflow_reported = true;
LOG_WARN("Native library limit reached (%d). Additional libraries will not be tracked.", MAX_NATIVE_LIBS);
}
return false;
}
} while (!__atomic_compare_exchange_n(&_reserved, &slot, slot + 1,
true, __ATOMIC_RELAXED, __ATOMIC_RELAXED));
assert(__atomic_load_n(&_libs[slot], __ATOMIC_RELAXED) == nullptr);
Expand All @@ -288,6 +299,7 @@ class CodeCacheArray {
// wait for preceding slots to publish
}
__atomic_store_n(&_count, slot + 1, __ATOMIC_RELEASE);
return true;
}

CodeCache* at(int index) const {
Expand Down
7 changes: 7 additions & 0 deletions ddprof-lib/src/main/cpp/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define _COMMON_H

#include <cstddef>
#include <cstdio>

// Knuth's multiplicative constant (golden ratio * 2^64 for 64-bit)
// Used for hash distribution in various components
Expand All @@ -16,4 +17,10 @@ constexpr size_t KNUTH_MULTIPLICATIVE_CONSTANT = 0x9e3779b97f4a7c15ULL;
#define TEST_LOG(fmt, ...) // No-op in non-debug mode
#endif

// Lightweight stderr warning that does not depend on the Log subsystem.
// Safe to call from low-level code where Log may not be initialized.
#define LOG_WARN(fmt, ...) do { \
fprintf(stderr, "[ddprof] [WARN] " fmt "\n", ##__VA_ARGS__); \
} while (0)

#endif // _COMMON_H
5 changes: 4 additions & 1 deletion ddprof-lib/src/main/cpp/counters.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@
X(WALKVM_STUB_FRAMESIZE_FALLBACK, "walkvm_stub_framesize_fallback") \
X(WALKVM_FP_CHAIN_ATTEMPT, "walkvm_fp_chain_attempt") \
X(WALKVM_FP_CHAIN_REACHED_CODEHEAP, "walkvm_fp_chain_reached_codeheap") \
X(WALKVM_ANCHOR_NOT_IN_JAVA, "walkvm_anchor_not_in_java")
X(WALKVM_ANCHOR_NOT_IN_JAVA, "walkvm_anchor_not_in_java") \
X(NATIVE_LIBS_DROPPED, "native_libs_dropped") \
X(SIGACTION_PATCHED_LIBS, "sigaction_patched_libs") \
X(SIGACTION_INTERCEPTED, "sigaction_intercepted")
#define X_ENUM(a, b) a,
typedef enum CounterId : int {
DD_COUNTER_TABLE(X_ENUM) DD_NUM_COUNTERS
Expand Down
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/libraries.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Libraries {
// Note: Parameter is uint32_t to match lib_index packing (17 bits = max 131K libraries)
CodeCache *getLibraryByIndex(uint32_t index) const {
if (index < _native_libs.count()) {
return _native_libs[index]; // may be NULL during concurrent add()
return _native_libs[index];
}
return nullptr;
}
Expand Down
7 changes: 7 additions & 0 deletions ddprof-lib/src/main/cpp/libraryPatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,19 @@ class LibraryPatcher {
static int _size;
static bool _patch_pthread_create;

// Separate tracking for sigaction patches
static PatchEntry _sigaction_entries[MAX_NATIVE_LIBS];
static int _sigaction_size;

static void patch_library_unlocked(CodeCache* lib);
static void patch_pthread_create();
static void patch_pthread_setspecific();
static void patch_sigaction_in_library(CodeCache* lib);
public:
static void initialize();
static void patch_libraries();
static void unpatch_libraries();
static void patch_sigaction();
};

#else
Expand All @@ -40,6 +46,7 @@ class LibraryPatcher {
static void initialize() { }
static void patch_libraries() { }
static void unpatch_libraries() { }
static void patch_sigaction() { }
};

#endif
Expand Down
56 changes: 55 additions & 1 deletion ddprof-lib/src/main/cpp/libraryPatcher_linux.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "libraryPatcher.h"

#ifdef __linux__
#include "counters.h"
#include "profiler.h"
#include "vmStructs.h"
#include "guards.h"
Expand All @@ -17,6 +18,8 @@ SpinLock LibraryPatcher::_lock;
const char* LibraryPatcher::_profiler_name = nullptr;
PatchEntry LibraryPatcher::_patched_entries[MAX_NATIVE_LIBS];
int LibraryPatcher::_size = 0;
PatchEntry LibraryPatcher::_sigaction_entries[MAX_NATIVE_LIBS];
int LibraryPatcher::_sigaction_size = 0;

void LibraryPatcher::initialize() {
if (_profiler_name == nullptr) {
Expand Down Expand Up @@ -238,8 +241,59 @@ void LibraryPatcher::patch_pthread_create() {
}
}
}
#endif // __linux__

// Patch sigaction in all libraries to prevent any library from overwriting
// our SIGSEGV/SIGBUS handlers. This protects against misbehaving libraries
// (like wasmtime) that install broken signal handlers calling malloc().
void LibraryPatcher::patch_sigaction_in_library(CodeCache* lib) {
if (lib->name() == nullptr) return;
if (_profiler_name == nullptr) return; // Not initialized yet

// Don't patch ourselves
char path[PATH_MAX];
char* resolved_path = realpath(lib->name(), path);
if (resolved_path != nullptr && strcmp(resolved_path, _profiler_name) == 0) {
return;
}

// Note: We intentionally patch sanitizer libraries (libasan, libtsan, libubsan) here.
// This keeps our handler on top for recoverable SIGSEGVs (e.g., safefetch) while
// still chaining to the sanitizer's handler for unexpected crashes.

void** sigaction_location = (void**)lib->findImport(im_sigaction);
if (sigaction_location == nullptr) {
return;
}

// Check if already patched or array is full
if (_sigaction_size >= MAX_NATIVE_LIBS) {
return;
}
for (int index = 0; index < _sigaction_size; index++) {
if (_sigaction_entries[index]._lib == lib) {
return;
}
}

void* hook = OS::getSigactionHook();
_sigaction_entries[_sigaction_size]._lib = lib;
_sigaction_entries[_sigaction_size]._location = sigaction_location;
_sigaction_entries[_sigaction_size]._func = (void*)__atomic_load_n(sigaction_location, __ATOMIC_RELAXED);
__atomic_store_n(sigaction_location, hook, __ATOMIC_RELAXED);
_sigaction_size++;
Counters::increment(SIGACTION_PATCHED_LIBS);
}

void LibraryPatcher::patch_sigaction() {
const CodeCacheArray& native_libs = Libraries::instance()->native_libs();
int num_of_libs = native_libs.count();
ExclusiveLockGuard locker(&_lock);
for (int index = 0; index < num_of_libs; index++) {
CodeCache* lib = native_libs.at(index);
if (lib != nullptr) {
patch_sigaction_in_library(lib);
}
}
}

#endif // __linux__
6 changes: 6 additions & 0 deletions ddprof-lib/src/main/cpp/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ class OS {
static SigAction replaceSigsegvHandler(SigAction action);
static SigAction replaceSigbusHandler(SigAction action);

// Signal handler protection - prevents other libraries from overwriting our handlers
static void protectSignalHandlers(SigAction segvHandler, SigAction busHandler);
static SigAction getSegvChainTarget();
static SigAction getBusChainTarget();
static void* getSigactionHook();

static int getMaxThreadId(int floor) {
int maxThreadId = getMaxThreadId();
return maxThreadId < floor ? floor : maxThreadId;
Expand Down
85 changes: 85 additions & 0 deletions ddprof-lib/src/main/cpp/os_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <time.h>
#include <unistd.h>
#include "common.h"
#include "counters.h"
#include "os.h"

#ifndef __musl__
Expand Down Expand Up @@ -726,4 +727,88 @@ SigAction OS::replaceSigbusHandler(SigAction action) {
return old_action;
}

// ============================================================================
// sigaction interposition to prevent other libraries from overwriting our
// SIGSEGV/SIGBUS handlers. This is needed because libraries like wasmtime
// install broken signal handlers that call malloc() (not async-signal-safe).
// ============================================================================

// Our protected handlers and their chain targets
static SigAction _protected_segv_handler = nullptr;
static SigAction _protected_bus_handler = nullptr;
static volatile SigAction _segv_chain_target = nullptr;
static volatile SigAction _bus_chain_target = nullptr;

// Real sigaction function pointer (resolved via dlsym)
typedef int (*real_sigaction_t)(int, const struct sigaction*, struct sigaction*);
static real_sigaction_t _real_sigaction = nullptr;

void OS::protectSignalHandlers(SigAction segvHandler, SigAction busHandler) {
// Resolve real sigaction BEFORE enabling protection, while we can still use RTLD_DEFAULT
if (_real_sigaction == nullptr) {
_real_sigaction = (real_sigaction_t)dlsym(RTLD_DEFAULT, "sigaction");
}
_protected_segv_handler = segvHandler;
_protected_bus_handler = busHandler;
}

SigAction OS::getSegvChainTarget() {
return __atomic_load_n(&_segv_chain_target, __ATOMIC_ACQUIRE);
}

SigAction OS::getBusChainTarget() {
return __atomic_load_n(&_bus_chain_target, __ATOMIC_ACQUIRE);
}

// sigaction hook - called via GOT patching to intercept sigaction calls
static int sigaction_hook(int signum, const struct sigaction* act, struct sigaction* oldact) {
// _real_sigaction must be resolved before any GOT patching happens
if (_real_sigaction == nullptr) {
errno = EFAULT;
return -1;
}

// If this is SIGSEGV or SIGBUS and we have protected handlers installed,
// intercept the call to keep our handler on top
if (act != nullptr) {
if (signum == SIGSEGV && _protected_segv_handler != nullptr) {
// Only intercept SA_SIGINFO handlers (3-arg form) for safe chaining
if (act->sa_flags & SA_SIGINFO) {
SigAction new_handler = act->sa_sigaction;
// Don't intercept if it's our own handler being installed
if (new_handler != _protected_segv_handler) {
// Save their handler as our chain target
__atomic_exchange_n(&_segv_chain_target, new_handler, __ATOMIC_ACQ_REL);
if (oldact != nullptr) {
_real_sigaction(signum, nullptr, oldact);
}
Counters::increment(SIGACTION_INTERCEPTED);
// Don't actually install their handler - keep ours on top
return 0;
}
}
// Let 1-arg handlers (without SA_SIGINFO) pass through - we can't safely chain them
} else if (signum == SIGBUS && _protected_bus_handler != nullptr) {
if (act->sa_flags & SA_SIGINFO) {
SigAction new_handler = act->sa_sigaction;
if (new_handler != _protected_bus_handler) {
__atomic_exchange_n(&_bus_chain_target, new_handler, __ATOMIC_ACQ_REL);
if (oldact != nullptr) {
_real_sigaction(signum, nullptr, oldact);
}
Counters::increment(SIGACTION_INTERCEPTED);
return 0;
}
}
}
}

// For all other cases, pass through to real sigaction
return _real_sigaction(signum, act, oldact);
}

void* OS::getSigactionHook() {
return (void*)sigaction_hook;
}

#endif // __linux__
12 changes: 12 additions & 0 deletions ddprof-lib/src/main/cpp/os_macos.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -484,4 +484,16 @@ SigAction OS::replaceSigsegvHandler(SigAction action) {
return old_action;
}

// No GOT-based sigaction interception on macOS — these are no-ops.
void OS::protectSignalHandlers(SigAction segvHandler, SigAction busHandler) {
}

SigAction OS::getSegvChainTarget() {
return nullptr;
}

SigAction OS::getBusChainTarget() {
return nullptr;
}

#endif // __APPLE__
Loading
Loading