Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/flightRecorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 5 additions & 5 deletions ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -842,7 +840,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;
}
}
Expand All @@ -863,7 +862,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;
Expand Down
20 changes: 5 additions & 15 deletions ddprof-lib/src/main/cpp/hotspot/vmStructs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename T, bool safe = false>
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;
Expand Down Expand Up @@ -1136,10 +1129,7 @@ DECLARE(VMFlag)
static VMFlag* find(const char* name);
static VMFlag *find(const char* name, std::initializer_list<Type> types);

const char* name() {
assert(_flag_name_offset >= 0);
return *(const char**) at(_flag_name_offset);
}
inline const char* name() const;

int type();

Expand Down
31 changes: 31 additions & 0 deletions ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "hotspot/hotspotSupport.h"
#include "hotspot/vmStructs.h"
#include "jvmThread.h"
#include "safeAccess.h"
#include "threadLocalData.h"

inline bool crashProtectionActive() {
Expand All @@ -28,6 +29,31 @@ inline T* cast_to(const void* ptr) {
return reinterpret_cast<T*>(const_cast<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);
}
Comment thread
Copilot marked this conversation as resolved.

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 <typename T, bool safe>
T VMStructs::load_at_offset(int offset) const {
const char* raw = (const char*)this + offset;
if (safe) {
// 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));
}
Comment thread
Copilot marked this conversation as resolved.


VMThread* VMThread::current() {
assert(VM::isHotspot());
return VMThread::cast(JVMThread::current());
Expand Down Expand Up @@ -188,5 +214,10 @@ u16 VMConstMethod::signatureIndex() const {
return *(u16*)at(_constmethod_sig_index_offset);
}

// 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<const char*, true /* safe load */>(_flag_name_offset);
Comment thread
zhengyu123 marked this conversation as resolved.
}

#endif // _HOTSPOT_VMSTRUCTS_INLINE_H
8 changes: 6 additions & 2 deletions ddprof-lib/src/main/cpp/libraries.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
/*
* Copyright 2026, Datadog, Inc.
* SPDX-License-Identifier: Apache-2.0
*/

#include "codeCache.h"
#include "common.h"
#include "findLibraryImpl.h"
#include "hotspot/vmStructs.h"
#include "libraries.h"
#include "libraryPatcher.h"
#include "log.h"
Expand Down Expand Up @@ -191,7 +195,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) {
Expand Down
2 changes: 1 addition & 1 deletion ddprof-lib/src/main/cpp/profiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1264,7 +1264,7 @@ Error Profiler::checkJvmCapabilities() {
}
}

if (!VMStructs::libjvm()->hasDebugSymbols() && !VM::isOpenJ9()) {
if (!VM::libjvm()->hasDebugSymbols()) {
Comment thread
zhengyu123 marked this conversation as resolved.
Log::warn("Install JVM debug symbols to improve profile accuracy");
}

Expand Down
54 changes: 41 additions & 13 deletions ddprof-lib/src/main/cpp/vmEntry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -204,15 +204,20 @@ int JavaVersionAccess::get_hotspot_version(char* prop_value) {
}

CodeCache* VM::openJvmLibrary() {
CodeCache* lib = __atomic_load_n(&_libjvm, __ATOMIC_ACQUIRE);
if (lib != nullptr) {
return lib;
}

Comment thread
zhengyu123 marked this conversation as resolved.
if ((void*)_asyncGetCallTrace == nullptr) {
return nullptr;
}
Comment thread
zhengyu123 marked this conversation as resolved.

Libraries* libraries = Libraries::instance();
CodeCache *lib =
isOpenJ9()
lib = isOpenJ9()
? libraries->findJvmLibrary("libj9vm")
: libraries->findLibraryByAddress((const void *)_asyncGetCallTrace);
__atomic_store_n(&_libjvm, lib, __ATOMIC_RELEASE);
return lib;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -328,9 +333,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);

Expand Down Expand Up @@ -449,6 +451,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(
Expand Down Expand Up @@ -517,13 +528,16 @@ 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);
} else {
// 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);
}
Expand Down Expand Up @@ -557,12 +571,26 @@ 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) {
Comment thread
zhengyu123 marked this conversation as resolved.
Profiler::check_JDK_8313796_workaround();
Profiler::setupSignalHandlers();
if (isHotspot()) {
// Hotspot specific
static bool init_signal = false;
static SpinLock lock;
ExclusiveLockGuard guard(&lock);
if (!init_signal) {
Profiler::check_JDK_8313796_workaround();
Profiler::setupSignalHandlers();
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();
}
}
Expand Down
8 changes: 7 additions & 1 deletion ddprof-lib/src/main/cpp/vmEntry.h
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -172,7 +173,12 @@ class VM {
static CodeCache* openJvmLibrary();

public:
static void *_libjvm;
static inline CodeCache* libjvm() {
CodeCache* lib = __atomic_load_n(&_libjvm, __ATOMIC_ACQUIRE);
assert(lib != nullptr && "Out of order initialization sequence");
return lib;
}
Comment thread
Copilot marked this conversation as resolved.

static AsyncGetCallTrace _asyncGetCallTrace;
static JVM_GetManagement _getManagement;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/*
* Copyright 2026, Datadog, Inc.
* SPDX-License-Identifier: Apache-2.0
*/
package com.datadoghq.profiler;

import java.io.IOException;
Expand Down Expand Up @@ -107,13 +111,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) {
Expand All @@ -124,6 +122,28 @@ 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 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 {
Comment thread
zhengyu123 marked this conversation as resolved.
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")))
.toAbsolutePath();
}
Comment thread
Copilot marked this conversation as resolved.

/**
* 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.
Expand Down
Loading
Loading