From a18740f8cccd1188830e37bba955724140112376 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Fri, 17 Jul 2026 18:04:00 +0000 Subject: [PATCH 01/35] v0 --- ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp | 3 +- ddprof-lib/src/main/cpp/hotspot/vmStructs.h | 20 ++++--------- .../src/main/cpp/hotspot/vmStructs.inline.h | 30 +++++++++++++++++++ 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp b/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp index ccbca45089..469138ac86 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp @@ -863,7 +863,8 @@ VMFlag *VMFlag::find(const char *name, int type_mask) { size_t count = *(size_t*)_flag_count; for (size_t i = 0; i < count; i++) { VMFlag* f = VMFlag::cast(*(const char**)_flags_addr + i * VMFlag::type_size()); - if (f->name() != NULL && strcmp(f->name(), name) == 0) { + const char* fname = f->name(); + if (fname != nullptr && strcmp(fname, name) == 0) { int masked = 0x1 << f->type(); if (masked & type_mask) { return (VMFlag*)f; diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h index 9ef2586851..139d430a34 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h @@ -449,18 +449,11 @@ class VMStructs { static void checkNativeBinding(jvmtiEnv *jvmti, JNIEnv *jni, jmethodID method, void *address); static const void *findHeapUsageFunc(); - const char* at(int offset) { - const char* ptr = (const char*)this + offset; - assert(crashProtectionActive() || SafeAccess::isReadable(ptr)); - // Poison only the returned pointer; the assert above sees the real ptr. - return INJECT_FAULT_ADDRESS_RARE(ptr); - } + inline const char* at(int offset); + inline const char* at(int offset) const; - const char* at(int offset) const { - const char* ptr = (const char*)this + offset; - assert(crashProtectionActive() || SafeAccess::isReadable(ptr)); - return INJECT_FAULT_ADDRESS_RARE(ptr); - } + template + T load_at_offset(int offset) const; static bool goodPtr(const void* ptr) { return (uintptr_t)ptr >= 0x1000 && ((uintptr_t)ptr & (sizeof(uintptr_t) - 1)) == 0; @@ -1136,10 +1129,7 @@ DECLARE(VMFlag) static VMFlag* find(const char* name); static VMFlag *find(const char* name, std::initializer_list types); - const char* name() { - assert(_flag_name_offset >= 0); - return *(const char**) at(_flag_name_offset); - } + inline const char* name() const; int type(); diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h index 2b7edb3250..7453280975 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h @@ -10,6 +10,7 @@ #include "hotspot/hotspotSupport.h" #include "hotspot/vmStructs.h" #include "jvmThread.h" +#include "safeAccess.h" #include "threadLocalData.h" inline bool crashProtectionActive() { @@ -28,6 +29,30 @@ inline T* cast_to(const void* ptr) { return reinterpret_cast(const_cast(ptr)); } +inline const char* VMStructs::at(int offset) { + const char* ptr = (const char*)this + offset; + assert(crashProtectionActive() || SafeAccess::isReadable(ptr)); + // Poison only the returned pointer; the assert above sees the real ptr. + return INJECT_FAULT_ADDRESS_RARE(ptr); +} + +inline const char* VMStructs::at(int offset) const { + const char* ptr = (const char*)this + offset; + assert(crashProtectionActive() || SafeAccess::isReadable(ptr)); + return INJECT_FAULT_ADDRESS_RARE(ptr); +} + +template +T VMStructs::load_at_offset(int offset) const { + const char* ptr = at(offset); + if (safe) { + return (T)SafeAccess::loadPtr((void**)ptr, nullptr); + } else { + return *((T*)ptr); + } +} + + VMThread* VMThread::current() { assert(VM::isHotspot()); return VMThread::cast(JVMThread::current()); @@ -188,5 +213,10 @@ u16 VMConstMethod::signatureIndex() const { return *(u16*)at(_constmethod_sig_index_offset); } +// The method is called without protection +inline const char* VMFlag::name() const { + assert(_flag_name_offset >= 0); + return load_at_offset(_flag_name_offset); +} #endif // _HOTSPOT_VMSTRUCTS_INLINE_H From 4db801f38eac983bad7b6791ccb2639866557596 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Mon, 20 Jul 2026 15:57:19 +0000 Subject: [PATCH 02/35] Fix startup --- ddprof-lib/src/main/cpp/vmEntry.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ddprof-lib/src/main/cpp/vmEntry.cpp b/ddprof-lib/src/main/cpp/vmEntry.cpp index 8eadc643d1..77a39a39e3 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.cpp +++ b/ddprof-lib/src/main/cpp/vmEntry.cpp @@ -328,9 +328,6 @@ bool VM::initShared(JavaVM* vm) { return false; } - // Initialize VMStructs - VMStructs::init(lib); - // Mark thread entry points for all JVMs (critical for correct stack unwinding) lib->mark(isThreadEntry, MARK_THREAD_ENTRY); @@ -563,6 +560,10 @@ void VM::ready(jvmtiEnv *jvmti, JNIEnv *jni) { Profiler::setupSignalHandlers(); if (isHotspot()) { JitWriteProtection jit(true); + CodeCache* lib = openJvmLibrary(); + assert(lib != nullptr); + // Initialize VMStructs + VMStructs::init(lib); VMStructs::ready(); } } From e11fe697f93ea269577715d214857028822423c2 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Tue, 21 Jul 2026 00:31:35 +0000 Subject: [PATCH 03/35] Fix startup and add test --- ddprof-lib/src/main/cpp/vmEntry.cpp | 29 ++++++++-- .../com/datadoghq/profiler/LibraryLoader.java | 24 +++++--- .../com/datadoghq/profiler/JVMAccessTest.java | 55 +++++++++++++++++++ 3 files changed, 95 insertions(+), 13 deletions(-) diff --git a/ddprof-lib/src/main/cpp/vmEntry.cpp b/ddprof-lib/src/main/cpp/vmEntry.cpp index 77a39a39e3..af793b0230 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.cpp +++ b/ddprof-lib/src/main/cpp/vmEntry.cpp @@ -446,6 +446,15 @@ bool VM::initProfilerBridge(JavaVM *vm, bool attach) { return false; } + // Under Agent_OnLoad (attach == false), this is the first native entry point and + // VM_INIT has not fired yet, so VMStructs would otherwise stay uninitialized until + // VM::VMInit() runs -- but CodeHeap::available()/VMFlag::find() below are used + // synchronously in this function. Get VMStructs (and crash-protection signal + // handlers) ready now; VM::ready() is idempotent, so the later VM::VMInit() + // callback (attach == false) or the direct call from VM::initLibrary() (already + // run before a JNI-triggered attach == true call gets here) is a safe no-op. + ready(jvmti(), jni()); + if (!attach && hotspot_version() == 8 && OS::isLinux()) { // Workaround for JDK-8185348 char *func = (char *)lib->findSymbol( @@ -514,6 +523,8 @@ bool VM::initProfilerBridge(JavaVM *vm, bool attach) { NULL); if (hotspot_version() == 0 || !CodeHeap::available()) { + TEST_LOG("CompiledMethodLoad workaround: hotspot_version=%d CodeHeap::available=%d", + hotspot_version(), CodeHeap::available()); // Workaround for JDK-8173361: avoid CompiledMethodLoad events when possible _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_LOAD, NULL); @@ -521,6 +532,7 @@ bool VM::initProfilerBridge(JavaVM *vm, bool attach) { // DebugNonSafepoints is automatically enabled with CompiledMethodLoad, // otherwise we set the flag manually VMFlag* f = VMFlag::find("DebugNonSafepoints", {VMFlag::Type::Bool}); + TEST_LOG("DebugNonSafepoints flag %s", f != NULL ? "found" : "not found"); if (f != NULL && f->isDefault()) { f->set(1); } @@ -554,17 +566,22 @@ bool VM::initProfilerBridge(JavaVM *vm, bool attach) { return true; } -// Run late initialization when JVM is ready +// Run late initialization when JVM is ready. May be called more than once (from +// initProfilerBridge() directly, and later from the VMInit JVMTI callback, or from +// initLibrary() followed by a JNI-triggered attach) -- the VMStructs init below only +// ever runs once. void VM::ready(jvmtiEnv *jvmti, JNIEnv *jni) { Profiler::check_JDK_8313796_workaround(); Profiler::setupSignalHandlers(); - if (isHotspot()) { + static bool vmstructs_ready = false; + if (isHotspot() && __sync_bool_compare_and_swap(&vmstructs_ready, false, true)) { JitWriteProtection jit(true); CodeCache* lib = openJvmLibrary(); - assert(lib != nullptr); - // Initialize VMStructs - VMStructs::init(lib); - VMStructs::ready(); + if (lib != nullptr) { + // Initialize VMStructs + VMStructs::init(lib); + VMStructs::ready(); + } } } diff --git a/ddprof-lib/src/main/java/com/datadoghq/profiler/LibraryLoader.java b/ddprof-lib/src/main/java/com/datadoghq/profiler/LibraryLoader.java index f7643e8fbc..b65000ed4e 100644 --- a/ddprof-lib/src/main/java/com/datadoghq/profiler/LibraryLoader.java +++ b/ddprof-lib/src/main/java/com/datadoghq/profiler/LibraryLoader.java @@ -107,13 +107,7 @@ private static Result loadLibrary(final String libraryLocation, String scratchDi if (state.get() == LoadingState.UNAVAILABLE) { return Result.UNAVAILABLE; } - Path libraryPath = libraryLocation != null ? Paths.get(libraryLocation) : null; - if (libraryPath == null) { - OperatingSystem os = OperatingSystem.current(); - String qualifier = (os == OperatingSystem.linux && os.isMusl()) ? "musl" : null; - - libraryPath = libraryFromClasspath(os, Arch.current(), qualifier, Paths.get(scratchDir != null ? scratchDir : System.getProperty("java.io.tmpdir"))); - } + Path libraryPath = libraryLocation != null ? Paths.get(libraryLocation) : resolveLibraryPath(scratchDir); System.load(libraryPath.toAbsolutePath().toString()); return Result.SUCCESS; } catch (Throwable t) { @@ -124,6 +118,22 @@ private static Result loadLibrary(final String libraryLocation, String scratchDi } } + /** + * Resolves the on-disk path of the bundled native library for the current OS/arch, extracting it + * from the classpath if necessary. Package-visible so tests can obtain a real file path (e.g. to + * pass via {@code -agentpath:}) without loading the library into the calling process. + * + * @param scratchDir The working scratch dir where to store the temp library file, or {@code null} + * to use {@code java.io.tmpdir} + * @return The library absolute path + * @throws IOException if the resource is not found on the classpath + */ + static Path resolveLibraryPath(String scratchDir) throws IOException { + OperatingSystem os = OperatingSystem.current(); + String qualifier = (os == OperatingSystem.linux && os.isMusl()) ? "musl" : null; + return libraryFromClasspath(os, Arch.current(), qualifier, Paths.get(scratchDir != null ? scratchDir : System.getProperty("java.io.tmpdir"))); + } + /** * Locates a library on class-path (eg. in a JAR) and creates a publicly accessible temporary copy * of the library which can then be used by the application by its absolute path. diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java index acb4ce3e2a..1335d5016e 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import java.lang.management.ManagementFactory; +import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -42,6 +43,60 @@ void sanityInitailizationTest() throws Exception { assertFalse(initProfilerFound.get(), "initProfilerBridge found"); } + /** + * Regression test for the Agent_OnLoad (static {@code -agentpath:}) startup path: unlike the + * {@code library}/{@code profiler} targets used elsewhere in this file, which load the native + * library via {@code System.load} ({@code JNI_OnLoad} -> {@code VM::initLibrary}), an + * {@code -agentpath:} JVM argument makes {@code Agent_OnLoad} -> {@code VM::initProfilerBridge} + * the *first* native entry point invoked, with no prior call to {@code VM::ready()}. + * {@code initProfilerBridge()} looks up the {@code DebugNonSafepoints} VM flag via + * {@code VMFlag::find()}, gated on {@code CodeHeap::available()} -- both depend on + * {@code VMStructs} offsets/addresses populated by {@code VMStructs::init()}. If that + * initialization has been deferred to the asynchronous {@code VMInit} callback without also + * covering this call site, {@code CodeHeap::available()} reads {@code false} (its backing + * address is still unset) and the code takes the JDK-8173361 workaround path instead of ever + * calling {@code VMFlag::find()} -- silently disabling {@code CompiledMethodLoad} events and + * skipping the {@code DebugNonSafepoints} detection, even on a JVM where both should have + * succeeded. + */ + @Test + void agentOnLoadVMFlagDetectionTest() throws Exception { + String config = System.getProperty("ddprof_test.config"); + assumeTrue("debug".equals(config)); + + Path agentLib = LibraryLoader.resolveLibraryPath(System.getProperty("java.io.tmpdir")); + + AtomicReference workaroundLine = new AtomicReference<>(null); + AtomicReference flagLine = new AtomicReference<>(null); + + boolean rslt = launch("noop", + Collections.singletonList("-agentpath:" + agentLib.toAbsolutePath()), + null, + l -> { + if (l.contains("[TEST::INFO] CompiledMethodLoad workaround:")) { + workaroundLine.set(l); + return LineConsumerResult.STOP; + } + if (l.contains("[TEST::INFO] DebugNonSafepoints flag")) { + flagLine.set(l); + return LineConsumerResult.STOP; + } + return LineConsumerResult.CONTINUE; + }, + null + ).inTime; + + assertTrue(rslt); + assertNull(workaroundLine.get(), + "VM::initProfilerBridge() took the JDK-8173361 CompiledMethodLoad workaround path (" + + workaroundLine.get() + ") instead of reaching the DebugNonSafepoints lookup -- " + + "CodeHeap::available() was false, meaning VMStructs was not initialized yet during " + + "Agent_OnLoad (startup-ordering regression)"); + assertNotNull(flagLine.get(), "expected DebugNonSafepoints flag lookup log line was not observed"); + assertTrue(flagLine.get().contains("flag found"), + "VMFlag::find(\"DebugNonSafepoints\") returned NULL during Agent_OnLoad: " + flagLine.get()); + } + @Test void jvmVersionTest() throws Exception { String config = System.getProperty("ddprof_test.config"); From 09bc992f0a2173acfa84d79a99a3c4005c082f7f Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Tue, 21 Jul 2026 13:24:18 +0000 Subject: [PATCH 04/35] Fix j9 --- ddprof-lib/src/main/cpp/flightRecorder.cpp | 2 +- ddprof-lib/src/main/cpp/profiler.cpp | 2 +- ddprof-lib/src/main/cpp/vmEntry.cpp | 13 +++++++++---- ddprof-lib/src/main/cpp/vmEntry.h | 7 ++++++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/ddprof-lib/src/main/cpp/flightRecorder.cpp b/ddprof-lib/src/main/cpp/flightRecorder.cpp index a622baf638..64c9e2d761 100644 --- a/ddprof-lib/src/main/cpp/flightRecorder.cpp +++ b/ddprof-lib/src/main/cpp/flightRecorder.cpp @@ -1204,7 +1204,7 @@ void Recording::writeSettings(Buffer *buf, Arguments &args) { } writeBoolSetting(buf, T_ACTIVE_RECORDING, "debugSymbols", - VMStructs::libjvm()->hasDebugSymbols()); + VM::libjvm()->hasDebugSymbols()); writeBoolSetting(buf, T_ACTIVE_RECORDING, "kernelSymbols", Symbols::haveKernelSymbols()); writeStringSetting(buf, T_ACTIVE_RECORDING, "cpuEngine", diff --git a/ddprof-lib/src/main/cpp/profiler.cpp b/ddprof-lib/src/main/cpp/profiler.cpp index 7bb3c95d4f..dfe319693b 100644 --- a/ddprof-lib/src/main/cpp/profiler.cpp +++ b/ddprof-lib/src/main/cpp/profiler.cpp @@ -1219,7 +1219,7 @@ Error Profiler::checkJvmCapabilities() { } } - if (!VMStructs::libjvm()->hasDebugSymbols() && !VM::isOpenJ9()) { + if (!VM::libjvm()->hasDebugSymbols()) { Log::warn("Install JVM debug symbols to improve profile accuracy"); } diff --git a/ddprof-lib/src/main/cpp/vmEntry.cpp b/ddprof-lib/src/main/cpp/vmEntry.cpp index af793b0230..32d8e8c7fb 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.cpp +++ b/ddprof-lib/src/main/cpp/vmEntry.cpp @@ -58,7 +58,7 @@ jvmtiError(JNICALL *VM::_orig_RedefineClasses)(jvmtiEnv *, jint, jvmtiError(JNICALL *VM::_orig_RetransformClasses)(jvmtiEnv *, jint, const jclass *classes); -void *VM::_libjvm; +CodeCache* VM::_libjvm = nullptr; AsyncGetCallTrace VM::_asyncGetCallTrace; JVM_GetManagement VM::_getManagement; @@ -204,6 +204,10 @@ int JavaVersionAccess::get_hotspot_version(char* prop_value) { } CodeCache* VM::openJvmLibrary() { + if (_libjvm != nullptr) { + return _libjvm; + } + if ((void*)_asyncGetCallTrace == nullptr) { return nullptr; } @@ -213,6 +217,7 @@ CodeCache* VM::openJvmLibrary() { isOpenJ9() ? libraries->findJvmLibrary("libj9vm") : libraries->findLibraryByAddress((const void *)_asyncGetCallTrace); + _libjvm = lib; return lib; } @@ -250,9 +255,9 @@ bool VM::initShared(JavaVM* vm) { prop = NULL; } - _libjvm = getLibraryHandle("libjvm.so"); - _asyncGetCallTrace = (AsyncGetCallTrace)dlsym(_libjvm, "AsyncGetCallTrace"); - _getManagement = (JVM_GetManagement)dlsym(_libjvm, "JVM_GetManagement"); + void *libjvm = getLibraryHandle("libjvm.so"); + _asyncGetCallTrace = (AsyncGetCallTrace)dlsym(libjvm, "AsyncGetCallTrace"); + _getManagement = (JVM_GetManagement)dlsym(libjvm, "JVM_GetManagement"); Libraries *libraries = Libraries::instance(); libraries->updateSymbols(false); diff --git a/ddprof-lib/src/main/cpp/vmEntry.h b/ddprof-lib/src/main/cpp/vmEntry.h index 875e7c39b1..317aeef132 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.h +++ b/ddprof-lib/src/main/cpp/vmEntry.h @@ -148,6 +148,7 @@ class VM { static bool _can_sample_objects; static bool _can_intercept_binding; static bool _is_adaptive_gc_boundary_flag_set; + static CodeCache *_libjvm; // HotSpot JFR async stack-trace extension (optional, JDK 27+). // _request_stack_trace is atomic (RELEASE/ACQUIRE) because canRequestStackTrace() @@ -172,7 +173,11 @@ class VM { static CodeCache* openJvmLibrary(); public: - static void *_libjvm; + static inline CodeCache* libjvm() { + assert(_libjvm != nullptr && "Out of order initialization sequence"); + return _libjvm; + } + static AsyncGetCallTrace _asyncGetCallTrace; static JVM_GetManagement _getManagement; From 1243287d403187875e094054c39d46938fa89248 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Tue, 21 Jul 2026 15:35:36 +0200 Subject: [PATCH 05/35] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h index 7453280975..3bfc4c8dc2 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h @@ -30,10 +30,10 @@ inline T* cast_to(const void* ptr) { } inline const char* VMStructs::at(int offset) { - const char* ptr = (const char*)this + offset; - assert(crashProtectionActive() || SafeAccess::isReadable(ptr)); - // Poison only the returned pointer; the assert above sees the real ptr. - return INJECT_FAULT_ADDRESS_RARE(ptr); + const char* ptr = (const char*)this + offset; + assert(crashProtectionActive() || SafeAccess::isReadable(ptr)); + // Poison only the returned pointer; the assert above sees the real ptr. + return INJECT_FAULT_ADDRESS_RARE(ptr); } inline const char* VMStructs::at(int offset) const { From 0c9562260a1b95aa5bf745b0a58871ccfbbf89ad Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Tue, 21 Jul 2026 16:10:50 +0200 Subject: [PATCH 06/35] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/vmEntry.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ddprof-lib/src/main/cpp/vmEntry.h b/ddprof-lib/src/main/cpp/vmEntry.h index 317aeef132..75725ef151 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.h +++ b/ddprof-lib/src/main/cpp/vmEntry.h @@ -174,8 +174,9 @@ class VM { public: static inline CodeCache* libjvm() { - assert(_libjvm != nullptr && "Out of order initialization sequence"); - return _libjvm; + CodeCache* lib = __atomic_load_n(&_libjvm, __ATOMIC_ACQUIRE); + assert(lib != nullptr && "Out of order initialization sequence"); + return lib; } static AsyncGetCallTrace _asyncGetCallTrace; From 73674860fd7894beb63b6af3a3a80a3e568c0850 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Tue, 21 Jul 2026 18:08:38 +0000 Subject: [PATCH 07/35] Fix memory ordering --- ddprof-lib/src/main/cpp/vmEntry.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ddprof-lib/src/main/cpp/vmEntry.cpp b/ddprof-lib/src/main/cpp/vmEntry.cpp index 32d8e8c7fb..a3c3a35b8c 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.cpp +++ b/ddprof-lib/src/main/cpp/vmEntry.cpp @@ -204,8 +204,9 @@ int JavaVersionAccess::get_hotspot_version(char* prop_value) { } CodeCache* VM::openJvmLibrary() { - if (_libjvm != nullptr) { - return _libjvm; + CodeCache* lib = __atomic_load_n(&_libjvm, __ATOMIC_ACQUIRE); + if (lib != nullptr) { + return lib; } if ((void*)_asyncGetCallTrace == nullptr) { @@ -213,11 +214,10 @@ CodeCache* VM::openJvmLibrary() { } Libraries* libraries = Libraries::instance(); - CodeCache *lib = - isOpenJ9() + lib = isOpenJ9() ? libraries->findJvmLibrary("libj9vm") : libraries->findLibraryByAddress((const void *)_asyncGetCallTrace); - _libjvm = lib; + __atomic_store_n(&_libjvm, lib, __ATOMIC_RELEASE); return lib; } From 00316b143fcda689b84f96d74ed5d750a86f5bb7 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Tue, 21 Jul 2026 20:09:50 +0200 Subject: [PATCH 08/35] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../java/com/datadoghq/profiler/LibraryLoader.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ddprof-lib/src/main/java/com/datadoghq/profiler/LibraryLoader.java b/ddprof-lib/src/main/java/com/datadoghq/profiler/LibraryLoader.java index b65000ed4e..94e9440adf 100644 --- a/ddprof-lib/src/main/java/com/datadoghq/profiler/LibraryLoader.java +++ b/ddprof-lib/src/main/java/com/datadoghq/profiler/LibraryLoader.java @@ -126,12 +126,18 @@ private static Result loadLibrary(final String libraryLocation, String scratchDi * @param scratchDir The working scratch dir where to store the temp library file, or {@code null} * to use {@code java.io.tmpdir} * @return The library absolute path - * @throws IOException if the resource is not found on the classpath + * @throws IOException if an I/O error occurs while extracting the library + * @throws IllegalStateException if the resource is not found on the classpath */ static Path resolveLibraryPath(String scratchDir) throws IOException { OperatingSystem os = OperatingSystem.current(); String qualifier = (os == OperatingSystem.linux && os.isMusl()) ? "musl" : null; - return libraryFromClasspath(os, Arch.current(), qualifier, Paths.get(scratchDir != null ? scratchDir : System.getProperty("java.io.tmpdir"))); + return libraryFromClasspath( + os, + Arch.current(), + qualifier, + Paths.get(scratchDir != null ? scratchDir : System.getProperty("java.io.tmpdir"))) + .toAbsolutePath(); } /** From 3848d40d2788f1930159d90785779eea3e9bb992 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Tue, 21 Jul 2026 20:10:34 +0200 Subject: [PATCH 09/35] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h index 3bfc4c8dc2..3f55e17421 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h @@ -213,7 +213,7 @@ u16 VMConstMethod::signatureIndex() const { return *(u16*)at(_constmethod_sig_index_offset); } -// The method is called without protection +// This method may be called without crash protection, so read the name via SafeAccess. inline const char* VMFlag::name() const { assert(_flag_name_offset >= 0); return load_at_offset(_flag_name_offset); From 94a667cf897bc11eb70546af83a6db3ef8734d79 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Tue, 21 Jul 2026 20:21:28 +0200 Subject: [PATCH 10/35] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/test/java/com/datadoghq/profiler/JVMAccessTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java index 1335d5016e..9acf73d203 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java @@ -79,7 +79,7 @@ void agentOnLoadVMFlagDetectionTest() throws Exception { } if (l.contains("[TEST::INFO] DebugNonSafepoints flag")) { flagLine.set(l); - return LineConsumerResult.STOP; + return LineConsumerResult.CONTINUE; } return LineConsumerResult.CONTINUE; }, From 9d3fd613ba67c68c147c8132c619e7f2ba28651c Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Tue, 21 Jul 2026 18:41:27 +0000 Subject: [PATCH 11/35] More memory ordering --- ddprof-lib/src/main/cpp/vmEntry.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddprof-lib/src/main/cpp/vmEntry.cpp b/ddprof-lib/src/main/cpp/vmEntry.cpp index a3c3a35b8c..2c1c360453 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.cpp +++ b/ddprof-lib/src/main/cpp/vmEntry.cpp @@ -579,7 +579,7 @@ void VM::ready(jvmtiEnv *jvmti, JNIEnv *jni) { Profiler::check_JDK_8313796_workaround(); Profiler::setupSignalHandlers(); static bool vmstructs_ready = false; - if (isHotspot() && __sync_bool_compare_and_swap(&vmstructs_ready, false, true)) { + if (isHotspot() && __sync_bool_compare_and_swap(&vmstructs_ready, false, true, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) { JitWriteProtection jit(true); CodeCache* lib = openJvmLibrary(); if (lib != nullptr) { From 6c9f113f09972dc260de6acbdc6ccefc2842ea73 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Tue, 21 Jul 2026 21:05:45 +0200 Subject: [PATCH 12/35] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/vmEntry.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ddprof-lib/src/main/cpp/vmEntry.cpp b/ddprof-lib/src/main/cpp/vmEntry.cpp index 2c1c360453..e498455402 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.cpp +++ b/ddprof-lib/src/main/cpp/vmEntry.cpp @@ -579,7 +579,10 @@ void VM::ready(jvmtiEnv *jvmti, JNIEnv *jni) { Profiler::check_JDK_8313796_workaround(); Profiler::setupSignalHandlers(); static bool vmstructs_ready = false; - if (isHotspot() && __sync_bool_compare_and_swap(&vmstructs_ready, false, true, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) { + bool expected = false; + if (isHotspot() && + __atomic_compare_exchange_n(&vmstructs_ready, &expected, true, false, + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) { JitWriteProtection jit(true); CodeCache* lib = openJvmLibrary(); if (lib != nullptr) { From 8da863ca7072b3c62355e2fb0cf47f0cba5ed983 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Tue, 21 Jul 2026 20:21:26 +0000 Subject: [PATCH 13/35] Cache flag name --- ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp b/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp index 469138ac86..1a68238c32 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp @@ -842,7 +842,8 @@ VMFlag* VMFlag::find(const char* name) { for (size_t i = 0; i < count; i++) { VMFlag* f = VMFlag::cast(*(const char**)_flags_addr + i * VMFlag::type_size()); - if (f->name() != NULL && strcmp(f->name(), name) == 0 && f->addr() != NULL) { + const char* fname = f->name(); + if (fname != nullptr && strcmp(fname, name) == 0 && f->addr() != nullptr) { return f; } } From 1cd76e1dfcf654e8cd06de72bba161ac7def65f7 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Fri, 17 Jul 2026 20:38:18 +0000 Subject: [PATCH 14/35] v0 --- ddprof-lib/src/main/cpp/counters.h | 2 +- .../src/main/cpp/hotspot/hotspotSupport.cpp | 5 +- ddprof-lib/src/main/cpp/profiler.cpp | 70 +++++++++++++++---- 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/ddprof-lib/src/main/cpp/counters.h b/ddprof-lib/src/main/cpp/counters.h index ffee1c0c4b..7c584df220 100644 --- a/ddprof-lib/src/main/cpp/counters.h +++ b/ddprof-lib/src/main/cpp/counters.h @@ -128,7 +128,7 @@ X(SAMPLES_DROPPED_THREAD_LOCAL, "samples_dropped_thread_local") \ X(SAFECOPY_FAILED, "safecopy_failed") \ X(SAFEFETCH_FAILED, "safefetch_failed") \ - X(WALKVM_LONGJMP_RECOVERED, "walkvm_longjmp_recovered") \ + X(STACKWALK_LONGJMP_RECOVERED, "stackwalk_longjmp_recovered") \ DD_COUNTER_TABLE_FAULT_INJECTION(X) \ DD_COUNTER_TABLE_FI_DEBUG(X) \ DD_COUNTER_TABLE_DEBUG(X) diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 1c925e492d..518c711564 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -972,7 +972,10 @@ void HotspotSupport::checkFault(ProfiledThread* thrd) { } thrd->resetCrashHandler(); - Counters::increment(WALKVM_LONGJMP_RECOVERED); + // Shared recovery point for every setjmp/longjmp-protected stack walk + // (walkVM's inner region and recordSample's native/AGCT unwind), so the + // counter is deliberately not walkVM-specific. + Counters::increment(STACKWALK_LONGJMP_RECOVERED); longjmp(*thrd->getJmpCtx(), 1); } diff --git a/ddprof-lib/src/main/cpp/profiler.cpp b/ddprof-lib/src/main/cpp/profiler.cpp index 7ab10dcd86..5cad601179 100644 --- a/ddprof-lib/src/main/cpp/profiler.cpp +++ b/ddprof-lib/src/main/cpp/profiler.cpp @@ -614,21 +614,67 @@ bool Profiler::recordSample(void *ucontext, u64 counter, int tid, #endif // COUNTERS ASGCT_CallFrame *frames = _calltrace_buffer[lock_index]->_asgct_frames; - int num_frames = 0; + // Read again after the setjmp landing below, so it must be volatile: a + // longjmp out of the unwind leaves non-volatile locals indeterminate. + volatile int num_frames = 0; StackContext java_ctx = {0}; - ASGCT_CallFrame *native_stop = frames + num_frames; - num_frames += getNativeTrace(ucontext, native_stop, event_type, tid, - &java_ctx, &truncated, lock_index); - assert(num_frames >= 0); - - int max_remaining = _max_stack_depth - num_frames; - if (max_remaining > 0) { - StackWalkRequest request = {event_type, lock_index, ucontext, frames + num_frames, max_remaining, &java_ctx, &truncated}; - num_frames += JVMSupport::walkJavaStack(request); + + // Establish setjmp/longjmp crash protection around the unwind. The native + // walkers (walkFP/walkDwarf) protect their pointer loads with SafeAccess + // safefetch, but the surrounding metadata reads (isJitCode, findFrameDesc, + // findLibraryByAddress, AGCT in getJavaTraceAsync, frame.link, ...) are raw + // dereferences of signal-supplied pc/sp/fp. Without an active jmp_buf a + // fault there is unrecoverable: crashHandlerInternal -> checkFault() finds + // isProtected()==false and chains to the JVM handler, crashing the process. + // walkVM installs its own inner jmp_buf and chains back to whatever we set + // here, so nesting is safe. setjmp is called unconditionally (kept as the + // whole controlling expression per C11 7.13.1.1); when there is no + // ProfiledThread we simply never publish the jmp_buf, and checkFault(null) + // returns without longjmp'ing, so the landing branch is unreachable. + ProfiledThread *walk_thread = ProfiledThread::currentSignalSafe(); + jmp_buf unwind_ctx; + jmp_buf *prev_jmp_buf = + (walk_thread != nullptr) ? walk_thread->getJmpCtx() : nullptr; + + if (setjmp(unwind_ctx) != 0) { + // A fault during unwinding longjmp'd back here (via checkFault). Reached + // only when walk_thread != nullptr (see above). The longjmp bypassed + // segvHandler's SignalHandlerScope destructor, so compensate, restore the + // previous jmp_buf chain, and record the partial trace with an error + // marker instead of crashing. + SIGNAL_HANDLER_UNWIND_AFTER_LONGJMP(); + walk_thread->setJmpCtx(prev_jmp_buf); + if (num_frames < _max_stack_depth) { + num_frames += makeFrame(frames + num_frames, BCI_ERROR, "break_unwind_fault"); + } + } else { + if (walk_thread != nullptr) { + walk_thread->setJmpCtx(&unwind_ctx); + } + + // truncated_local is never read after a longjmp landing (only on the + // clean path below), so it need not be volatile; the outer `truncated` + // stays false on the recovery path. + bool truncated_local = false; + ASGCT_CallFrame *native_stop = frames + num_frames; + num_frames += getNativeTrace(ucontext, native_stop, event_type, tid, + &java_ctx, &truncated_local, lock_index); + assert(num_frames >= 0); + + int max_remaining = _max_stack_depth - num_frames; + if (max_remaining > 0) { + StackWalkRequest request = {event_type, lock_index, ucontext, frames + num_frames, max_remaining, &java_ctx, &truncated_local}; + num_frames += JVMSupport::walkJavaStack(request); + } + assert(num_frames >= 0); + + if (walk_thread != nullptr) { + walk_thread->setJmpCtx(prev_jmp_buf); + } + truncated = truncated_local; } - - assert(num_frames >= 0); + if (num_frames == 0) { num_frames += makeFrame(frames + num_frames, BCI_ERROR, "no_Java_frame"); } From 4f20993955b3cad76ac56381d32849e3ee430900 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Tue, 21 Jul 2026 20:17:41 +0000 Subject: [PATCH 15/35] Fix merge --- ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp | 3 +++ ddprof-lib/src/main/cpp/profiler.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 518c711564..0f3998c386 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -575,6 +575,9 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex level >= 1 && level <= 3 ? FRAME_C1_COMPILED : FRAME_JIT_COMPILED; } VMMethod* method = scope.method(); + if (method == nullptr) { + break; + } jmethodID method_id = method->id(); fillFrame(frames[depth++], type, scope.bci(), method_id, method); } while (scope_offset > 0 && depth < max_depth); diff --git a/ddprof-lib/src/main/cpp/profiler.cpp b/ddprof-lib/src/main/cpp/profiler.cpp index 5cad601179..4f3b3de5a2 100644 --- a/ddprof-lib/src/main/cpp/profiler.cpp +++ b/ddprof-lib/src/main/cpp/profiler.cpp @@ -632,7 +632,7 @@ bool Profiler::recordSample(void *ucontext, u64 counter, int tid, // whole controlling expression per C11 7.13.1.1); when there is no // ProfiledThread we simply never publish the jmp_buf, and checkFault(null) // returns without longjmp'ing, so the landing branch is unreachable. - ProfiledThread *walk_thread = ProfiledThread::currentSignalSafe(); + ProfiledThread *walk_thread = ProfiledThread::current(); jmp_buf unwind_ctx; jmp_buf *prev_jmp_buf = (walk_thread != nullptr) ? walk_thread->getJmpCtx() : nullptr; From f8cdcf6025c7edcc5b29e933bcbe31ecbc2267de Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Tue, 21 Jul 2026 21:34:08 +0000 Subject: [PATCH 16/35] Fix --- ddprof-lib/src/main/cpp/libraries.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ddprof-lib/src/main/cpp/libraries.cpp b/ddprof-lib/src/main/cpp/libraries.cpp index a0c8e83ca2..06263812a3 100644 --- a/ddprof-lib/src/main/cpp/libraries.cpp +++ b/ddprof-lib/src/main/cpp/libraries.cpp @@ -1,7 +1,6 @@ #include "codeCache.h" #include "common.h" #include "findLibraryImpl.h" -#include "hotspot/vmStructs.h" #include "libraries.h" #include "libraryPatcher.h" #include "log.h" @@ -191,7 +190,7 @@ const void *Libraries::resolveSymbol(const char *name) { } CodeCache *Libraries::findJvmLibrary(const char *j9_lib_name) { - return VM::isOpenJ9() ? findLibraryByName(j9_lib_name) : VMStructs::libjvm(); + return VM::isOpenJ9() ? findLibraryByName(j9_lib_name) : VM::libjvm(); } CodeCache *Libraries::findLibraryByName(const char *lib_name) { From fd7237d67e09ae240b24182d08bb786e872c187f Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 22 Jul 2026 00:04:38 +0200 Subject: [PATCH 17/35] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h index 3f55e17421..5de4c94cad 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h @@ -44,12 +44,13 @@ inline const char* VMStructs::at(int offset) const { template T VMStructs::load_at_offset(int offset) const { - const char* ptr = at(offset); + const char* raw = (const char*)this + offset; if (safe) { - return (T)SafeAccess::loadPtr((void**)ptr, nullptr); - } else { - return *((T*)ptr); + // SafeAccess loads must work even when crash protection isn't active; avoid at() asserts. + return (T)SafeAccess::loadPtr((void**)INJECT_FAULT_ADDRESS_RARE(raw), nullptr); } + assert(crashProtectionActive() || SafeAccess::isReadable(raw)); + return *((T*)INJECT_FAULT_ADDRESS_RARE(raw)); } From d258efa30b7833be8b818467c05f489454a8ee9f Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Tue, 21 Jul 2026 22:18:05 +0000 Subject: [PATCH 18/35] Copyright headers --- ddprof-lib/src/main/cpp/libraries.cpp | 5 +++++ .../src/main/java/com/datadoghq/profiler/LibraryLoader.java | 4 ++++ .../src/test/java/com/datadoghq/profiler/JVMAccessTest.java | 5 +++++ 3 files changed, 14 insertions(+) diff --git a/ddprof-lib/src/main/cpp/libraries.cpp b/ddprof-lib/src/main/cpp/libraries.cpp index 06263812a3..66382e9d1b 100644 --- a/ddprof-lib/src/main/cpp/libraries.cpp +++ b/ddprof-lib/src/main/cpp/libraries.cpp @@ -1,3 +1,8 @@ +/* + * Copyright 2026, Datadog, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + #include "codeCache.h" #include "common.h" #include "findLibraryImpl.h" diff --git a/ddprof-lib/src/main/java/com/datadoghq/profiler/LibraryLoader.java b/ddprof-lib/src/main/java/com/datadoghq/profiler/LibraryLoader.java index 94e9440adf..c0003f03b4 100644 --- a/ddprof-lib/src/main/java/com/datadoghq/profiler/LibraryLoader.java +++ b/ddprof-lib/src/main/java/com/datadoghq/profiler/LibraryLoader.java @@ -1,3 +1,7 @@ +/* + * Copyright 2026, Datadog, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ package com.datadoghq.profiler; import java.io.IOException; diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java index 9acf73d203..f6b983f603 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/JVMAccessTest.java @@ -1,3 +1,8 @@ +/* + * Copyright 2026, Datadog, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + package com.datadoghq.profiler; import org.junit.jupiter.api.BeforeAll; From c614684bd33fb36b8802a3053aa8a8018ef899f5 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 22 Jul 2026 13:29:20 +0000 Subject: [PATCH 19/35] Fix test --- ddprof-lib/src/test/cpp/faultInjection_ut.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp index abb81b8ed3..1c910dbd60 100644 --- a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp +++ b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp @@ -167,7 +167,9 @@ TEST_F(FaultInjectionTest, WalkVmSetjmpRecoversFromInjectedFault) { for (int i = 0; i < 5000 && faults == 0; i++) { // Raw deref of the (possibly poisoned) base — mirrors walkVM's raw reads. uintptr_t v = *(uintptr_t*)INJECT_FAULT_ADDRESS_LIKELY(base); - (void)v; + // Tells the compiler the variable is read from and written to ("+r,m") + // and clobbers memory to prevent reordering loads/stores/optimizes away the returned value + asm volatile("" : "+r,m"(v) : : "memory"); reads++; } t->setJmpCtx(nullptr); From 2fae7387843b7bebdf125f88db321cab1a66ac65 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 22 Jul 2026 16:03:18 +0200 Subject: [PATCH 20/35] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/profiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddprof-lib/src/main/cpp/profiler.cpp b/ddprof-lib/src/main/cpp/profiler.cpp index 4f3b3de5a2..37088f511c 100644 --- a/ddprof-lib/src/main/cpp/profiler.cpp +++ b/ddprof-lib/src/main/cpp/profiler.cpp @@ -637,7 +637,7 @@ bool Profiler::recordSample(void *ucontext, u64 counter, int tid, jmp_buf *prev_jmp_buf = (walk_thread != nullptr) ? walk_thread->getJmpCtx() : nullptr; - if (setjmp(unwind_ctx) != 0) { + if (walk_thread != nullptr && setjmp(unwind_ctx) != 0) { // A fault during unwinding longjmp'd back here (via checkFault). Reached // only when walk_thread != nullptr (see above). The longjmp bypassed // segvHandler's SignalHandlerScope destructor, so compensate, restore the From e1be0e0059fe057e777b9081bcdbc2ac085b22b7 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 22 Jul 2026 16:28:03 +0200 Subject: [PATCH 21/35] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/profiler.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ddprof-lib/src/main/cpp/profiler.cpp b/ddprof-lib/src/main/cpp/profiler.cpp index 37088f511c..6c1ffd3959 100644 --- a/ddprof-lib/src/main/cpp/profiler.cpp +++ b/ddprof-lib/src/main/cpp/profiler.cpp @@ -628,10 +628,10 @@ bool Profiler::recordSample(void *ucontext, u64 counter, int tid, // fault there is unrecoverable: crashHandlerInternal -> checkFault() finds // isProtected()==false and chains to the JVM handler, crashing the process. // walkVM installs its own inner jmp_buf and chains back to whatever we set - // here, so nesting is safe. setjmp is called unconditionally (kept as the - // whole controlling expression per C11 7.13.1.1); when there is no - // ProfiledThread we simply never publish the jmp_buf, and checkFault(null) - // returns without longjmp'ing, so the landing branch is unreachable. + // here, so nesting is safe. setjmp() must be evaluated as a standalone + // expression/controlling expression (C11 7.13.1.1), hence the explicit jmp_rc. + // When there is no ProfiledThread we do not publish a jmp_buf, and + // checkFault(nullptr) returns without longjmp'ing. ProfiledThread *walk_thread = ProfiledThread::current(); jmp_buf unwind_ctx; jmp_buf *prev_jmp_buf = From 2d85119b3e440f87dd26a1a9b696aa65860bdf1e Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 22 Jul 2026 16:30:36 +0200 Subject: [PATCH 22/35] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/profiler.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ddprof-lib/src/main/cpp/profiler.cpp b/ddprof-lib/src/main/cpp/profiler.cpp index 6c1ffd3959..c23604db58 100644 --- a/ddprof-lib/src/main/cpp/profiler.cpp +++ b/ddprof-lib/src/main/cpp/profiler.cpp @@ -637,7 +637,12 @@ bool Profiler::recordSample(void *ucontext, u64 counter, int tid, jmp_buf *prev_jmp_buf = (walk_thread != nullptr) ? walk_thread->getJmpCtx() : nullptr; - if (walk_thread != nullptr && setjmp(unwind_ctx) != 0) { + int jmp_rc = 0; + if (walk_thread != nullptr) { + jmp_rc = setjmp(unwind_ctx); + } + + if (jmp_rc != 0) { // A fault during unwinding longjmp'd back here (via checkFault). Reached // only when walk_thread != nullptr (see above). The longjmp bypassed // segvHandler's SignalHandlerScope destructor, so compensate, restore the From 264b0af4eee973e1ec922082c229c95f4f04b7ec Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 22 Jul 2026 17:06:23 +0200 Subject: [PATCH 23/35] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/test/cpp/faultInjection_ut.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp index 1c910dbd60..8de3d97c26 100644 --- a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp +++ b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp @@ -167,9 +167,9 @@ TEST_F(FaultInjectionTest, WalkVmSetjmpRecoversFromInjectedFault) { for (int i = 0; i < 5000 && faults == 0; i++) { // Raw deref of the (possibly poisoned) base — mirrors walkVM's raw reads. uintptr_t v = *(uintptr_t*)INJECT_FAULT_ADDRESS_LIKELY(base); - // Tells the compiler the variable is read from and written to ("+r,m") - // and clobbers memory to prevent reordering loads/stores/optimizes away the returned value - asm volatile("" : "+r,m"(v) : : "memory"); + // Optimization barrier: tell the compiler `v` is read/write and clobber memory to prevent + // reordering/optimizing away the load. + asm volatile("" : "+r"(v) : : "memory"); reads++; } t->setJmpCtx(nullptr); From 2d4dd88f3a41a99e1fbc42d95580719fc728e911 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 22 Jul 2026 17:06:47 +0200 Subject: [PATCH 24/35] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/profiler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ddprof-lib/src/main/cpp/profiler.cpp b/ddprof-lib/src/main/cpp/profiler.cpp index c23604db58..1d827fa6d1 100644 --- a/ddprof-lib/src/main/cpp/profiler.cpp +++ b/ddprof-lib/src/main/cpp/profiler.cpp @@ -650,6 +650,7 @@ bool Profiler::recordSample(void *ucontext, u64 counter, int tid, // marker instead of crashing. SIGNAL_HANDLER_UNWIND_AFTER_LONGJMP(); walk_thread->setJmpCtx(prev_jmp_buf); + truncated = true; if (num_frames < _max_stack_depth) { num_frames += makeFrame(frames + num_frames, BCI_ERROR, "break_unwind_fault"); } From 679e98f66be144a2ec081b10b77a036c35e64d52 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 22 Jul 2026 15:11:30 +0000 Subject: [PATCH 25/35] Added asserts --- ddprof-lib/src/main/cpp/vmEntry.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ddprof-lib/src/main/cpp/vmEntry.cpp b/ddprof-lib/src/main/cpp/vmEntry.cpp index e498455402..ab5240bc31 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.cpp +++ b/ddprof-lib/src/main/cpp/vmEntry.cpp @@ -217,6 +217,10 @@ CodeCache* VM::openJvmLibrary() { lib = isOpenJ9() ? libraries->findJvmLibrary("libj9vm") : libraries->findLibraryByAddress((const void *)_asyncGetCallTrace); + + // The libaray must have been loaded. Otherwise, we cannot get to here due + // to JVM initialization + assert(lib != nullptr && "JVM library must be loaded"); __atomic_store_n(&_libjvm, lib, __ATOMIC_RELEASE); return lib; } @@ -585,11 +589,10 @@ void VM::ready(jvmtiEnv *jvmti, JNIEnv *jni) { __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) { JitWriteProtection jit(true); CodeCache* lib = openJvmLibrary(); - if (lib != nullptr) { - // Initialize VMStructs - VMStructs::init(lib); - VMStructs::ready(); - } + assert(lib != nullptr && "JVM library must have been loaded"); + // Initialize VMStructs + VMStructs::init(lib); + VMStructs::ready(); } } From 4403952834236bbb3f5743ffd98ee7e558cc3dee Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 22 Jul 2026 17:24:04 +0200 Subject: [PATCH 26/35] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/profiler.cpp | 41 ++++++++++++++-------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/ddprof-lib/src/main/cpp/profiler.cpp b/ddprof-lib/src/main/cpp/profiler.cpp index 1d827fa6d1..10d0e25ff5 100644 --- a/ddprof-lib/src/main/cpp/profiler.cpp +++ b/ddprof-lib/src/main/cpp/profiler.cpp @@ -655,31 +655,32 @@ bool Profiler::recordSample(void *ucontext, u64 counter, int tid, num_frames += makeFrame(frames + num_frames, BCI_ERROR, "break_unwind_fault"); } } else { - if (walk_thread != nullptr) { + if (walk_thread == nullptr) { + // No thread-local jmp_buf available: avoid unsafe raw dereferences during the unwind. + num_frames += makeFrame(frames + num_frames, BCI_ERROR, "no_ProfiledThread"); + truncated = true; + } else { walk_thread->setJmpCtx(&unwind_ctx); - } - // truncated_local is never read after a longjmp landing (only on the - // clean path below), so it need not be volatile; the outer `truncated` - // stays false on the recovery path. - bool truncated_local = false; - ASGCT_CallFrame *native_stop = frames + num_frames; - num_frames += getNativeTrace(ucontext, native_stop, event_type, tid, - &java_ctx, &truncated_local, lock_index); - assert(num_frames >= 0); - - int max_remaining = _max_stack_depth - num_frames; - if (max_remaining > 0) { - StackWalkRequest request = {event_type, lock_index, ucontext, frames + num_frames, max_remaining, &java_ctx, &truncated_local}; - num_frames += JVMSupport::walkJavaStack(request); - } - assert(num_frames >= 0); + // truncated_local is never read after a longjmp landing (only on the + // clean path below), so it need not be volatile; the outer `truncated` + // stays false on the recovery path. + bool truncated_local = false; + ASGCT_CallFrame *native_stop = frames + num_frames; + num_frames += getNativeTrace(ucontext, native_stop, event_type, tid, + &java_ctx, &truncated_local, lock_index); + assert(num_frames >= 0); + + int max_remaining = _max_stack_depth - num_frames; + if (max_remaining > 0) { + StackWalkRequest request = {event_type, lock_index, ucontext, frames + num_frames, max_remaining, &java_ctx, &truncated_local}; + num_frames += JVMSupport::walkJavaStack(request); + } + assert(num_frames >= 0); - if (walk_thread != nullptr) { walk_thread->setJmpCtx(prev_jmp_buf); + truncated = truncated_local; } - truncated = truncated_local; - } if (num_frames == 0) { num_frames += makeFrame(frames + num_frames, BCI_ERROR, "no_Java_frame"); From 0f92bade94d9f117a12cc2862dd073e09328a48d Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 22 Jul 2026 17:43:29 +0000 Subject: [PATCH 27/35] fix --- .../src/main/cpp/hotspot/hotspotSupport.cpp | 19 ----- .../src/main/cpp/hotspot/hotspotSupport.h | 2 - ddprof-lib/src/main/cpp/profiler.cpp | 85 +++++++++++-------- ddprof-lib/src/main/cpp/profiler.h | 2 + ddprof-lib/src/test/cpp/faultInjection_ut.cpp | 3 +- .../test/cpp/hotspot_crash_protection_ut.cpp | 4 +- 6 files changed, 57 insertions(+), 58 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 0f3998c386..b8d9463b44 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -963,25 +963,6 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex return depth; } -void HotspotSupport::checkFault(ProfiledThread* thrd) { - // Should not get to here (?) - if (thrd == nullptr) { - return; - } - - // Check if longjmp is setup for this thread - if (!thrd->isProtected()) { - return; - } - - thrd->resetCrashHandler(); - // Shared recovery point for every setjmp/longjmp-protected stack walk - // (walkVM's inner region and recordSample's native/AGCT unwind), so the - // counter is deliberately not walkVM-specific. - Counters::increment(STACKWALK_LONGJMP_RECOVERED); - longjmp(*thrd->getJmpCtx(), 1); -} - int HotspotSupport::getJavaTraceAsync(void *ucontext, ASGCT_CallFrame *frames, int max_depth, StackContext *java_ctx, bool *truncated) { diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h index 2fe809139f..4fe177c3ea 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h @@ -35,8 +35,6 @@ class HotspotSupport { static bool loadMethodIDsIfNeededImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass, bool load_all); public: static void initClassloaderInfo(JNIEnv* jni); - - static void checkFault(ProfiledThread* thrd = nullptr); static int walkJavaStack(StackWalkRequest& request); static inline bool canUnwind(const StackFrame& frame, const void*& pc) { return HotspotStackFrame::unwindAtomicStub(frame, pc); diff --git a/ddprof-lib/src/main/cpp/profiler.cpp b/ddprof-lib/src/main/cpp/profiler.cpp index 10d0e25ff5..9552282d37 100644 --- a/ddprof-lib/src/main/cpp/profiler.cpp +++ b/ddprof-lib/src/main/cpp/profiler.cpp @@ -628,37 +628,32 @@ bool Profiler::recordSample(void *ucontext, u64 counter, int tid, // fault there is unrecoverable: crashHandlerInternal -> checkFault() finds // isProtected()==false and chains to the JVM handler, crashing the process. // walkVM installs its own inner jmp_buf and chains back to whatever we set - // here, so nesting is safe. setjmp() must be evaluated as a standalone - // expression/controlling expression (C11 7.13.1.1), hence the explicit jmp_rc. - // When there is no ProfiledThread we do not publish a jmp_buf, and - // checkFault(nullptr) returns without longjmp'ing. + // here, so nesting is safe. When there is no ProfiledThread we have nowhere + // to publish a jmp_buf, so we skip the unwind entirely rather than risk an + // unrecoverable fault on a raw dereference. ProfiledThread *walk_thread = ProfiledThread::current(); - jmp_buf unwind_ctx; - jmp_buf *prev_jmp_buf = - (walk_thread != nullptr) ? walk_thread->getJmpCtx() : nullptr; - int jmp_rc = 0; - if (walk_thread != nullptr) { - jmp_rc = setjmp(unwind_ctx); - } - - if (jmp_rc != 0) { - // A fault during unwinding longjmp'd back here (via checkFault). Reached - // only when walk_thread != nullptr (see above). The longjmp bypassed - // segvHandler's SignalHandlerScope destructor, so compensate, restore the - // previous jmp_buf chain, and record the partial trace with an error - // marker instead of crashing. - SIGNAL_HANDLER_UNWIND_AFTER_LONGJMP(); - walk_thread->setJmpCtx(prev_jmp_buf); + if (walk_thread == nullptr) { + num_frames += makeFrame(frames + num_frames, BCI_ERROR, "no_ProfiledThread"); truncated = true; - if (num_frames < _max_stack_depth) { - num_frames += makeFrame(frames + num_frames, BCI_ERROR, "break_unwind_fault"); - } } else { - if (walk_thread == nullptr) { - // No thread-local jmp_buf available: avoid unsafe raw dereferences during the unwind. - num_frames += makeFrame(frames + num_frames, BCI_ERROR, "no_ProfiledThread"); + jmp_buf unwind_ctx; + jmp_buf *prev_jmp_buf = walk_thread->getJmpCtx(); + + // setjmp() must be evaluated as a standalone expression/controlling + // expression (C11 7.13.1.1), hence the explicit jmp_rc. + int jmp_rc = setjmp(unwind_ctx); + if (jmp_rc != 0) { + // A fault during unwinding longjmp'd back here (via checkFault). The + // longjmp bypassed segvHandler's SignalHandlerScope destructor, so + // compensate, restore the previous jmp_buf chain, and record the + // partial trace with an error marker instead of crashing. + SIGNAL_HANDLER_UNWIND_AFTER_LONGJMP(); + walk_thread->setJmpCtx(prev_jmp_buf); truncated = true; + if (num_frames < _max_stack_depth) { + num_frames += makeFrame(frames + num_frames, BCI_ERROR, "break_unwind_fault"); + } } else { walk_thread->setJmpCtx(&unwind_ctx); @@ -681,6 +676,7 @@ bool Profiler::recordSample(void *ucontext, u64 counter, int tid, walk_thread->setJmpCtx(prev_jmp_buf); truncated = truncated_local; } + } if (num_frames == 0) { num_frames += makeFrame(frames + num_frames, BCI_ERROR, "no_Java_frame"); @@ -688,9 +684,8 @@ bool Profiler::recordSample(void *ucontext, u64 counter, int tid, call_trace_id = _call_trace_storage.put(num_frames, frames, truncated, counter); - ProfiledThread *thread = ProfiledThread::current(); - if (thread != nullptr) { - thread->recordCallTraceId(call_trace_id); + if (walk_thread != nullptr) { + walk_thread->recordCallTraceId(call_trace_id); } #ifdef COUNTERS u64 duration = TSC::ticks() - startTime; @@ -977,6 +972,27 @@ void Profiler::busHandler(int signo, siginfo_t *siginfo, void *ucontext) { } } +void Profiler::checkFault(ProfiledThread* thrd) { + // Should not get to here (?) + if (thrd == nullptr) { + return; + } + + // Check if longjmp is setup for this thread + if (!thrd->isProtected()) { + return; + } + + thrd->resetCrashHandler(); + // Shared recovery point for every setjmp/longjmp-protected stack walk + // (walkVM's inner region and recordSample's native/AGCT unwind), so the + // counter is deliberately not walkVM-specific. + Counters::increment(STACKWALK_LONGJMP_RECOVERED); + longjmp(*thrd->getJmpCtx(), 1); +} + + + // Returns: 0 = not handled (chain to next handler), non-zero = handled int Profiler::crashHandlerInternal(int signo, siginfo_t *siginfo, void *ucontext) { ProfiledThread* thrd = ProfiledThread::current(); @@ -1019,14 +1035,15 @@ int Profiler::crashHandlerInternal(int signo, siginfo_t *siginfo, void *ucontext return 1; // handled } + + // checkFault has its own check if we're in a protected stack walk. + // If the fault is from our protected walk, it will longjmp and never return. + // If it returns, the fault wasn't from our code. + checkFault(thrd); + if (VM::isHotspot()) { // the following checks require vmstructs and therefore HotSpot - // HotspotSupport::checkFault has its own check if we're in a protected stack walk. - // If the fault is from our protected walk, it will longjmp and never return. - // If it returns, the fault wasn't from our code. - HotspotSupport::checkFault(thrd); - // Workaround for JDK-8313796 if needed. Setting cstack=dwarf also helps if (_need_JDK_8313796_workaround && VMStructs::isInterpretedFrameValidFunc((const void *)pc) && diff --git a/ddprof-lib/src/main/cpp/profiler.h b/ddprof-lib/src/main/cpp/profiler.h index c4d7e6e24e..af566fbb92 100644 --- a/ddprof-lib/src/main/cpp/profiler.h +++ b/ddprof-lib/src/main/cpp/profiler.h @@ -215,6 +215,8 @@ class alignas(alignof(SpinLock)) Profiler { } } + static void checkFault(ProfiledThread* thrd = nullptr); + static inline Profiler *instance() { return _instance; } diff --git a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp index 8de3d97c26..7e1e3626f5 100644 --- a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp +++ b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp @@ -14,6 +14,7 @@ #include "safeAccess.h" #include "os.h" #include "threadLocalData.h" +#include "profiler.h" #include "hotspot/hotspotSupport.h" #include "../../main/cpp/gtest_crash_handler.h" @@ -60,7 +61,7 @@ static void fi_signal_wrapper(int signo, siginfo_t* siginfo, void* context) { if (SafeAccess::handle_safefetch(signo, context)) { return; // safefetch load recovered; PC already rewritten to _cont. } - HotspotSupport::checkFault(ProfiledThread::current()); // longjmp if protected + Profiler::checkFault(ProfiledThread::current()); // longjmp if protected // Not protected and not a safefetch fault — real crash. if (signo == SIGBUS && orig_busHandler != nullptr) { orig_busHandler(signo, siginfo, context); diff --git a/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp b/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp index 1f434aa4b4..c264ca86dc 100644 --- a/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp +++ b/ddprof-lib/src/test/cpp/hotspot_crash_protection_ut.cpp @@ -30,7 +30,7 @@ #include #include "threadLocalData.h" #include "hotspot/hotspotSupport.h" - +#include "profiler.h" #include "jvmThread.h" #include "safeAccess.h" #include "os.h" @@ -334,7 +334,7 @@ TEST_F(JmpCtxChainingTest, FaultInInnerFrameDoesNotDisturbOuterFrame) { // --------------------------------------------------------------------------- TEST(CheckFaultGuardTest, NullThreadIsNoop) { - HotspotSupport::checkFault(nullptr); // must not crash + Profiler::checkFault(nullptr); // must not crash } // --------------------------------------------------------------------------- From 01de20f53e156c41d9a2089d77258b516c2dfddb Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 22 Jul 2026 19:56:50 +0200 Subject: [PATCH 28/35] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/profiler.cpp | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/ddprof-lib/src/main/cpp/profiler.cpp b/ddprof-lib/src/main/cpp/profiler.cpp index 9552282d37..d9d27c4154 100644 --- a/ddprof-lib/src/main/cpp/profiler.cpp +++ b/ddprof-lib/src/main/cpp/profiler.cpp @@ -973,26 +973,17 @@ void Profiler::busHandler(int signo, siginfo_t *siginfo, void *ucontext) { } void Profiler::checkFault(ProfiledThread* thrd) { - // Should not get to here (?) - if (thrd == nullptr) { - return; - } - - // Check if longjmp is setup for this thread - if (!thrd->isProtected()) { - return; - } + if (thrd == nullptr || !thrd->isProtected()) { + return; + } - thrd->resetCrashHandler(); - // Shared recovery point for every setjmp/longjmp-protected stack walk - // (walkVM's inner region and recordSample's native/AGCT unwind), so the - // counter is deliberately not walkVM-specific. - Counters::increment(STACKWALK_LONGJMP_RECOVERED); - longjmp(*thrd->getJmpCtx(), 1); + thrd->resetCrashHandler(); + // Shared recovery point for every setjmp/longjmp-protected stack walk + // (walkVM's inner region and recordSample's native/AGCT unwind), so the + // counter is deliberately not walkVM-specific. + Counters::increment(STACKWALK_LONGJMP_RECOVERED); + longjmp(*thrd->getJmpCtx(), 1); } - - - // Returns: 0 = not handled (chain to next handler), non-zero = handled int Profiler::crashHandlerInternal(int signo, siginfo_t *siginfo, void *ucontext) { ProfiledThread* thrd = ProfiledThread::current(); From a66e913022c97e8eb8ff017b544a817828030288 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 22 Jul 2026 19:59:47 +0200 Subject: [PATCH 29/35] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- ddprof-lib/src/main/cpp/vmEntry.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ddprof-lib/src/main/cpp/vmEntry.cpp b/ddprof-lib/src/main/cpp/vmEntry.cpp index ab5240bc31..7391e4c133 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.cpp +++ b/ddprof-lib/src/main/cpp/vmEntry.cpp @@ -218,7 +218,7 @@ CodeCache* VM::openJvmLibrary() { ? libraries->findJvmLibrary("libj9vm") : libraries->findLibraryByAddress((const void *)_asyncGetCallTrace); - // The libaray must have been loaded. Otherwise, we cannot get to here due + // The library must have been loaded. Otherwise, we cannot get to here due // to JVM initialization assert(lib != nullptr && "JVM library must be loaded"); __atomic_store_n(&_libjvm, lib, __ATOMIC_RELEASE); From 040565b32d28423098479b5c8e39a45c0a9d6e3d Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 23 Jul 2026 13:15:54 +0000 Subject: [PATCH 30/35] Fix --- ddprof-lib/src/main/cpp/vmEntry.cpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/ddprof-lib/src/main/cpp/vmEntry.cpp b/ddprof-lib/src/main/cpp/vmEntry.cpp index e498455402..d7d5cb8d32 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.cpp +++ b/ddprof-lib/src/main/cpp/vmEntry.cpp @@ -576,20 +576,23 @@ bool VM::initProfilerBridge(JavaVM *vm, bool attach) { // initLibrary() followed by a JNI-triggered attach) -- the VMStructs init below only // ever runs once. void VM::ready(jvmtiEnv *jvmti, JNIEnv *jni) { - Profiler::check_JDK_8313796_workaround(); - Profiler::setupSignalHandlers(); + if (!VM::isHotspot()) { + return; + } + // Hotspot specific static bool vmstructs_ready = false; - bool expected = false; - if (isHotspot() && - __atomic_compare_exchange_n(&vmstructs_ready, &expected, true, false, - __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) { + static SpinLock lock; + ExclusiveLockGuard guard(&lock); + if (!vmstructs_ready) { + Profiler::check_JDK_8313796_workaround(); + Profiler::setupSignalHandlers(); JitWriteProtection jit(true); CodeCache* lib = openJvmLibrary(); - if (lib != nullptr) { - // Initialize VMStructs - VMStructs::init(lib); - VMStructs::ready(); - } + assert(lib != nullptr && "JVM library must have been loaded"); + // Initialize VMStructs + VMStructs::init(lib); + VMStructs::ready(); + vmstructs_ready = true; } } From 4fe08081e53605562989b5197aa9ce4e33725eee Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 23 Jul 2026 14:26:33 +0000 Subject: [PATCH 31/35] Fix --- ddprof-lib/src/main/cpp/vmEntry.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/ddprof-lib/src/main/cpp/vmEntry.cpp b/ddprof-lib/src/main/cpp/vmEntry.cpp index d7d5cb8d32..01cc1d140a 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.cpp +++ b/ddprof-lib/src/main/cpp/vmEntry.cpp @@ -576,9 +576,6 @@ bool VM::initProfilerBridge(JavaVM *vm, bool attach) { // initLibrary() followed by a JNI-triggered attach) -- the VMStructs init below only // ever runs once. void VM::ready(jvmtiEnv *jvmti, JNIEnv *jni) { - if (!VM::isHotspot()) { - return; - } // Hotspot specific static bool vmstructs_ready = false; static SpinLock lock; @@ -586,12 +583,14 @@ void VM::ready(jvmtiEnv *jvmti, JNIEnv *jni) { if (!vmstructs_ready) { Profiler::check_JDK_8313796_workaround(); Profiler::setupSignalHandlers(); - JitWriteProtection jit(true); - CodeCache* lib = openJvmLibrary(); - assert(lib != nullptr && "JVM library must have been loaded"); - // Initialize VMStructs - VMStructs::init(lib); - VMStructs::ready(); + if (VM::isHotspot()) { + JitWriteProtection jit(true); + CodeCache* lib = openJvmLibrary(); + assert(lib != nullptr && "JVM library must have been loaded"); + // Initialize VMStructs + VMStructs::init(lib); + VMStructs::ready(); + } vmstructs_ready = true; } } From d01cffe24d7b647443f5243b72ffc89ba6fddcd1 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 23 Jul 2026 17:35:30 +0000 Subject: [PATCH 32/35] Fix --- ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp | 4 +--- ddprof-lib/src/main/cpp/vmEntry.cpp | 10 +++++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp b/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp index 1a68238c32..73d7e2aec6 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp @@ -342,9 +342,7 @@ void VMStructs::initOffsets() { } void VMStructs::resolveOffsets() { - if (VM::isOpenJ9() || VM::isZing()) { - return; - } + assert(VM::isHotspot() && "Should not reach here"); if (_klass_offset_addr != NULL) { _klass = (jfieldID)(uintptr_t)(*(int*)_klass_offset_addr << 2 | 2); diff --git a/ddprof-lib/src/main/cpp/vmEntry.cpp b/ddprof-lib/src/main/cpp/vmEntry.cpp index 01cc1d140a..d93740fbe3 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.cpp +++ b/ddprof-lib/src/main/cpp/vmEntry.cpp @@ -577,10 +577,10 @@ bool VM::initProfilerBridge(JavaVM *vm, bool attach) { // ever runs once. void VM::ready(jvmtiEnv *jvmti, JNIEnv *jni) { // Hotspot specific - static bool vmstructs_ready = false; + static bool vmstructs_init = false; static SpinLock lock; ExclusiveLockGuard guard(&lock); - if (!vmstructs_ready) { + if (!vmstructs_init) { Profiler::check_JDK_8313796_workaround(); Profiler::setupSignalHandlers(); if (VM::isHotspot()) { @@ -591,7 +591,11 @@ void VM::ready(jvmtiEnv *jvmti, JNIEnv *jni) { VMStructs::init(lib); VMStructs::ready(); } - vmstructs_ready = true; + vmstructs_init = true; + } else { + if (VM::isHotspot()) { + VMStructs::ready(); + } } } From a5ec8f8b80b9668d00378e64ba4640cade2ffcb2 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 23 Jul 2026 18:20:37 +0000 Subject: [PATCH 33/35] Fix --- ddprof-lib/src/main/cpp/vmEntry.cpp | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/ddprof-lib/src/main/cpp/vmEntry.cpp b/ddprof-lib/src/main/cpp/vmEntry.cpp index d93740fbe3..670c54af6c 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.cpp +++ b/ddprof-lib/src/main/cpp/vmEntry.cpp @@ -577,25 +577,21 @@ bool VM::initProfilerBridge(JavaVM *vm, bool attach) { // ever runs once. void VM::ready(jvmtiEnv *jvmti, JNIEnv *jni) { // Hotspot specific - static bool vmstructs_init = false; + static bool init_signal = false; static SpinLock lock; ExclusiveLockGuard guard(&lock); - if (!vmstructs_init) { + if (!init_signal) { Profiler::check_JDK_8313796_workaround(); Profiler::setupSignalHandlers(); - if (VM::isHotspot()) { - JitWriteProtection jit(true); - CodeCache* lib = openJvmLibrary(); - assert(lib != nullptr && "JVM library must have been loaded"); - // Initialize VMStructs - VMStructs::init(lib); - VMStructs::ready(); - } - vmstructs_init = true; - } else { - if (VM::isHotspot()) { - VMStructs::ready(); - } + init_signal = true; + } + if (VM::isHotspot()) { + JitWriteProtection jit(true); + CodeCache* lib = openJvmLibrary(); + assert(lib != nullptr && "JVM library must have been loaded"); + // Initialize VMStructs + VMStructs::init(lib); + VMStructs::ready(); } } From fdce496f07faee05c1a26a80b90b8b27b4e3c389 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 23 Jul 2026 21:43:18 +0000 Subject: [PATCH 34/35] Add a test --- ddprof-lib/src/test/cpp/faultInjection_ut.cpp | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp index 7e1e3626f5..3ea481b493 100644 --- a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp +++ b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp @@ -10,6 +10,7 @@ #include #include +#include "counters.h" #include "faultInjection.h" #include "safeAccess.h" #include "os.h" @@ -183,4 +184,79 @@ TEST_F(FaultInjectionTest, WalkVmSetjmpRecoversFromInjectedFault) { SUCCEED(); } +// (c3) recordSample's outer setjmp region (PROF-15447): Profiler::recordSample() +// wraps the native/Java unwind in its own jmp_buf, chaining off whatever +// context a caller may already have installed, so a fault in the +// *unprotected* metadata reads surrounding walkVM/walkFP/walkDwarf (isJitCode, +// findFrameDesc, findLibraryByAddress, AGCT in getJavaTraceAsync, frame.link, +// ...) recovers to a partial trace instead of crashing — and, critically, +// restores the caller's jmp_buf chain on both the clean and the recovery path. +// +// recordSample() itself needs a live JVM (ASGCT, VMStructs, an allocated +// _calltrace_buffer) unavailable in this gtest binary, so — following the +// same "replicate the protocol" approach used elsewhere in this suite for +// JVM-dependent code — this test reproduces its exact save/install/setjmp/ +// longjmp/restore sequence verbatim, including the `volatile int num_frames` +// accumulator that must survive the longjmp landing, and drives it with the +// same probabilistic fault injection as WalkVmSetjmpRecoversFromInjectedFault. +// Unlike that test, this one also (a) starts from an already-installed +// "grandparent" jmp_buf to verify nesting/chain-restore, matching how +// recordSample can itself run nested under another sampler's protection, and +// (b) asserts STACKWALK_LONGJMP_RECOVERED actually increments, confirming the +// real Profiler::checkFault() (wired in via fi_signal_wrapper) did the +// recovery rather than some other signal-handling path. +TEST_F(FaultInjectionTest, RecordSampleOuterSetjmpRecoversAndRestoresChain) { + ProfiledThread* t = ProfiledThread::current(); + ASSERT_NE(t, nullptr); + + // Simulate a pre-existing outer protection context, as recordSample must + // support when nested inside another sampler's own protected region. + jmp_buf grandparent_ctx; + t->setJmpCtx(&grandparent_ctx); + + uintptr_t real_slot = 0; // a valid, readable "metadata" slot + uintptr_t base = (uintptr_t)&real_slot; + long long recovered_before = Counters::getCounter(STACKWALK_LONGJMP_RECOVERED); + + // Read again after the setjmp landing below, so it must be volatile — mirrors + // recordSample's own num_frames. + volatile int num_frames = 0; + + jmp_buf unwind_ctx; + jmp_buf* prev_jmp_buf = t->getJmpCtx(); + ASSERT_EQ(&grandparent_ctx, prev_jmp_buf); + + t->setFiRng(0xC0FFEEC0FFEEC0FFULL); + + int jmp_rc = setjmp(unwind_ctx); + if (jmp_rc != 0) { + // Landed here via the real Profiler::checkFault() -> longjmp, triggered by + // an actual injected fault below. + t->setJmpCtx(prev_jmp_buf); + num_frames += 1; // stand-in for makeFrame(..., "break_unwind_fault") + } else { + t->setJmpCtx(&unwind_ctx); + + // Force at least one fire deterministically, then let the tier drive the rest. + for (int i = 0; i < 5000 && num_frames == 0; i++) { + // Raw deref of the (possibly poisoned) base — mirrors the unprotected + // metadata reads recordSample's outer setjmp now guards. + uintptr_t v = *(uintptr_t*)INJECT_FAULT_ADDRESS_LIKELY(base); + asm volatile("" : "+r"(v) : : "memory"); + } + t->setJmpCtx(prev_jmp_buf); + } + + EXPECT_EQ(&grandparent_ctx, t->getJmpCtx()) + << "must restore the caller's jmp_buf chain on both the clean and the " + "recovery path, never leave it cleared or pointing at unwind_ctx"; + EXPECT_EQ(1, num_frames) + << "the volatile accumulator mutated before the fault must retain its " + "value across the longjmp landing"; + EXPECT_GT(Counters::getCounter(STACKWALK_LONGJMP_RECOVERED), recovered_before) + << "checkFault() must have run and incremented the shared recovery counter"; + + t->setJmpCtx(nullptr); // leave the thread unprotected for subsequent tests +} + #endif // __FAULT_INJECTION__ From 490b0557b9d16f598c0d6426e84de29eefac6b67 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 23 Jul 2026 21:58:56 +0000 Subject: [PATCH 35/35] Fix test --- ddprof-lib/src/test/cpp/faultInjection_ut.cpp | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp index 3ea481b493..0ae923b534 100644 --- a/ddprof-lib/src/test/cpp/faultInjection_ut.cpp +++ b/ddprof-lib/src/test/cpp/faultInjection_ut.cpp @@ -221,6 +221,14 @@ TEST_F(FaultInjectionTest, RecordSampleOuterSetjmpRecoversAndRestoresChain) { // Read again after the setjmp landing below, so it must be volatile — mirrors // recordSample's own num_frames. volatile int num_frames = 0; + // Frames "collected" before the fault, e.g. by a getNativeTrace() call that + // partially succeeded before walkJavaStack() faulted. Seeding a non-zero + // value here — mutated between setjmp() and the longjmp below — is what + // actually exercises the volatile qualifier: a non-volatile local would be + // indeterminate after the jump, so the post-recovery checks below would not + // reliably see it. Without this seed, num_frames would still read 0 whether + // or not it were volatile, and the test would prove nothing about volatile. + constexpr int kSeededFrames = 3; jmp_buf unwind_ctx; jmp_buf* prev_jmp_buf = t->getJmpCtx(); @@ -231,14 +239,18 @@ TEST_F(FaultInjectionTest, RecordSampleOuterSetjmpRecoversAndRestoresChain) { int jmp_rc = setjmp(unwind_ctx); if (jmp_rc != 0) { // Landed here via the real Profiler::checkFault() -> longjmp, triggered by - // an actual injected fault below. + // an actual injected fault below. Mirrors recordSample's + // `if (num_frames < _max_stack_depth) { num_frames += makeFrame(...); }`: + // the recovery marker is appended to whatever partial progress survived + // the jump, never resets it. t->setJmpCtx(prev_jmp_buf); num_frames += 1; // stand-in for makeFrame(..., "break_unwind_fault") } else { t->setJmpCtx(&unwind_ctx); + num_frames = kSeededFrames; // mutated before the fault; must survive the longjmp // Force at least one fire deterministically, then let the tier drive the rest. - for (int i = 0; i < 5000 && num_frames == 0; i++) { + for (int i = 0; i < 5000 && num_frames == kSeededFrames; i++) { // Raw deref of the (possibly poisoned) base — mirrors the unprotected // metadata reads recordSample's outer setjmp now guards. uintptr_t v = *(uintptr_t*)INJECT_FAULT_ADDRESS_LIKELY(base); @@ -250,9 +262,9 @@ TEST_F(FaultInjectionTest, RecordSampleOuterSetjmpRecoversAndRestoresChain) { EXPECT_EQ(&grandparent_ctx, t->getJmpCtx()) << "must restore the caller's jmp_buf chain on both the clean and the " "recovery path, never leave it cleared or pointing at unwind_ctx"; - EXPECT_EQ(1, num_frames) - << "the volatile accumulator mutated before the fault must retain its " - "value across the longjmp landing"; + EXPECT_EQ(kSeededFrames + 1, num_frames) + << "the seeded pre-fault value must survive the longjmp landing and the " + "recovery marker must append to it, not overwrite it"; EXPECT_GT(Counters::getCounter(STACKWALK_LONGJMP_RECOVERED), recovered_before) << "checkFault() must have run and incremented the shared recovery counter";