From d7b2bd97eb6e22db5075b615bb5d951d872876f6 Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Tue, 17 Mar 2026 12:07:00 +0000 Subject: [PATCH 1/2] Intercept sigaction to prevent wasmtime from overwriting signal handlers Wasmtime's SIGSEGV handler calls malloc() via __tls_get_addr, which is not async-signal-safe and causes deadlocks when profiler uses safefetch. Patch wasmtime's sigaction GOT entry to intercept handler installations. Their handlers are stored as chain targets and called from our handlers. Co-Authored-By: Claude Opus 4.5 (cherry picked from commit 2fcf6040f1ca439354f3ac913203b81c254df5ac) --- ddprof-lib/src/main/cpp/codeCache.cpp | 5 + ddprof-lib/src/main/cpp/codeCache.h | 1 + ddprof-lib/src/main/cpp/counters.h | 4 +- ddprof-lib/src/main/cpp/libraries.h | 2 +- ddprof-lib/src/main/cpp/libraryPatcher.h | 6 + .../src/main/cpp/libraryPatcher_linux.cpp | 56 ++++++++- ddprof-lib/src/main/cpp/os.h | 6 + ddprof-lib/src/main/cpp/os_linux.cpp | 85 +++++++++++++ ddprof-lib/src/main/cpp/profiler.cpp | 37 +++++- doc/architecture/SigsegvPatching.md | 118 ++++++++++++++++++ 10 files changed, 311 insertions(+), 9 deletions(-) create mode 100644 doc/architecture/SigsegvPatching.md diff --git a/ddprof-lib/src/main/cpp/codeCache.cpp b/ddprof-lib/src/main/cpp/codeCache.cpp index a8a360160..6ea17e3d0 100644 --- a/ddprof-lib/src/main/cpp/codeCache.cpp +++ b/ddprof-lib/src/main/cpp/codeCache.cpp @@ -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; } } diff --git a/ddprof-lib/src/main/cpp/codeCache.h b/ddprof-lib/src/main/cpp/codeCache.h index bde65960e..716a50fc9 100644 --- a/ddprof-lib/src/main/cpp/codeCache.h +++ b/ddprof-lib/src/main/cpp/codeCache.h @@ -31,6 +31,7 @@ enum ImportId { im_calloc, im_realloc, im_free, + im_sigaction, NUM_IMPORTS }; diff --git a/ddprof-lib/src/main/cpp/counters.h b/ddprof-lib/src/main/cpp/counters.h index 648972f4a..c5ec73466 100644 --- a/ddprof-lib/src/main/cpp/counters.h +++ b/ddprof-lib/src/main/cpp/counters.h @@ -92,7 +92,9 @@ 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(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 diff --git a/ddprof-lib/src/main/cpp/libraries.h b/ddprof-lib/src/main/cpp/libraries.h index 8b626ad9b..55672b02d 100644 --- a/ddprof-lib/src/main/cpp/libraries.h +++ b/ddprof-lib/src/main/cpp/libraries.h @@ -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; } diff --git a/ddprof-lib/src/main/cpp/libraryPatcher.h b/ddprof-lib/src/main/cpp/libraryPatcher.h index c163c16bc..1046cee1c 100644 --- a/ddprof-lib/src/main/cpp/libraryPatcher.h +++ b/ddprof-lib/src/main/cpp/libraryPatcher.h @@ -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 diff --git a/ddprof-lib/src/main/cpp/libraryPatcher_linux.cpp b/ddprof-lib/src/main/cpp/libraryPatcher_linux.cpp index d33558350..57be36a8f 100644 --- a/ddprof-lib/src/main/cpp/libraryPatcher_linux.cpp +++ b/ddprof-lib/src/main/cpp/libraryPatcher_linux.cpp @@ -1,6 +1,7 @@ #include "libraryPatcher.h" #ifdef __linux__ +#include "counters.h" #include "profiler.h" #include "vmStructs.h" #include "guards.h" @@ -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) { @@ -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__ diff --git a/ddprof-lib/src/main/cpp/os.h b/ddprof-lib/src/main/cpp/os.h index a578f225f..1093feff0 100644 --- a/ddprof-lib/src/main/cpp/os.h +++ b/ddprof-lib/src/main/cpp/os.h @@ -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; diff --git a/ddprof-lib/src/main/cpp/os_linux.cpp b/ddprof-lib/src/main/cpp/os_linux.cpp index 7bb054311..1b0fe9626 100644 --- a/ddprof-lib/src/main/cpp/os_linux.cpp +++ b/ddprof-lib/src/main/cpp/os_linux.cpp @@ -28,6 +28,7 @@ #include #include #include "common.h" +#include "counters.h" #include "os.h" #ifndef __musl__ @@ -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__ diff --git a/ddprof-lib/src/main/cpp/profiler.cpp b/ddprof-lib/src/main/cpp/profiler.cpp index fedb7e1d6..625df412f 100644 --- a/ddprof-lib/src/main/cpp/profiler.cpp +++ b/ddprof-lib/src/main/cpp/profiler.cpp @@ -1007,20 +1007,27 @@ void Profiler::writeHeapUsage(long value, bool live) { void *Profiler::dlopen_hook(const char *filename, int flags) { void *result = dlopen(filename, flags); + if (result != NULL) { // Static function of Profiler -> can not use the instance variable _libs // Since Libraries is a singleton, this does not matter Libraries::instance()->updateSymbols(false); + // Patch sigaction in newly loaded libraries + LibraryPatcher::patch_sigaction(); // Extract build-ids for newly loaded libraries if remote symbolication is enabled Profiler* profiler = instance(); if (profiler != nullptr && profiler->_remote_symbolication) { Libraries::instance()->updateBuildIds(); } } + return result; } void Profiler::switchLibraryTrap(bool enable) { + if (_dlopen_entry == NULL) { + return; // Not initialized yet, nothing to do + } void *impl = enable ? (void *)dlopen_hook : (void *)dlopen; __atomic_store_n(_dlopen_entry, impl, __ATOMIC_RELEASE); } @@ -1037,13 +1044,25 @@ void Profiler::disableEngines() { void Profiler::segvHandler(int signo, siginfo_t *siginfo, void *ucontext) { if (!crashHandler(signo, siginfo, ucontext)) { - orig_segvHandler(signo, siginfo, ucontext); + // Check dynamic chain target first (set by intercepted sigaction calls) + SigAction chain = OS::getSegvChainTarget(); + if (chain != nullptr) { + chain(signo, siginfo, ucontext); + } else if (orig_segvHandler != nullptr) { + orig_segvHandler(signo, siginfo, ucontext); + } } } void Profiler::busHandler(int signo, siginfo_t *siginfo, void *ucontext) { if (!crashHandler(signo, siginfo, ucontext)) { - orig_busHandler(signo, siginfo, ucontext); + // Check dynamic chain target first (set by intercepted sigaction calls) + SigAction chain = OS::getBusChainTarget(); + if (chain != nullptr) { + chain(signo, siginfo, ucontext); + } else if (orig_busHandler != nullptr) { + orig_busHandler(signo, siginfo, ucontext); + } } } @@ -1102,17 +1121,22 @@ bool Profiler::crashHandler(int signo, siginfo_t *siginfo, void *ucontext) { } void Profiler::setupSignalHandlers() { - // do not re-run the signal setup (run only when VM has not been loaded yet) + // Do not re-run the signal setup (run only when VM has not been loaded yet) if (__sync_bool_compare_and_swap(&_signals_initialized, false, true)) { if (VM::isHotspot() || VM::isOpenJ9()) { - // HotSpot and J9 tolerate interposed SIGSEGV/SIGBUS handler; other JVMs - // probably not + // HotSpot and J9 tolerate interposed SIGSEGV/SIGBUS handler; other JVMs probably not orig_segvHandler = OS::replaceSigsegvHandler(segvHandler); orig_busHandler = OS::replaceSigbusHandler(busHandler); + // Protect our handlers from being overwritten by other libraries (e.g., wasmtime). + // Their handlers will be stored as chain targets and called from our handlers. + OS::protectSignalHandlers(segvHandler, busHandler); + // Patch sigaction GOT in libraries with broken signal handlers (already loaded) + LibraryPatcher::patch_sigaction(); } } } + /** * Update thread name for the given thread */ @@ -1417,7 +1441,8 @@ Error Profiler::start(Arguments &args, bool reset) { enableEngines(); - switchLibraryTrap(_cstack == CSTACK_DWARF || _remote_symbolication); + // Always enable library trap to catch wasmtime loading and patch its broken sigaction + switchLibraryTrap(true); JfrMetadata::initialize(args._context_attributes); _num_context_attributes = args._context_attributes.size(); diff --git a/doc/architecture/SigsegvPatching.md b/doc/architecture/SigsegvPatching.md new file mode 100644 index 000000000..dc05f34d9 --- /dev/null +++ b/doc/architecture/SigsegvPatching.md @@ -0,0 +1,118 @@ +# SIGSEGV Handler Protection via sigaction Interposition + +## Problem + +Some native libraries install SIGSEGV/SIGBUS signal handlers that violate POSIX async-signal-safety requirements. A notable example is **wasmtime**, whose signal handler calls `__tls_get_addr` for TLS access, which in turn may call `malloc()`. + +When the profiler uses `safefetch` (safe memory access via intentional SIGSEGV), the following deadlock can occur: + +1. Application code holds malloc's internal lock +2. Profiler's signal handler runs and calls `safefetch` +3. `safefetch` triggers SIGSEGV +4. Wasmtime's handler (installed on top of ours) runs first +5. Wasmtime's handler calls `__tls_get_addr` → `malloc()` +6. `malloc()` tries to acquire its lock → **deadlock** + +## Solution + +The profiler intercepts `sigaction()` calls via GOT (Global Offset Table) patching. When libraries try to install signal handlers, we: + +1. Store their handler as a "chain target" +2. Keep our handler installed +3. Call their handler from within ours (after our logic completes) + +This ensures: +- Our handler always runs first +- We control when/if the chained handler is invoked +- Problematic handlers never become the "top" handler + +## Implementation + +### Components + +1. **`os_linux.cpp`** - Signal handler protection logic: + - `protectSignalHandlers()` - Registers our handlers for protection + - `sigaction_hook()` - Intercepts sigaction calls, stores chain targets + - `getSegvChainTarget()` / `getBusChainTarget()` - Returns current chain target + +2. **`libraryPatcher_linux.cpp`** - GOT patching: + - `patch_sigaction_in_library()` - Patches sigaction GOT entry in a library + - `patch_sigaction()` - Iterates all libraries and patches them + +3. **`codeCache.cpp`** - Import tracking: + - Tracks `sigaction` imports in loaded libraries (via `im_sigaction`) + +4. **`profiler.cpp`** - Integration: + - `setupSignalHandlers()` - Installs handlers and patches already-loaded libs + - `dlopen_hook()` - Patches newly loaded libraries + - `switchLibraryTrap()` - Enables/disables dlopen hook + +### Initialization Flow + +``` +JVM Initialization + └── VM::ready() + └── Profiler::setupSignalHandlers() + ├── Install SIGSEGV/SIGBUS handlers + ├── OS::protectSignalHandlers() - Mark handlers as protected + └── LibraryPatcher::patch_sigaction() - Patch already-loaded libs + +Profiling Start + └── Profiler::start() + └── switchLibraryTrap(true) - Enable dlopen hook + +Library Load (via dlopen) + └── dlopen_hook() + └── LibraryPatcher::patch_sigaction() - Patch newly loaded libs +``` + +### Signal Handler Chain + +``` +SIGSEGV occurs + └── Profiler::segvHandler() + ├── Handle profiler-related faults (safefetch, etc.) + └── If not handled: call OS::getSegvChainTarget() + └── Invoke chained handler (e.g., wasmtime's, ASAN's) +``` + +## Scope + +All native libraries are patched, including: +- Application libraries (e.g., wasmtime) +- Sanitizer runtime libraries (libasan, libtsan, libubsan) + +This provides defense-in-depth against any library that might install a SIGSEGV/SIGBUS handler. Sanitizer libraries are intentionally patched so our handler can intercept recoverable SIGSEGVs (e.g., from `safefetch`) while still chaining to the sanitizer's handler for unexpected crashes. + +**Exclusions:** +- The profiler's own library +- Only SA_SIGINFO handlers (3-arg form) are intercepted for safe chaining + +## Counters + +Two counters track sigaction patching activity: +- `SIGACTION_PATCHED_LIBS` - Number of libraries where sigaction GOT was patched +- `SIGACTION_INTERCEPTED` - Number of sigaction calls intercepted (handler installations prevented) + +## Why GOT Patching? + +Alternative approaches considered: + +1. **LD_PRELOAD** - Requires modifying JVM launch, not always possible +2. **Rebinding after load** - Libraries install handlers lazily, timing is unreliable +3. **Disabling safefetch** - Would disable core profiler functionality + +GOT patching allows us to intercept function calls from specific libraries without affecting the rest of the process. + +## Thread Safety + +- `_segv_chain_target` / `_bus_chain_target` use atomic operations +- `LibraryPatcher::patch_sigaction()` uses a spinlock +- Signal handlers are async-signal-safe (no allocations) + +## Limitations + +1. Only works on Linux (uses ELF GOT patching) +2. Requires the library to call `sigaction()` via PLT (not inline) +3. Library must be dynamically linked +4. Only SA_SIGINFO (3-arg) handlers are chained; 1-arg handlers pass through From f825d3bdfb600041af384c2d6151adb750581b6c Mon Sep 17 00:00:00 2001 From: Jaroslav Bachorik Date: Wed, 18 Mar 2026 13:46:11 +0100 Subject: [PATCH 2/2] Track and warn when native library limit is exceeded - Add NATIVE_LIBS_DROPPED counter and LOG_WARN macro - CodeCacheArray::add() returns bool, logs once on overflow - Fix memory leak: delete CodeCache on failed add - Add missing macOS stubs for sigaction protection Co-Authored-By: Claude Opus 4.6 (1M context) (cherry picked from commit e35fff97d7ec84b718dc00e58c59fe71224b2fd3) --- ddprof-lib/src/main/cpp/codeCache.h | 17 ++++++++++++++--- ddprof-lib/src/main/cpp/common.h | 7 +++++++ ddprof-lib/src/main/cpp/counters.h | 1 + ddprof-lib/src/main/cpp/libraryPatcher.h | 1 + ddprof-lib/src/main/cpp/os_macos.cpp | 12 ++++++++++++ ddprof-lib/src/main/cpp/profiler.cpp | 8 ++++---- ddprof-lib/src/main/cpp/symbols_linux.cpp | 8 ++++++-- ddprof-lib/src/main/cpp/symbols_macos.cpp | 4 +++- 8 files changed, 48 insertions(+), 10 deletions(-) diff --git a/ddprof-lib/src/main/cpp/codeCache.h b/ddprof-lib/src/main/cpp/codeCache.h index 716a50fc9..e5f4faefb 100644 --- a/ddprof-lib/src/main/cpp/codeCache.h +++ b/ddprof-lib/src/main/cpp/codeCache.h @@ -6,6 +6,8 @@ #ifndef _CODECACHE_H #define _CODECACHE_H +#include "common.h" +#include "counters.h" #include "utils.h" #include @@ -258,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 *)); } @@ -272,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); @@ -289,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 { diff --git a/ddprof-lib/src/main/cpp/common.h b/ddprof-lib/src/main/cpp/common.h index 1dae50f14..943c92fb4 100644 --- a/ddprof-lib/src/main/cpp/common.h +++ b/ddprof-lib/src/main/cpp/common.h @@ -2,6 +2,7 @@ #define _COMMON_H #include +#include // Knuth's multiplicative constant (golden ratio * 2^64 for 64-bit) // Used for hash distribution in various components @@ -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 \ No newline at end of file diff --git a/ddprof-lib/src/main/cpp/counters.h b/ddprof-lib/src/main/cpp/counters.h index c5ec73466..275149cac 100644 --- a/ddprof-lib/src/main/cpp/counters.h +++ b/ddprof-lib/src/main/cpp/counters.h @@ -93,6 +93,7 @@ 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(NATIVE_LIBS_DROPPED, "native_libs_dropped") \ X(SIGACTION_PATCHED_LIBS, "sigaction_patched_libs") \ X(SIGACTION_INTERCEPTED, "sigaction_intercepted") #define X_ENUM(a, b) a, diff --git a/ddprof-lib/src/main/cpp/libraryPatcher.h b/ddprof-lib/src/main/cpp/libraryPatcher.h index 1046cee1c..39cf6822c 100644 --- a/ddprof-lib/src/main/cpp/libraryPatcher.h +++ b/ddprof-lib/src/main/cpp/libraryPatcher.h @@ -46,6 +46,7 @@ class LibraryPatcher { static void initialize() { } static void patch_libraries() { } static void unpatch_libraries() { } + static void patch_sigaction() { } }; #endif diff --git a/ddprof-lib/src/main/cpp/os_macos.cpp b/ddprof-lib/src/main/cpp/os_macos.cpp index 1da5bc8f6..8337a7420 100644 --- a/ddprof-lib/src/main/cpp/os_macos.cpp +++ b/ddprof-lib/src/main/cpp/os_macos.cpp @@ -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__ diff --git a/ddprof-lib/src/main/cpp/profiler.cpp b/ddprof-lib/src/main/cpp/profiler.cpp index 625df412f..6ff5e1651 100644 --- a/ddprof-lib/src/main/cpp/profiler.cpp +++ b/ddprof-lib/src/main/cpp/profiler.cpp @@ -1202,9 +1202,9 @@ Engine *Profiler::selectCpuEngine(Arguments &args) { if (VM::isOpenJ9()) { if (!J9Ext::shouldUseAsgct() || !J9Ext::can_use_ASGCT()) { if (!J9Ext::is_jvmti_jmethodid_safe()) { - fprintf(stderr, "[ddprof] [WARN] Safe jmethodID access is not available on this JVM. Using " + LOG_WARN("Safe jmethodID access is not available on this JVM. Using " "CPU profiler on your own risk. Use -XX:+KeepJNIIDs=true JVM " - "flag to make access to jmethodIDs safe, if your JVM supports it\n"); + "flag to make access to jmethodIDs safe, if your JVM supports it"); } TEST_LOG("J9[cpu]=jvmti"); return &j9_engine; @@ -1234,9 +1234,9 @@ Engine *Profiler::selectWallEngine(Arguments &args) { if (VM::isOpenJ9()) { if (args._wallclock_sampler == JVMTI || !J9Ext::shouldUseAsgct() || !J9Ext::can_use_ASGCT()) { if (!J9Ext::is_jvmti_jmethodid_safe()) { - fprintf(stderr, "[ddprof] [WARN] Safe jmethodID access is not available on this JVM. Using " + LOG_WARN("Safe jmethodID access is not available on this JVM. Using " "wallclock profiler on your own risk. Use -XX:+KeepJNIIDs=true JVM " - "flag to make access to jmethodIDs safe, if your JVM supports it\n"); + "flag to make access to jmethodIDs safe, if your JVM supports it"); } j9_engine.sampleIdleThreads(); TEST_LOG("J9[wall]=jvmti"); diff --git a/ddprof-lib/src/main/cpp/symbols_linux.cpp b/ddprof-lib/src/main/cpp/symbols_linux.cpp index 8b6a7cf22..3efc7857c 100644 --- a/ddprof-lib/src/main/cpp/symbols_linux.cpp +++ b/ddprof-lib/src/main/cpp/symbols_linux.cpp @@ -957,7 +957,9 @@ void Symbols::parseLibraries(CodeCacheArray* array, bool kernel_symbols) { if (haveKernelSymbols()) { cc->sort(); - array->add(cc); + if (!array->add(cc)) { + delete cc; + } } else { delete cc; } @@ -995,7 +997,9 @@ void Symbols::parseLibraries(CodeCacheArray* array, bool kernel_symbols) { cc->sort(); applyPatch(cc); - array->add(cc); + if (!array->add(cc)) { + delete cc; + } } if (array->count() >= MAX_NATIVE_LIBS && !_libs_limit_reported) { diff --git a/ddprof-lib/src/main/cpp/symbols_macos.cpp b/ddprof-lib/src/main/cpp/symbols_macos.cpp index 268d0cad9..1c7786ae9 100644 --- a/ddprof-lib/src/main/cpp/symbols_macos.cpp +++ b/ddprof-lib/src/main/cpp/symbols_macos.cpp @@ -213,7 +213,9 @@ void Symbols::parseLibraries(CodeCacheArray* array, bool kernel_symbols) { Log::warn("Could not parse symbols from %s", path); } cc->sort(); - array->add(cc); + if (!array->add(cc)) { + delete cc; + } } else { delete cc; }