From 3a5b1e9b315464bec250ff39cca3849210618c6e Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Mon, 13 Apr 2026 21:16:06 +0000 Subject: [PATCH 01/19] v0 --- .../hotspot/compressedLinenumberStream.cpp | 50 ++++++++++++++++++ .../cpp/hotspot/compressedLinenumberStream.h | 51 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.cpp create mode 100644 ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.h diff --git a/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.cpp b/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.cpp new file mode 100644 index 000000000..e1d4ab1c4 --- /dev/null +++ b/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.cpp @@ -0,0 +1,50 @@ +#include "hotspot/compressedLinenumberStream.h" + +CompressedLineNumberStream::CompressedLineNumberStream(unsigned char* buffer) : + _buffer(buffer), _position(0), _bci(0), _line(0) { +}; + +void CompressedLineNumberStream::reset() { + _position = 0; + _bci = 0; + _line = 0; +} + +bool CompressedLineNumberStream::read_pair() { + unsigned char next = read_byte(); + // Check for terminator + if (next == 0) return false; + if (next == 0xFF) { + // Escape character, regular compression used + _bci += read_signed_int(); + _line += read_signed_int(); + } else { + // Single byte compression used + _bci += next >> 3; + _line += next & 0x7; + } + return true; +} + +uint32_t CompressedLineNumberStream::read_uint() { + const int pos = _position; + const uint32_t b_0 = (uint8_t)_buffer[pos]; //b_0 = a[0] + assert(b_0 >= X); + uint32_t sum = b_0 - X; + if (sum < L) { // common case + _position = pos + 1; + return sum; + } + // must collect more bytes: b[1]...b[4] + int lg_H_i = lg_H; // lg(H)*i == lg(H^^i) + for (int i = 1; ; i++) { // for i in [1..4] + const uint32_t b_i = (uint8_t) _buffer[pos + i]; //b_i = a[i] + assert(b_i >= X); + sum += (b_i - X) << lg_H_i; // sum += (b[i]-X)*(64^^i) + if (b_i < X+L || i == MAX_LENGTH-1) { + _position = pos + i + 1; + return sum; + } + lg_H_i += lg_H; + } + } \ No newline at end of file diff --git a/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.h b/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.h new file mode 100644 index 000000000..def30c1c3 --- /dev/null +++ b/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.h @@ -0,0 +1,51 @@ +#ifndef _HOTSPOT_COMPRESSED_LINENUMBER_STREAM_H +#define _HOTSPOT_COMPRESSED_LINENUMBER_STREAM_H + +#include +#include + +/** + * Implementation of openjdk CompressedLineNumberStream + * https://github.com/openjdk/jdk/blob/master/src/hotspot/share/oops/method.hpp#L910 + * + * Based on open jdk source: + * https://github.com/openjdk/jdk/blob/master/src/hotspot/share/code/compressedStream.hpp/cpp + * https://github.com/openjdk/jdk/blob/master/src/hotspot/share/utilities/unsigned5.hpp/cpp + */ + +class CompressedLineNumberStream { +private: + // Math constants for the modified UNSIGNED5 coding of Pack200 + static const int BitsPerByte = 8; + static const int lg_H = 6; // log-base-2 of H (lg 64 == 6) + static const int H = 1<> 1) ^ -(int)(value & 1); } + int read_signed_int() { return decode_sign(read_uint()); } + uint32_t read_uint(); +}; + +#endif // _HOTSPOT_COMPRESSED_LINENUMBER_STREAM_H \ No newline at end of file From 4f6491ba28b0fd4c6386847148445f9bff7997c5 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Fri, 17 Apr 2026 21:26:08 +0000 Subject: [PATCH 02/19] v1 --- ddprof-lib/src/main/cpp/frame.h | 15 +-- .../src/main/cpp/hotspot/hotspotSupport.cpp | 90 +++++++++++++++++ .../src/main/cpp/hotspot/hotspotSupport.h | 12 +++ ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp | 30 ++++++ ddprof-lib/src/main/cpp/hotspot/vmStructs.h | 57 +++++++++-- .../src/main/cpp/hotspot/vmStructs.inline.h | 90 ++++++++++++++++- ddprof-lib/src/main/cpp/jvmSupport.cpp | 63 ++++++++++++ ddprof-lib/src/main/cpp/jvmSupport.h | 16 ++- ddprof-lib/src/main/cpp/stackWalker.cpp | 1 - ddprof-lib/src/main/cpp/vmEntry.cpp | 99 ++++++------------- ddprof-lib/src/main/cpp/vmEntry.h | 11 +-- 11 files changed, 390 insertions(+), 94 deletions(-) diff --git a/ddprof-lib/src/main/cpp/frame.h b/ddprof-lib/src/main/cpp/frame.h index dbd27c2e0..210ed492e 100644 --- a/ddprof-lib/src/main/cpp/frame.h +++ b/ddprof-lib/src/main/cpp/frame.h @@ -3,13 +3,14 @@ enum FrameTypeId { FRAME_INTERPRETED = 0, - FRAME_JIT_COMPILED = 1, - FRAME_INLINED = 2, - FRAME_NATIVE = 3, - FRAME_CPP = 4, - FRAME_KERNEL = 5, - FRAME_C1_COMPILED = 6, - FRAME_NATIVE_REMOTE = 7, // Native frame with remote symbolication (build-id + pc-offset) + FRAME_INTERPRETED_METHOD = 1, + FRAME_JIT_COMPILED = 2, + FRAME_INLINED = 3, + FRAME_NATIVE = 4, + FRAME_CPP = 5, + FRAME_KERNEL = 6, + FRAME_C1_COMPILED = 7, + FRAME_NATIVE_REMOTE = 8, // Native frame with remote symbolication (build-id + pc-offset) FRAME_TYPE_MAX = FRAME_NATIVE_REMOTE // Maximum valid frame type }; diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 2b33f163f..198bfe37b 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -967,3 +967,93 @@ int HotspotSupport::walkJavaStack(StackWalkRequest& request) { } return java_frames; } + + +void HotspotSupport::loadAllMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni) { + assert(VM::isHotspot() && "Hotspot only"); + + jint class_count = 0; + jclass *classes = nullptr; + int loaded_count = 0; + + if (jvmti->GetLoadedClasses(&class_count, &classes) == 0) { + for (int i = 0; i < class_count; i++) { + jclass klass = classes[i]; + jobject cld; + // Hotpsot only: loaded by bootstrap class loader, which is never unloaded, + // we use Method instead. + if (jvmti->GetClassLoader(klass, &cld) == JVMTI_ERROR_NONE && cld == nullptr) { + VMOopHandle* klass_handle = (VMOopHandle*)klass; + VMKlass* vmklass = VMKlass::fromOop(klass_handle->oop()); + assert(vmklass != nullptr); + VMClassLoaderData* cld = vmklass->classLoaderData(); + assert(cld != nullptr); + char* signature_ptr; + jvmti->GetClassSignature(klass, &signature_ptr, nullptr); + TEST_LOG("processing bootstrap class %s", signature_ptr); + // Lambda classes can be unloaded, exlcude them + if (!cld->hasClassMirrorHolder() && !cld->isAnonymous()) { + TEST_LOG("Skipping class %s",signature_ptr); + continue; + } + loadMethodIDsImpl(jvmti, jni, klass); + loaded_count++; + } + } + jvmti->Deallocate((unsigned char *)classes); + } + TEST_LOG("Preloaded jmethodIDs for %d/%d classes", loaded_count, class_count); +} + +static void patchClassLoaderData(JNIEnv* jni, jclass klass) { + bool needs_patch = VM::hotspot_version() == 8; + if (needs_patch) { + // Workaround for JVM bug https://bugs.openjdk.org/browse/JDK-8062116 + // Preallocate space for jmethodIDs at the beginning of the list (rather than at the end) + // This is relevant only for JDK 8 - later versions do not have this bug + if (VMStructs::hasClassLoaderData()) { + VMKlass *vmklass = VMKlass::fromJavaClass(jni, klass); + int method_count = vmklass->methodCount(); + if (method_count > 0) { + VMClassLoaderData *cld = vmklass->classLoaderData(); + cld->lock(); + for (int i = 0; i < method_count; i += MethodList::SIZE) { + *cld->methodList() = new MethodList(*cld->methodList()); + } + cld->unlock(); + } + } + } +} + +bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass) { + patchClassLoaderData(jni, klass); + + jobject cld; + // Hotpsot only: loaded by bootstrap class loader, which is never unloaded, + // we use Method instead. + if (jvmti->GetClassLoader(klass, &cld) == JVMTI_ERROR_NONE && cld == nullptr) { + VMOopHandle* klass_handle = VMOopHandle::cast(klass); + VMKlass* vmklass = VMKlass::fromOop(klass_handle->oop()); + assert(vmklass != nullptr); + VMClassLoaderData* cld = vmklass->classLoaderData(); + assert(cld != nullptr); + char* signature_ptr; + jvmti->GetClassSignature(klass, &signature_ptr, nullptr); + TEST_LOG("processing bootstrap class %s", signature_ptr); + // Lambda classes can be unloaded, exlcude them + if (!cld->hasClassMirrorHolder() && !cld->isAnonymous()) { + TEST_LOG("Skipping class %s",signature_ptr); + return false; + } + } + + return JVMSupport::loadMethodIDsImpl(jvmti, jni, klass); +} + +void JNICALL HotspotSupport::NativeMethodBind(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, + jmethodID method, void *address, + void **new_address_ptr) { + VMStructs::NativeMethodBind(jvmti, jni, thread, method, address, new_address_ptr); +} + diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h index 591d61dcb..2b85d8d8f 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h @@ -9,9 +9,14 @@ #include "stackWalker.h" +#include +#include + class ProfiledThread; class HotspotSupport { + friend class JVMSupport; + private: static int walkVM(void* ucontext, ASGCT_CallFrame* frames, int max_depth, StackWalkFeatures features, EventType event_type, @@ -24,9 +29,16 @@ class HotspotSupport { int max_depth, StackContext *java_ctx, bool *truncated); + static bool loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass); public: static void checkFault(ProfiledThread* thrd = nullptr); static int walkJavaStack(StackWalkRequest& request); + static void loadAllMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni); + + + static void JNICALL NativeMethodBind(jvmtiEnv *jvmti, JNIEnv *jni, + jthread thread, jmethodID method, + void *address, void **new_address_ptr); }; #endif // _HOTSPOT_HOTSPOTSUPPORT_H diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp b/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp index b3d047b27..459d7e541 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp @@ -8,6 +8,7 @@ #include #include #include +#include "hotspot/compressedLinenumberStream.h" #include "hotspot/vmStructs.inline.h" #include "vmEntry.h" #include "jniHelper.h" @@ -757,6 +758,35 @@ jmethodID VMMethod::validatedId() { return NULL; } +bool VMMethod::getLineNumberTable(jint* entry_count_ptr, + jvmtiLineNumberEntry** table_ptr) { + if (!hasLineNumberTable()) { + return false; + } + + assert(entry_count_ptr != nullptr); + assert(table_ptr != nullptr); + unsigned char* table_start = (unsigned char*)codeBase() + codeSize(); + int count = 0; + CompressedLineNumberStream stream(table_start); + while (stream.read_pair()) { + count++; + } + + jvmtiLineNumberEntry* table = (jvmtiLineNumberEntry*)malloc(count * sizeof(jvmtiLineNumberEntry)); + stream.reset(); + count = 0; + while (stream.read_pair()) { + table[count].start_location = (jlocation)stream.bci(); + table[count].line_number = (jint)stream.line(); + count++; + } + *table_ptr = table; + *entry_count_ptr = count; + + return true; +} + VMNMethod* CodeHeap::findNMethod(char* heap, const void* pc) { unsigned char* heap_start = *(unsigned char**)(heap + _code_heap_memory_offset + _vs_low_offset); unsigned char* segmap = *(unsigned char**)(heap + _code_heap_segmap_offset + _vs_low_offset); diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h index d1d217039..4ec850b26 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h @@ -104,7 +104,7 @@ inline T* cast_to(const void* ptr) { * For example: * f(VMClassLoaderData, MATCH_SYMBOLS("ClassLoaderData")) -> * if (matchAny((char*)[] {"ClassLoaderData", nullptr})) { - * _ClassLoaderData_size = size; + * _VMClassLoaderData_size = size; * continue; * } * @@ -126,7 +126,8 @@ inline T* cast_to(const void* ptr) { f(VMMethod, MATCH_SYMBOLS("Method")) \ f(VMNMethod, MATCH_SYMBOLS("nmethod")) \ f(VMSymbol, MATCH_SYMBOLS("Symbol")) \ - f(VMThread, MATCH_SYMBOLS("Thread")) + f(VMThread, MATCH_SYMBOLS("Thread")) \ + f(VMOopHandle, MATCH_SYMBOLS("OopHandle")) /** * Following macros define field offsets, addresses or values of JVM classes that are exported by @@ -170,6 +171,10 @@ typedef void* address; type_begin(VMConstMethod, MATCH_SYMBOLS("ConstMethod")) \ field(_constmethod_constants_offset, offset, MATCH_SYMBOLS("_constants")) \ field(_constmethod_idnum_offset, offset, MATCH_SYMBOLS("_method_idnum")) \ + field(_constmethod_flags_offset, offset, MATCH_SYMBOLS("_flags._flags", "_flags")) \ + field(_constmethod_code_size, offset, MATCH_SYMBOLS("_code_size")) \ + field(_constmethod_name_index_offset, offset, MATCH_SYMBOLS("_name_index")) \ + field(_constmethod_sig_index_offset, offset, MATCH_SYMBOLS("_signatire+index")) \ type_end() \ type_begin(VMConstantPool, MATCH_SYMBOLS("ConstantPool")) \ field(_pool_holder_offset, offset, MATCH_SYMBOLS("_pool_holder")) \ @@ -182,6 +187,8 @@ typedef void* address; type_end() \ type_begin(VMClassLoaderData, MATCH_SYMBOLS("ClassLoaderData")) \ field(_class_loader_data_next_offset, offset, MATCH_SYMBOLS("_next")) \ + field_with_version(_class_loader_data_has_class_mirror_holder_offset, offset, 17, MAX_VERSION, MATCH_SYMBOLS("_has_class_mirror_holder")) \ + field_with_version(_class_loader_data_is_anonymous_offset, offset, 11, MAX_VERSION, MATCH_SYMBOLS("_is_anonymous")) \ type_end() \ type_begin(VMJavaClass, MATCH_SYMBOLS("java_lang_Class")) \ field(_klass_offset_addr, address, MATCH_SYMBOLS("_klass_offset")) \ @@ -274,6 +281,9 @@ typedef void* address; field(_narrow_klass_base_addr, address, MATCH_SYMBOLS("_narrow_klass._base", "_base")) \ field(_narrow_klass_shift_addr, address, MATCH_SYMBOLS("_narrow_klass._shift", "_shift")) \ field(_collected_heap_addr, address, MATCH_SYMBOLS("_collectedHeap")) \ + type_end() \ + type_begin(VMOopHandle, MATCH_SYMBOLS("OopHandle")) \ + field(_oop_handle_obj_offset, offset, MATCH_SYMBOLS("_obj")) \ type_end() /** @@ -403,6 +413,13 @@ class VMStructs { return ptr; } + const char* at(int offset) const { + const char* ptr = (const char*)this + offset; + assert(crashProtectionActive() || SafeAccess::isReadable(ptr)); + return ptr; + + } + static bool goodPtr(const void* ptr) { return (uintptr_t)ptr >= 0x1000 && ((uintptr_t)ptr & (sizeof(uintptr_t) - 1)) == 0; } @@ -574,6 +591,9 @@ DECLARE(VMClassLoaderData) MethodList** methodList() { return (MethodList**) at(sizeof(uintptr_t) * 6 + 8); } + + inline bool hasClassMirrorHolder() const; + inline bool isAnonymous() const; DECLARE_END DECLARE(VMKlass) @@ -676,6 +696,11 @@ DECLARE(VMJavaFrameAnchor) } DECLARE_END +DECLARE(VMOopHandle) +public: + inline uintptr_t oop() const; +DECLARE_END + // Copied from JDK's globalDefinitions.hpp 'JavaThreadState' enum enum JVMJavaThreadState { _thread_uninitialized = 0, // should never happen (missing initialization) @@ -768,13 +793,22 @@ DECLARE_END DECLARE(VMConstMethod) DECLARE_END +DECLARE(VMConstantPool) +public: + inline VMKlass* holder() const; + inline VMSymbol* symbolAt(int index) const; +private: + inline intptr_t* base() const; +DECLARE_END DECLARE(VMMethod) - private: +private: + // ref: constMethodFlags.hpp in hotspot src + static constexpr uint32_t has_linenumber_table = 1 << 0; + static bool check_jmethodID_J9(jmethodID id); static bool check_jmethodID_hotspot(jmethodID id); - - public: +public: jmethodID id(); // Performs extra validation when VMMethod comes from incomplete frame @@ -793,8 +827,17 @@ DECLARE(VMMethod) return *(const char**) at(_method_constmethod_offset) + VMConstMethod::type_size(); } - inline VMNMethod* code(); - + inline VMNMethod* code() const; + inline uint16_t codeSize() const; + inline uint32_t flags() const; + inline bool hasLineNumberTable() const; + inline void* codeBase() const; + inline VMConstantPool* constantPool() const; + inline VMSymbol* name() const; + inline VMSymbol* signature() const; + inline VMKlass* methodHolder() const; + bool getLineNumberTable(jint* entry_count_ptr, + jvmtiLineNumberEntry** table_ptr); static bool check_jmethodID(jmethodID id); DECLARE_END diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h index 03a81fea6..3b7dd3879 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h @@ -10,6 +10,8 @@ #include "hotspot/vmStructs.h" #include "jvmThread.h" +#include + VMThread* VMThread::current() { return VMThread::cast(JVMThread::current()); } @@ -46,7 +48,88 @@ bool VMThread::hasJavaThreadVtable() { (SafeAccess::load(&vtbl[5]) == _java_thread_vtbl[5]) >= 2; } -VMNMethod* VMMethod::code() { +bool VMClassLoaderData::isAnonymous() const { + if (_class_loader_data_is_anonymous_offset >= 0) { + return *(bool*) at(_class_loader_data_is_anonymous_offset); + } + return false; +} + +bool VMClassLoaderData::hasClassMirrorHolder() const { + if (_class_loader_data_has_class_mirror_holder_offset >= 0) { + return *(bool*) at(_class_loader_data_has_class_mirror_holder_offset); + } + return false; +} + +VMKlass* VMConstantPool::holder() const { + assert(_pool_holder_offset >= 0); + return VMKlass::cast(at(_pool_holder_offset)); +} + +VMSymbol* VMConstantPool::symbolAt(int index) const { + return VMSymbol::cast(&base()[index]); +} + +intptr_t* VMConstantPool::base() const { + assert(_VMConstantPool_size > 0); + return (intptr_t*)(((char*)this) + _VMConstantPool_size); +} + +uint16_t VMMethod::codeSize() const { + assert(_constmethod_code_size >= 0); + address code_size_addr = *(unsigned char**)at(_method_constmethod_offset) + _constmethod_code_size; + uint16_t code_size = *(uint16_t*)code_size_addr; + TEST_LOG("VMMethod::codeSize(): code_size=%u\n", code_size); + return code_size; +} + +uint32_t VMMethod::flags() const { + assert(_constmethod_flags_offset >= 0); + return *(uint32_t*) ( *(const char**) at(_method_constmethod_offset) + _constmethod_flags_offset ); +} + +bool VMMethod::hasLineNumberTable() const { + return (flags() & has_linenumber_table) != 0; +} + +address VMMethod::codeBase() const { + assert(_method_constmethod_offset >= 0); + const char* const_method = (const char*) SafeAccess::load((void**) at(_method_constmethod_offset)); + return (address)(const_method+1); +} + +VMConstantPool* VMMethod::constantPool() const { + assert(_method_constmethod_offset >= 0); + const char* const_method = (const char*) SafeAccess::load((void**) at(_method_constmethod_offset)); + assert(goodPtr(const_method)); + assert(_constmethod_constants_offset >= 0); + VMConstantPool* cpool = *(VMConstantPool**) (const_method + _constmethod_constants_offset); + return cpool; +} + +VMSymbol* VMMethod::name() const { + VMConstantPool* cpool = constantPool(); + assert(_method_constmethod_offset >= 0); + const char* const_method = (const char*) SafeAccess::load((void**) at(_method_constmethod_offset)); + assert(goodPtr(const_method)); + assert(_constmethod_name_index_offset >= 0); + int name_index = *(uint16_t*) (const_method + _constmethod_name_index_offset); + return cpool->symbolAt(name_index); + +} + +VMSymbol* VMMethod::signature() const { + VMConstantPool* cpool = constantPool(); + assert(_method_constmethod_offset >= 0); + const char* const_method = (const char*) SafeAccess::load((void**) at(_method_constmethod_offset)); + assert(goodPtr(const_method)); + assert(_constmethod_sig_index_offset >= 0); + int signature_index = *(uint16_t*) (const_method + _constmethod_sig_index_offset); + return cpool->symbolAt(signature_index); +} + +VMNMethod* VMMethod::code() const { assert(_method_code_offset >= 0); const void* code_ptr = *(const void**) at(_method_code_offset); return VMNMethod::cast(code_ptr); @@ -67,4 +150,9 @@ VMMethod* VMThread::compiledMethod() { return NULL; } +uintptr_t VMOopHandle::oop() const { + assert(_oop_handle_obj_offset >= 0); + return *(uintptr_t*) at(_oop_handle_obj_offset); +} + #endif // _HOTSPOT_VMSTRUCTS_INLINE_H diff --git a/ddprof-lib/src/main/cpp/jvmSupport.cpp b/ddprof-lib/src/main/cpp/jvmSupport.cpp index 2e9b71d6d..f29fb8f20 100644 --- a/ddprof-lib/src/main/cpp/jvmSupport.cpp +++ b/ddprof-lib/src/main/cpp/jvmSupport.cpp @@ -14,6 +14,7 @@ #include "hotspot/hotspotSupport.h" #include +#include int JVMSupport::walkJavaStack(StackWalkRequest& request) { if (VM::isHotspot()) { @@ -54,3 +55,65 @@ int JVMSupport::asyncGetCallTrace(ASGCT_CallFrame *frames, int max_depth, void* Profiler::instance()->incFailure(-trace.num_frames); return makeFrame(frames, BCI_ERROR, err_string); } + +void JVMSupport::loadAllMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni) { + jint class_count = 0; + jclass *classes = nullptr; + int loaded_count = 0; + + if (jvmti->GetLoadedClasses(&class_count, &classes) == JVMTI_ERROR_NONE) { + for (int i = 0; i < class_count; i++) { + if(loadMethodIDs(jvmti, jni, classes[i])) { + loaded_count++; + } + } + jvmti->Deallocate((unsigned char *)classes); + } + TEST_LOG("Preloaded jmethodIDs for %d/%d classes", loaded_count, class_count); +} + +bool JVMSupport::loadMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass) { + if (VM::isHotspot()) { + return HotspotSupport::loadMethodIDsImpl(jvmti, jni, klass); + } else { + return loadMethodIDsImpl(jvmti, jni, klass); + } + +} + +bool JVMSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass) { + // CRITICAL: GetClassMethods must be called to preallocate jmethodIDs for AsyncGetCallTrace. + // AGCT operates in signal handlers where lock acquisition is forbidden, so jmethodIDs must + // exist before profiling encounters them. Without preallocation, AGCT cannot identify methods + // in stack traces, breaking profiling functionality. + // + // JVM-internal allocation: This triggers JVM to allocate jmethodIDs internally, which persist + // until class unload. High class churn causes significant memory growth, but this is inherent + // to AGCT architecture and necessary for signal-safe profiling. + // + // See: https://mostlynerdless.de/blog/2023/07/17/jmethodids-in-profiling-a-tale-of-nightmares/ + jint method_count; + jmethodID *methods; + if (jvmti->GetClassMethods(klass, &method_count, &methods) == JVMTI_ERROR_NONE) { + jvmti->Deallocate((unsigned char *)methods); + return true; + } + return false; +} + + + // JVMTI callbacks +void JNICALL JVMSupport::ClassPrepare(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, + jclass klass) { + if (VM::isHotspot()) { + + } else { + loadMethodIDsImpl(jvmti, jni, klass); + } +} + +void JNICALL JVMSupport::ClassLoad(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, + jclass klass) { + // Needed only for AsyncGetCallTrace support +} + diff --git a/ddprof-lib/src/main/cpp/jvmSupport.h b/ddprof-lib/src/main/cpp/jvmSupport.h index 9b8a65dc2..be0177148 100644 --- a/ddprof-lib/src/main/cpp/jvmSupport.h +++ b/ddprof-lib/src/main/cpp/jvmSupport.h @@ -20,11 +20,25 @@ enum StackRecovery { PROBE_SP = 0x100, }; - class JVMSupport { + friend class HotspotSupport; + static int asyncGetCallTrace(ASGCT_CallFrame *frames, int max_depth, void* ucontext); + + // J9 and Zing shared implementation + static bool loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass); + public: static int walkJavaStack(StackWalkRequest& request); + + static void loadAllMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni); + static bool loadMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass); + + // JVMTI callback + static void JNICALL ClassPrepare(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, + jclass klass); + static void JNICALL ClassLoad(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, + jclass klass); }; #endif // _JVMSUPPORT_H diff --git a/ddprof-lib/src/main/cpp/stackWalker.cpp b/ddprof-lib/src/main/cpp/stackWalker.cpp index cc608efe5..642d54088 100644 --- a/ddprof-lib/src/main/cpp/stackWalker.cpp +++ b/ddprof-lib/src/main/cpp/stackWalker.cpp @@ -10,7 +10,6 @@ #include "profiler.h" #include "stackFrame.h" #include "symbols.h" -#include "hotspot/vmStructs.inline.h" #include "jvmThread.h" #include "thread.h" diff --git a/ddprof-lib/src/main/cpp/vmEntry.cpp b/ddprof-lib/src/main/cpp/vmEntry.cpp index 84acd256e..e9d94fefb 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.cpp +++ b/ddprof-lib/src/main/cpp/vmEntry.cpp @@ -10,6 +10,7 @@ #include "context.h" #include "j9/j9Support.h" #include "jniHelper.h" +#include "jvmSupport.h" #include "jvmThread.h" #include "libraries.h" #include "log.h" @@ -18,6 +19,7 @@ #include "safeAccess.h" #include "hotspot/vmStructs.h" #include "hotspot/jitCodeCache.h" +#include "hotspot/hotspotSupport.h" #include #include #include @@ -425,41 +427,48 @@ bool VM::initProfilerBridge(JavaVM *vm, bool attach) { _jvmti->AddCapabilities(&capabilities); jvmtiEventCallbacks callbacks = {0}; + _jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)); + callbacks.VMInit = VMInit; callbacks.VMDeath = VMDeath; - callbacks.ClassLoad = ClassLoad; - callbacks.ClassPrepare = ClassPrepare; - callbacks.CompiledMethodLoad = JitCodeCache::CompiledMethodLoad; - callbacks.DynamicCodeGenerated = JitCodeCache::DynamicCodeGenerated; + callbacks.ClassLoad = JVMSupport::ClassLoad; + callbacks.ClassPrepare = JVMSupport::ClassPrepare; callbacks.ThreadStart = Profiler::ThreadStart; callbacks.ThreadEnd = Profiler::ThreadEnd; callbacks.SampledObjectAlloc = ObjectSampler::SampledObjectAlloc; callbacks.GarbageCollectionFinish = LivenessTracker::GarbageCollectionFinish; - callbacks.NativeMethodBind = VMStructs::NativeMethodBind; - _jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)); + + // Hotspot only + callbacks.CompiledMethodLoad = JitCodeCache::CompiledMethodLoad; + callbacks.DynamicCodeGenerated = JitCodeCache::DynamicCodeGenerated; + callbacks.NativeMethodBind = HotspotSupport::NativeMethodBind; + _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, NULL); _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_LOAD, NULL); _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, NULL); - _jvmti->SetEventNotificationMode(JVMTI_ENABLE, - JVMTI_EVENT_DYNAMIC_CODE_GENERATED, NULL); - _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_NATIVE_METHOD_BIND, - NULL); - - if (hotspot_version() == 0 || !CodeHeap::available()) { - // Workaround for JDK-8173361: avoid CompiledMethodLoad events when possible + // Hotspot only + if (isHotspot()) { _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}); - if (f != NULL && f->isDefault()) { - f->set(1); + JVMTI_EVENT_DYNAMIC_CODE_GENERATED, NULL); + _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_NATIVE_METHOD_BIND, + NULL); + if (hotspot_version() == 0 || !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}); + if (f != NULL && f->isDefault()) { + f->set(1); + } } } + // if the user sets -XX:+UseAdaptiveGCBoundary we will just disable the // profiler to avoid the risk of crashing flag was made obsolete (inert) in 15 // (see JDK-8228991) and removed in 16 (see JDK-8231560) @@ -525,52 +534,8 @@ void *VM::getLibraryHandle(const char *name) { return RTLD_DEFAULT; } -void VM::loadMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass) { - bool needs_patch = VM::hotspot_version() == 8; - if (needs_patch) { - // Workaround for JVM bug https://bugs.openjdk.org/browse/JDK-8062116 - // Preallocate space for jmethodIDs at the beginning of the list (rather than at the end) - // This is relevant only for JDK 8 - later versions do not have this bug - if (VMStructs::hasClassLoaderData()) { - VMKlass *vmklass = VMKlass::fromJavaClass(jni, klass); - int method_count = vmklass->methodCount(); - if (method_count > 0) { - VMClassLoaderData *cld = vmklass->classLoaderData(); - cld->lock(); - for (int i = 0; i < method_count; i += MethodList::SIZE) { - *cld->methodList() = new MethodList(*cld->methodList()); - } - cld->unlock(); - } - } - } - - // CRITICAL: GetClassMethods must be called to preallocate jmethodIDs for AsyncGetCallTrace. - // AGCT operates in signal handlers where lock acquisition is forbidden, so jmethodIDs must - // exist before profiling encounters them. Without preallocation, AGCT cannot identify methods - // in stack traces, breaking profiling functionality. - // - // JVM-internal allocation: This triggers JVM to allocate jmethodIDs internally, which persist - // until class unload. High class churn causes significant memory growth, but this is inherent - // to AGCT architecture and necessary for signal-safe profiling. - // - // See: https://mostlynerdless.de/blog/2023/07/17/jmethodids-in-profiling-a-tale-of-nightmares/ - jint method_count; - jmethodID *methods; - if (jvmti->GetClassMethods(klass, &method_count, &methods) == 0) { - jvmti->Deallocate((unsigned char *)methods); - } -} - void VM::loadAllMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni) { - jint class_count; - jclass *classes; - if (jvmti->GetLoadedClasses(&class_count, &classes) == 0) { - for (int i = 0; i < class_count; i++) { - loadMethodIDs(jvmti, jni, classes[i]); - } - jvmti->Deallocate((unsigned char *)classes); - } + JVMSupport::loadAllMethodIDs(jvmti, jni); } void JNICALL VM::VMInit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) { @@ -602,7 +567,7 @@ VM::RedefineClassesHook(jvmtiEnv *jvmti, jint class_count, JNIEnv *env = jni(); for (int i = 0; i < class_count; i++) { if (class_definitions[i].klass != NULL) { - loadMethodIDs(jvmti, env, class_definitions[i].klass); + JVMSupport::loadMethodIDs(jvmti, env, class_definitions[i].klass); } } } @@ -619,7 +584,7 @@ jvmtiError VM::RetransformClassesHook(jvmtiEnv *jvmti, jint class_count, JNIEnv *env = jni(); for (int i = 0; i < class_count; i++) { if (classes[i] != NULL) { - loadMethodIDs(jvmti, env, classes[i]); + JVMSupport::loadMethodIDs(jvmti, env, classes[i]); } } } diff --git a/ddprof-lib/src/main/cpp/vmEntry.h b/ddprof-lib/src/main/cpp/vmEntry.h index 322436558..b6049f797 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.h +++ b/ddprof-lib/src/main/cpp/vmEntry.h @@ -75,6 +75,7 @@ typedef struct _asgct_callframe { jmethodID method_id; unsigned long packed_remote_frame; // packed RemoteFrameInfo data const char* native_function_name; + const void* method; // Hotspot only, direct pointer to JVM method }; } ASGCT_CallFrame; @@ -135,7 +136,6 @@ class VM { static void ready(jvmtiEnv *jvmti, JNIEnv *jni); static void applyPatch(char *func, const char *patch, const char *end_patch); static void *getLibraryHandle(const char *name); - static void loadMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass); static void loadAllMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni); static bool initShared(JavaVM *vm); @@ -192,15 +192,6 @@ class VM { static void JNICALL VMInit(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread); static void JNICALL VMDeath(jvmtiEnv *jvmti, JNIEnv *jni); - static void JNICALL ClassLoad(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, - jclass klass) { - // Needed only for AsyncGetCallTrace support - } - - static void JNICALL ClassPrepare(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, - jclass klass) { - loadMethodIDs(jvmti, jni, klass); - } static jvmtiError JNICALL RedefineClassesHook(jvmtiEnv *jvmti, jint class_count, From 491de93143019d4e5c12fd51c54b9d5310f00b75 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 22 Apr 2026 13:20:01 +0000 Subject: [PATCH 03/19] v1 --- ddprof-lib/src/main/cpp/flightRecorder.cpp | 377 +---------------- ddprof-lib/src/main/cpp/flightRecorder.h | 116 +----- ddprof-lib/src/main/cpp/hotspot/classloader.h | 19 + .../src/main/cpp/hotspot/classloader.inline.h | 29 ++ .../src/main/cpp/hotspot/hotspotSupport.cpp | 88 ++-- ddprof-lib/src/main/cpp/hotspot/vmStructs.h | 22 +- .../src/main/cpp/hotspot/vmStructs.inline.h | 45 ++- ddprof-lib/src/main/cpp/lookup.cpp | 378 ++++++++++++++++++ ddprof-lib/src/main/cpp/lookup.h | 46 +++ ddprof-lib/src/main/cpp/methodInfo.cpp | 35 ++ ddprof-lib/src/main/cpp/methodInfo.h | 112 ++++++ ddprof-lib/src/main/cpp/stackWalker.cpp | 2 +- ddprof-lib/src/main/cpp/stackWalker.inline.h | 8 - 13 files changed, 715 insertions(+), 562 deletions(-) create mode 100644 ddprof-lib/src/main/cpp/hotspot/classloader.h create mode 100644 ddprof-lib/src/main/cpp/hotspot/classloader.inline.h create mode 100644 ddprof-lib/src/main/cpp/lookup.cpp create mode 100644 ddprof-lib/src/main/cpp/lookup.h create mode 100644 ddprof-lib/src/main/cpp/methodInfo.cpp create mode 100644 ddprof-lib/src/main/cpp/methodInfo.h diff --git a/ddprof-lib/src/main/cpp/flightRecorder.cpp b/ddprof-lib/src/main/cpp/flightRecorder.cpp index eeaa3f32d..ea7cb7ec4 100644 --- a/ddprof-lib/src/main/cpp/flightRecorder.cpp +++ b/ddprof-lib/src/main/cpp/flightRecorder.cpp @@ -17,6 +17,7 @@ #include "incbin.h" #include "jfrMetadata.h" #include "jniHelper.h" +#include "lookup.h" #include "os.h" #include "profiler.h" #include "rustDemangler.h" @@ -44,382 +45,6 @@ static const char *const SETTING_RING[] = {NULL, "kernel", "user", "any"}; static const char *const SETTING_CSTACK[] = {NULL, "no", "fp", "dwarf", "lbr"}; -SharedLineNumberTable::~SharedLineNumberTable() { - // Always attempt to deallocate if we have a valid pointer - // JVMTI spec requires that memory allocated by GetLineNumberTable - // must be freed with Deallocate - if (_ptr != nullptr) { - jvmtiEnv *jvmti = VM::jvmti(); - if (jvmti != nullptr) { - jvmtiError err = jvmti->Deallocate((unsigned char *)_ptr); - // If Deallocate fails, log it for debugging (this could indicate a JVM bug) - // JVMTI_ERROR_ILLEGAL_ARGUMENT means the memory wasn't allocated by JVMTI - // which would be a serious bug in GetLineNumberTable - if (err != JVMTI_ERROR_NONE) { - TEST_LOG("Unexpected error while deallocating linenumber table: %d", err); - } - } else { - TEST_LOG("WARNING: Cannot deallocate line number table - JVMTI is null"); - } - // Decrement counter whenever destructor runs (symmetric with increment at creation) - Counters::decrement(LINE_NUMBER_TABLES); - } -} - -void Lookup::fillNativeMethodInfo(MethodInfo *mi, const char *name, - const char *lib_name) { - mi->_class = _classes->lookup(""); - // TODO return the library name once we figured out how to cooperate with the - // backend - // if (lib_name == NULL) { - // mi->_class = _classes->lookup(""); - // } else if (lib_name[0] == '[' && lib_name[1] != 0) { - // mi->_class = _classes->lookup(lib_name + 1, strlen(lib_name) - - // 2); - // } else { - // mi->_class = _classes->lookup(lib_name); - // } - - mi->_modifiers = 0x100; - mi->_line_number_table = nullptr; - - if (name[0] == '_' && name[1] == 'Z') { - int status; - char *demangled = abi::__cxa_demangle(name, NULL, NULL, &status); - if (demangled != NULL) { - cutArguments(demangled); - mi->_sig = _symbols.lookup("()L;"); - mi->_type = FRAME_CPP; - - // Rust legacy demangling - if (RustDemangler::is_probably_rust_legacy(demangled)) { - std::string rust_demangled = RustDemangler::demangle(demangled); - mi->_name = _symbols.lookup(rust_demangled.c_str()); - } else { - mi->_name = _symbols.lookup(demangled); - } - free(demangled); - return; - } - } - - size_t len = strlen(name); - if (len >= 4 && strcmp(name + len - 4, "_[k]") == 0) { - mi->_name = _symbols.lookup(name, len - 4); - mi->_sig = _symbols.lookup("(Lk;)L;"); - mi->_type = FRAME_KERNEL; - } else { - mi->_name = _symbols.lookup(name); - mi->_sig = _symbols.lookup("()L;"); - mi->_type = FRAME_NATIVE; - } -} - -void Lookup::fillRemoteFrameInfo(MethodInfo *mi, const RemoteFrameInfo *rfi) { - // Store build-id in the class name field - mi->_class = _classes->lookup(rfi->build_id); - - // Store PC offset in hex format in the signature field - char offset_hex[32]; - snprintf(offset_hex, sizeof(offset_hex), "0x%" PRIxPTR, rfi->pc_offset); - mi->_sig = _symbols.lookup(offset_hex); - - // Use same modifiers as regular native frames (0x100 = ACC_NATIVE for consistency) - mi->_modifiers = 0x100; - // Use FRAME_NATIVE_REMOTE type to indicate remote symbolication - mi->_type = FRAME_NATIVE_REMOTE; - mi->_line_number_table = nullptr; - - // Method name indicates need for remote symbolication - mi->_name = _symbols.lookup(""); -} - -void Lookup::cutArguments(char *func) { - char *p = strrchr(func, ')'); - if (p == NULL) - return; - - int balance = 1; - while (--p > func) { - if (*p == '(' && --balance == 0) { - *p = 0; - return; - } else if (*p == ')') { - balance++; - } - } -} - -void Lookup::fillJavaMethodInfo(MethodInfo *mi, jmethodID method, - bool first_time) { - JNIEnv *jni = VM::jni(); - if (jni->PushLocalFrame(64) != 0) { - return; - } - jvmtiEnv *jvmti = VM::jvmti(); - - jvmtiPhase phase; - jclass method_class = NULL; - // invariant: these strings must remain null, or be assigned by JVMTI - char *class_name = nullptr; - char *method_name = nullptr; - char *method_sig = nullptr; - u32 class_name_id = 0; - u32 method_name_id = 0; - u32 method_sig_id = 0; - - jint line_number_table_size = 0; - jvmtiLineNumberEntry *line_number_table = NULL; - - jvmti->GetPhase(&phase); - if ((phase & (JVMTI_PHASE_START | JVMTI_PHASE_LIVE)) != 0) { - bool entry = false; - if (VMMethod::check_jmethodID(method) && - jvmti->GetMethodDeclaringClass(method, &method_class) == 0 && - // On some older versions of J9, the JVMTI call to GetMethodDeclaringClass will return OK = 0, but when a - // classloader is unloaded they free all JNIIDs. This means that anyone holding on to a jmethodID is - // pointing to corrupt data and the behaviour is undefined. - // The behaviour is adjusted so that when asgct() is used or if `-XX:+KeepJNIIDs` is specified, - // when a classloader is unloaded, the jmethodIDs are not freed, but instead marked as -1. - // The nested check below is to mitigate these crashes. - // In more recent versions, the condition above will short-circuit safely. - ((!VM::isOpenJ9() || method_class != reinterpret_cast(-1)) && jvmti->GetClassSignature(method_class, &class_name, NULL) == 0) && - jvmti->GetMethodName(method, &method_name, &method_sig, NULL) == 0) { - - if (first_time) { - jvmtiError line_table_error = jvmti->GetLineNumberTable(method, &line_number_table_size, - &line_number_table); - // Defensive: if GetLineNumberTable failed, clean up any potentially allocated memory - // Some buggy JVMTI implementations might allocate despite returning an error - if (line_table_error != JVMTI_ERROR_NONE) { - if (line_number_table != nullptr) { - // Try to deallocate to prevent leak from buggy JVM - jvmti->Deallocate((unsigned char *)line_number_table); - } - line_number_table = nullptr; - line_number_table_size = 0; - } - } - - // Check if the frame is Thread.run or inherits from it - if (strncmp(method_name, "run", 4) == 0 && - strncmp(method_sig, "()V", 3) == 0) { - jclass Thread_class = jni->FindClass("java/lang/Thread"); - jclass Class_class = jni->FindClass("java/lang/Class"); - if (Thread_class != nullptr && Class_class != nullptr) { - jmethodID equals = jni->GetMethodID(Class_class, - "equals", "(Ljava/lang/Object;)Z"); - if (equals != nullptr) { - jclass klass = method_class; - do { - entry = jni->CallBooleanMethod(Thread_class, equals, klass); - if (jniExceptionCheck(jni)) { - entry = false; - break; - } - if (entry) { - break; - } - } while ((klass = jni->GetSuperclass(klass)) != NULL); - } - } - // Clear any exceptions from the reflection calls above - jniExceptionCheck(jni); - } else if (strncmp(method_name, "main", 5) == 0 && - strncmp(method_sig, "(Ljava/lang/String;)V", 21)) { - // public static void main(String[] args) - 'public static' translates - // to modifier bits 0 and 3, hence check for '9' - entry = true; - } - - // maybe we should store the lookups below in initialisation-time - // constants... - if (has_prefix(class_name, - "Ljdk/internal/reflect/GeneratedConstructorAccessor")) { - class_name_id = _classes->lookup( - "jdk/internal/reflect/GeneratedConstructorAccessor"); - method_name_id = - _symbols.lookup("Object " - "jdk.internal.reflect.GeneratedConstructorAccessor." - "newInstance(Object[])"); - method_sig_id = _symbols.lookup(method_sig); - } else if (has_prefix(class_name, - "Lsun/reflect/GeneratedConstructorAccessor")) { - class_name_id = - _classes->lookup("sun/reflect/GeneratedConstructorAccessor"); - method_name_id = _symbols.lookup( - "Object " - "sun.reflect.GeneratedConstructorAccessor.newInstance(Object[])"); - method_sig_id = _symbols.lookup(method_sig); - } else if (has_prefix(class_name, - "Ljdk/internal/reflect/GeneratedMethodAccessor")) { - class_name_id = - _classes->lookup("jdk/internal/reflect.GeneratedMethodAccessor"); - method_name_id = - _symbols.lookup("Object " - "jdk.internal.reflect.GeneratedMethodAccessor." - "invoke(Object, Object[])"); - method_sig_id = _symbols.lookup(method_sig); - } else if (has_prefix(class_name, - "Lsun/reflect/GeneratedMethodAccessor")) { - class_name_id = _classes->lookup("sun/reflect/GeneratedMethodAccessor"); - method_name_id = _symbols.lookup( - "Object sun.reflect.GeneratedMethodAccessor.invoke(Object, " - "Object[])"); - method_sig_id = _symbols.lookup(method_sig); - } else if (has_prefix(class_name, "Ljava/lang/invoke/LambdaForm$")) { - const int lambdaFormPrefixLength = - strlen("Ljava/lang/invoke/LambdaForm$"); - // we want to normalise to java/lang/invoke/LambdaForm$MH, - // java/lang/invoke/LambdaForm$DMH, java/lang/invoke/LambdaForm$BMH, - if (has_prefix(class_name + lambdaFormPrefixLength, "MH")) { - class_name_id = _classes->lookup("java/lang/invoke/LambdaForm$MH"); - } else if (has_prefix(class_name + lambdaFormPrefixLength, "BMH")) { - class_name_id = _classes->lookup("java/lang/invoke/LambdaForm$BMH"); - } else if (has_prefix(class_name + lambdaFormPrefixLength, "DMH")) { - class_name_id = _classes->lookup("java/lang/invoke/LambdaForm$DMH"); - } else { - // don't recognise the suffix, so don't normalise - class_name_id = - _classes->lookup(class_name + 1, strlen(class_name) - 2); - } - method_name_id = _symbols.lookup(method_name); - method_sig_id = _symbols.lookup(method_sig); - } else { - class_name_id = - _classes->lookup(class_name + 1, strlen(class_name) - 2); - method_name_id = _symbols.lookup(method_name); - method_sig_id = _symbols.lookup(method_sig); - } - } else { - Counters::increment(JMETHODID_SKIPPED); - class_name_id = _classes->lookup(""); - method_name_id = _symbols.lookup("jvmtiError"); - method_sig_id = _symbols.lookup("()L;"); - } - - mi->_class = class_name_id; - mi->_name = method_name_id; - mi->_sig = method_sig_id; - mi->_type = FRAME_INTERPRETED; - mi->_is_entry = entry; - if (line_number_table != nullptr) { - mi->_line_number_table = std::make_shared( - line_number_table_size, line_number_table); - // Increment counter for tracking live line number tables - Counters::increment(LINE_NUMBER_TABLES); - } - - // strings are null or came from JVMTI - if (method_name) { - jvmti->Deallocate((unsigned char *)method_name); - } - if (method_sig) { - jvmti->Deallocate((unsigned char *)method_sig); - } - if (class_name) { - jvmti->Deallocate((unsigned char *)class_name); - } - } - jni->PopLocalFrame(NULL); -} - -MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) { - static const char* UNKNOWN = "unknown"; - unsigned long key; - jint bci = frame.bci; - - jmethodID method = frame.method_id; - if (method == nullptr) { - key = MethodMap::makeKey(UNKNOWN); - } else if (bci == BCI_ERROR || bci == BCI_NATIVE_FRAME) { - key = MethodMap::makeKey(frame.native_function_name); - } else if (bci == BCI_NATIVE_FRAME_REMOTE) { - key = MethodMap::makeKey(frame.packed_remote_frame); - } else { - FrameTypeId frame_type = FrameType::decode(bci); - assert(frame_type == FRAME_INTERPRETED || frame_type == FRAME_JIT_COMPILED || - frame_type == FRAME_INLINED || frame_type == FRAME_C1_COMPILED || - VM::isOpenJ9()); // OpenJ9 may have bugs that produce invalid frame types - key = MethodMap::makeKey(method); - } - - MethodInfo *mi = &(*_method_map)[key]; - - if (!mi->_mark) { - mi->_mark = true; - bool first_time = mi->_key == 0; - if (first_time) { - mi->_key = _method_map->size() + 1; // avoid zero key - } - if (method == nullptr) { - fillNativeMethodInfo(mi, UNKNOWN, nullptr); - } else if (bci == BCI_ERROR) { - fillNativeMethodInfo(mi, (const char *)method, nullptr); - } else if (bci == BCI_NATIVE_FRAME) { - const char *name = (const char *)method; - fillNativeMethodInfo(mi, name, - Profiler::instance()->getLibraryName(name)); - } else if (bci == BCI_NATIVE_FRAME_REMOTE) { - // Unpack remote symbolication data using utility struct - // Layout: pc_offset (44 bits) | mark (3 bits) | lib_index (15 bits) - unsigned long packed_remote_frame = frame.packed_remote_frame; - uintptr_t pc_offset = Profiler::RemoteFramePacker::unpackPcOffset(packed_remote_frame); - [[maybe_unused]] char mark = Profiler::RemoteFramePacker::unpackMark(packed_remote_frame); - uint32_t lib_index = Profiler::RemoteFramePacker::unpackLibIndex(packed_remote_frame); - - TEST_LOG("Unpacking remote frame: packed=0x%zx, pc_offset=0x%lx, mark=%d, lib_index=%u", - packed_remote_frame, pc_offset, (int)mark, lib_index); - - // Lookup library by index to get build_id - // Note: This is called during JFR serialization with lockAll() held (see Profiler::dump), - // so the library array is stable - no concurrent dlopen_hook calls can modify it. - CodeCache* lib = Libraries::instance()->getLibraryByIndex(lib_index); - if (lib != nullptr && lib->hasBuildId() && Profiler::instance()->isRemoteSymbolication()) { - TEST_LOG("Found library: %s, build_id=%s", lib->name(), lib->buildId()); - // Remote symbolication: defer to backend - RemoteFrameInfo rfi(lib->buildId(), pc_offset, lib_index); - fillRemoteFrameInfo(mi, &rfi); - } else if (lib != nullptr) { - // Locally unsymbolized: render as [libname+0xoffset] - char name_buf[256]; - const char* s = lib->name(); - const char* basename = strrchr(s, '/'); - if (basename) basename++; else basename = s; - snprintf(name_buf, sizeof(name_buf), "[%s+0x%" PRIxPTR "]", basename, pc_offset); - fillNativeMethodInfo(mi, name_buf, nullptr); - } else { - TEST_LOG("WARNING: Library lookup failed for index %u", lib_index); - fillNativeMethodInfo(mi, "unknown_library", nullptr); - } - } else { - fillJavaMethodInfo(mi, method, first_time); - } - } - - return mi; -} - -u32 Lookup::getPackage(const char *class_name) { - const char *package = strrchr(class_name, '/'); - if (package == NULL) { - return 0; - } - if (package[1] >= '0' && package[1] <= '9') { - // Seems like a hidden or anonymous class, e.g. com/example/Foo/0x012345 - do { - if (package == class_name) - return 0; - } while (*--package != '/'); - } - if (class_name[0] == '[') { - class_name = strchr(class_name, 'L') + 1; - } - return _packages.lookup(class_name, package - class_name); -} - -u32 Lookup::getSymbol(const char *name) { return _symbols.lookup(name); } - char *Recording::_agent_properties = NULL; char *Recording::_jvm_args = NULL; char *Recording::_jvm_flags = NULL; diff --git a/ddprof-lib/src/main/cpp/flightRecorder.h b/ddprof-lib/src/main/cpp/flightRecorder.h index 02efdebc0..bcb9abe02 100644 --- a/ddprof-lib/src/main/cpp/flightRecorder.h +++ b/ddprof-lib/src/main/cpp/flightRecorder.h @@ -22,6 +22,7 @@ #include "frame.h" #include "jfrMetadata.h" #include "log.h" +#include "methodInfo.h" #include "mutex.h" #include "objectSampler.h" #include "threadFilter.h" @@ -55,92 +56,6 @@ struct CpuTimes { CpuTime proc; CpuTime total; }; - -class SharedLineNumberTable { -public: - int _size; - void *_ptr; - - SharedLineNumberTable(int size, void *ptr) : _size(size), _ptr(ptr) {} - ~SharedLineNumberTable(); -}; - -class MethodInfo { -public: - MethodInfo() - : _mark(false), _is_entry(false), _referenced(false), _age(0), _key(0), _class(0), - _name(0), _sig(0), _modifiers(0), _line_number_table(nullptr), _type() {} - - bool _mark; - bool _is_entry; - bool _referenced; // Tracked during writeStackTraces() for cleanup - int _age; // Consecutive chunks without reference (0 = recently used) - u32 _key; - u32 _class; - u32 _name; - u32 _sig; - jint _modifiers; - std::shared_ptr _line_number_table; - FrameTypeId _type; - - jint getLineNumber(jint bci) { - // if the shared pointer is not pointing to the line number table, consider - // size 0 - if (!_line_number_table || _line_number_table->_size == 0) { - return 0; - } - - int i = 1; - while (i < _line_number_table->_size && - bci >= ((jvmtiLineNumberEntry *)_line_number_table->_ptr)[i] - .start_location) { - i++; - } - return ((jvmtiLineNumberEntry *)_line_number_table->_ptr)[i - 1] - .line_number; - } - - bool isHidden() { - // 0x1400 = ACC_SYNTHETIC(0x1000) | ACC_BRIDGE(0x0040) - return _modifiers == 0 || (_modifiers & 0x1040); - } -}; - -// MethodMap's key can be derived from 3 sources: -// 1) jmethodID for Java methods -// 2) void* address for native method names -// 3) Encoded RemoteFrameInfo -// The values of the keys are potentially overlapping, so we use -// the highest 2 bits to distinguish them. -// 00 - jmethodID -// 10 - void* address -// 01 - RemoteFrameInfo -class MethodMap : public std::map { -public: - static constexpr unsigned long ADDRESS_MARK = 0x8000000000000000ULL; - static constexpr unsigned long REMOTE_FRAME_MARK = 0x4000000000000000ULL; - static constexpr unsigned long KEY_TYPE_MASK = ADDRESS_MARK | REMOTE_FRAME_MARK; - - MethodMap() {} - - static unsigned long makeKey(jmethodID method) { - unsigned long key = (unsigned long)method; - assert((key & KEY_TYPE_MASK) == 0); - return key; - } - - static unsigned long makeKey(const char* addr) { - unsigned long key = (unsigned long)addr; - assert((key & KEY_TYPE_MASK) == 0); - return (key | ADDRESS_MARK); - } - - static unsigned long makeKey(unsigned long packed_remote_frame) { - unsigned long key = packed_remote_frame; - assert((key & KEY_TYPE_MASK) == 0); - return (key | REMOTE_FRAME_MARK);} -}; - class Recording { friend ObjectSampler; friend Profiler; @@ -295,35 +210,6 @@ class Recording { private: void cleanupUnreferencedMethods(); }; - -class Lookup { -public: - Recording *_rec; - MethodMap *_method_map; - Dictionary *_classes; - Dictionary _packages; - Dictionary _symbols; - -private: - void fillNativeMethodInfo(MethodInfo *mi, const char *name, - const char *lib_name); - void fillRemoteFrameInfo(MethodInfo *mi, const RemoteFrameInfo *rfi); - void cutArguments(char *func); - void fillJavaMethodInfo(MethodInfo *mi, jmethodID method, bool first_time); - bool has_prefix(const char *str, const char *prefix) const { - return strncmp(str, prefix, strlen(prefix)) == 0; - } - -public: - Lookup(Recording *rec, MethodMap *method_map, Dictionary *classes) - : _rec(rec), _method_map(method_map), _classes(classes), _packages(), - _symbols() {} - - MethodInfo *resolveMethod(ASGCT_CallFrame &frame); - u32 getPackage(const char *class_name); - u32 getSymbol(const char *name); -}; - class FlightRecorder { friend Profiler; diff --git a/ddprof-lib/src/main/cpp/hotspot/classloader.h b/ddprof-lib/src/main/cpp/hotspot/classloader.h new file mode 100644 index 000000000..c886c041b --- /dev/null +++ b/ddprof-lib/src/main/cpp/hotspot/classloader.h @@ -0,0 +1,19 @@ +/* + * Copyright The async-profiler authors + * Copyright 2026, Datadog, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _HOTSPOT_CLASSLOADER_H +#define _HOTSPOT_CLASSLOADER_H + +#include "hotspot/vmStructs.h" + +class VMClassLoader { +public: + // Is the method belongs to a class that is loaded by bootstrap class loader + static inline bool isLoadedByBootstrapClassLoader(const VMMethod* method); +}; + +#endif // _HOTSPOT_CLASSLOADER_H + diff --git a/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h b/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h new file mode 100644 index 000000000..c45765fb7 --- /dev/null +++ b/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h @@ -0,0 +1,29 @@ +/* + * Copyright The async-profiler authors + * Copyright 2026, Datadog, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _HOTSPOT_CLASSLOADER_INLINE_H +#define _HOTSPOT_CLASSLOADER_INLINE_H + +#include "hotspot/classloader.h" + +#include + +bool VMClassLoader::isLoadedByBootstrapClassLoader(const VMMethod* method) { + VMKlass* method_klass = method->methodHolder(); + + // java/lang/Object must be loaded by bootstrap class loader + VMKlass* obj_klass = VMClasses::obj_klass(); + + assert(method_klass != nullptr && "No Klass for the method"); + assert(obj_klass != nullptr && "VMClasses not yet initialized"); + assert(method_klass->classLoaderData() != nullptr && "Method holder has no class loader data"); + assert(obj_klass->classLoaderData() != nullptr && "Object class has no class loader data"); + + return method_klass->classLoaderData() == obj_klass->classLoaderData(); +} + +#endif // _HOTSPOT_CLASSLOADER_INLINE_H + diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 198bfe37b..eb9fe1da3 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -6,6 +6,7 @@ #include #include "asyncSampleMutex.h" +#include "hotspot/classloader.inline.h" #include "hotspot/hotspotSupport.h" #include "hotspot/jitCodeCache.h" #include "hotspot/vmStructs.inline.h" @@ -29,6 +30,14 @@ static bool isAddressInCode(const void *pc, bool include_stubs = true) { } } +inline jmethodID getMethodId(VMMethod* method) { + if (!StackWalkValidation::inDeadZone(method) && StackWalkValidation::aligned((uintptr_t)method) + && SafeAccess::isReadableRange(method, VMMethod::type_size())) { + return method->validatedId(); + } + return NULL; +} + /** * Converts a BCI_* frame type value to the corresponding EventType enum value. * @@ -106,6 +115,11 @@ static void fillFrameTypes(ASGCT_CallFrame *frames, int num_frames, VMNMethod *n } } +static inline void fillFrame(ASGCT_CallFrame& frame, FrameTypeId type, int bci, const VMMethod* method) { + frame.bci = FrameType::encode(type, bci); + frame.method = static_cast(method); +} + static ucontext_t empty_ucontext{}; __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontext, ASGCT_CallFrame* frames, int max_depth, @@ -258,15 +272,21 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex bool is_plausible_interpreter_frame = StackWalkValidation::isPlausibleInterpreterFrame(fp, sp, bcp_offset); if (is_plausible_interpreter_frame) { - VMMethod* method = ((VMMethod**)fp)[InterpreterFrame::method_offset]; + VMMethod* method = VMMethod::load_then_cast(((void**)fp)[InterpreterFrame::method_offset]); + assert(method != nullptr && "No method for the interpreter frame"); + jmethodID method_id = getMethodId(method); - if (method_id != NULL) { + if (method_id != NULL || VMClassLoader::isLoadedByBootstrapClassLoader(method)) { Counters::increment(WALKVM_JAVA_FRAME_OK); const char* bytecode_start = method->bytecode(); const char* bcp = ((const char**)fp)[bcp_offset]; int bci = bytecode_start == NULL || bcp < bytecode_start ? 0 : bcp - bytecode_start; - fillFrame(frames[depth++], FRAME_INTERPRETED, bci, method_id); + if (method_id != nullptr) { + fillFrame(frames[depth++], FRAME_INTERPRETED, bci, method_id); + } else { + fillFrame(frames[depth++], FRAME_INTERPRETED_METHOD, bci, method); + } sp = ((uintptr_t*)fp)[InterpreterFrame::sender_sp_offset]; pc = stripPointer(((void**)fp)[FRAME_PC_SLOT]); fp = *(uintptr_t*)fp; @@ -277,10 +297,13 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex if (depth == 0) { VMMethod* method = (VMMethod*)frame.method(); jmethodID method_id = getMethodId(method); - if (method_id != NULL) { + if (method_id != NULL || VMClassLoader::isLoadedByBootstrapClassLoader(method)) { Counters::increment(WALKVM_JAVA_FRAME_OK); - fillFrame(frames[depth++], FRAME_INTERPRETED, 0, method_id); - + if (method_id != nullptr) { + fillFrame(frames[depth++], FRAME_INTERPRETED, 0, method_id); + } else { + fillFrame(frames[depth++], FRAME_INTERPRETED, 0, method); + } if (is_plausible_interpreter_frame) { pc = stripPointer(((void**)fp)[FRAME_PC_SLOT]); sp = frame.senderSP(); @@ -969,42 +992,6 @@ int HotspotSupport::walkJavaStack(StackWalkRequest& request) { } -void HotspotSupport::loadAllMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni) { - assert(VM::isHotspot() && "Hotspot only"); - - jint class_count = 0; - jclass *classes = nullptr; - int loaded_count = 0; - - if (jvmti->GetLoadedClasses(&class_count, &classes) == 0) { - for (int i = 0; i < class_count; i++) { - jclass klass = classes[i]; - jobject cld; - // Hotpsot only: loaded by bootstrap class loader, which is never unloaded, - // we use Method instead. - if (jvmti->GetClassLoader(klass, &cld) == JVMTI_ERROR_NONE && cld == nullptr) { - VMOopHandle* klass_handle = (VMOopHandle*)klass; - VMKlass* vmklass = VMKlass::fromOop(klass_handle->oop()); - assert(vmklass != nullptr); - VMClassLoaderData* cld = vmklass->classLoaderData(); - assert(cld != nullptr); - char* signature_ptr; - jvmti->GetClassSignature(klass, &signature_ptr, nullptr); - TEST_LOG("processing bootstrap class %s", signature_ptr); - // Lambda classes can be unloaded, exlcude them - if (!cld->hasClassMirrorHolder() && !cld->isAnonymous()) { - TEST_LOG("Skipping class %s",signature_ptr); - continue; - } - loadMethodIDsImpl(jvmti, jni, klass); - loaded_count++; - } - } - jvmti->Deallocate((unsigned char *)classes); - } - TEST_LOG("Preloaded jmethodIDs for %d/%d classes", loaded_count, class_count); -} - static void patchClassLoaderData(JNIEnv* jni, jclass klass) { bool needs_patch = VM::hotspot_version() == 8; if (needs_patch) { @@ -1026,13 +1013,22 @@ static void patchClassLoaderData(JNIEnv* jni, jclass klass) { } } +constexpr const char* LAMBDA_PREFIX = "Ljava/lang/invoke/LambdaForm$"; +// constexpr const size_t LAMBDA_PREFIX_LEN = strlen(LAMBDA_PREFIX); +static bool isLambdaClass(const char* signature) { + return strncmp(signature, LAMBDA_PREFIX, strlen(LAMBDA_PREFIX)) == 0 || + strstr(signature, "$$Lambda.") != nullptr || + strstr(signature, ".lambda$") != nullptr; +} + bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass) { + patchClassLoaderData(jni, klass); - jobject cld; + jobject cl; // Hotpsot only: loaded by bootstrap class loader, which is never unloaded, // we use Method instead. - if (jvmti->GetClassLoader(klass, &cld) == JVMTI_ERROR_NONE && cld == nullptr) { + if (jvmti->GetClassLoader(klass, &cl) == JVMTI_ERROR_NONE && cl == nullptr) { VMOopHandle* klass_handle = VMOopHandle::cast(klass); VMKlass* vmklass = VMKlass::fromOop(klass_handle->oop()); assert(vmklass != nullptr); @@ -1042,9 +1038,11 @@ bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klas jvmti->GetClassSignature(klass, &signature_ptr, nullptr); TEST_LOG("processing bootstrap class %s", signature_ptr); // Lambda classes can be unloaded, exlcude them - if (!cld->hasClassMirrorHolder() && !cld->isAnonymous()) { + if (!isLambdaClass(signature_ptr)) { TEST_LOG("Skipping class %s",signature_ptr); return false; + } else { + TEST_LOG("Lambda class: %s", signature_ptr); } } diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h index 4ec850b26..86e845fc5 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h @@ -127,7 +127,8 @@ inline T* cast_to(const void* ptr) { f(VMNMethod, MATCH_SYMBOLS("nmethod")) \ f(VMSymbol, MATCH_SYMBOLS("Symbol")) \ f(VMThread, MATCH_SYMBOLS("Thread")) \ - f(VMOopHandle, MATCH_SYMBOLS("OopHandle")) + f(VMOopHandle, MATCH_SYMBOLS("OopHandle")) \ + f(VMClasses, MATCH_SYMBOLS("vmClasses")) /** * Following macros define field offsets, addresses or values of JVM classes that are exported by @@ -284,6 +285,10 @@ typedef void* address; type_end() \ type_begin(VMOopHandle, MATCH_SYMBOLS("OopHandle")) \ field(_oop_handle_obj_offset, offset, MATCH_SYMBOLS("_obj")) \ + type_end() \ + type_begin(VMClasses, MATCH_SYMBOLS("vmClasses")) \ + field(_obj_class_addr, address, MATCH_SYMBOLS("_klasses[static_cast(vmClassID::Object_klass_knum)]")) \ + field(_thread_class_addr, address, MATCH_SYMBOLS("_klasses[static_cast(vmClassID::Thread_klass_knum)]")) \ type_end() /** @@ -790,9 +795,6 @@ DECLARE(VMThread) DECLARE_END -DECLARE(VMConstMethod) -DECLARE_END - DECLARE(VMConstantPool) public: inline VMKlass* holder() const; @@ -801,6 +803,11 @@ DECLARE(VMConstantPool) inline intptr_t* base() const; DECLARE_END +DECLARE(VMConstMethod) +public: + inline VMConstantPool* constants() const; +DECLARE_END + DECLARE(VMMethod) private: // ref: constMethodFlags.hpp in hotspot src @@ -827,6 +834,7 @@ DECLARE(VMMethod) return *(const char**) at(_method_constmethod_offset) + VMConstMethod::type_size(); } + inline VMConstMethod* constMethod() const; inline VMNMethod* code() const; inline uint16_t codeSize() const; inline uint32_t flags() const; @@ -978,6 +986,12 @@ DECLARE(VMNMethod) int findScopeOffset(const void* pc); DECLARE_END +DECLARE(VMClasses) +public: + static inline VMKlass* obj_klass(); + static inline VMKlass* thread_klass(); +DECLARE_END + class CodeHeap : VMStructs { private: static bool contains(char* heap, const void* pc) { diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h index 3b7dd3879..ec1ace953 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h @@ -38,6 +38,21 @@ void** VMThread::vtable() { return *(void***)this; } +VMMethod* VMThread::compiledMethod() { + if (!isJavaThread(this)) return NULL; + assert(_comp_method_offset >= 0); + assert(_comp_env_offset >= 0); + assert(_comp_task_offset >= 0); + const char* env = *(const char**) at(_comp_env_offset); + if (env != NULL) { + const char* task = *(const char**) (env + _comp_task_offset); + if (task != NULL) { + return VMMethod::load_then_cast((const void*)(task + _comp_method_offset)); + } + } + return NULL; +} + // This thread is considered a JavaThread if at least 2 of the selected 3 vtable entries // match those of a known JavaThread (which is either application thread or AttachListener). // Indexes were carefully chosen to work on OpenJDK 8 to 25, both product an debug builds. @@ -76,6 +91,10 @@ intptr_t* VMConstantPool::base() const { return (intptr_t*)(((char*)this) + _VMConstantPool_size); } +VMConstMethod* VMMethod::constMethod() const { + return VMConstMethod::load_then_cast(at(_method_constmethod_offset)); +} + uint16_t VMMethod::codeSize() const { assert(_constmethod_code_size >= 0); address code_size_addr = *(unsigned char**)at(_method_constmethod_offset) + _constmethod_code_size; @@ -135,19 +154,12 @@ VMNMethod* VMMethod::code() const { return VMNMethod::cast(code_ptr); } -VMMethod* VMThread::compiledMethod() { - if (!isJavaThread(this)) return NULL; - assert(_comp_method_offset >= 0); - assert(_comp_env_offset >= 0); - assert(_comp_task_offset >= 0); - const char* env = *(const char**) at(_comp_env_offset); - if (env != NULL) { - const char* task = *(const char**) (env + _comp_task_offset); - if (task != NULL) { - return VMMethod::load_then_cast((const void*)(task + _comp_method_offset)); - } - } - return NULL; +VMKlass* VMMethod::methodHolder() const { + return constMethod()->constants()->holder(); +} + +VMConstantPool* VMConstMethod::constants() const { + return VMConstantPool::load_then_cast(at(_constmethod_constants_offset)); } uintptr_t VMOopHandle::oop() const { @@ -155,4 +167,11 @@ uintptr_t VMOopHandle::oop() const { return *(uintptr_t*) at(_oop_handle_obj_offset); } +VMKlass* VMClasses::obj_klass() { + return VMKlass::load_then_cast(_obj_class_addr); +} +VMKlass* VMClasses::thread_klass() { + return VMKlass::load_then_cast(_thread_class_addr); +} + #endif // _HOTSPOT_VMSTRUCTS_INLINE_H diff --git a/ddprof-lib/src/main/cpp/lookup.cpp b/ddprof-lib/src/main/cpp/lookup.cpp new file mode 100644 index 000000000..721386ca7 --- /dev/null +++ b/ddprof-lib/src/main/cpp/lookup.cpp @@ -0,0 +1,378 @@ +/* + * Copyright The async-profiler authors + * Copyright 2026, Datadog, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include "lookup.h" + +#include +#include +#include + +#include "codeCache.h" +#include "common.h" +#include "counters.h" +#include "jniHelper.h" +#include "libraries.h" +#include "methodInfo.h" +#include "profiler.h" +#include "rustDemangler.h" + +#include "hotspot/vmStructs.h" + +void Lookup::fillNativeMethodInfo(MethodInfo *mi, const char *name, + const char *lib_name) { + mi->_class = _classes->lookup(""); + // TODO return the library name once we figured out how to cooperate with the + // backend + // if (lib_name == NULL) { + // mi->_class = _classes->lookup(""); + // } else if (lib_name[0] == '[' && lib_name[1] != 0) { + // mi->_class = _classes->lookup(lib_name + 1, strlen(lib_name) - + // 2); + // } else { + // mi->_class = _classes->lookup(lib_name); + // } + + mi->_modifiers = 0x100; + mi->_line_number_table = nullptr; + + if (name[0] == '_' && name[1] == 'Z') { + int status; + char *demangled = abi::__cxa_demangle(name, NULL, NULL, &status); + if (demangled != NULL) { + cutArguments(demangled); + mi->_sig = _symbols.lookup("()L;"); + mi->_type = FRAME_CPP; + + // Rust legacy demangling + if (RustDemangler::is_probably_rust_legacy(demangled)) { + std::string rust_demangled = RustDemangler::demangle(demangled); + mi->_name = _symbols.lookup(rust_demangled.c_str()); + } else { + mi->_name = _symbols.lookup(demangled); + } + free(demangled); + return; + } + } + + size_t len = strlen(name); + if (len >= 4 && strcmp(name + len - 4, "_[k]") == 0) { + mi->_name = _symbols.lookup(name, len - 4); + mi->_sig = _symbols.lookup("(Lk;)L;"); + mi->_type = FRAME_KERNEL; + } else { + mi->_name = _symbols.lookup(name); + mi->_sig = _symbols.lookup("()L;"); + mi->_type = FRAME_NATIVE; + } +} + +void Lookup::fillRemoteFrameInfo(MethodInfo *mi, const RemoteFrameInfo *rfi) { + // Store build-id in the class name field + mi->_class = _classes->lookup(rfi->build_id); + + // Store PC offset in hex format in the signature field + char offset_hex[32]; + snprintf(offset_hex, sizeof(offset_hex), "0x%" PRIxPTR, rfi->pc_offset); + mi->_sig = _symbols.lookup(offset_hex); + + // Use same modifiers as regular native frames (0x100 = ACC_NATIVE for consistency) + mi->_modifiers = 0x100; + // Use FRAME_NATIVE_REMOTE type to indicate remote symbolication + mi->_type = FRAME_NATIVE_REMOTE; + mi->_line_number_table = nullptr; + + // Method name indicates need for remote symbolication + mi->_name = _symbols.lookup(""); +} + +void Lookup::cutArguments(char *func) { + char *p = strrchr(func, ')'); + if (p == NULL) + return; + + int balance = 1; + while (--p > func) { + if (*p == '(' && --balance == 0) { + *p = 0; + return; + } else if (*p == ')') { + balance++; + } + } +} + +void Lookup::fillJavaMethodInfo(MethodInfo *mi, jmethodID method, + bool first_time) { + JNIEnv *jni = VM::jni(); + if (jni->PushLocalFrame(64) != 0) { + return; + } + jvmtiEnv *jvmti = VM::jvmti(); + + jvmtiPhase phase; + jclass method_class = NULL; + // invariant: these strings must remain null, or be assigned by JVMTI + char *class_name = nullptr; + char *method_name = nullptr; + char *method_sig = nullptr; + u32 class_name_id = 0; + u32 method_name_id = 0; + u32 method_sig_id = 0; + + jint line_number_table_size = 0; + jvmtiLineNumberEntry *line_number_table = NULL; + + jvmti->GetPhase(&phase); + if ((phase & (JVMTI_PHASE_START | JVMTI_PHASE_LIVE)) != 0) { + bool entry = false; + if (VMMethod::check_jmethodID(method) && + jvmti->GetMethodDeclaringClass(method, &method_class) == 0 && + // On some older versions of J9, the JVMTI call to GetMethodDeclaringClass will return OK = 0, but when a + // classloader is unloaded they free all JNIIDs. This means that anyone holding on to a jmethodID is + // pointing to corrupt data and the behaviour is undefined. + // The behaviour is adjusted so that when asgct() is used or if `-XX:+KeepJNIIDs` is specified, + // when a classloader is unloaded, the jmethodIDs are not freed, but instead marked as -1. + // The nested check below is to mitigate these crashes. + // In more recent versions, the condition above will short-circuit safely. + ((!VM::isOpenJ9() || method_class != reinterpret_cast(-1)) && jvmti->GetClassSignature(method_class, &class_name, NULL) == 0) && + jvmti->GetMethodName(method, &method_name, &method_sig, NULL) == 0) { + + if (first_time) { + jvmtiError line_table_error = jvmti->GetLineNumberTable(method, &line_number_table_size, + &line_number_table); + // Defensive: if GetLineNumberTable failed, clean up any potentially allocated memory + // Some buggy JVMTI implementations might allocate despite returning an error + if (line_table_error != JVMTI_ERROR_NONE) { + if (line_number_table != nullptr) { + // Try to deallocate to prevent leak from buggy JVM + jvmti->Deallocate((unsigned char *)line_number_table); + } + line_number_table = nullptr; + line_number_table_size = 0; + } + } + + // Check if the frame is Thread.run or inherits from it + if (strncmp(method_name, "run", 4) == 0 && + strncmp(method_sig, "()V", 3) == 0) { + jclass Thread_class = jni->FindClass("java/lang/Thread"); + jclass Class_class = jni->FindClass("java/lang/Class"); + if (Thread_class != nullptr && Class_class != nullptr) { + jmethodID equals = jni->GetMethodID(Class_class, + "equals", "(Ljava/lang/Object;)Z"); + if (equals != nullptr) { + jclass klass = method_class; + do { + entry = jni->CallBooleanMethod(Thread_class, equals, klass); + if (jniExceptionCheck(jni)) { + entry = false; + break; + } + if (entry) { + break; + } + } while ((klass = jni->GetSuperclass(klass)) != NULL); + } + } + // Clear any exceptions from the reflection calls above + jniExceptionCheck(jni); + } else if (strncmp(method_name, "main", 5) == 0 && + strncmp(method_sig, "(Ljava/lang/String;)V", 21)) { + // public static void main(String[] args) - 'public static' translates + // to modifier bits 0 and 3, hence check for '9' + entry = true; + } + + // maybe we should store the lookups below in initialisation-time + // constants... + if (has_prefix(class_name, + "Ljdk/internal/reflect/GeneratedConstructorAccessor")) { + class_name_id = _classes->lookup( + "jdk/internal/reflect/GeneratedConstructorAccessor"); + method_name_id = + _symbols.lookup("Object " + "jdk.internal.reflect.GeneratedConstructorAccessor." + "newInstance(Object[])"); + method_sig_id = _symbols.lookup(method_sig); + } else if (has_prefix(class_name, + "Lsun/reflect/GeneratedConstructorAccessor")) { + class_name_id = + _classes->lookup("sun/reflect/GeneratedConstructorAccessor"); + method_name_id = _symbols.lookup( + "Object " + "sun.reflect.GeneratedConstructorAccessor.newInstance(Object[])"); + method_sig_id = _symbols.lookup(method_sig); + } else if (has_prefix(class_name, + "Ljdk/internal/reflect/GeneratedMethodAccessor")) { + class_name_id = + _classes->lookup("jdk/internal/reflect.GeneratedMethodAccessor"); + method_name_id = + _symbols.lookup("Object " + "jdk.internal.reflect.GeneratedMethodAccessor." + "invoke(Object, Object[])"); + method_sig_id = _symbols.lookup(method_sig); + } else if (has_prefix(class_name, + "Lsun/reflect/GeneratedMethodAccessor")) { + class_name_id = _classes->lookup("sun/reflect/GeneratedMethodAccessor"); + method_name_id = _symbols.lookup( + "Object sun.reflect.GeneratedMethodAccessor.invoke(Object, " + "Object[])"); + method_sig_id = _symbols.lookup(method_sig); + } else if (has_prefix(class_name, "Ljava/lang/invoke/LambdaForm$")) { + const int lambdaFormPrefixLength = + strlen("Ljava/lang/invoke/LambdaForm$"); + // we want to normalise to java/lang/invoke/LambdaForm$MH, + // java/lang/invoke/LambdaForm$DMH, java/lang/invoke/LambdaForm$BMH, + if (has_prefix(class_name + lambdaFormPrefixLength, "MH")) { + class_name_id = _classes->lookup("java/lang/invoke/LambdaForm$MH"); + } else if (has_prefix(class_name + lambdaFormPrefixLength, "BMH")) { + class_name_id = _classes->lookup("java/lang/invoke/LambdaForm$BMH"); + } else if (has_prefix(class_name + lambdaFormPrefixLength, "DMH")) { + class_name_id = _classes->lookup("java/lang/invoke/LambdaForm$DMH"); + } else { + // don't recognise the suffix, so don't normalise + class_name_id = + _classes->lookup(class_name + 1, strlen(class_name) - 2); + } + method_name_id = _symbols.lookup(method_name); + method_sig_id = _symbols.lookup(method_sig); + } else { + class_name_id = + _classes->lookup(class_name + 1, strlen(class_name) - 2); + method_name_id = _symbols.lookup(method_name); + method_sig_id = _symbols.lookup(method_sig); + } + } else { + Counters::increment(JMETHODID_SKIPPED); + class_name_id = _classes->lookup(""); + method_name_id = _symbols.lookup("jvmtiError"); + method_sig_id = _symbols.lookup("()L;"); + } + + mi->_class = class_name_id; + mi->_name = method_name_id; + mi->_sig = method_sig_id; + mi->_type = FRAME_INTERPRETED; + mi->_is_entry = entry; + if (line_number_table != nullptr) { + mi->_line_number_table = std::make_shared( + line_number_table_size, line_number_table); + // Increment counter for tracking live line number tables + Counters::increment(LINE_NUMBER_TABLES); + } + + // strings are null or came from JVMTI + if (method_name) { + jvmti->Deallocate((unsigned char *)method_name); + } + if (method_sig) { + jvmti->Deallocate((unsigned char *)method_sig); + } + if (class_name) { + jvmti->Deallocate((unsigned char *)class_name); + } + } + jni->PopLocalFrame(NULL); +} + +MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) { + static const char* UNKNOWN = "unknown"; + unsigned long key; + jint bci = frame.bci; + FrameTypeId frame_type = FrameType::decode(bci); + + jmethodID method = frame.method_id; + if (method == nullptr) { + key = MethodMap::makeKey(UNKNOWN); + } else if (bci == BCI_ERROR || bci == BCI_NATIVE_FRAME) { + key = MethodMap::makeKey(frame.native_function_name); + } else if (bci == BCI_NATIVE_FRAME_REMOTE) { + key = MethodMap::makeKey(frame.packed_remote_frame); + } else { + FrameTypeId frame_type = FrameType::decode(bci); + assert(frame_type == FRAME_INTERPRETED || frame_type == FRAME_JIT_COMPILED || + frame_type == FRAME_INLINED || frame_type == FRAME_C1_COMPILED || + VM::isOpenJ9()); // OpenJ9 may have bugs that produce invalid frame types + key = MethodMap::makeKey(method); + } + + MethodInfo *mi = &(*_method_map)[key]; + + if (!mi->_mark) { + mi->_mark = true; + bool first_time = mi->_key == 0; + if (first_time) { + mi->_key = _method_map->size() + 1; // avoid zero key + } + if (method == nullptr) { + fillNativeMethodInfo(mi, UNKNOWN, nullptr); + } else if (bci == BCI_ERROR) { + fillNativeMethodInfo(mi, (const char *)method, nullptr); + } else if (bci == BCI_NATIVE_FRAME) { + const char *name = (const char *)method; + fillNativeMethodInfo(mi, name, + Profiler::instance()->getLibraryName(name)); + } else if (bci == BCI_NATIVE_FRAME_REMOTE) { + // Unpack remote symbolication data using utility struct + // Layout: pc_offset (44 bits) | mark (3 bits) | lib_index (15 bits) + unsigned long packed_remote_frame = frame.packed_remote_frame; + uintptr_t pc_offset = Profiler::RemoteFramePacker::unpackPcOffset(packed_remote_frame); + [[maybe_unused]] char mark = Profiler::RemoteFramePacker::unpackMark(packed_remote_frame); + uint32_t lib_index = Profiler::RemoteFramePacker::unpackLibIndex(packed_remote_frame); + + TEST_LOG("Unpacking remote frame: packed=0x%zx, pc_offset=0x%lx, mark=%d, lib_index=%u", + packed_remote_frame, pc_offset, (int)mark, lib_index); + + // Lookup library by index to get build_id + // Note: This is called during JFR serialization with lockAll() held (see Profiler::dump), + // so the library array is stable - no concurrent dlopen_hook calls can modify it. + CodeCache* lib = Libraries::instance()->getLibraryByIndex(lib_index); + if (lib != nullptr && lib->hasBuildId() && Profiler::instance()->isRemoteSymbolication()) { + TEST_LOG("Found library: %s, build_id=%s", lib->name(), lib->buildId()); + // Remote symbolication: defer to backend + RemoteFrameInfo rfi(lib->buildId(), pc_offset, lib_index); + fillRemoteFrameInfo(mi, &rfi); + } else if (lib != nullptr) { + // Locally unsymbolized: render as [libname+0xoffset] + char name_buf[256]; + const char* s = lib->name(); + const char* basename = strrchr(s, '/'); + if (basename) basename++; else basename = s; + snprintf(name_buf, sizeof(name_buf), "[%s+0x%" PRIxPTR "]", basename, pc_offset); + fillNativeMethodInfo(mi, name_buf, nullptr); + } else { + TEST_LOG("WARNING: Library lookup failed for index %u", lib_index); + fillNativeMethodInfo(mi, "unknown_library", nullptr); + } + } else { + fillJavaMethodInfo(mi, method, first_time); + } + } + + return mi; +} + +u32 Lookup::getPackage(const char *class_name) { + const char *package = strrchr(class_name, '/'); + if (package == NULL) { + return 0; + } + if (package[1] >= '0' && package[1] <= '9') { + // Seems like a hidden or anonymous class, e.g. com/example/Foo/0x012345 + do { + if (package == class_name) + return 0; + } while (*--package != '/'); + } + if (class_name[0] == '[') { + class_name = strchr(class_name, 'L') + 1; + } + return _packages.lookup(class_name, package - class_name); +} + +u32 Lookup::getSymbol(const char *name) { return _symbols.lookup(name); } diff --git a/ddprof-lib/src/main/cpp/lookup.h b/ddprof-lib/src/main/cpp/lookup.h new file mode 100644 index 000000000..9bd5b9232 --- /dev/null +++ b/ddprof-lib/src/main/cpp/lookup.h @@ -0,0 +1,46 @@ +/* + * Copyright The async-profiler authors + * Copyright 2026, Datadog, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _LOOKUP_H +#define _LOOKUP_H + +#include "dictionary.h" +#include "vmEntry.h" + +class MethodInfo; +class MethodMap; +class Recording; +class RemoteFrameInfo; + +class Lookup { +public: + Recording *_rec; + MethodMap *_method_map; + Dictionary *_classes; + Dictionary _packages; + Dictionary _symbols; + +private: + void fillNativeMethodInfo(MethodInfo *mi, const char *name, + const char *lib_name); + void fillRemoteFrameInfo(MethodInfo *mi, const RemoteFrameInfo *rfi); + void cutArguments(char *func); + void fillJavaMethodInfo(MethodInfo *mi, jmethodID method, bool first_time); + bool has_prefix(const char *str, const char *prefix) const { + return strncmp(str, prefix, strlen(prefix)) == 0; + } + +public: + Lookup(Recording *rec, MethodMap *method_map, Dictionary *classes) + : _rec(rec), _method_map(method_map), _classes(classes), _packages(), + _symbols() {} + + MethodInfo *resolveMethod(ASGCT_CallFrame &frame); + u32 getPackage(const char *class_name); + u32 getSymbol(const char *name); +}; + +#endif // _LOOKUP_H diff --git a/ddprof-lib/src/main/cpp/methodInfo.cpp b/ddprof-lib/src/main/cpp/methodInfo.cpp new file mode 100644 index 000000000..702378561 --- /dev/null +++ b/ddprof-lib/src/main/cpp/methodInfo.cpp @@ -0,0 +1,35 @@ +/* + * Copyright The async-profiler authors + * Copyright 2026, Datadog, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "methodInfo.h" + +#include + +#include "common.h" +#include "counters.h" +#include "vmEntry.h" + +SharedLineNumberTable::~SharedLineNumberTable() { + // Always attempt to deallocate if we have a valid pointer + // JVMTI spec requires that memory allocated by GetLineNumberTable + // must be freed with Deallocate + if (_ptr != nullptr) { + jvmtiEnv *jvmti = VM::jvmti(); + if (jvmti != nullptr) { + jvmtiError err = jvmti->Deallocate((unsigned char *)_ptr); + // If Deallocate fails, log it for debugging (this could indicate a JVM bug) + // JVMTI_ERROR_ILLEGAL_ARGUMENT means the memory wasn't allocated by JVMTI + // which would be a serious bug in GetLineNumberTable + if (err != JVMTI_ERROR_NONE) { + TEST_LOG("Unexpected error while deallocating linenumber table: %d", err); + } + } else { + TEST_LOG("WARNING: Cannot deallocate line number table - JVMTI is null"); + } + // Decrement counter whenever destructor runs (symmetric with increment at creation) + Counters::decrement(LINE_NUMBER_TABLES); + } +} diff --git a/ddprof-lib/src/main/cpp/methodInfo.h b/ddprof-lib/src/main/cpp/methodInfo.h new file mode 100644 index 000000000..3be39a334 --- /dev/null +++ b/ddprof-lib/src/main/cpp/methodInfo.h @@ -0,0 +1,112 @@ +/* + * Copyright The async-profiler authors + * Copyright 2026, Datadog, Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _METHODINFO_H +#define _METHODINFO_H + +#include +#include +#include +#include +#include + +#include "arch.h" +#include "frame.h" + +class SharedLineNumberTable { +public: + int _size; + void *_ptr; + + SharedLineNumberTable(int size, void *ptr) : _size(size), _ptr(ptr) {} + ~SharedLineNumberTable(); +}; + +class MethodInfo { +public: + MethodInfo() + : _mark(false), _is_entry(false), _referenced(false), _age(0), _key(0), _class(0), + _name(0), _sig(0), _modifiers(0), _line_number_table(nullptr), _type() {} + + bool _mark; + bool _is_entry; + bool _referenced; // Tracked during writeStackTraces() for cleanup + int _age; // Consecutive chunks without reference (0 = recently used) + u32 _key; + u32 _class; + u32 _name; + u32 _sig; + jint _modifiers; + std::shared_ptr _line_number_table; + FrameTypeId _type; + + jint getLineNumber(jint bci) { + // if the shared pointer is not pointing to the line number table, consider + // size 0 + if (!_line_number_table || _line_number_table->_size == 0) { + return 0; + } + + int i = 1; + while (i < _line_number_table->_size && + bci >= ((jvmtiLineNumberEntry *)_line_number_table->_ptr)[i] + .start_location) { + i++; + } + return ((jvmtiLineNumberEntry *)_line_number_table->_ptr)[i - 1] + .line_number; + } + + bool isHidden() { + // 0x1400 = ACC_SYNTHETIC(0x1000) | ACC_BRIDGE(0x0040) + return _modifiers == 0 || (_modifiers & 0x1040); + } +}; + +// MethodMap's key can be derived from 3 sources: +// 1) jmethodID for Java methods +// 2) void* address for native method names +// 3) Encoded RemoteFrameInfo +// The values of the keys are potentially overlapping, so we use +// the highest 2 bits to distinguish them. +// 00 - jmethodID +// 10 - void* address +// 01 - RemoteFrameInfo +class MethodMap : public std::map { +public: + static constexpr unsigned long ADDRESS_MARK = 0x8000000000000000ULL; + static constexpr unsigned long REMOTE_FRAME_MARK = 0x4000000000000000ULL; + static constexpr unsigned long METHOD_MARK = 0xc000000000000000ULL; + static constexpr unsigned long KEY_TYPE_MASK = ADDRESS_MARK | REMOTE_FRAME_MARK | METHOD_MARK; + + MethodMap() {} + + static unsigned long makeKey(jmethodID method) { + unsigned long key = (unsigned long)method; + assert((key & KEY_TYPE_MASK) == 0); + return key; + } + + static unsigned long makeKey(const char* addr) { + unsigned long key = (unsigned long)addr; + assert((key & KEY_TYPE_MASK) == 0); + return (key | ADDRESS_MARK); + } + + static unsigned long makeKey(unsigned long packed_remote_frame) { + unsigned long key = packed_remote_frame; + assert((key & KEY_TYPE_MASK) == 0); + return (key | REMOTE_FRAME_MARK); + } + + static unsigned long makeKey(const void* method) { + unsigned long key = reinterpret_cast(method); + assert((key & KEY_TYPE_MASK) == 0); + return (key | METHOD_MARK); + } +}; + +#endif // _METHODINFO_H \ No newline at end of file diff --git a/ddprof-lib/src/main/cpp/stackWalker.cpp b/ddprof-lib/src/main/cpp/stackWalker.cpp index 642d54088..dece30d0b 100644 --- a/ddprof-lib/src/main/cpp/stackWalker.cpp +++ b/ddprof-lib/src/main/cpp/stackWalker.cpp @@ -43,7 +43,7 @@ int StackWalker::walkFP(void* ucontext, const void** callchain, int max_depth, S // Walk until the bottom of the stack or until the first Java frame while (depth < actual_max_depth) { if (CodeHeap::contains(pc) && !(depth == 0 && frame.unwindAtomicStub(pc)) && - VMThread::current() != nullptr) { // If it is not a JVM thread, it cannot have Java frame + JVMThread::current() != nullptr) { // If it is not a JVM thread, it cannot have Java frame java_ctx->set(pc, sp, fp); break; } diff --git a/ddprof-lib/src/main/cpp/stackWalker.inline.h b/ddprof-lib/src/main/cpp/stackWalker.inline.h index 35877e54d..848be599d 100644 --- a/ddprof-lib/src/main/cpp/stackWalker.inline.h +++ b/ddprof-lib/src/main/cpp/stackWalker.inline.h @@ -47,12 +47,4 @@ inline void fillFrame(ASGCT_CallFrame& frame, FrameTypeId type, int bci, jmethod frame.method_id = method; } -inline jmethodID getMethodId(VMMethod* method) { - if (!StackWalkValidation::inDeadZone(method) && StackWalkValidation::aligned((uintptr_t)method) - && SafeAccess::isReadableRange(method, VMMethod::type_size())) { - return method->validatedId(); - } - return NULL; -} - #endif // _STACKWALKER_INLINE_H From 3a22b0159224092a2111165d0e3bef806b5116ff Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 23 Apr 2026 21:42:21 +0000 Subject: [PATCH 04/19] v2 --- .../hotspot/compressedLinenumberStream.cpp | 4 +- .../cpp/hotspot/compressedLinenumberStream.h | 4 +- .../src/main/cpp/hotspot/hotspotSupport.cpp | 17 +- ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp | 6 +- ddprof-lib/src/main/cpp/hotspot/vmStructs.h | 32 +- .../src/main/cpp/hotspot/vmStructs.inline.h | 95 +++--- ddprof-lib/src/main/cpp/jvmSupport.cpp | 2 +- ddprof-lib/src/main/cpp/lookup.cpp | 289 +++++++++++------- ddprof-lib/src/main/cpp/lookup.h | 5 +- ddprof-lib/src/main/cpp/methodInfo.cpp | 22 +- ddprof-lib/src/main/cpp/methodInfo.h | 6 +- ddprof-lib/src/main/cpp/profiler.cpp | 1 + ddprof-lib/src/main/cpp/vmEntry.cpp | 2 +- .../profiler/cpu/ContextCpuTest.java | 2 + 14 files changed, 293 insertions(+), 194 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.cpp b/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.cpp index e1d4ab1c4..b7b9564cf 100644 --- a/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.cpp @@ -1,6 +1,6 @@ #include "hotspot/compressedLinenumberStream.h" -CompressedLineNumberStream::CompressedLineNumberStream(unsigned char* buffer) : +CompressedLineNumberStream::CompressedLineNumberStream(const char* buffer) : _buffer(buffer), _position(0), _bci(0), _line(0) { }; @@ -29,7 +29,7 @@ bool CompressedLineNumberStream::read_pair() { uint32_t CompressedLineNumberStream::read_uint() { const int pos = _position; const uint32_t b_0 = (uint8_t)_buffer[pos]; //b_0 = a[0] - assert(b_0 >= X); + assert(b_0 >= X && "avoid excluded bytes"); uint32_t sum = b_0 - X; if (sum < L) { // common case _position = pos + 1; diff --git a/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.h b/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.h index def30c1c3..c73bc41fc 100644 --- a/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.h +++ b/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.h @@ -26,14 +26,14 @@ class CompressedLineNumberStream { static const uint32_t MAX_VALUE = (uint32_t)-1; // 2^^32-1 - unsigned char* _buffer; + const char* _buffer; int _position; int _bci; int _line; public: - CompressedLineNumberStream(unsigned char* buffer); + CompressedLineNumberStream(const char* buffer); bool read_pair(); void reset(); diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 094bc8a6a..3d4e10c59 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -271,10 +271,11 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex bool is_plausible_interpreter_frame = StackWalkValidation::isPlausibleInterpreterFrame(fp, sp, bcp_offset); if (is_plausible_interpreter_frame) { - VMMethod* method = VMMethod::load_then_cast(((void**)fp)[InterpreterFrame::method_offset]); + VMMethod* method = VMMethod::cast(((void**)fp)[InterpreterFrame::method_offset]); assert(method != nullptr && "No method for the interpreter frame"); jmethodID method_id = getMethodId(method); +// assert(method_id != nullptr || VMClassLoader::isLoadedByBootstrapClassLoader(method)); if (method_id != NULL || VMClassLoader::isLoadedByBootstrapClassLoader(method)) { Counters::increment(WALKVM_JAVA_FRAME_OK); const char* bytecode_start = method->bytecode(); @@ -283,8 +284,8 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex if (method_id != nullptr) { fillFrame(frames[depth++], FRAME_INTERPRETED, bci, method_id); } else { + TEST_LOG("Fill FRAME_INTERPRETED_METHOD frame"); fillFrame(frames[depth++], FRAME_INTERPRETED_METHOD, bci, method); - } sp = ((uintptr_t*)fp)[InterpreterFrame::sender_sp_offset]; pc = stripPointer(((void**)fp)[FRAME_PC_SLOT]); @@ -296,12 +297,14 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex if (depth == 0) { VMMethod* method = (VMMethod*)frame.method(); jmethodID method_id = getMethodId(method); +// assert(method_id != nullptr || VMClassLoader::isLoadedByBootstrapClassLoader(method)); if (method_id != NULL || VMClassLoader::isLoadedByBootstrapClassLoader(method)) { Counters::increment(WALKVM_JAVA_FRAME_OK); if (method_id != nullptr) { fillFrame(frames[depth++], FRAME_INTERPRETED, 0, method_id); } else { - fillFrame(frames[depth++], FRAME_INTERPRETED, 0, method); + TEST_LOG("Fill FRAME_INTERPRETED_METHOD frame"); + fillFrame(frames[depth++], FRAME_INTERPRETED_METHOD, 0, method); } if (is_plausible_interpreter_frame) { pc = stripPointer(((void**)fp)[FRAME_PC_SLOT]); @@ -1013,11 +1016,13 @@ static void patchClassLoaderData(JNIEnv* jni, jclass klass) { } constexpr const char* LAMBDA_PREFIX = "Ljava/lang/invoke/LambdaForm$"; +constexpr const char* FFM_PREFIX = "Ljdk/internal/foreign/abi/"; // constexpr const size_t LAMBDA_PREFIX_LEN = strlen(LAMBDA_PREFIX); static bool isLambdaClass(const char* signature) { return strncmp(signature, LAMBDA_PREFIX, strlen(LAMBDA_PREFIX)) == 0 || strstr(signature, "$$Lambda.") != nullptr || - strstr(signature, ".lambda$") != nullptr; + strstr(signature, ".lambda$") != nullptr || + strncmp(signature, FFM_PREFIX, strlen(FFM_PREFIX)) == 0; } bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass) { @@ -1043,6 +1048,10 @@ bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klas } else { TEST_LOG("Lambda class: %s", signature_ptr); } + } else if (cl != nullptr) { + char* signature_ptr; + jvmti->GetClassSignature(klass, &signature_ptr, nullptr); + TEST_LOG("processing none bootstrap class %s", signature_ptr); } return JVMSupport::loadMethodIDsImpl(jvmti, jni, klass); diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp b/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp index 459d7e541..41f91ab64 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp @@ -758,15 +758,15 @@ jmethodID VMMethod::validatedId() { return NULL; } -bool VMMethod::getLineNumberTable(jint* entry_count_ptr, - jvmtiLineNumberEntry** table_ptr) { +bool VMConstMethod::getLineNumberTable(jint* entry_count_ptr, + jvmtiLineNumberEntry** table_ptr) { if (!hasLineNumberTable()) { return false; } assert(entry_count_ptr != nullptr); assert(table_ptr != nullptr); - unsigned char* table_start = (unsigned char*)codeBase() + codeSize(); + const char* table_start = codeEnd(); int count = 0; CompressedLineNumberStream stream(table_start); while (stream.read_pair()) { diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h index 86e845fc5..3400d5d9a 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h @@ -175,7 +175,7 @@ typedef void* address; field(_constmethod_flags_offset, offset, MATCH_SYMBOLS("_flags._flags", "_flags")) \ field(_constmethod_code_size, offset, MATCH_SYMBOLS("_code_size")) \ field(_constmethod_name_index_offset, offset, MATCH_SYMBOLS("_name_index")) \ - field(_constmethod_sig_index_offset, offset, MATCH_SYMBOLS("_signatire+index")) \ + field(_constmethod_sig_index_offset, offset, MATCH_SYMBOLS("_signature_index")) \ type_end() \ type_begin(VMConstantPool, MATCH_SYMBOLS("ConstantPool")) \ field(_pool_holder_offset, offset, MATCH_SYMBOLS("_pool_holder")) \ @@ -189,7 +189,7 @@ typedef void* address; type_begin(VMClassLoaderData, MATCH_SYMBOLS("ClassLoaderData")) \ field(_class_loader_data_next_offset, offset, MATCH_SYMBOLS("_next")) \ field_with_version(_class_loader_data_has_class_mirror_holder_offset, offset, 17, MAX_VERSION, MATCH_SYMBOLS("_has_class_mirror_holder")) \ - field_with_version(_class_loader_data_is_anonymous_offset, offset, 11, MAX_VERSION, MATCH_SYMBOLS("_is_anonymous")) \ + field_with_version(_class_loader_data_is_anonymous_offset, offset, 11, 11, MATCH_SYMBOLS("_is_anonymous")) \ type_end() \ type_begin(VMJavaClass, MATCH_SYMBOLS("java_lang_Class")) \ field(_klass_offset_addr, address, MATCH_SYMBOLS("_klass_offset")) \ @@ -422,7 +422,6 @@ class VMStructs { const char* ptr = (const char*)this + offset; assert(crashProtectionActive() || SafeAccess::isReadable(ptr)); return ptr; - } static bool goodPtr(const void* ptr) { @@ -798,21 +797,31 @@ DECLARE_END DECLARE(VMConstantPool) public: inline VMKlass* holder() const; - inline VMSymbol* symbolAt(int index) const; + inline VMSymbol* symbolAt(u16 index) const; private: inline intptr_t* base() const; DECLARE_END DECLARE(VMConstMethod) + // ref: constMethodFlags.hpp in hotspot src + static constexpr uint32_t has_linenumber_table = 1 << 0; public: inline VMConstantPool* constants() const; + inline uint16_t codeSize() const; + inline const char* base() const; + inline const char* codeEnd() const; + inline u16 nameIndex() const; + inline u16 signatureIndex() const; + inline VMSymbol* name() const; + inline VMSymbol* signature() const; + inline uint32_t flags() const; + inline bool hasLineNumberTable() const; + bool getLineNumberTable(jint* entry_count_ptr, + jvmtiLineNumberEntry** table_ptr); DECLARE_END DECLARE(VMMethod) private: - // ref: constMethodFlags.hpp in hotspot src - static constexpr uint32_t has_linenumber_table = 1 << 0; - static bool check_jmethodID_J9(jmethodID id); static bool check_jmethodID_hotspot(jmethodID id); public: @@ -836,16 +845,7 @@ DECLARE(VMMethod) inline VMConstMethod* constMethod() const; inline VMNMethod* code() const; - inline uint16_t codeSize() const; - inline uint32_t flags() const; - inline bool hasLineNumberTable() const; - inline void* codeBase() const; - inline VMConstantPool* constantPool() const; - inline VMSymbol* name() const; - inline VMSymbol* signature() const; inline VMKlass* methodHolder() const; - bool getLineNumberTable(jint* entry_count_ptr, - jvmtiLineNumberEntry** table_ptr); static bool check_jmethodID(jmethodID id); DECLARE_END diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h index ec1ace953..d04de45f5 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h @@ -79,11 +79,11 @@ bool VMClassLoaderData::hasClassMirrorHolder() const { VMKlass* VMConstantPool::holder() const { assert(_pool_holder_offset >= 0); - return VMKlass::cast(at(_pool_holder_offset)); + return VMKlass::load_then_cast(at(_pool_holder_offset)); } -VMSymbol* VMConstantPool::symbolAt(int index) const { - return VMSymbol::cast(&base()[index]); +VMSymbol* VMConstantPool::symbolAt(u16 index) const { + return VMSymbol::cast(*(void**)&base()[index]); } intptr_t* VMConstantPool::base() const { @@ -95,71 +95,68 @@ VMConstMethod* VMMethod::constMethod() const { return VMConstMethod::load_then_cast(at(_method_constmethod_offset)); } -uint16_t VMMethod::codeSize() const { - assert(_constmethod_code_size >= 0); - address code_size_addr = *(unsigned char**)at(_method_constmethod_offset) + _constmethod_code_size; - uint16_t code_size = *(uint16_t*)code_size_addr; - TEST_LOG("VMMethod::codeSize(): code_size=%u\n", code_size); - return code_size; +VMNMethod* VMMethod::code() const { + assert(_method_code_offset >= 0); + const void* code_ptr = *(const void**) at(_method_code_offset); + return VMNMethod::cast(code_ptr); } -uint32_t VMMethod::flags() const { - assert(_constmethod_flags_offset >= 0); - return *(uint32_t*) ( *(const char**) at(_method_constmethod_offset) + _constmethod_flags_offset ); +VMKlass* VMMethod::methodHolder() const { +// return constMethod()->constants()->holder(); + VMConstMethod* constMthd = constMethod(); + VMConstantPool* pool = constMthd->constants(); + VMKlass* holder = pool->holder(); + return holder; } -bool VMMethod::hasLineNumberTable() const { - return (flags() & has_linenumber_table) != 0; +VMConstantPool* VMConstMethod::constants() const { + return VMConstantPool::load_then_cast(at(_constmethod_constants_offset)); } -address VMMethod::codeBase() const { - assert(_method_constmethod_offset >= 0); - const char* const_method = (const char*) SafeAccess::load((void**) at(_method_constmethod_offset)); - return (address)(const_method+1); +uint16_t VMConstMethod::codeSize() const { + assert(_constmethod_code_size >= 0); + uint16_t code_size = *(uint16_t*)at(_constmethod_code_size); + TEST_LOG("VMConstMethod::codeSize(): code_size=%u", code_size); + return code_size; } -VMConstantPool* VMMethod::constantPool() const { - assert(_method_constmethod_offset >= 0); - const char* const_method = (const char*) SafeAccess::load((void**) at(_method_constmethod_offset)); - assert(goodPtr(const_method)); - assert(_constmethod_constants_offset >= 0); - VMConstantPool* cpool = *(VMConstantPool**) (const_method + _constmethod_constants_offset); - return cpool; +const char* VMConstMethod::base() const { + return (const char*)this + _VMConstMethod_size; } -VMSymbol* VMMethod::name() const { - VMConstantPool* cpool = constantPool(); - assert(_method_constmethod_offset >= 0); - const char* const_method = (const char*) SafeAccess::load((void**) at(_method_constmethod_offset)); - assert(goodPtr(const_method)); - assert(_constmethod_name_index_offset >= 0); - int name_index = *(uint16_t*) (const_method + _constmethod_name_index_offset); - return cpool->symbolAt(name_index); +const char* VMConstMethod::codeEnd() const { + return base() + codeSize(); +} +u16 VMConstMethod::nameIndex() const { + assert(_constmethod_name_index_offset >= 0 && "Invalid name index"); + return *(u16*)at(_constmethod_name_index_offset); } -VMSymbol* VMMethod::signature() const { - VMConstantPool* cpool = constantPool(); - assert(_method_constmethod_offset >= 0); - const char* const_method = (const char*) SafeAccess::load((void**) at(_method_constmethod_offset)); - assert(goodPtr(const_method)); - assert(_constmethod_sig_index_offset >= 0); - int signature_index = *(uint16_t*) (const_method + _constmethod_sig_index_offset); - return cpool->symbolAt(signature_index); +u16 VMConstMethod::signatureIndex() const { + assert(_constmethod_sig_index_offset >= 0 && "Invalid signature index"); + return *(u16*)at(_constmethod_sig_index_offset); } -VMNMethod* VMMethod::code() const { - assert(_method_code_offset >= 0); - const void* code_ptr = *(const void**) at(_method_code_offset); - return VMNMethod::cast(code_ptr); +VMSymbol* VMConstMethod::name() const { + VMConstantPool* cpool = constants(); + u16 name_index = nameIndex(); + return cpool->symbolAt(name_index); } -VMKlass* VMMethod::methodHolder() const { - return constMethod()->constants()->holder(); +VMSymbol* VMConstMethod::signature() const { + VMConstantPool* cpool = constants(); + u16 sig_index = signatureIndex(); + return cpool->symbolAt(sig_index); } -VMConstantPool* VMConstMethod::constants() const { - return VMConstantPool::load_then_cast(at(_constmethod_constants_offset)); +uint32_t VMConstMethod::flags() const { + assert(_constmethod_flags_offset >= 0); + return *(uint32_t*)at(_constmethod_flags_offset ); +} + +bool VMConstMethod::hasLineNumberTable() const { + return (flags() & has_linenumber_table) != 0; } uintptr_t VMOopHandle::oop() const { diff --git a/ddprof-lib/src/main/cpp/jvmSupport.cpp b/ddprof-lib/src/main/cpp/jvmSupport.cpp index bea90ff78..967a686c6 100644 --- a/ddprof-lib/src/main/cpp/jvmSupport.cpp +++ b/ddprof-lib/src/main/cpp/jvmSupport.cpp @@ -107,7 +107,7 @@ bool JVMSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass) { void JNICALL JVMSupport::ClassPrepare(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, jclass klass) { if (VM::isHotspot()) { - + HotspotSupport::loadMethodIDsImpl(jvmti, jni, klass); } else { loadMethodIDsImpl(jvmti, jni, klass); } diff --git a/ddprof-lib/src/main/cpp/lookup.cpp b/ddprof-lib/src/main/cpp/lookup.cpp index 721386ca7..10453dc83 100644 --- a/ddprof-lib/src/main/cpp/lookup.cpp +++ b/ddprof-lib/src/main/cpp/lookup.cpp @@ -20,7 +20,7 @@ #include "profiler.h" #include "rustDemangler.h" -#include "hotspot/vmStructs.h" +#include "hotspot/vmStructs.inline.h" void Lookup::fillNativeMethodInfo(MethodInfo *mi, const char *name, const char *lib_name) { @@ -120,16 +120,12 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, jmethodID method, char *class_name = nullptr; char *method_name = nullptr; char *method_sig = nullptr; - u32 class_name_id = 0; - u32 method_name_id = 0; - u32 method_sig_id = 0; jint line_number_table_size = 0; jvmtiLineNumberEntry *line_number_table = NULL; jvmti->GetPhase(&phase); if ((phase & (JVMTI_PHASE_START | JVMTI_PHASE_LIVE)) != 0) { - bool entry = false; if (VMMethod::check_jmethodID(method) && jvmti->GetMethodDeclaringClass(method, &method_class) == 0 && // On some older versions of J9, the JVMTI call to GetMethodDeclaringClass will return OK = 0, but when a @@ -157,101 +153,137 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, jmethodID method, } } - // Check if the frame is Thread.run or inherits from it - if (strncmp(method_name, "run", 4) == 0 && - strncmp(method_sig, "()V", 3) == 0) { - jclass Thread_class = jni->FindClass("java/lang/Thread"); - jclass Class_class = jni->FindClass("java/lang/Class"); - if (Thread_class != nullptr && Class_class != nullptr) { - jmethodID equals = jni->GetMethodID(Class_class, - "equals", "(Ljava/lang/Object;)Z"); - if (equals != nullptr) { - jclass klass = method_class; - do { - entry = jni->CallBooleanMethod(Thread_class, equals, klass); - if (jniExceptionCheck(jni)) { - entry = false; - break; - } - if (entry) { - break; - } - } while ((klass = jni->GetSuperclass(klass)) != NULL); + fillMethodInfo(mi, method_class, class_name, method_name, method_sig, line_number_table_size, line_number_table); + + // strings are null or came from JVMTI + if (method_name) { + jvmti->Deallocate((unsigned char *)method_name); + } + if (method_sig) { + jvmti->Deallocate((unsigned char *)method_sig); + } + if (class_name) { + jvmti->Deallocate((unsigned char *)class_name); + } + } else { + Counters::increment(JMETHODID_SKIPPED); + mi->_class = _classes->lookup(""); + mi->_name = _symbols.lookup("jvmtiError"); + mi->_sig = _symbols.lookup("()L;"); + mi->_type = FRAME_INTERPRETED; + mi->_is_entry = false; + if (line_number_table != nullptr) { + mi->_line_number_table = std::make_shared( + line_number_table_size, line_number_table); + // Increment counter for tracking live line number tables + Counters::increment(LINE_NUMBER_TABLES); + } + + } + } + jni->PopLocalFrame(NULL); +} + +void Lookup::fillMethodInfo(MethodInfo *mi, jclass method_class, char* class_name, char* method_name, char* method_sig, jint line_number_table_size, jvmtiLineNumberEntry* line_number_table) { + bool entry = false; + u32 class_name_id = 0; + u32 method_name_id = 0; + u32 method_sig_id = 0; + + JNIEnv *jni = VM::jni(); + if (jni == nullptr) { + return; + } + + // Check if the frame is Thread.run or inherits from it + if (strncmp(method_name, "run", 4) == 0 && + strncmp(method_sig, "()V", 3) == 0) { + jclass Thread_class = jni->FindClass("java/lang/Thread"); + jclass Class_class = jni->FindClass("java/lang/Class"); + if (Thread_class != nullptr && Class_class != nullptr) { + jmethodID equals = jni->GetMethodID(Class_class, + "equals", "(Ljava/lang/Object;)Z"); + if (equals != nullptr) { + jclass klass = method_class; + do { + entry = jni->CallBooleanMethod(Thread_class, equals, klass); + if (jniExceptionCheck(jni)) { + entry = false; + break; } - } - // Clear any exceptions from the reflection calls above - jniExceptionCheck(jni); - } else if (strncmp(method_name, "main", 5) == 0 && - strncmp(method_sig, "(Ljava/lang/String;)V", 21)) { - // public static void main(String[] args) - 'public static' translates - // to modifier bits 0 and 3, hence check for '9' - entry = true; + if (entry) { + break; + } + } while ((klass = jni->GetSuperclass(klass)) != NULL); } + } + // Clear any exceptions from the reflection calls above + jniExceptionCheck(jni); + } else if (strncmp(method_name, "main", 5) == 0 && + strncmp(method_sig, "(Ljava/lang/String;)V", 21)) { + // public static void main(String[] args) - 'public static' translates + // to modifier bits 0 and 3, hence check for '9' + entry = true; + } - // maybe we should store the lookups below in initialisation-time - // constants... - if (has_prefix(class_name, - "Ljdk/internal/reflect/GeneratedConstructorAccessor")) { - class_name_id = _classes->lookup( - "jdk/internal/reflect/GeneratedConstructorAccessor"); - method_name_id = - _symbols.lookup("Object " - "jdk.internal.reflect.GeneratedConstructorAccessor." - "newInstance(Object[])"); - method_sig_id = _symbols.lookup(method_sig); - } else if (has_prefix(class_name, - "Lsun/reflect/GeneratedConstructorAccessor")) { - class_name_id = - _classes->lookup("sun/reflect/GeneratedConstructorAccessor"); - method_name_id = _symbols.lookup( + // maybe we should store the lookups below in initialisation-time + // constants... + if (has_prefix(class_name, + "Ljdk/internal/reflect/GeneratedConstructorAccessor")) { + class_name_id = _classes->lookup( + "jdk/internal/reflect/GeneratedConstructorAccessor"); + method_name_id = + _symbols.lookup("Object " + "jdk.internal.reflect.GeneratedConstructorAccessor." + "newInstance(Object[])"); + method_sig_id = _symbols.lookup(method_sig); + } else if (has_prefix(class_name, + "Lsun/reflect/GeneratedConstructorAccessor")) { + class_name_id = + _classes->lookup("sun/reflect/GeneratedConstructorAccessor"); + method_name_id = _symbols.lookup( "Object " "sun.reflect.GeneratedConstructorAccessor.newInstance(Object[])"); - method_sig_id = _symbols.lookup(method_sig); - } else if (has_prefix(class_name, - "Ljdk/internal/reflect/GeneratedMethodAccessor")) { - class_name_id = - _classes->lookup("jdk/internal/reflect.GeneratedMethodAccessor"); - method_name_id = - _symbols.lookup("Object " - "jdk.internal.reflect.GeneratedMethodAccessor." - "invoke(Object, Object[])"); - method_sig_id = _symbols.lookup(method_sig); - } else if (has_prefix(class_name, - "Lsun/reflect/GeneratedMethodAccessor")) { - class_name_id = _classes->lookup("sun/reflect/GeneratedMethodAccessor"); - method_name_id = _symbols.lookup( + method_sig_id = _symbols.lookup(method_sig); + } else if (has_prefix(class_name, + "Ljdk/internal/reflect/GeneratedMethodAccessor")) { + class_name_id = + _classes->lookup("jdk/internal/reflect.GeneratedMethodAccessor"); + method_name_id = + _symbols.lookup("Object " + "jdk.internal.reflect.GeneratedMethodAccessor." + "invoke(Object, Object[])"); + method_sig_id = _symbols.lookup(method_sig); + } else if (has_prefix(class_name, + "Lsun/reflect/GeneratedMethodAccessor")) { + class_name_id = _classes->lookup("sun/reflect/GeneratedMethodAccessor"); + method_name_id = _symbols.lookup( "Object sun.reflect.GeneratedMethodAccessor.invoke(Object, " "Object[])"); - method_sig_id = _symbols.lookup(method_sig); - } else if (has_prefix(class_name, "Ljava/lang/invoke/LambdaForm$")) { - const int lambdaFormPrefixLength = - strlen("Ljava/lang/invoke/LambdaForm$"); - // we want to normalise to java/lang/invoke/LambdaForm$MH, - // java/lang/invoke/LambdaForm$DMH, java/lang/invoke/LambdaForm$BMH, - if (has_prefix(class_name + lambdaFormPrefixLength, "MH")) { - class_name_id = _classes->lookup("java/lang/invoke/LambdaForm$MH"); - } else if (has_prefix(class_name + lambdaFormPrefixLength, "BMH")) { - class_name_id = _classes->lookup("java/lang/invoke/LambdaForm$BMH"); - } else if (has_prefix(class_name + lambdaFormPrefixLength, "DMH")) { - class_name_id = _classes->lookup("java/lang/invoke/LambdaForm$DMH"); - } else { - // don't recognise the suffix, so don't normalise - class_name_id = - _classes->lookup(class_name + 1, strlen(class_name) - 2); - } - method_name_id = _symbols.lookup(method_name); - method_sig_id = _symbols.lookup(method_sig); + method_sig_id = _symbols.lookup(method_sig); + } else if (has_prefix(class_name, "Ljava/lang/invoke/LambdaForm$")) { + const int lambdaFormPrefixLength = + strlen("Ljava/lang/invoke/LambdaForm$"); + // we want to normalise to java/lang/invoke/LambdaForm$MH, + // java/lang/invoke/LambdaForm$DMH, java/lang/invoke/LambdaForm$BMH, + if (has_prefix(class_name + lambdaFormPrefixLength, "MH")) { + class_name_id = _classes->lookup("java/lang/invoke/LambdaForm$MH"); + } else if (has_prefix(class_name + lambdaFormPrefixLength, "BMH")) { + class_name_id = _classes->lookup("java/lang/invoke/LambdaForm$BMH"); + } else if (has_prefix(class_name + lambdaFormPrefixLength, "DMH")) { + class_name_id = _classes->lookup("java/lang/invoke/LambdaForm$DMH"); } else { + // don't recognise the suffix, so don't normalise class_name_id = _classes->lookup(class_name + 1, strlen(class_name) - 2); - method_name_id = _symbols.lookup(method_name); - method_sig_id = _symbols.lookup(method_sig); } - } else { - Counters::increment(JMETHODID_SKIPPED); - class_name_id = _classes->lookup(""); - method_name_id = _symbols.lookup("jvmtiError"); - method_sig_id = _symbols.lookup("()L;"); + method_name_id = _symbols.lookup(method_name); + method_sig_id = _symbols.lookup(method_sig); + } else { + class_name_id = + _classes->lookup(class_name + 1, strlen(class_name) - 2); + method_name_id = _symbols.lookup(method_name); + method_sig_id = _symbols.lookup(method_sig); } mi->_class = class_name_id; @@ -265,19 +297,6 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, jmethodID method, // Increment counter for tracking live line number tables Counters::increment(LINE_NUMBER_TABLES); } - - // strings are null or came from JVMTI - if (method_name) { - jvmti->Deallocate((unsigned char *)method_name); - } - if (method_sig) { - jvmti->Deallocate((unsigned char *)method_sig); - } - if (class_name) { - jvmti->Deallocate((unsigned char *)class_name); - } - } - jni->PopLocalFrame(NULL); } MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) { @@ -293,8 +312,10 @@ MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) { key = MethodMap::makeKey(frame.native_function_name); } else if (bci == BCI_NATIVE_FRAME_REMOTE) { key = MethodMap::makeKey(frame.packed_remote_frame); + } else if (frame_type == FRAME_INTERPRETED_METHOD) { + TEST_LOG("Found FRAME_INTERPRETED_METHOD"); + key = MethodMap::makeKey(frame.method); } else { - FrameTypeId frame_type = FrameType::decode(bci); assert(frame_type == FRAME_INTERPRETED || frame_type == FRAME_JIT_COMPILED || frame_type == FRAME_INLINED || frame_type == FRAME_C1_COMPILED || VM::isOpenJ9()); // OpenJ9 may have bugs that produce invalid frame types @@ -349,8 +370,10 @@ MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) { TEST_LOG("WARNING: Library lookup failed for index %u", lib_index); fillNativeMethodInfo(mi, "unknown_library", nullptr); } + } else if (frame_type == FRAME_INTERPRETED_METHOD) { + fillJavaMethodInfo(mi, frame.method, first_time); } else { - fillJavaMethodInfo(mi, method, first_time); + fillJavaMethodInfo(mi, method, first_time); } } @@ -375,4 +398,60 @@ u32 Lookup::getPackage(const char *class_name) { return _packages.lookup(class_name, package - class_name); } -u32 Lookup::getSymbol(const char *name) { return _symbols.lookup(name); } +u32 Lookup::getSymbol(const char *name) { + return _symbols.lookup(name); +} + +void Lookup::fillJavaMethodInfo(MethodInfo *mi, const void* method, bool first_time) { + assert(VM::isHotspot()); + assert(method != nullptr); + VMMethod* vm_method = VMMethod::cast(method); + VMConstMethod* const_method = vm_method->constMethod(); + + VMSymbol* name_sym = const_method->name(); + VMSymbol* sig_sym = const_method->signature(); + VMKlass* klass = vm_method->methodHolder(); + VMSymbol* klass_sym = klass->name(); + char* method_name = (char*)malloc(name_sym->length() + 1); + char* method_signature = (char*)malloc(sig_sym->length() + 1); + char* klass_name = (char*)malloc(klass_sym->length() + 1); + + memcpy(method_name, name_sym->body(), name_sym->length()); + method_name[name_sym->length()] = '\0'; + memcpy(method_signature, sig_sym->body(), sig_sym->length()); + method_signature[sig_sym->length()] = '\0'; + memcpy(klass_name, klass_sym->body(), klass_sym->length()); + klass_name[klass_sym->length()] = '\0'; + + TEST_LOG("Lookup VMMethod: %s %s : Class: %s", method_name, method_signature, klass_name); + JNIEnv *jni = VM::jni(); + jclass clz = jni->FindClass(klass_name); + assert(clz != nullptr && "Could not find jclass"); + jint entry_count = 0; + jvmtiLineNumberEntry* table = nullptr; + + /* + jmethodID mthd = jni->GetMethodID(clz, method_name, method_signature); + + jvmtiEnv* jvmti = VM::jvmti(); + if (jvmti->GetLineNumberTable(mthd, &entry_count, &table) != JVMTI_ERROR_NONE) { + table = nullptr; + entry_count = 0; + } + +// fillMethodInfo(mi, clz, klass_name, method_name, method_signature, entry_count, table); +*/ + if (first_time && const_method->hasLineNumberTable()) { + if (!const_method->getLineNumberTable(&entry_count, &table)) { + entry_count = 0; + table = nullptr; + } else { + TEST_LOG("Load line number table for %s count = %d", method_name, entry_count); + } + } + fillMethodInfo(mi, clz, klass_name, method_name, method_signature, -entry_count, table); + + free(method_name); + free(method_signature); + free(klass_name); +} diff --git a/ddprof-lib/src/main/cpp/lookup.h b/ddprof-lib/src/main/cpp/lookup.h index 9bd5b9232..36a9719cc 100644 --- a/ddprof-lib/src/main/cpp/lookup.h +++ b/ddprof-lib/src/main/cpp/lookup.h @@ -28,11 +28,14 @@ class Lookup { const char *lib_name); void fillRemoteFrameInfo(MethodInfo *mi, const RemoteFrameInfo *rfi); void cutArguments(char *func); - void fillJavaMethodInfo(MethodInfo *mi, jmethodID method, bool first_time); bool has_prefix(const char *str, const char *prefix) const { return strncmp(str, prefix, strlen(prefix)) == 0; } + void fillJavaMethodInfo(MethodInfo *mi, jmethodID method, bool first_time); + void fillJavaMethodInfo(MethodInfo *mi, const void* method, bool first_time); + void fillMethodInfo(MethodInfo *mi, jclass method_class, char* class_name, char* method_name, char* method_sig, + jint line_number_table_size, jvmtiLineNumberEntry* line_number_table); public: Lookup(Recording *rec, MethodMap *method_map, Dictionary *classes) : _rec(rec), _method_map(method_map), _classes(classes), _packages(), diff --git a/ddprof-lib/src/main/cpp/methodInfo.cpp b/ddprof-lib/src/main/cpp/methodInfo.cpp index 702378561..2023df7c7 100644 --- a/ddprof-lib/src/main/cpp/methodInfo.cpp +++ b/ddprof-lib/src/main/cpp/methodInfo.cpp @@ -17,17 +17,21 @@ SharedLineNumberTable::~SharedLineNumberTable() { // JVMTI spec requires that memory allocated by GetLineNumberTable // must be freed with Deallocate if (_ptr != nullptr) { - jvmtiEnv *jvmti = VM::jvmti(); - if (jvmti != nullptr) { - jvmtiError err = jvmti->Deallocate((unsigned char *)_ptr); - // If Deallocate fails, log it for debugging (this could indicate a JVM bug) - // JVMTI_ERROR_ILLEGAL_ARGUMENT means the memory wasn't allocated by JVMTI - // which would be a serious bug in GetLineNumberTable - if (err != JVMTI_ERROR_NONE) { - TEST_LOG("Unexpected error while deallocating linenumber table: %d", err); + if (_size > 0) { + jvmtiEnv *jvmti = VM::jvmti(); + if (jvmti != nullptr) { + jvmtiError err = jvmti->Deallocate((unsigned char *)_ptr); + // If Deallocate fails, log it for debugging (this could indicate a JVM bug) + // JVMTI_ERROR_ILLEGAL_ARGUMENT means the memory wasn't allocated by JVMTI + // which would be a serious bug in GetLineNumberTable + if (err != JVMTI_ERROR_NONE) { + TEST_LOG("Unexpected error while deallocating linenumber table: %d", err); + } + } else { + TEST_LOG("WARNING: Cannot deallocate line number table - JVMTI is null"); } } else { - TEST_LOG("WARNING: Cannot deallocate line number table - JVMTI is null"); + free(_ptr); } // Decrement counter whenever destructor runs (symmetric with increment at creation) Counters::decrement(LINE_NUMBER_TABLES); diff --git a/ddprof-lib/src/main/cpp/methodInfo.h b/ddprof-lib/src/main/cpp/methodInfo.h index 3be39a334..8e2622e73 100644 --- a/ddprof-lib/src/main/cpp/methodInfo.h +++ b/ddprof-lib/src/main/cpp/methodInfo.h @@ -18,6 +18,7 @@ class SharedLineNumberTable { public: + /* positive: the table is allocated by jvmti. negative: the table is allocated profiler */ int _size; void *_ptr; @@ -51,7 +52,10 @@ class MethodInfo { } int i = 1; - while (i < _line_number_table->_size && + int table_size = _line_number_table->_size; + table_size = table_size > 0 ? table_size : - table_size; + + while (i < table_size && bci >= ((jvmtiLineNumberEntry *)_line_number_table->_ptr)[i] .start_location) { i++; diff --git a/ddprof-lib/src/main/cpp/profiler.cpp b/ddprof-lib/src/main/cpp/profiler.cpp index 18a6230f1..a163bfacd 100644 --- a/ddprof-lib/src/main/cpp/profiler.cpp +++ b/ddprof-lib/src/main/cpp/profiler.cpp @@ -149,6 +149,7 @@ const char *Profiler::asgctError(int code) { // Zing sometimes returns it return "unknown_state"; default: + TEST_LOG("-> error: unexpected_state"); // Should not happen return "unexpected_state"; } diff --git a/ddprof-lib/src/main/cpp/vmEntry.cpp b/ddprof-lib/src/main/cpp/vmEntry.cpp index 38096232f..6d7cfafe1 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.cpp +++ b/ddprof-lib/src/main/cpp/vmEntry.cpp @@ -429,7 +429,6 @@ bool VM::initProfilerBridge(JavaVM *vm, bool attach) { _jvmti->AddCapabilities(&capabilities); jvmtiEventCallbacks callbacks = {0}; - _jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)); callbacks.VMInit = VMInit; callbacks.VMDeath = VMDeath; @@ -445,6 +444,7 @@ bool VM::initProfilerBridge(JavaVM *vm, bool attach) { callbacks.DynamicCodeGenerated = JitCodeCache::DynamicCodeGenerated; callbacks.NativeMethodBind = HotspotSupport::NativeMethodBind; + _jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)); _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, NULL); _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_LOAD, NULL); diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/ContextCpuTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/ContextCpuTest.java index 3afd598cb..6403af1d2 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/ContextCpuTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/ContextCpuTest.java @@ -72,6 +72,8 @@ public void test(@CStack String cstack) throws ExecutionException, InterruptedEx IMemberAccessor stateAccessor = THREAD_STATE.getAccessor(cpuSamples.getType()); for (IItem sample : cpuSamples) { String stackTrace = frameAccessor.getMember(sample); + System.out.println("Stack trace: "); + System.out.println(stackTrace); long spanId = spanIdAccessor.getMember(sample).longValue(); long rootSpanId = rootSpanIdAccessor.getMember(sample).longValue(); String state = stateAccessor.getMember(sample); From 9e83514b61f33fe23cf6a2f667efac5a9c036aa9 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Fri, 24 Apr 2026 16:31:42 +0000 Subject: [PATCH 05/19] Remove OopHandle --- .../src/main/cpp/hotspot/hotspotSupport.cpp | 5 ----- ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp | 7 +++++++ ddprof-lib/src/main/cpp/hotspot/vmStructs.h | 15 +++------------ .../src/main/cpp/hotspot/vmStructs.inline.h | 5 ----- ddprof-lib/src/main/cpp/lookup.cpp | 11 ----------- 5 files changed, 10 insertions(+), 33 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 3d4e10c59..6faaa56cd 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -1033,11 +1033,6 @@ bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klas // Hotpsot only: loaded by bootstrap class loader, which is never unloaded, // we use Method instead. if (jvmti->GetClassLoader(klass, &cl) == JVMTI_ERROR_NONE && cl == nullptr) { - VMOopHandle* klass_handle = VMOopHandle::cast(klass); - VMKlass* vmklass = VMKlass::fromOop(klass_handle->oop()); - assert(vmklass != nullptr); - VMClassLoaderData* cld = vmklass->classLoaderData(); - assert(cld != nullptr); char* signature_ptr; jvmti->GetClassSignature(klass, &signature_ptr, nullptr); TEST_LOG("processing bootstrap class %s", signature_ptr); diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp b/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp index 41f91ab64..ecf5eb9eb 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp @@ -772,8 +772,15 @@ bool VMConstMethod::getLineNumberTable(jint* entry_count_ptr, while (stream.read_pair()) { count++; } + if (count == 0) { + return false; + } jvmtiLineNumberEntry* table = (jvmtiLineNumberEntry*)malloc(count * sizeof(jvmtiLineNumberEntry)); + if (table == nullptr) { + return false; + } + stream.reset(); count = 0; while (stream.read_pair()) { diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h index 3400d5d9a..1a27ab277 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h @@ -127,8 +127,7 @@ inline T* cast_to(const void* ptr) { f(VMNMethod, MATCH_SYMBOLS("nmethod")) \ f(VMSymbol, MATCH_SYMBOLS("Symbol")) \ f(VMThread, MATCH_SYMBOLS("Thread")) \ - f(VMOopHandle, MATCH_SYMBOLS("OopHandle")) \ - f(VMClasses, MATCH_SYMBOLS("vmClasses")) + f(VMClasses, MATCH_SYMBOLS("vmClasses", "SystemDictionary")) /** * Following macros define field offsets, addresses or values of JVM classes that are exported by @@ -283,12 +282,9 @@ typedef void* address; field(_narrow_klass_shift_addr, address, MATCH_SYMBOLS("_narrow_klass._shift", "_shift")) \ field(_collected_heap_addr, address, MATCH_SYMBOLS("_collectedHeap")) \ type_end() \ - type_begin(VMOopHandle, MATCH_SYMBOLS("OopHandle")) \ - field(_oop_handle_obj_offset, offset, MATCH_SYMBOLS("_obj")) \ - type_end() \ type_begin(VMClasses, MATCH_SYMBOLS("vmClasses")) \ - field(_obj_class_addr, address, MATCH_SYMBOLS("_klasses[static_cast(vmClassID::Object_klass_knum)]")) \ - field(_thread_class_addr, address, MATCH_SYMBOLS("_klasses[static_cast(vmClassID::Thread_klass_knum)]")) \ + field(_obj_class_addr, address, MATCH_SYMBOLS("_klasses[static_cast(vmClassID::Object_klass_knum)]", "_well_known_klasses[SystemDictionary::Object_klass_knum]")) \ + field(_thread_class_addr, address, MATCH_SYMBOLS("_klasses[static_cast(vmClassID::Thread_klass_knum)]", "_well_known_klasses[SystemDictionary::Thread_klass_knum]")) \ type_end() /** @@ -700,11 +696,6 @@ DECLARE(VMJavaFrameAnchor) } DECLARE_END -DECLARE(VMOopHandle) -public: - inline uintptr_t oop() const; -DECLARE_END - // Copied from JDK's globalDefinitions.hpp 'JavaThreadState' enum enum JVMJavaThreadState { _thread_uninitialized = 0, // should never happen (missing initialization) diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h index d04de45f5..3012645c2 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h @@ -159,11 +159,6 @@ bool VMConstMethod::hasLineNumberTable() const { return (flags() & has_linenumber_table) != 0; } -uintptr_t VMOopHandle::oop() const { - assert(_oop_handle_obj_offset >= 0); - return *(uintptr_t*) at(_oop_handle_obj_offset); -} - VMKlass* VMClasses::obj_klass() { return VMKlass::load_then_cast(_obj_class_addr); } diff --git a/ddprof-lib/src/main/cpp/lookup.cpp b/ddprof-lib/src/main/cpp/lookup.cpp index 10453dc83..7b80a394c 100644 --- a/ddprof-lib/src/main/cpp/lookup.cpp +++ b/ddprof-lib/src/main/cpp/lookup.cpp @@ -430,17 +430,6 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, const void* method, bool first_t jint entry_count = 0; jvmtiLineNumberEntry* table = nullptr; - /* - jmethodID mthd = jni->GetMethodID(clz, method_name, method_signature); - - jvmtiEnv* jvmti = VM::jvmti(); - if (jvmti->GetLineNumberTable(mthd, &entry_count, &table) != JVMTI_ERROR_NONE) { - table = nullptr; - entry_count = 0; - } - -// fillMethodInfo(mi, clz, klass_name, method_name, method_signature, entry_count, table); -*/ if (first_time && const_method->hasLineNumberTable()) { if (!const_method->getLineNumberTable(&entry_count, &table)) { entry_count = 0; From a86fdc2ac6eb30f2d3a3a4a6d22a8e2a1a7f8f89 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Fri, 24 Apr 2026 20:41:22 +0000 Subject: [PATCH 06/19] v3 --- .../hotspot/compressedLinenumberStream.cpp | 25 +------- .../cpp/hotspot/compressedLinenumberStream.h | 60 +++++++++++++++---- .../src/main/cpp/hotspot/hotspotSupport.cpp | 2 - ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp | 2 +- ddprof-lib/src/main/cpp/lookup.cpp | 17 ++++++ 5 files changed, 67 insertions(+), 39 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.cpp b/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.cpp index b7b9564cf..14e36e418 100644 --- a/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.cpp @@ -1,6 +1,6 @@ #include "hotspot/compressedLinenumberStream.h" -CompressedLineNumberStream::CompressedLineNumberStream(const char* buffer) : +CompressedLineNumberStream::CompressedLineNumberStream(unsigned char* buffer) : _buffer(buffer), _position(0), _bci(0), _line(0) { }; @@ -25,26 +25,3 @@ bool CompressedLineNumberStream::read_pair() { } return true; } - -uint32_t CompressedLineNumberStream::read_uint() { - const int pos = _position; - const uint32_t b_0 = (uint8_t)_buffer[pos]; //b_0 = a[0] - assert(b_0 >= X && "avoid excluded bytes"); - uint32_t sum = b_0 - X; - if (sum < L) { // common case - _position = pos + 1; - return sum; - } - // must collect more bytes: b[1]...b[4] - int lg_H_i = lg_H; // lg(H)*i == lg(H^^i) - for (int i = 1; ; i++) { // for i in [1..4] - const uint32_t b_i = (uint8_t) _buffer[pos + i]; //b_i = a[i] - assert(b_i >= X); - sum += (b_i - X) << lg_H_i; // sum += (b[i]-X)*(64^^i) - if (b_i < X+L || i == MAX_LENGTH-1) { - _position = pos + i + 1; - return sum; - } - lg_H_i += lg_H; - } - } \ No newline at end of file diff --git a/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.h b/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.h index c73bc41fc..6f0850dcc 100644 --- a/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.h +++ b/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.h @@ -17,23 +17,21 @@ class CompressedLineNumberStream { private: // Math constants for the modified UNSIGNED5 coding of Pack200 static const int BitsPerByte = 8; - static const int lg_H = 6; // log-base-2 of H (lg 64 == 6) - static const int H = 1<> 1) ^ -(int)(value & 1); } - int read_signed_int() { return decode_sign(read_uint()); } - uint32_t read_uint(); + int read_signed_int() { return decode_sign(read_int()); } + int read_int() { + int b0 = read_byte(); + if (b0 < L) return b0; + else return read_int_mb(b0); + } + + // This encoding, called UNSIGNED5, is taken from J2SE Pack200. + // It assumes that most values have lots of leading zeroes. + // Very small values, in the range [0..191], code in one byte. + // Any 32-bit value (including negatives) can be coded, in + // up to five bytes. The grammar is: + // low_byte = [0..191] + // high_byte = [192..255] + // any_byte = low_byte | high_byte + // coding = low_byte + // | high_byte low_byte + // | high_byte high_byte low_byte + // | high_byte high_byte high_byte low_byte + // | high_byte high_byte high_byte high_byte any_byte + // Each high_byte contributes six bits of payload. + // The encoding is one-to-one (except for integer overflow) + // and easy to parse and unparse. + int read_int_mb(int b0) { + int pos = _position - 1; + unsigned char* buf = _buffer + pos; + assert(buf[0] == b0 && b0 >= L && "correctly called"); + int sum = b0; + // must collect more bytes: b[1]...b[4] + int lg_H_i = lg_H; + for (int i = 0; ; ) { + int b_i = buf[++i]; // b_i = read(); ++i; + sum += b_i << lg_H_i; // sum += b[i]*(64**i) + if (b_i < L || i == MAX_i) { + _position = (pos+i+1); + return sum; + } + lg_H_i += lg_H; + } + } }; #endif // _HOTSPOT_COMPRESSED_LINENUMBER_STREAM_H \ No newline at end of file diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index cb8acc7ec..7a49ce0e2 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -422,7 +422,6 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex if (method_id != nullptr) { fillFrame(frames[depth++], FRAME_INTERPRETED, bci, method_id); } else { - TEST_LOG("Fill FRAME_INTERPRETED_METHOD frame"); fillFrame(frames[depth++], FRAME_INTERPRETED_METHOD, bci, method); } sp = ((uintptr_t*)fp)[InterpreterFrame::sender_sp_offset]; @@ -441,7 +440,6 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex if (method_id != nullptr) { fillFrame(frames[depth++], FRAME_INTERPRETED, 0, method_id); } else { - TEST_LOG("Fill FRAME_INTERPRETED_METHOD frame"); fillFrame(frames[depth++], FRAME_INTERPRETED_METHOD, 0, method); } if (is_plausible_interpreter_frame) { diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp b/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp index a28c50131..d241ffe94 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp @@ -809,7 +809,7 @@ bool VMConstMethod::getLineNumberTable(jint* entry_count_ptr, assert(entry_count_ptr != nullptr); assert(table_ptr != nullptr); - const char* table_start = codeEnd(); + unsigned char* table_start = (unsigned char*)codeEnd(); int count = 0; CompressedLineNumberStream stream(table_start); while (stream.read_pair()) { diff --git a/ddprof-lib/src/main/cpp/lookup.cpp b/ddprof-lib/src/main/cpp/lookup.cpp index 7b80a394c..16ae0fb6e 100644 --- a/ddprof-lib/src/main/cpp/lookup.cpp +++ b/ddprof-lib/src/main/cpp/lookup.cpp @@ -430,12 +430,29 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, const void* method, bool first_t jint entry_count = 0; jvmtiLineNumberEntry* table = nullptr; +// Debug linenumber table + #if 1 + jmethodID mid = jni->GetMethodID(clz, method_name, method_signature); + jvmtiEnv* jvmti = VM::jvmti(); + jint debug_count = 0; + jvmtiLineNumberEntry* debug_table = nullptr; + + jvmti->GetLineNumberTable(mid, &debug_count, &debug_table); + #endif + if (first_time && const_method->hasLineNumberTable()) { if (!const_method->getLineNumberTable(&entry_count, &table)) { entry_count = 0; table = nullptr; } else { TEST_LOG("Load line number table for %s count = %d", method_name, entry_count); + if (mid != nullptr) { + assert(entry_count == debug_count); + for (int index = 0; index < entry_count; index++) { + assert(debug_table[index].start_location == table[index].start_location && "start location does not match"); + assert(debug_table[index].line_number == table[index].line_number && "line number does not match"); + } + } } } fillMethodInfo(mi, clz, klass_name, method_name, method_signature, -entry_count, table); From b8a3110b406caff3d321059abdbbb9521cc8e993 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Mon, 27 Apr 2026 17:40:17 +0000 Subject: [PATCH 07/19] v3 --- .../hotspot/compressedLinenumberStream.cpp | 27 ------ .../cpp/hotspot/compressedLinenumberStream.h | 87 ------------------- .../src/main/cpp/hotspot/hotspotSupport.cpp | 1 + ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp | 41 --------- ddprof-lib/src/main/cpp/hotspot/vmStructs.h | 7 +- .../src/main/cpp/hotspot/vmStructs.inline.h | 4 - ddprof-lib/src/main/cpp/lookup.cpp | 39 ++++----- .../ContendedCallTraceStorageTest.java | 7 +- 8 files changed, 24 insertions(+), 189 deletions(-) delete mode 100644 ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.cpp delete mode 100644 ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.h diff --git a/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.cpp b/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.cpp deleted file mode 100644 index 14e36e418..000000000 --- a/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "hotspot/compressedLinenumberStream.h" - -CompressedLineNumberStream::CompressedLineNumberStream(unsigned char* buffer) : - _buffer(buffer), _position(0), _bci(0), _line(0) { -}; - -void CompressedLineNumberStream::reset() { - _position = 0; - _bci = 0; - _line = 0; -} - -bool CompressedLineNumberStream::read_pair() { - unsigned char next = read_byte(); - // Check for terminator - if (next == 0) return false; - if (next == 0xFF) { - // Escape character, regular compression used - _bci += read_signed_int(); - _line += read_signed_int(); - } else { - // Single byte compression used - _bci += next >> 3; - _line += next & 0x7; - } - return true; -} diff --git a/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.h b/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.h deleted file mode 100644 index 6f0850dcc..000000000 --- a/ddprof-lib/src/main/cpp/hotspot/compressedLinenumberStream.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef _HOTSPOT_COMPRESSED_LINENUMBER_STREAM_H -#define _HOTSPOT_COMPRESSED_LINENUMBER_STREAM_H - -#include -#include - -/** - * Implementation of openjdk CompressedLineNumberStream - * https://github.com/openjdk/jdk/blob/master/src/hotspot/share/oops/method.hpp#L910 - * - * Based on open jdk source: - * https://github.com/openjdk/jdk/blob/master/src/hotspot/share/code/compressedStream.hpp/cpp - * https://github.com/openjdk/jdk/blob/master/src/hotspot/share/utilities/unsigned5.hpp/cpp - */ - -class CompressedLineNumberStream { -private: - // Math constants for the modified UNSIGNED5 coding of Pack200 - static const int BitsPerByte = 8; - enum { - // Constants for UNSIGNED5 coding of Pack200 - lg_H = 6, H = 1<> 1) ^ -(int)(value & 1); } - int read_signed_int() { return decode_sign(read_int()); } - int read_int() { - int b0 = read_byte(); - if (b0 < L) return b0; - else return read_int_mb(b0); - } - - // This encoding, called UNSIGNED5, is taken from J2SE Pack200. - // It assumes that most values have lots of leading zeroes. - // Very small values, in the range [0..191], code in one byte. - // Any 32-bit value (including negatives) can be coded, in - // up to five bytes. The grammar is: - // low_byte = [0..191] - // high_byte = [192..255] - // any_byte = low_byte | high_byte - // coding = low_byte - // | high_byte low_byte - // | high_byte high_byte low_byte - // | high_byte high_byte high_byte low_byte - // | high_byte high_byte high_byte high_byte any_byte - // Each high_byte contributes six bits of payload. - // The encoding is one-to-one (except for integer overflow) - // and easy to parse and unparse. - int read_int_mb(int b0) { - int pos = _position - 1; - unsigned char* buf = _buffer + pos; - assert(buf[0] == b0 && b0 >= L && "correctly called"); - int sum = b0; - // must collect more bytes: b[1]...b[4] - int lg_H_i = lg_H; - for (int i = 0; ; ) { - int b_i = buf[++i]; // b_i = read(); ++i; - sum += b_i << lg_H_i; // sum += b[i]*(64**i) - if (b_i < L || i == MAX_i) { - _position = (pos+i+1); - return sum; - } - lg_H_i += lg_H; - } - } -}; - -#endif // _HOTSPOT_COMPRESSED_LINENUMBER_STREAM_H \ No newline at end of file diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 7a49ce0e2..e9eea70d5 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -1181,6 +1181,7 @@ constexpr const char* FFM_PREFIX = "Ljdk/internal/foreign/abi/"; static bool isLambdaClass(const char* signature) { return strncmp(signature, LAMBDA_PREFIX, strlen(LAMBDA_PREFIX)) == 0 || strstr(signature, "$$Lambda.") != nullptr || + strstr(signature, "$$Lambda$") != nullptr || strstr(signature, ".lambda$") != nullptr || strncmp(signature, FFM_PREFIX, strlen(FFM_PREFIX)) == 0; } diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp b/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp index d241ffe94..4f24af4eb 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.cpp @@ -8,7 +8,6 @@ #include #include #include -#include "hotspot/compressedLinenumberStream.h" #include "hotspot/vmStructs.inline.h" #include "vmEntry.h" #include "jniHelper.h" @@ -226,7 +225,6 @@ void VMStructs::init_type_sizes() { continue; \ } - void VMStructs::init_constants() { // Int constants uintptr_t entry = readSymbol("gHotSpotVMIntConstants"); @@ -246,7 +244,6 @@ void VMStructs::init_constants() { // Special case _frame_entry_frame_call_wrapper_offset *= sizeof(uintptr_t); - // Long constants entry = readSymbol("gHotSpotVMLongConstants"); stride = readSymbol("gHotSpotVMLongConstantEntryArrayStride"); @@ -265,10 +262,8 @@ void VMStructs::init_constants() { } } } - #undef READ_CONSTANT - #ifdef DEBUG void VMStructs::verify_offsets() { int hotspot_version = VM::hotspot_version(); @@ -801,42 +796,6 @@ jmethodID VMMethod::validatedId() { return NULL; } -bool VMConstMethod::getLineNumberTable(jint* entry_count_ptr, - jvmtiLineNumberEntry** table_ptr) { - if (!hasLineNumberTable()) { - return false; - } - - assert(entry_count_ptr != nullptr); - assert(table_ptr != nullptr); - unsigned char* table_start = (unsigned char*)codeEnd(); - int count = 0; - CompressedLineNumberStream stream(table_start); - while (stream.read_pair()) { - count++; - } - if (count == 0) { - return false; - } - - jvmtiLineNumberEntry* table = (jvmtiLineNumberEntry*)malloc(count * sizeof(jvmtiLineNumberEntry)); - if (table == nullptr) { - return false; - } - - stream.reset(); - count = 0; - while (stream.read_pair()) { - table[count].start_location = (jlocation)stream.bci(); - table[count].line_number = (jint)stream.line(); - count++; - } - *table_ptr = table; - *entry_count_ptr = count; - - return true; -} - VMNMethod* CodeHeap::findNMethod(char* heap, const void* pc) { unsigned char* heap_start = *(unsigned char**)(heap + _code_heap_memory_offset + _vs_low_offset); unsigned char* segmap = *(unsigned char**)(heap + _code_heap_segmap_offset + _vs_low_offset); diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h index 8197dba9f..b66d8e896 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h @@ -314,7 +314,7 @@ typedef void* address; field(_narrow_klass_shift_addr, address, MATCH_SYMBOLS("_narrow_klass._shift", "_shift")) \ field(_collected_heap_addr, address, MATCH_SYMBOLS("_collectedHeap")) \ type_end() \ - type_begin(VMClasses, MATCH_SYMBOLS("vmClasses")) \ + type_begin(VMClasses, MATCH_SYMBOLS("vmClasses", "SystemDictionary")) \ field(_obj_class_addr, address, MATCH_SYMBOLS("_klasses[static_cast(vmClassID::Object_klass_knum)]", "_well_known_klasses[SystemDictionary::Object_klass_knum]")) \ field(_thread_class_addr, address, MATCH_SYMBOLS("_klasses[static_cast(vmClassID::Thread_klass_knum)]", "_well_known_klasses[SystemDictionary::Thread_klass_knum]")) \ type_end() @@ -892,8 +892,6 @@ DECLARE(VMConstantPool) DECLARE_END DECLARE(VMConstMethod) - // ref: constMethodFlags.hpp in hotspot src - static constexpr uint32_t has_linenumber_table = 1 << 0; public: inline VMConstantPool* constants() const; inline uint16_t codeSize() const; @@ -904,9 +902,6 @@ DECLARE(VMConstMethod) inline VMSymbol* name() const; inline VMSymbol* signature() const; inline uint32_t flags() const; - inline bool hasLineNumberTable() const; - bool getLineNumberTable(jint* entry_count_ptr, - jvmtiLineNumberEntry** table_ptr); DECLARE_END DECLARE(VMMethod) diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h index 3012645c2..bb21572a0 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h @@ -155,10 +155,6 @@ uint32_t VMConstMethod::flags() const { return *(uint32_t*)at(_constmethod_flags_offset ); } -bool VMConstMethod::hasLineNumberTable() const { - return (flags() & has_linenumber_table) != 0; -} - VMKlass* VMClasses::obj_klass() { return VMKlass::load_then_cast(_obj_class_addr); } diff --git a/ddprof-lib/src/main/cpp/lookup.cpp b/ddprof-lib/src/main/cpp/lookup.cpp index 16ae0fb6e..08ccbe42c 100644 --- a/ddprof-lib/src/main/cpp/lookup.cpp +++ b/ddprof-lib/src/main/cpp/lookup.cpp @@ -190,6 +190,8 @@ void Lookup::fillMethodInfo(MethodInfo *mi, jclass method_class, char* class_nam u32 method_name_id = 0; u32 method_sig_id = 0; + TEST_LOG("FillMethodInfo: class: [%s] method: [%s] signature: [%s]", class_name, method_name, method_sig); + JNIEnv *jni = VM::jni(); if (jni == nullptr) { return; @@ -423,39 +425,30 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, const void* method, bool first_t memcpy(klass_name, klass_sym->body(), klass_sym->length()); klass_name[klass_sym->length()] = '\0'; - TEST_LOG("Lookup VMMethod: %s %s : Class: %s", method_name, method_signature, klass_name); JNIEnv *jni = VM::jni(); jclass clz = jni->FindClass(klass_name); assert(clz != nullptr && "Could not find jclass"); jint entry_count = 0; jvmtiLineNumberEntry* table = nullptr; + jmethodID mid = nullptr; + if (first_time) { + jmethodID mid = jni->GetMethodID(clz, method_name, method_signature); +TEST_LOG("Lookup method ID for %s %s %s --> %d", klass_name, method_name, method_signature, mid != nullptr); -// Debug linenumber table - #if 1 - jmethodID mid = jni->GetMethodID(clz, method_name, method_signature); - jvmtiEnv* jvmti = VM::jvmti(); - jint debug_count = 0; - jvmtiLineNumberEntry* debug_table = nullptr; - - jvmti->GetLineNumberTable(mid, &debug_count, &debug_table); - #endif - - if (first_time && const_method->hasLineNumberTable()) { - if (!const_method->getLineNumberTable(&entry_count, &table)) { - entry_count = 0; - table = nullptr; - } else { - TEST_LOG("Load line number table for %s count = %d", method_name, entry_count); + jvmtiEnv* jvmti = VM::jvmti(); if (mid != nullptr) { - assert(entry_count == debug_count); - for (int index = 0; index < entry_count; index++) { - assert(debug_table[index].start_location == table[index].start_location && "start location does not match"); - assert(debug_table[index].line_number == table[index].line_number && "line number does not match"); + if (jvmti->GetLineNumberTable(mid, &entry_count, &table) != JVMTI_ERROR_NONE) { + if (table != nullptr) { + jvmti->Deallocate((unsigned char*)table); + } + entry_count = 0; + table = nullptr; } + } else { + jni->ExceptionClear(); } - } } - fillMethodInfo(mi, clz, klass_name, method_name, method_signature, -entry_count, table); + fillMethodInfo(mi, clz, klass_name, method_name, method_signature, entry_count, table); free(method_name); free(method_signature); diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/ContendedCallTraceStorageTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/ContendedCallTraceStorageTest.java index 80da81c91..6008171a7 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/ContendedCallTraceStorageTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/ContendedCallTraceStorageTest.java @@ -108,13 +108,18 @@ private List measureContention() throws Exception { // Trigger contention by calling dump during heavy allocation // This forces processTraces() to acquire exclusive lock while put() operations are active + try { for (int i = 0; i < 3; i++) { Path tempDump = Paths.get("temp-contention-" + i + ".jfr"); dump(tempDump); // This will cause contention in CallTraceStorage recordings.add(tempDump); Thread.sleep(500); } - + } catch (Throwable e) { + e.printStackTrace(); + throw e; + + } // Wait for all allocation threads to finish finishLatch.await(); From 50d300c9207dcd7636516dfadc9d79d926ebe191 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Mon, 27 Apr 2026 19:56:15 +0000 Subject: [PATCH 08/19] Reduce debug messages --- ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp | 8 ++++---- ddprof-lib/src/main/cpp/lookup.cpp | 5 ----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index e9eea70d5..be4744462 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -1196,18 +1196,18 @@ bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klas if (jvmti->GetClassLoader(klass, &cl) == JVMTI_ERROR_NONE && cl == nullptr) { char* signature_ptr; jvmti->GetClassSignature(klass, &signature_ptr, nullptr); - TEST_LOG("processing bootstrap class %s", signature_ptr); +// TEST_LOG("processing bootstrap class %s", signature_ptr); // Lambda classes can be unloaded, exlcude them if (!isLambdaClass(signature_ptr)) { - TEST_LOG("Skipping class %s",signature_ptr); +// TEST_LOG("Skipping class %s",signature_ptr); return false; } else { - TEST_LOG("Lambda class: %s", signature_ptr); +// TEST_LOG("Lambda class: %s", signature_ptr); } } else if (cl != nullptr) { char* signature_ptr; jvmti->GetClassSignature(klass, &signature_ptr, nullptr); - TEST_LOG("processing none bootstrap class %s", signature_ptr); +// TEST_LOG("processing none bootstrap class %s", signature_ptr); } return JVMSupport::loadMethodIDsImpl(jvmti, jni, klass); diff --git a/ddprof-lib/src/main/cpp/lookup.cpp b/ddprof-lib/src/main/cpp/lookup.cpp index 08ccbe42c..f07ffb007 100644 --- a/ddprof-lib/src/main/cpp/lookup.cpp +++ b/ddprof-lib/src/main/cpp/lookup.cpp @@ -190,8 +190,6 @@ void Lookup::fillMethodInfo(MethodInfo *mi, jclass method_class, char* class_nam u32 method_name_id = 0; u32 method_sig_id = 0; - TEST_LOG("FillMethodInfo: class: [%s] method: [%s] signature: [%s]", class_name, method_name, method_sig); - JNIEnv *jni = VM::jni(); if (jni == nullptr) { return; @@ -315,7 +313,6 @@ MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) { } else if (bci == BCI_NATIVE_FRAME_REMOTE) { key = MethodMap::makeKey(frame.packed_remote_frame); } else if (frame_type == FRAME_INTERPRETED_METHOD) { - TEST_LOG("Found FRAME_INTERPRETED_METHOD"); key = MethodMap::makeKey(frame.method); } else { assert(frame_type == FRAME_INTERPRETED || frame_type == FRAME_JIT_COMPILED || @@ -433,8 +430,6 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, const void* method, bool first_t jmethodID mid = nullptr; if (first_time) { jmethodID mid = jni->GetMethodID(clz, method_name, method_signature); -TEST_LOG("Lookup method ID for %s %s %s --> %d", klass_name, method_name, method_signature, mid != nullptr); - jvmtiEnv* jvmti = VM::jvmti(); if (mid != nullptr) { if (jvmti->GetLineNumberTable(mid, &entry_count, &table) != JVMTI_ERROR_NONE) { From e71118329c2f8451c1c315b4196f2f4fb31fb91e Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Mon, 27 Apr 2026 17:08:22 -0400 Subject: [PATCH 09/19] Fix --- .../src/main/cpp/hotspot/hotspotSupport.cpp | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index be4744462..63c07e83b 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -410,24 +410,24 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex bool is_plausible_interpreter_frame = StackWalkValidation::isPlausibleInterpreterFrame(fp, sp, bcp_offset); if (is_plausible_interpreter_frame) { VMMethod* method = VMMethod::cast(((void**)fp)[InterpreterFrame::method_offset]); - assert(method != nullptr && "No method for the interpreter frame"); - - jmethodID method_id = getMethodId(method); -// assert(method_id != nullptr || VMClassLoader::isLoadedByBootstrapClassLoader(method)); - if (method_id != NULL || VMClassLoader::isLoadedByBootstrapClassLoader(method)) { - Counters::increment(WALKVM_JAVA_FRAME_OK); - const char* bytecode_start = method->bytecode(); - const char* bcp = ((const char**)fp)[bcp_offset]; - int bci = bytecode_start == NULL || bcp < bytecode_start ? 0 : bcp - bytecode_start; - if (method_id != nullptr) { - fillFrame(frames[depth++], FRAME_INTERPRETED, bci, method_id); - } else { - fillFrame(frames[depth++], FRAME_INTERPRETED_METHOD, bci, method); + if (method != nullptr) { + jmethodID method_id = getMethodId(method); + // assert(method_id != nullptr || VMClassLoader::isLoadedByBootstrapClassLoader(method)); + if (method_id != NULL || VMClassLoader::isLoadedByBootstrapClassLoader(method)) { + Counters::increment(WALKVM_JAVA_FRAME_OK); + const char* bytecode_start = method->bytecode(); + const char* bcp = ((const char**)fp)[bcp_offset]; + int bci = bytecode_start == NULL || bcp < bytecode_start ? 0 : bcp - bytecode_start; + if (method_id != nullptr) { + fillFrame(frames[depth++], FRAME_INTERPRETED, bci, method_id); + } else { + fillFrame(frames[depth++], FRAME_INTERPRETED_METHOD, bci, method); + } + sp = ((uintptr_t*)fp)[InterpreterFrame::sender_sp_offset]; + pc = stripPointer(((void**)fp)[FRAME_PC_SLOT]); + fp = *(uintptr_t*)fp; + continue; } - sp = ((uintptr_t*)fp)[InterpreterFrame::sender_sp_offset]; - pc = stripPointer(((void**)fp)[FRAME_PC_SLOT]); - fp = *(uintptr_t*)fp; - continue; } } From 609fc17ad6a3944b0db1304a09fafa3a81fd8b40 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Tue, 28 Apr 2026 13:16:59 +0000 Subject: [PATCH 10/19] v4 --- .../src/main/cpp/hotspot/classloader.inline.h | 5 +- .../src/main/cpp/hotspot/hotspotSupport.cpp | 41 ++++++++-------- ddprof-lib/src/main/cpp/hotspot/vmStructs.h | 15 +++++- ddprof-lib/src/main/cpp/lookup.cpp | 49 +++++++++++-------- ddprof-lib/src/main/cpp/methodInfo.cpp | 22 ++++----- 5 files changed, 74 insertions(+), 58 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h b/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h index c45765fb7..e14ef98d7 100644 --- a/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h @@ -13,11 +13,12 @@ bool VMClassLoader::isLoadedByBootstrapClassLoader(const VMMethod* method) { VMKlass* method_klass = method->methodHolder(); + if (method_klass == nullptr) { + return false; + } // java/lang/Object must be loaded by bootstrap class loader VMKlass* obj_klass = VMClasses::obj_klass(); - - assert(method_klass != nullptr && "No Klass for the method"); assert(obj_klass != nullptr && "VMClasses not yet initialized"); assert(method_klass->classLoaderData() != nullptr && "Method holder has no class loader data"); assert(obj_klass->classLoaderData() != nullptr && "Object class has no class loader data"); diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 63c07e83b..c39d59fbb 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -31,11 +31,14 @@ static bool isAddressInCode(const void *pc, bool include_stubs = true) { } static jmethodID getMethodId(VMMethod* method) { + if (method == nullptr) { + return nullptr; + } if (!inDeadZone(method) && aligned((uintptr_t)method) && SafeAccess::isReadableRange(method, VMMethod::type_size())) { return method->validatedId(); } - return NULL; + return nullptr; } /** @@ -409,32 +412,28 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex bool is_plausible_interpreter_frame = StackWalkValidation::isPlausibleInterpreterFrame(fp, sp, bcp_offset); if (is_plausible_interpreter_frame) { - VMMethod* method = VMMethod::cast(((void**)fp)[InterpreterFrame::method_offset]); - if (method != nullptr) { - jmethodID method_id = getMethodId(method); - // assert(method_id != nullptr || VMClassLoader::isLoadedByBootstrapClassLoader(method)); - if (method_id != NULL || VMClassLoader::isLoadedByBootstrapClassLoader(method)) { - Counters::increment(WALKVM_JAVA_FRAME_OK); - const char* bytecode_start = method->bytecode(); - const char* bcp = ((const char**)fp)[bcp_offset]; - int bci = bytecode_start == NULL || bcp < bytecode_start ? 0 : bcp - bytecode_start; - if (method_id != nullptr) { - fillFrame(frames[depth++], FRAME_INTERPRETED, bci, method_id); - } else { - fillFrame(frames[depth++], FRAME_INTERPRETED_METHOD, bci, method); - } - sp = ((uintptr_t*)fp)[InterpreterFrame::sender_sp_offset]; - pc = stripPointer(((void**)fp)[FRAME_PC_SLOT]); - fp = *(uintptr_t*)fp; - continue; + VMMethod* method = VMMethod::cast_or_null(((void**)fp)[InterpreterFrame::method_offset]); + jmethodID method_id = getMethodId(method); + if (method_id != NULL || VMClassLoader::isLoadedByBootstrapClassLoader(method)) { + Counters::increment(WALKVM_JAVA_FRAME_OK); + const char* bytecode_start = method->bytecode(); + const char* bcp = ((const char**)fp)[bcp_offset]; + int bci = bytecode_start == NULL || bcp < bytecode_start ? 0 : bcp - bytecode_start; + if (method_id != nullptr) { + fillFrame(frames[depth++], FRAME_INTERPRETED, bci, method_id); + } else { + fillFrame(frames[depth++], FRAME_INTERPRETED_METHOD, bci, method); } + sp = ((uintptr_t*)fp)[InterpreterFrame::sender_sp_offset]; + pc = stripPointer(((void**)fp)[FRAME_PC_SLOT]); + fp = *(uintptr_t*)fp; + continue; } } if (depth == 0) { - VMMethod* method = (VMMethod*)frame.method(); + VMMethod* method = VMMethod::cast_or_null((const void*)frame.method()); jmethodID method_id = getMethodId(method); -// assert(method_id != nullptr || VMClassLoader::isLoadedByBootstrapClassLoader(method)); if (method_id != NULL || VMClassLoader::isLoadedByBootstrapClassLoader(method)) { Counters::increment(WALKVM_JAVA_FRAME_OK); if (method_id != nullptr) { diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h index b66d8e896..0e04e2f7b 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h @@ -47,6 +47,18 @@ inline T* cast_to(const void* ptr) { return reinterpret_cast(const_cast(ptr)); } +template +inline T* cast_or_null(const void* ptr) { + assert(VM::isHotspot()); // This should only be used in HotSpot-specific code + assert(T::type_size() > 0); // Ensure type size has been initialized + if(ptr == nullptr || SafeAccess::isReadableRange(ptr, T::type_size())) { + return reinterpret_cast(const_cast(ptr)); + } else { + return nullptr; + } +} + + #define TYPE_SIZE_NAME(name) _##name##_size // MATCH_SYMBOLS macro expands into a string list, that is consumed by matchAny() method @@ -71,7 +83,8 @@ inline T* cast_to(const void* ptr) { class name : VMStructs { \ public: \ static uint64_t type_size() { return TYPE_SIZE_NAME(name); } \ - static name * cast(const void* ptr) { return cast_to(ptr); } \ + static name * cast(const void* ptr) { return ::cast_to(ptr); } \ + static name * cast_or_null(const void* ptr) { return ::cast_or_null(ptr); } \ static name * cast_raw(const void* ptr) { return (name *)ptr; } \ static name * load_then_cast(const void* ptr) { \ assert(ptr != nullptr); \ diff --git a/ddprof-lib/src/main/cpp/lookup.cpp b/ddprof-lib/src/main/cpp/lookup.cpp index f07ffb007..e32057fcc 100644 --- a/ddprof-lib/src/main/cpp/lookup.cpp +++ b/ddprof-lib/src/main/cpp/lookup.cpp @@ -413,39 +413,46 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, const void* method, bool first_t VMSymbol* klass_sym = klass->name(); char* method_name = (char*)malloc(name_sym->length() + 1); char* method_signature = (char*)malloc(sig_sym->length() + 1); - char* klass_name = (char*)malloc(klass_sym->length() + 1); + int klass_name_len = klass_sym->length(); + char* klass_name = (char*)malloc(klass_name_len + 1); memcpy(method_name, name_sym->body(), name_sym->length()); method_name[name_sym->length()] = '\0'; memcpy(method_signature, sig_sym->body(), sig_sym->length()); method_signature[sig_sym->length()] = '\0'; - memcpy(klass_name, klass_sym->body(), klass_sym->length()); - klass_name[klass_sym->length()] = '\0'; + memcpy(klass_name, klass_sym->body(), klass_name_len); + klass_name[klass_name_len] = '\0'; JNIEnv *jni = VM::jni(); jclass clz = jni->FindClass(klass_name); - assert(clz != nullptr && "Could not find jclass"); - jint entry_count = 0; - jvmtiLineNumberEntry* table = nullptr; jmethodID mid = nullptr; - if (first_time) { - jmethodID mid = jni->GetMethodID(clz, method_name, method_signature); - jvmtiEnv* jvmti = VM::jvmti(); - if (mid != nullptr) { - if (jvmti->GetLineNumberTable(mid, &entry_count, &table) != JVMTI_ERROR_NONE) { - if (table != nullptr) { - jvmti->Deallocate((unsigned char*)table); - } - entry_count = 0; - table = nullptr; - } - } else { - jni->ExceptionClear(); - } + if (clz == nullptr) { + jni->ExceptionClear(); + } else { + mid = jni->GetMethodID(clz, method_name, method_signature); + if (mid != nullptr) { + fillJavaMethodInfo(mi, mid, first_time); + } else { + jni->ExceptionClear(); + } + } + + // Construct jvmti class signature, e.g. `Ljava/lang/Object;` which + // is expected by fillMethodInfo() + char* jvmti_klass_name = nullptr; + if (mid == nullptr) { + jvmti_klass_name = (char*)malloc(klass_name_len + 3); + jvmti_klass_name[0] = 'L'; + memcpy(&jvmti_klass_name[1], klass_name, klass_name_len); + jvmti_klass_name[klass_name_len + 1] = ';'; + jvmti_klass_name[klass_name_len + 2] = '\0'; + jint entry_count = 0; + jvmtiLineNumberEntry* table = nullptr; + fillMethodInfo(mi, clz, jvmti_klass_name, method_name, method_signature, entry_count, table); } - fillMethodInfo(mi, clz, klass_name, method_name, method_signature, entry_count, table); free(method_name); free(method_signature); free(klass_name); + free(jvmti_klass_name); } diff --git a/ddprof-lib/src/main/cpp/methodInfo.cpp b/ddprof-lib/src/main/cpp/methodInfo.cpp index 2023df7c7..702378561 100644 --- a/ddprof-lib/src/main/cpp/methodInfo.cpp +++ b/ddprof-lib/src/main/cpp/methodInfo.cpp @@ -17,21 +17,17 @@ SharedLineNumberTable::~SharedLineNumberTable() { // JVMTI spec requires that memory allocated by GetLineNumberTable // must be freed with Deallocate if (_ptr != nullptr) { - if (_size > 0) { - jvmtiEnv *jvmti = VM::jvmti(); - if (jvmti != nullptr) { - jvmtiError err = jvmti->Deallocate((unsigned char *)_ptr); - // If Deallocate fails, log it for debugging (this could indicate a JVM bug) - // JVMTI_ERROR_ILLEGAL_ARGUMENT means the memory wasn't allocated by JVMTI - // which would be a serious bug in GetLineNumberTable - if (err != JVMTI_ERROR_NONE) { - TEST_LOG("Unexpected error while deallocating linenumber table: %d", err); - } - } else { - TEST_LOG("WARNING: Cannot deallocate line number table - JVMTI is null"); + jvmtiEnv *jvmti = VM::jvmti(); + if (jvmti != nullptr) { + jvmtiError err = jvmti->Deallocate((unsigned char *)_ptr); + // If Deallocate fails, log it for debugging (this could indicate a JVM bug) + // JVMTI_ERROR_ILLEGAL_ARGUMENT means the memory wasn't allocated by JVMTI + // which would be a serious bug in GetLineNumberTable + if (err != JVMTI_ERROR_NONE) { + TEST_LOG("Unexpected error while deallocating linenumber table: %d", err); } } else { - free(_ptr); + TEST_LOG("WARNING: Cannot deallocate line number table - JVMTI is null"); } // Decrement counter whenever destructor runs (symmetric with increment at creation) Counters::decrement(LINE_NUMBER_TABLES); From eb8b5f43b02519e1afc7c9d6dd23b861ae4de28d Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Tue, 28 Apr 2026 16:22:47 +0000 Subject: [PATCH 11/19] v5 --- ddprof-lib/src/main/cpp/frame.h | 18 +++++++++++------- .../src/main/cpp/hotspot/hotspotSupport.cpp | 1 + ddprof-lib/src/main/cpp/j9/j9Support.h | 1 + ddprof-lib/src/main/cpp/lookup.cpp | 14 +++++++------- ddprof-lib/src/main/cpp/stackWalker.inline.h | 1 + ddprof-lib/src/main/cpp/vmEntry.h | 1 - 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/ddprof-lib/src/main/cpp/frame.h b/ddprof-lib/src/main/cpp/frame.h index 210ed492e..914cf4c5d 100644 --- a/ddprof-lib/src/main/cpp/frame.h +++ b/ddprof-lib/src/main/cpp/frame.h @@ -1,15 +1,18 @@ #ifndef _FRAME_H #define _FRAME_H +#include +#include "vmEntry.h" + enum FrameTypeId { FRAME_INTERPRETED = 0, - FRAME_INTERPRETED_METHOD = 1, - FRAME_JIT_COMPILED = 2, - FRAME_INLINED = 3, - FRAME_NATIVE = 4, - FRAME_CPP = 5, - FRAME_KERNEL = 6, - FRAME_C1_COMPILED = 7, + FRAME_JIT_COMPILED = 1, + FRAME_INLINED = 2, + FRAME_NATIVE = 3, + FRAME_CPP = 4, + FRAME_KERNEL = 5, + FRAME_C1_COMPILED = 6, + FRAME_INTERPRETED_METHOD = 7, FRAME_NATIVE_REMOTE = 8, // Native frame with remote symbolication (build-id + pc-offset) FRAME_TYPE_MAX = FRAME_NATIVE_REMOTE // Maximum valid frame type }; @@ -17,6 +20,7 @@ enum FrameTypeId { class FrameType { public: static inline int encode(int type, int bci) { + assert((type != FRAME_INTERPRETED_METHOD || VM::isHotspot()) && "FRAME_INTERPRETED_METHOD is only valid for hotspot"); return (1 << 24) | (type << 25) | (bci & 0xffffff); } diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index c39d59fbb..890d1b58d 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -122,6 +122,7 @@ static void fillFrameTypes(ASGCT_CallFrame *frames, int num_frames, VMNMethod *n static inline void fillFrame(ASGCT_CallFrame& frame, FrameTypeId type, int bci, const VMMethod* method) { frame.bci = FrameType::encode(type, bci); + assert(FrameType::decode(frame.bci) == type && "FrameType::encode/decode is not reversable"); frame.method = static_cast(method); } diff --git a/ddprof-lib/src/main/cpp/j9/j9Support.h b/ddprof-lib/src/main/cpp/j9/j9Support.h index 7031fb8f5..2e67dc42d 100644 --- a/ddprof-lib/src/main/cpp/j9/j9Support.h +++ b/ddprof-lib/src/main/cpp/j9/j9Support.h @@ -20,6 +20,7 @@ #include +#include "frame.h" #include "log.h" #include "vmEntry.h" diff --git a/ddprof-lib/src/main/cpp/lookup.cpp b/ddprof-lib/src/main/cpp/lookup.cpp index e32057fcc..d42963014 100644 --- a/ddprof-lib/src/main/cpp/lookup.cpp +++ b/ddprof-lib/src/main/cpp/lookup.cpp @@ -305,8 +305,8 @@ MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) { jint bci = frame.bci; FrameTypeId frame_type = FrameType::decode(bci); - jmethodID method = frame.method_id; - if (method == nullptr) { + jmethodID method_id = frame.method_id; + if (method_id == nullptr) { key = MethodMap::makeKey(UNKNOWN); } else if (bci == BCI_ERROR || bci == BCI_NATIVE_FRAME) { key = MethodMap::makeKey(frame.native_function_name); @@ -318,7 +318,7 @@ MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) { assert(frame_type == FRAME_INTERPRETED || frame_type == FRAME_JIT_COMPILED || frame_type == FRAME_INLINED || frame_type == FRAME_C1_COMPILED || VM::isOpenJ9()); // OpenJ9 may have bugs that produce invalid frame types - key = MethodMap::makeKey(method); + key = MethodMap::makeKey(method_id); } MethodInfo *mi = &(*_method_map)[key]; @@ -329,12 +329,12 @@ MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) { if (first_time) { mi->_key = _method_map->size() + 1; // avoid zero key } - if (method == nullptr) { + if (method_id == nullptr) { fillNativeMethodInfo(mi, UNKNOWN, nullptr); } else if (bci == BCI_ERROR) { - fillNativeMethodInfo(mi, (const char *)method, nullptr); + fillNativeMethodInfo(mi, (const char *)method_id, nullptr); } else if (bci == BCI_NATIVE_FRAME) { - const char *name = (const char *)method; + const char *name = (const char *)method_id; fillNativeMethodInfo(mi, name, Profiler::instance()->getLibraryName(name)); } else if (bci == BCI_NATIVE_FRAME_REMOTE) { @@ -372,7 +372,7 @@ MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) { } else if (frame_type == FRAME_INTERPRETED_METHOD) { fillJavaMethodInfo(mi, frame.method, first_time); } else { - fillJavaMethodInfo(mi, method, first_time); + fillJavaMethodInfo(mi, method_id, first_time); } } diff --git a/ddprof-lib/src/main/cpp/stackWalker.inline.h b/ddprof-lib/src/main/cpp/stackWalker.inline.h index d8399242c..b4764b1bf 100644 --- a/ddprof-lib/src/main/cpp/stackWalker.inline.h +++ b/ddprof-lib/src/main/cpp/stackWalker.inline.h @@ -7,6 +7,7 @@ #ifndef _STACKWALKER_INLINE_H #define _STACKWALKER_INLINE_H +#include "frame.h" #include "stackWalker.h" #include "safeAccess.h" diff --git a/ddprof-lib/src/main/cpp/vmEntry.h b/ddprof-lib/src/main/cpp/vmEntry.h index d04a33172..8a70f0f48 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.h +++ b/ddprof-lib/src/main/cpp/vmEntry.h @@ -12,7 +12,6 @@ #include "arch.h" #include "codeCache.h" -#include "frame.h" #ifdef __clang__ #define DLLEXPORT __attribute__((visibility("default"))) From 708f97998809f637ece3ef4441e50a2179b61772 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 29 Apr 2026 01:23:23 +0000 Subject: [PATCH 12/19] v6 --- .../src/main/cpp/hotspot/classloader.inline.h | 4 ++ .../src/main/cpp/hotspot/hotspotSupport.cpp | 48 ++++++++++++- .../src/main/cpp/hotspot/hotspotSupport.h | 2 + ddprof-lib/src/main/cpp/jvmSupport.cpp | 5 +- ddprof-lib/src/main/cpp/jvmSupport.h | 10 +++ ddprof-lib/src/main/cpp/lookup.cpp | 72 +++---------------- ddprof-lib/src/main/cpp/lookup.h | 1 - 7 files changed, 75 insertions(+), 67 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h b/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h index e14ef98d7..6805be4d4 100644 --- a/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h @@ -12,6 +12,10 @@ #include bool VMClassLoader::isLoadedByBootstrapClassLoader(const VMMethod* method) { + if (method == nullptr) { + return false; + } + VMKlass* method_klass = method->methodHolder(); if (method_klass == nullptr) { return false; diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 890d1b58d..95ab9eed1 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -1194,7 +1194,7 @@ bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klas // Hotpsot only: loaded by bootstrap class loader, which is never unloaded, // we use Method instead. if (jvmti->GetClassLoader(klass, &cl) == JVMTI_ERROR_NONE && cl == nullptr) { - char* signature_ptr; + char* signature_ptr = nullptr; jvmti->GetClassSignature(klass, &signature_ptr, nullptr); // TEST_LOG("processing bootstrap class %s", signature_ptr); // Lambda classes can be unloaded, exlcude them @@ -1213,6 +1213,52 @@ bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klas return JVMSupport::loadMethodIDsImpl(jvmti, jni, klass); } +jmethodID HotspotSupport::resolve(const void* method) { + assert(VM::isHotspot()); + assert(method != nullptr); + VMMethod* vm_method = VMMethod::cast(method); + + // May have been populated by following code + jmethodID method_id = vm_method->validatedId(); + if (method_id != nullptr) { + return method_id; + } + + VMConstMethod* const_method = vm_method->constMethod(); + VMSymbol* name_sym = const_method->name(); + VMSymbol* sig_sym = const_method->signature(); + VMKlass* klass = vm_method->methodHolder(); + VMSymbol* klass_sym = klass->name(); + char* method_name = (char*)malloc(name_sym->length() + 1); + char* method_signature = (char*)malloc(sig_sym->length() + 1); + int klass_name_len = klass_sym->length(); + char* klass_name = (char*)malloc(klass_name_len + 1); + + memcpy(method_name, name_sym->body(), name_sym->length()); + method_name[name_sym->length()] = '\0'; + memcpy(method_signature, sig_sym->body(), sig_sym->length()); + method_signature[sig_sym->length()] = '\0'; + memcpy(klass_name, klass_sym->body(), klass_name_len); + klass_name[klass_name_len] = '\0'; + + JNIEnv *jni = VM::jni(); + jclass clz = jni->FindClass(klass_name); + if (clz == nullptr) { + jni->ExceptionClear(); + } else { + method_id = jni->GetMethodID(clz, method_name, method_signature); + if (method_id == nullptr) { + jni->ExceptionClear(); + } + } + + free(method_name); + free(method_signature); + free(klass_name); + + return method_id; +} + void JNICALL HotspotSupport::NativeMethodBind(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, jmethodID method, void *address, void **new_address_ptr) { diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h index 44332522c..7405a5779 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h @@ -45,6 +45,8 @@ class HotspotSupport { return JitCodeCache::isJitCode(p); } + static jmethodID resolve(const void* method); + static void loadAllMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni); static void JNICALL NativeMethodBind(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, jmethodID method, diff --git a/ddprof-lib/src/main/cpp/jvmSupport.cpp b/ddprof-lib/src/main/cpp/jvmSupport.cpp index 93bdb9509..9bb10df30 100644 --- a/ddprof-lib/src/main/cpp/jvmSupport.cpp +++ b/ddprof-lib/src/main/cpp/jvmSupport.cpp @@ -12,8 +12,6 @@ #include "thread.h" #include "vmEntry.h" -#include "hotspot/hotspotSupport.h" - #include #include @@ -104,8 +102,7 @@ bool JVMSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass) { return false; } - - // JVMTI callbacks +// JVMTI callbacks void JNICALL JVMSupport::ClassPrepare(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, jclass klass) { if (VM::isHotspot()) { diff --git a/ddprof-lib/src/main/cpp/jvmSupport.h b/ddprof-lib/src/main/cpp/jvmSupport.h index d12af372f..df0b49b86 100644 --- a/ddprof-lib/src/main/cpp/jvmSupport.h +++ b/ddprof-lib/src/main/cpp/jvmSupport.h @@ -6,6 +6,7 @@ #ifndef _JVMSUPPORT_H #define _JVMSUPPORT_H +#include "hotspot/hotspotSupport.h" #include "stackFrame.h" #include "stackWalker.h" #include "vmEntry.h" @@ -37,6 +38,15 @@ class JVMSupport { static void loadAllMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni); static bool loadMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass); + // Resolve method pointer to jmethodID + static jmethodID resolve(const void* method) { + if (VM::isHotspot()) { + return HotspotSupport::resolve(method); + } else { + assert(false && "Should not reache here"); + } + } + // JVMTI callback static void JNICALL ClassPrepare(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, jclass klass); diff --git a/ddprof-lib/src/main/cpp/lookup.cpp b/ddprof-lib/src/main/cpp/lookup.cpp index d42963014..7847e09a7 100644 --- a/ddprof-lib/src/main/cpp/lookup.cpp +++ b/ddprof-lib/src/main/cpp/lookup.cpp @@ -15,6 +15,7 @@ #include "common.h" #include "counters.h" #include "jniHelper.h" +#include "jvmSupport.h" #include "libraries.h" #include "methodInfo.h" #include "profiler.h" @@ -128,16 +129,19 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, jmethodID method, if ((phase & (JVMTI_PHASE_START | JVMTI_PHASE_LIVE)) != 0) { if (VMMethod::check_jmethodID(method) && jvmti->GetMethodDeclaringClass(method, &method_class) == 0 && + // GetMethodDeclaringClass may return a jclass wrapping a stale/garbage oop when the class was + // unloaded between sample capture and dump (TOCTOU race with class unloading). Guard against + // null handles before calling GetClassSignature. + method_class != NULL && // On some older versions of J9, the JVMTI call to GetMethodDeclaringClass will return OK = 0, but when a // classloader is unloaded they free all JNIIDs. This means that anyone holding on to a jmethodID is // pointing to corrupt data and the behaviour is undefined. // The behaviour is adjusted so that when asgct() is used or if `-XX:+KeepJNIIDs` is specified, // when a classloader is unloaded, the jmethodIDs are not freed, but instead marked as -1. - // The nested check below is to mitigate these crashes. - // In more recent versions, the condition above will short-circuit safely. - ((!VM::isOpenJ9() || method_class != reinterpret_cast(-1)) && jvmti->GetClassSignature(method_class, &class_name, NULL) == 0) && + // The check below mitigates these crashes on J9. + (!VM::isOpenJ9() || method_class != reinterpret_cast(-1)) && + jvmti->GetClassSignature(method_class, &class_name, NULL) == 0 && jvmti->GetMethodName(method, &method_name, &method_sig, NULL) == 0) { - if (first_time) { jvmtiError line_table_error = jvmti->GetLineNumberTable(method, &line_number_table_size, &line_number_table); @@ -370,7 +374,9 @@ MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) { fillNativeMethodInfo(mi, "unknown_library", nullptr); } } else if (frame_type == FRAME_INTERPRETED_METHOD) { - fillJavaMethodInfo(mi, frame.method, first_time); + jmethodID id = JVMSupport::resolve(frame.method); + frame.bci = FrameType::encode(FRAME_INTERPRETED, bci); + fillJavaMethodInfo(mi, id, first_time); } else { fillJavaMethodInfo(mi, method_id, first_time); } @@ -400,59 +406,3 @@ u32 Lookup::getPackage(const char *class_name) { u32 Lookup::getSymbol(const char *name) { return _symbols.lookup(name); } - -void Lookup::fillJavaMethodInfo(MethodInfo *mi, const void* method, bool first_time) { - assert(VM::isHotspot()); - assert(method != nullptr); - VMMethod* vm_method = VMMethod::cast(method); - VMConstMethod* const_method = vm_method->constMethod(); - - VMSymbol* name_sym = const_method->name(); - VMSymbol* sig_sym = const_method->signature(); - VMKlass* klass = vm_method->methodHolder(); - VMSymbol* klass_sym = klass->name(); - char* method_name = (char*)malloc(name_sym->length() + 1); - char* method_signature = (char*)malloc(sig_sym->length() + 1); - int klass_name_len = klass_sym->length(); - char* klass_name = (char*)malloc(klass_name_len + 1); - - memcpy(method_name, name_sym->body(), name_sym->length()); - method_name[name_sym->length()] = '\0'; - memcpy(method_signature, sig_sym->body(), sig_sym->length()); - method_signature[sig_sym->length()] = '\0'; - memcpy(klass_name, klass_sym->body(), klass_name_len); - klass_name[klass_name_len] = '\0'; - - JNIEnv *jni = VM::jni(); - jclass clz = jni->FindClass(klass_name); - jmethodID mid = nullptr; - if (clz == nullptr) { - jni->ExceptionClear(); - } else { - mid = jni->GetMethodID(clz, method_name, method_signature); - if (mid != nullptr) { - fillJavaMethodInfo(mi, mid, first_time); - } else { - jni->ExceptionClear(); - } - } - - // Construct jvmti class signature, e.g. `Ljava/lang/Object;` which - // is expected by fillMethodInfo() - char* jvmti_klass_name = nullptr; - if (mid == nullptr) { - jvmti_klass_name = (char*)malloc(klass_name_len + 3); - jvmti_klass_name[0] = 'L'; - memcpy(&jvmti_klass_name[1], klass_name, klass_name_len); - jvmti_klass_name[klass_name_len + 1] = ';'; - jvmti_klass_name[klass_name_len + 2] = '\0'; - jint entry_count = 0; - jvmtiLineNumberEntry* table = nullptr; - fillMethodInfo(mi, clz, jvmti_klass_name, method_name, method_signature, entry_count, table); - } - - free(method_name); - free(method_signature); - free(klass_name); - free(jvmti_klass_name); -} diff --git a/ddprof-lib/src/main/cpp/lookup.h b/ddprof-lib/src/main/cpp/lookup.h index 36a9719cc..f084c7edd 100644 --- a/ddprof-lib/src/main/cpp/lookup.h +++ b/ddprof-lib/src/main/cpp/lookup.h @@ -33,7 +33,6 @@ class Lookup { } void fillJavaMethodInfo(MethodInfo *mi, jmethodID method, bool first_time); - void fillJavaMethodInfo(MethodInfo *mi, const void* method, bool first_time); void fillMethodInfo(MethodInfo *mi, jclass method_class, char* class_name, char* method_name, char* method_sig, jint line_number_table_size, jvmtiLineNumberEntry* line_number_table); public: From f83f48eb4ecc6b41193dba63cb92ca03b1bb750a Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 29 Apr 2026 14:49:04 +0000 Subject: [PATCH 13/19] v6 --- ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp | 11 ++++++++--- ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h | 1 - ddprof-lib/src/main/cpp/lookup.cpp | 8 ++++++-- ddprof-lib/src/main/cpp/methodInfo.h | 1 - .../profiler/ContendedCallTraceStorageTest.java | 6 ------ .../datadoghq/profiler/context/TagContextTest.java | 3 ++- .../com/datadoghq/profiler/cpu/ContextCpuTest.java | 2 -- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 95ab9eed1..3e1ef8d7e 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -1189,7 +1189,6 @@ static bool isLambdaClass(const char* signature) { bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass) { patchClassLoaderData(jni, klass); - jobject cl; // Hotpsot only: loaded by bootstrap class loader, which is never unloaded, // we use Method instead. @@ -1209,14 +1208,16 @@ bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klas jvmti->GetClassSignature(klass, &signature_ptr, nullptr); // TEST_LOG("processing none bootstrap class %s", signature_ptr); } - return JVMSupport::loadMethodIDsImpl(jvmti, jni, klass); } jmethodID HotspotSupport::resolve(const void* method) { assert(VM::isHotspot()); assert(method != nullptr); - VMMethod* vm_method = VMMethod::cast(method); + VMMethod* vm_method = VMMethod::cast_or_null(method); + if (vm_method == nullptr) { + return nullptr; + } // May have been populated by following code jmethodID method_id = vm_method->validatedId(); @@ -1249,6 +1250,10 @@ jmethodID HotspotSupport::resolve(const void* method) { method_id = jni->GetMethodID(clz, method_name, method_signature); if (method_id == nullptr) { jni->ExceptionClear(); + method_id = jni->GetStaticMethodID(clz, method_name, method_signature); + if (method_id == nullptr) { + jni->ExceptionClear(); + } } } diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h index 7405a5779..f155d25ac 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h @@ -47,7 +47,6 @@ class HotspotSupport { static jmethodID resolve(const void* method); - static void loadAllMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni); static void JNICALL NativeMethodBind(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, jmethodID method, void *address, void **new_address_ptr); diff --git a/ddprof-lib/src/main/cpp/lookup.cpp b/ddprof-lib/src/main/cpp/lookup.cpp index 7847e09a7..10535a21b 100644 --- a/ddprof-lib/src/main/cpp/lookup.cpp +++ b/ddprof-lib/src/main/cpp/lookup.cpp @@ -140,9 +140,13 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, jmethodID method, // when a classloader is unloaded, the jmethodIDs are not freed, but instead marked as -1. // The check below mitigates these crashes on J9. (!VM::isOpenJ9() || method_class != reinterpret_cast(-1)) && - jvmti->GetClassSignature(method_class, &class_name, NULL) == 0 && - jvmti->GetMethodName(method, &method_name, &method_sig, NULL) == 0) { + jvmti->GetClassSignature(method_class, &class_name, NULL) == JVMTI_ERROR_NONE && + jvmti->GetMethodName(method, &method_name, &method_sig, NULL) == JVMTI_ERROR_NONE) { if (first_time) { + if (jvmti->GetMethodModifiers(method, &mi->_modifiers) != JVMTI_ERROR_NONE) { + mi->_modifiers = 0; + } + jvmtiError line_table_error = jvmti->GetLineNumberTable(method, &line_number_table_size, &line_number_table); // Defensive: if GetLineNumberTable failed, clean up any potentially allocated memory diff --git a/ddprof-lib/src/main/cpp/methodInfo.h b/ddprof-lib/src/main/cpp/methodInfo.h index 8e2622e73..a54469254 100644 --- a/ddprof-lib/src/main/cpp/methodInfo.h +++ b/ddprof-lib/src/main/cpp/methodInfo.h @@ -18,7 +18,6 @@ class SharedLineNumberTable { public: - /* positive: the table is allocated by jvmti. negative: the table is allocated profiler */ int _size; void *_ptr; diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/ContendedCallTraceStorageTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/ContendedCallTraceStorageTest.java index 6008171a7..a0685a71a 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/ContendedCallTraceStorageTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/ContendedCallTraceStorageTest.java @@ -108,18 +108,12 @@ private List measureContention() throws Exception { // Trigger contention by calling dump during heavy allocation // This forces processTraces() to acquire exclusive lock while put() operations are active - try { for (int i = 0; i < 3; i++) { Path tempDump = Paths.get("temp-contention-" + i + ".jfr"); dump(tempDump); // This will cause contention in CallTraceStorage recordings.add(tempDump); Thread.sleep(500); } - } catch (Throwable e) { - e.printStackTrace(); - throw e; - - } // Wait for all allocation threads to finish finishLatch.await(); diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/context/TagContextTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/context/TagContextTest.java index 9b8fe4404..21b82ac55 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/context/TagContextTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/context/TagContextTest.java @@ -67,6 +67,7 @@ public void test() throws InterruptedException { IMemberAccessor stacktraceAccessor = JdkAttributes.STACK_TRACE_STRING.getAccessor(wallclockSamples.getType()); for (IItem sample : wallclockSamples) { String stacktrace = stacktraceAccessor.getMember(sample); +System.out.println(stacktrace); if (!stacktrace.contains("sleep")) { // we don't know the context has been set for sure until the sleep has started continue; @@ -82,7 +83,6 @@ public void test() throws InterruptedException { droppedSamplesWeight += weight; continue; } - String tag = tag1Accessor.getMember(sample); weightsByTagValue.computeIfAbsent(tag, v -> new AtomicLong()) .addAndGet(weight); @@ -92,6 +92,7 @@ public void test() throws InterruptedException { long sum = 0; long[] weights = new long[strings.length]; System.out.println("Found tag values: " + weightsByTagValue.keySet()); + System.out.println("Strings: " + strings.length); for (int i = 0; i < strings.length; i++) { AtomicLong weight = weightsByTagValue.get(strings[i]); assertNotNull(weight, "Weight for " + strings[i] + " not found. Found: " + weightsByTagValue.keySet()); diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/ContextCpuTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/ContextCpuTest.java index 6403af1d2..3afd598cb 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/ContextCpuTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/cpu/ContextCpuTest.java @@ -72,8 +72,6 @@ public void test(@CStack String cstack) throws ExecutionException, InterruptedEx IMemberAccessor stateAccessor = THREAD_STATE.getAccessor(cpuSamples.getType()); for (IItem sample : cpuSamples) { String stackTrace = frameAccessor.getMember(sample); - System.out.println("Stack trace: "); - System.out.println(stackTrace); long spanId = spanIdAccessor.getMember(sample).longValue(); long rootSpanId = rootSpanIdAccessor.getMember(sample).longValue(); String state = stateAccessor.getMember(sample); From d2ab30f7f330cca4cb56451a90c493c00aa3698b Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 29 Apr 2026 15:58:30 +0000 Subject: [PATCH 14/19] v7 --- ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp | 12 ++---------- ddprof-lib/src/main/cpp/hotspot/vmStructs.h | 4 ---- ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h | 13 ------------- ddprof-lib/src/main/cpp/methodInfo.h | 2 +- ddprof-lib/src/main/cpp/profiler.cpp | 1 - 5 files changed, 3 insertions(+), 29 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 3e1ef8d7e..6201b761d 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -1177,7 +1177,6 @@ static void patchClassLoaderData(JNIEnv* jni, jclass klass) { constexpr const char* LAMBDA_PREFIX = "Ljava/lang/invoke/LambdaForm$"; constexpr const char* FFM_PREFIX = "Ljdk/internal/foreign/abi/"; -// constexpr const size_t LAMBDA_PREFIX_LEN = strlen(LAMBDA_PREFIX); static bool isLambdaClass(const char* signature) { return strncmp(signature, LAMBDA_PREFIX, strlen(LAMBDA_PREFIX)) == 0 || strstr(signature, "$$Lambda.") != nullptr || @@ -1195,18 +1194,11 @@ bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klas if (jvmti->GetClassLoader(klass, &cl) == JVMTI_ERROR_NONE && cl == nullptr) { char* signature_ptr = nullptr; jvmti->GetClassSignature(klass, &signature_ptr, nullptr); -// TEST_LOG("processing bootstrap class %s", signature_ptr); - // Lambda classes can be unloaded, exlcude them + // Lambda classes, even loaded by bootstrap class loader, can be unloaded, + // fallback to jmethodID if (!isLambdaClass(signature_ptr)) { -// TEST_LOG("Skipping class %s",signature_ptr); return false; - } else { -// TEST_LOG("Lambda class: %s", signature_ptr); } - } else if (cl != nullptr) { - char* signature_ptr; - jvmti->GetClassSignature(klass, &signature_ptr, nullptr); -// TEST_LOG("processing none bootstrap class %s", signature_ptr); } return JVMSupport::loadMethodIDsImpl(jvmti, jni, klass); } diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h index 0e04e2f7b..60e4a0052 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h @@ -216,7 +216,6 @@ typedef void* address; type_begin(VMConstMethod, MATCH_SYMBOLS("ConstMethod")) \ field(_constmethod_constants_offset, offset, MATCH_SYMBOLS("_constants")) \ field(_constmethod_idnum_offset, offset, MATCH_SYMBOLS("_method_idnum")) \ - field(_constmethod_flags_offset, offset, MATCH_SYMBOLS("_flags._flags", "_flags")) \ field(_constmethod_code_size, offset, MATCH_SYMBOLS("_code_size")) \ field(_constmethod_name_index_offset, offset, MATCH_SYMBOLS("_name_index")) \ field(_constmethod_sig_index_offset, offset, MATCH_SYMBOLS("_signature_index")) \ @@ -329,7 +328,6 @@ typedef void* address; type_end() \ type_begin(VMClasses, MATCH_SYMBOLS("vmClasses", "SystemDictionary")) \ field(_obj_class_addr, address, MATCH_SYMBOLS("_klasses[static_cast(vmClassID::Object_klass_knum)]", "_well_known_klasses[SystemDictionary::Object_klass_knum]")) \ - field(_thread_class_addr, address, MATCH_SYMBOLS("_klasses[static_cast(vmClassID::Thread_klass_knum)]", "_well_known_klasses[SystemDictionary::Thread_klass_knum]")) \ type_end() /** @@ -909,7 +907,6 @@ DECLARE(VMConstMethod) inline VMConstantPool* constants() const; inline uint16_t codeSize() const; inline const char* base() const; - inline const char* codeEnd() const; inline u16 nameIndex() const; inline u16 signatureIndex() const; inline VMSymbol* name() const; @@ -1086,7 +1083,6 @@ DECLARE_END DECLARE(VMClasses) public: static inline VMKlass* obj_klass(); - static inline VMKlass* thread_klass(); DECLARE_END class CodeHeap : VMStructs { diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h index bb21572a0..7211f89e2 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h @@ -116,7 +116,6 @@ VMConstantPool* VMConstMethod::constants() const { uint16_t VMConstMethod::codeSize() const { assert(_constmethod_code_size >= 0); uint16_t code_size = *(uint16_t*)at(_constmethod_code_size); - TEST_LOG("VMConstMethod::codeSize(): code_size=%u", code_size); return code_size; } @@ -124,10 +123,6 @@ const char* VMConstMethod::base() const { return (const char*)this + _VMConstMethod_size; } -const char* VMConstMethod::codeEnd() const { - return base() + codeSize(); -} - u16 VMConstMethod::nameIndex() const { assert(_constmethod_name_index_offset >= 0 && "Invalid name index"); return *(u16*)at(_constmethod_name_index_offset); @@ -150,16 +145,8 @@ VMSymbol* VMConstMethod::signature() const { return cpool->symbolAt(sig_index); } -uint32_t VMConstMethod::flags() const { - assert(_constmethod_flags_offset >= 0); - return *(uint32_t*)at(_constmethod_flags_offset ); -} - VMKlass* VMClasses::obj_klass() { return VMKlass::load_then_cast(_obj_class_addr); } -VMKlass* VMClasses::thread_klass() { - return VMKlass::load_then_cast(_thread_class_addr); -} #endif // _HOTSPOT_VMSTRUCTS_INLINE_H diff --git a/ddprof-lib/src/main/cpp/methodInfo.h b/ddprof-lib/src/main/cpp/methodInfo.h index a54469254..52809afdc 100644 --- a/ddprof-lib/src/main/cpp/methodInfo.h +++ b/ddprof-lib/src/main/cpp/methodInfo.h @@ -112,4 +112,4 @@ class MethodMap : public std::map { } }; -#endif // _METHODINFO_H \ No newline at end of file +#endif // _METHODINFO_H diff --git a/ddprof-lib/src/main/cpp/profiler.cpp b/ddprof-lib/src/main/cpp/profiler.cpp index c5bce0ade..c70b5df17 100644 --- a/ddprof-lib/src/main/cpp/profiler.cpp +++ b/ddprof-lib/src/main/cpp/profiler.cpp @@ -151,7 +151,6 @@ const char *Profiler::asgctError(int code) { // Zing sometimes returns it return "unknown_state"; default: - TEST_LOG("-> error: unexpected_state"); // Should not happen return "unexpected_state"; } From ab0076e736e270e9e6f5a5d87ec18fcdb3773d2a Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 29 Apr 2026 14:19:43 -0400 Subject: [PATCH 15/19] Cleanup --- .../src/main/cpp/hotspot/hotspotSupport.cpp | 57 +++++++++++-------- ddprof-lib/src/main/cpp/jvmSupport.h | 1 + ddprof-lib/src/main/cpp/lookup.cpp | 11 +--- .../wallclock/BaseContextWallClockTest.java | 4 ++ .../wallclock/CollapsingSleepTest.java | 2 + 5 files changed, 42 insertions(+), 33 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 6201b761d..4e40f6550 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -1193,11 +1193,16 @@ bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klas // we use Method instead. if (jvmti->GetClassLoader(klass, &cl) == JVMTI_ERROR_NONE && cl == nullptr) { char* signature_ptr = nullptr; - jvmti->GetClassSignature(klass, &signature_ptr, nullptr); - // Lambda classes, even loaded by bootstrap class loader, can be unloaded, - // fallback to jmethodID - if (!isLambdaClass(signature_ptr)) { - return false; + if (jvmti->GetClassSignature(klass, &signature_ptr, nullptr) == JVMTI_ERROR_NONE) { + // Lambda classes, even loaded by bootstrap class loader, can be unloaded, + // fallback to jmethodID + if (!isLambdaClass(signature_ptr)) { + jvmti->Deallocate((unsigned char*)signature_ptr); + return false; + } + } + if (signature_ptr != nullptr) { + jvmti->Deallocate((unsigned char*)signature_ptr); } } return JVMSupport::loadMethodIDsImpl(jvmti, jni, klass); @@ -1222,33 +1227,35 @@ jmethodID HotspotSupport::resolve(const void* method) { VMSymbol* sig_sym = const_method->signature(); VMKlass* klass = vm_method->methodHolder(); VMSymbol* klass_sym = klass->name(); + char* method_name = (char*)malloc(name_sym->length() + 1); char* method_signature = (char*)malloc(sig_sym->length() + 1); int klass_name_len = klass_sym->length(); char* klass_name = (char*)malloc(klass_name_len + 1); - - memcpy(method_name, name_sym->body(), name_sym->length()); - method_name[name_sym->length()] = '\0'; - memcpy(method_signature, sig_sym->body(), sig_sym->length()); - method_signature[sig_sym->length()] = '\0'; - memcpy(klass_name, klass_sym->body(), klass_name_len); - klass_name[klass_name_len] = '\0'; - - JNIEnv *jni = VM::jni(); - jclass clz = jni->FindClass(klass_name); - if (clz == nullptr) { - jni->ExceptionClear(); - } else { - method_id = jni->GetMethodID(clz, method_name, method_signature); - if (method_id == nullptr) { - jni->ExceptionClear(); - method_id = jni->GetStaticMethodID(clz, method_name, method_signature); - if (method_id == nullptr) { + if (method_name !=nullptr && method_signature != nullptr && klass_name != nullptr) { + memcpy(method_name, name_sym->body(), name_sym->length()); + method_name[name_sym->length()] = '\0'; + memcpy(method_signature, sig_sym->body(), sig_sym->length()); + method_signature[sig_sym->length()] = '\0'; + memcpy(klass_name, klass_sym->body(), klass_name_len); + klass_name[klass_name_len] = '\0'; + + JNIEnv *jni = VM::jni(); + jclass clz = jni->FindClass(klass_name); + if (clz == nullptr) { jni->ExceptionClear(); + } else { + method_id = jni->GetMethodID(clz, method_name, method_signature); + if (method_id == nullptr) { + jni->ExceptionClear(); + method_id = jni->GetStaticMethodID(clz, method_name, method_signature); + if (method_id == nullptr) { + jni->ExceptionClear(); + } + } } - } } - + free(method_name); free(method_signature); free(klass_name); diff --git a/ddprof-lib/src/main/cpp/jvmSupport.h b/ddprof-lib/src/main/cpp/jvmSupport.h index df0b49b86..15b8c6c80 100644 --- a/ddprof-lib/src/main/cpp/jvmSupport.h +++ b/ddprof-lib/src/main/cpp/jvmSupport.h @@ -44,6 +44,7 @@ class JVMSupport { return HotspotSupport::resolve(method); } else { assert(false && "Should not reache here"); + return nullptr; } } diff --git a/ddprof-lib/src/main/cpp/lookup.cpp b/ddprof-lib/src/main/cpp/lookup.cpp index 10535a21b..532918bd5 100644 --- a/ddprof-lib/src/main/cpp/lookup.cpp +++ b/ddprof-lib/src/main/cpp/lookup.cpp @@ -180,13 +180,6 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, jmethodID method, mi->_sig = _symbols.lookup("()L;"); mi->_type = FRAME_INTERPRETED; mi->_is_entry = false; - if (line_number_table != nullptr) { - mi->_line_number_table = std::make_shared( - line_number_table_size, line_number_table); - // Increment counter for tracking live line number tables - Counters::increment(LINE_NUMBER_TABLES); - } - } } jni->PopLocalFrame(NULL); @@ -228,7 +221,7 @@ void Lookup::fillMethodInfo(MethodInfo *mi, jclass method_class, char* class_nam // Clear any exceptions from the reflection calls above jniExceptionCheck(jni); } else if (strncmp(method_name, "main", 5) == 0 && - strncmp(method_sig, "(Ljava/lang/String;)V", 21)) { + strncmp(method_sig, "(Ljava/lang/String;)V", 21) == 0) { // public static void main(String[] args) - 'public static' translates // to modifier bits 0 and 3, hence check for '9' entry = true; @@ -378,8 +371,10 @@ MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) { fillNativeMethodInfo(mi, "unknown_library", nullptr); } } else if (frame_type == FRAME_INTERPRETED_METHOD) { + // Resolve this frame into FRAME_INTERPRETED jmethodID id = JVMSupport::resolve(frame.method); frame.bci = FrameType::encode(FRAME_INTERPRETED, bci); + frame.method_id = id; fillJavaMethodInfo(mi, id, first_time); } else { fillJavaMethodInfo(mi, method_id, first_time); diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/BaseContextWallClockTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/BaseContextWallClockTest.java index 2c9f5e464..3e2f169c1 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/BaseContextWallClockTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/BaseContextWallClockTest.java @@ -100,6 +100,10 @@ void test(AbstractProfilerTest test, boolean assertContext, String cstack) throw IMemberAccessor modeAccessor = THREAD_EXECUTION_MODE.getAccessor(wallclockSamples.getType()); for (IItem sample : wallclockSamples) { String stackTrace = frameAccessor.getMember(sample); + System.out.println("StackTrace:"); + System.out.println(stackTrace); + System.out.println(); + long spanId = spanIdAccessor.getMember(sample).longValue(); long rootSpanId = rootSpanIdAccessor.getMember(sample).longValue(); long weight = weightAccessor.getMember(sample).longValue(); diff --git a/ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/CollapsingSleepTest.java b/ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/CollapsingSleepTest.java index ff362085f..3c9b749fc 100644 --- a/ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/CollapsingSleepTest.java +++ b/ddprof-test/src/test/java/com/datadoghq/profiler/wallclock/CollapsingSleepTest.java @@ -27,6 +27,8 @@ public void testSleep() { stopProfiler(); IItemCollection events = verifyEvents("datadog.MethodSample"); assertTrue(events.hasItems()); + + System.out.println("Weight: " + events.getAggregate(Aggregators.sum(WEIGHT)).longValue() + " count = " + events.getAggregate(Aggregators.count()).longValue()); assertTrue(events.getAggregate(Aggregators.sum(WEIGHT)).longValue() > 700); assertTrue(events.getAggregate(Aggregators.count()).longValue() > 9); } From f314731d3f60f2ec80722fc72f5ab7535335466b Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Wed, 29 Apr 2026 20:35:30 -0400 Subject: [PATCH 16/19] v8 --- .../src/main/cpp/hotspot/hotspotSupport.cpp | 68 ++++++++++++------- ddprof-lib/src/main/cpp/hotspot/vmStructs.h | 3 - .../src/main/cpp/hotspot/vmStructs.inline.h | 10 --- ddprof-lib/src/main/cpp/jvmSupport.cpp | 6 +- ddprof-lib/src/main/cpp/jvmSupport.h | 2 +- ddprof-lib/src/main/cpp/lookup.cpp | 20 +++--- ddprof-lib/src/main/cpp/methodInfo.h | 5 +- ddprof-lib/src/main/cpp/vmEntry.cpp | 8 +-- ddprof-lib/src/main/cpp/vmEntry.h | 1 - 9 files changed, 61 insertions(+), 62 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 4e40f6550..9fa511538 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -34,11 +34,28 @@ static jmethodID getMethodId(VMMethod* method) { if (method == nullptr) { return nullptr; } + + jmethodID method_id = nullptr; if (!inDeadZone(method) && aligned((uintptr_t)method) && SafeAccess::isReadableRange(method, VMMethod::type_size())) { - return method->validatedId(); + method_id = method->validatedId(); } - return nullptr; + + return method_id; +} + +static void printMethod(VMMethod* m) { + if (m == nullptr) { + TEST_LOG("*** Method == nullptr"); + } + VMConstMethod* const_method = m->constMethod(); + VMSymbol* name_sym = const_method->name(); + VMSymbol* sig_sym = const_method->signature(); + VMKlass* klass = m->methodHolder(); + VMSymbol* klass_sym = klass->name(); + + TEST_LOG("*** Method class: %.*s method: %.*s %.*s", klass_sym->length(), klass_sym->body(), + name_sym->length(), name_sym->body(), sig_sym->length(), sig_sym->body()); } /** @@ -83,13 +100,10 @@ inline EventType eventTypeFromBCI(jint bci_type) { static void fillFrameTypes(ASGCT_CallFrame *frames, int num_frames, VMNMethod *nmethod) { if (nmethod->isNMethod() && nmethod->isAlive()) { VMMethod *method = nmethod->method(); - if (method == NULL) { - return; - } - jmethodID current_method_id = method->id(); + jmethodID current_method_id = getMethodId(method); if (current_method_id == NULL) { - return; + current_method_id = reinterpret_cast(method); } // Mark current_method as COMPILED and frames above current_method as @@ -120,9 +134,9 @@ static void fillFrameTypes(ASGCT_CallFrame *frames, int num_frames, VMNMethod *n } } -static inline void fillFrame(ASGCT_CallFrame& frame, FrameTypeId type, int bci, const VMMethod* method) { - frame.bci = FrameType::encode(type, bci); - assert(FrameType::decode(frame.bci) == type && "FrameType::encode/decode is not reversable"); +static inline void fillFrame(ASGCT_CallFrame& frame, int bci, const VMMethod* method) { + frame.bci = FrameType::encode(FRAME_INTERPRETED_METHOD, bci); + assert(FrameType::decode(frame.bci) == FRAME_INTERPRETED_METHOD && "FrameType::encode/decode is not reversable"); frame.method = static_cast(method); } @@ -423,7 +437,7 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex if (method_id != nullptr) { fillFrame(frames[depth++], FRAME_INTERPRETED, bci, method_id); } else { - fillFrame(frames[depth++], FRAME_INTERPRETED_METHOD, bci, method); + fillFrame(frames[depth++], bci, method); } sp = ((uintptr_t*)fp)[InterpreterFrame::sender_sp_offset]; pc = stripPointer(((void**)fp)[FRAME_PC_SLOT]); @@ -440,7 +454,7 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex if (method_id != nullptr) { fillFrame(frames[depth++], FRAME_INTERPRETED, 0, method_id); } else { - fillFrame(frames[depth++], FRAME_INTERPRETED_METHOD, 0, method); + fillFrame(frames[depth++], 0, method); } if (is_plausible_interpreter_frame) { pc = stripPointer(((void**)fp)[FRAME_PC_SLOT]); @@ -602,7 +616,7 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex } else if (mark == MARK_COMPILER_ENTRY && features.comp_task && vm_thread != NULL) { // Insert current compile task as a pseudo Java frame VMMethod* method = vm_thread->compiledMethod(); - jmethodID method_id = method != NULL ? method->id() : NULL; + jmethodID method_id = getMethodId(method); if (method_id != NULL) { fillFrame(frames[depth++], FRAME_JIT_COMPILED, 0, method_id); } @@ -647,7 +661,7 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex if (StackWalkValidation::isPlausibleInterpreterFrame(recovery_fp, recovery_sp, bcp_offset)) { VMMethod* method = ((VMMethod**)recovery_fp)[InterpreterFrame::method_offset]; jmethodID method_id = getMethodId(method); - if (method_id != NULL) { + if (method_id != nullptr || VMClassLoader::isLoadedByBootstrapClassLoader(method)) { anchor = NULL; prev_native_pc = NULL; if (depth > 0 && depth + 1 < actual_max_depth) { @@ -657,8 +671,11 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex const char* bytecode_start = method->bytecode(); const char* bcp = ((const char**)recovery_fp)[bcp_offset]; int bci = bytecode_start == NULL || bcp < bytecode_start ? 0 : bcp - bytecode_start; - fillFrame(frames[depth++], FRAME_INTERPRETED, bci, method_id); - + if (method_id != nullptr) { + fillFrame(frames[depth++], FRAME_INTERPRETED, bci, method_id); + } else { + fillFrame(frames[depth++], bci, method); + } sp = ((uintptr_t*)recovery_fp)[InterpreterFrame::sender_sp_offset]; pc = stripPointer(((void**)recovery_fp)[FRAME_PC_SLOT]); fp = *(uintptr_t*)recovery_fp; @@ -805,7 +822,7 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex if (StackWalkValidation::isPlausibleInterpreterFrame(anchor_fp, anchor_sp, bcp_offset)) { VMMethod* method = ((VMMethod**)anchor_fp)[InterpreterFrame::method_offset]; jmethodID method_id = getMethodId(method); - if (method_id != NULL) { + if (method_id != nullptr || VMClassLoader::isLoadedByBootstrapClassLoader(method)) { Counters::increment(WALKVM_ANCHOR_FALLBACK); Counters::increment(WALKVM_JAVA_FRAME_OK); anchor = NULL; @@ -814,7 +831,11 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex const char* bytecode_start = method->bytecode(); const char* bcp = ((const char**)anchor_fp)[bcp_offset]; int bci = bytecode_start == NULL || bcp < bytecode_start ? 0 : bcp - bytecode_start; - fillFrame(frames[depth++], FRAME_INTERPRETED, bci, method_id); + if (method_id != nullptr) { + fillFrame(frames[depth++], FRAME_INTERPRETED, bci, method_id); + } else { + fillFrame(frames[depth++], bci, method); + } sp = ((uintptr_t*)anchor_fp)[InterpreterFrame::sender_sp_offset]; pc = stripPointer(((void**)anchor_fp)[FRAME_PC_SLOT]); fp = *(uintptr_t*)anchor_fp; @@ -1004,7 +1025,7 @@ int HotspotSupport::getJavaTraceAsync(void *ucontext, ASGCT_CallFrame *frames, if (nmethod != NULL && nmethod->isNMethod() && nmethod->isAlive()) { VMMethod *method = nmethod->method(); if (method != NULL) { - jmethodID method_id = method->id(); + jmethodID method_id = getMethodId(method); if (method_id != NULL) { max_depth -= makeFrame(trace.frames++, 0, method_id); } @@ -1108,7 +1129,6 @@ int HotspotSupport::getJavaTraceAsync(void *ucontext, ASGCT_CallFrame *frames, return trace.frames - frames + 1; } - int HotspotSupport::walkJavaStack(StackWalkRequest& request) { CStack cstack = Profiler::instance()->cstackMode(); StackWalkFeatures features = Profiler::instance()->stackWalkFeatures(); @@ -1153,7 +1173,6 @@ int HotspotSupport::walkJavaStack(StackWalkRequest& request) { return java_frames; } - static void patchClassLoaderData(JNIEnv* jni, jclass klass) { bool needs_patch = VM::hotspot_version() == 8; if (needs_patch) { @@ -1186,8 +1205,6 @@ static bool isLambdaClass(const char* signature) { } bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass) { - - patchClassLoaderData(jni, klass); jobject cl; // Hotpsot only: loaded by bootstrap class loader, which is never unloaded, // we use Method instead. @@ -1205,6 +1222,7 @@ bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klas jvmti->Deallocate((unsigned char*)signature_ptr); } } + patchClassLoaderData(jni, klass); return JVMSupport::loadMethodIDsImpl(jvmti, jni, klass); } @@ -1244,22 +1262,26 @@ jmethodID HotspotSupport::resolve(const void* method) { jclass clz = jni->FindClass(klass_name); if (clz == nullptr) { jni->ExceptionClear(); + TEST_LOG("Failed to resolve class: %s", klass_name); } else { method_id = jni->GetMethodID(clz, method_name, method_signature); if (method_id == nullptr) { jni->ExceptionClear(); method_id = jni->GetStaticMethodID(clz, method_name, method_signature); if (method_id == nullptr) { + TEST_LOG("Failed to resolve method: %s %s", method_name, method_signature); jni->ExceptionClear(); } } } } +TEST_LOG("Resolved: %s: %s %s", klass_name, method_name, method_signature); free(method_name); free(method_signature); free(klass_name); + return method_id; } diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h index 60e4a0052..010a56268 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h @@ -905,13 +905,10 @@ DECLARE_END DECLARE(VMConstMethod) public: inline VMConstantPool* constants() const; - inline uint16_t codeSize() const; - inline const char* base() const; inline u16 nameIndex() const; inline u16 signatureIndex() const; inline VMSymbol* name() const; inline VMSymbol* signature() const; - inline uint32_t flags() const; DECLARE_END DECLARE(VMMethod) diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h index 7211f89e2..8dbc5956a 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h @@ -113,16 +113,6 @@ VMConstantPool* VMConstMethod::constants() const { return VMConstantPool::load_then_cast(at(_constmethod_constants_offset)); } -uint16_t VMConstMethod::codeSize() const { - assert(_constmethod_code_size >= 0); - uint16_t code_size = *(uint16_t*)at(_constmethod_code_size); - return code_size; -} - -const char* VMConstMethod::base() const { - return (const char*)this + _VMConstMethod_size; -} - u16 VMConstMethod::nameIndex() const { assert(_constmethod_name_index_offset >= 0 && "Invalid name index"); return *(u16*)at(_constmethod_name_index_offset); diff --git a/ddprof-lib/src/main/cpp/jvmSupport.cpp b/ddprof-lib/src/main/cpp/jvmSupport.cpp index 9bb10df30..e665347cc 100644 --- a/ddprof-lib/src/main/cpp/jvmSupport.cpp +++ b/ddprof-lib/src/main/cpp/jvmSupport.cpp @@ -105,11 +105,7 @@ bool JVMSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass) { // JVMTI callbacks void JNICALL JVMSupport::ClassPrepare(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, jclass klass) { - if (VM::isHotspot()) { - HotspotSupport::loadMethodIDsImpl(jvmti, jni, klass); - } else { - loadMethodIDsImpl(jvmti, jni, klass); - } + loadMethodIDs(jvmti, jni, klass); } void JNICALL JVMSupport::ClassLoad(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, diff --git a/ddprof-lib/src/main/cpp/jvmSupport.h b/ddprof-lib/src/main/cpp/jvmSupport.h index 15b8c6c80..5912eaf4c 100644 --- a/ddprof-lib/src/main/cpp/jvmSupport.h +++ b/ddprof-lib/src/main/cpp/jvmSupport.h @@ -43,7 +43,7 @@ class JVMSupport { if (VM::isHotspot()) { return HotspotSupport::resolve(method); } else { - assert(false && "Should not reache here"); + assert(false && "Should not reach here"); return nullptr; } } diff --git a/ddprof-lib/src/main/cpp/lookup.cpp b/ddprof-lib/src/main/cpp/lookup.cpp index 532918bd5..2436aed46 100644 --- a/ddprof-lib/src/main/cpp/lookup.cpp +++ b/ddprof-lib/src/main/cpp/lookup.cpp @@ -143,6 +143,7 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, jmethodID method, jvmti->GetClassSignature(method_class, &class_name, NULL) == JVMTI_ERROR_NONE && jvmti->GetMethodName(method, &method_name, &method_sig, NULL) == JVMTI_ERROR_NONE) { if (first_time) { + // Populate modifier (async profiler has this call) if (jvmti->GetMethodModifiers(method, &mi->_modifiers) != JVMTI_ERROR_NONE) { mi->_modifiers = 0; } @@ -305,16 +306,23 @@ MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) { unsigned long key; jint bci = frame.bci; FrameTypeId frame_type = FrameType::decode(bci); - jmethodID method_id = frame.method_id; + + // Resolve method into jmethodID + if (frame_type == FRAME_INTERPRETED_METHOD) { + // Resolve this frame into FRAME_INTERPRETED + method_id = JVMSupport::resolve(frame.method); + frame.bci = FrameType::encode(FRAME_INTERPRETED, frame.bci); + frame.method_id = method_id; + frame_type = FRAME_INTERPRETED; + } + if (method_id == nullptr) { key = MethodMap::makeKey(UNKNOWN); } else if (bci == BCI_ERROR || bci == BCI_NATIVE_FRAME) { key = MethodMap::makeKey(frame.native_function_name); } else if (bci == BCI_NATIVE_FRAME_REMOTE) { key = MethodMap::makeKey(frame.packed_remote_frame); - } else if (frame_type == FRAME_INTERPRETED_METHOD) { - key = MethodMap::makeKey(frame.method); } else { assert(frame_type == FRAME_INTERPRETED || frame_type == FRAME_JIT_COMPILED || frame_type == FRAME_INLINED || frame_type == FRAME_C1_COMPILED || @@ -370,12 +378,6 @@ MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) { TEST_LOG("WARNING: Library lookup failed for index %u", lib_index); fillNativeMethodInfo(mi, "unknown_library", nullptr); } - } else if (frame_type == FRAME_INTERPRETED_METHOD) { - // Resolve this frame into FRAME_INTERPRETED - jmethodID id = JVMSupport::resolve(frame.method); - frame.bci = FrameType::encode(FRAME_INTERPRETED, bci); - frame.method_id = id; - fillJavaMethodInfo(mi, id, first_time); } else { fillJavaMethodInfo(mi, method_id, first_time); } diff --git a/ddprof-lib/src/main/cpp/methodInfo.h b/ddprof-lib/src/main/cpp/methodInfo.h index 52809afdc..9b2c45138 100644 --- a/ddprof-lib/src/main/cpp/methodInfo.h +++ b/ddprof-lib/src/main/cpp/methodInfo.h @@ -51,10 +51,7 @@ class MethodInfo { } int i = 1; - int table_size = _line_number_table->_size; - table_size = table_size > 0 ? table_size : - table_size; - - while (i < table_size && + while (i < _line_number_table->_size && bci >= ((jvmtiLineNumberEntry *)_line_number_table->_ptr)[i] .start_location) { i++; diff --git a/ddprof-lib/src/main/cpp/vmEntry.cpp b/ddprof-lib/src/main/cpp/vmEntry.cpp index 6d7cfafe1..402adfa4c 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.cpp +++ b/ddprof-lib/src/main/cpp/vmEntry.cpp @@ -487,7 +487,7 @@ bool VM::initProfilerBridge(JavaVM *vm, bool attach) { functions->RetransformClasses = RetransformClassesHook; if (attach) { - loadAllMethodIDs(_jvmti, jni()); + JVMSupport::loadAllMethodIDs(_jvmti, jni()); _jvmti->GenerateEvents(JVMTI_EVENT_DYNAMIC_CODE_GENERATED); _jvmti->GenerateEvents(JVMTI_EVENT_COMPILED_METHOD_LOAD); } else { @@ -536,13 +536,9 @@ void *VM::getLibraryHandle(const char *name) { return RTLD_DEFAULT; } -void VM::loadAllMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni) { - JVMSupport::loadAllMethodIDs(jvmti, jni); -} - void JNICALL VM::VMInit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) { ready(jvmti, jni); - loadAllMethodIDs(jvmti, jni); + JVMSupport::loadAllMethodIDs(jvmti, jni); // initialize the heap usage tracking only after the VM is ready HeapUsage::initJMXUsage(VM::jni()); diff --git a/ddprof-lib/src/main/cpp/vmEntry.h b/ddprof-lib/src/main/cpp/vmEntry.h index 8a70f0f48..e793fcdb8 100644 --- a/ddprof-lib/src/main/cpp/vmEntry.h +++ b/ddprof-lib/src/main/cpp/vmEntry.h @@ -136,7 +136,6 @@ class VM { static void ready(jvmtiEnv *jvmti, JNIEnv *jni); static void applyPatch(char *func, const char *patch, const char *end_patch); static void *getLibraryHandle(const char *name); - static void loadAllMethodIDs(jvmtiEnv *jvmti, JNIEnv *jni); static bool initShared(JavaVM *vm); From 00995515afa23f1f6eb97855bb2c3798e895ed09 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Thu, 30 Apr 2026 23:33:17 +0000 Subject: [PATCH 17/19] v9 --- .../src/main/cpp/hotspot/classloader.inline.h | 10 ++++-- .../src/main/cpp/hotspot/hotspotSupport.cpp | 36 ++++++++++++++----- ddprof-lib/src/main/cpp/hotspot/vmStructs.h | 19 ++++++++-- .../src/main/cpp/hotspot/vmStructs.inline.h | 24 ++++++++++++- ddprof-lib/src/main/cpp/lookup.cpp | 5 ++- 5 files changed, 75 insertions(+), 19 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h b/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h index 6805be4d4..b043b1775 100644 --- a/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h @@ -16,18 +16,22 @@ bool VMClassLoader::isLoadedByBootstrapClassLoader(const VMMethod* method) { return false; } - VMKlass* method_klass = method->methodHolder(); + VMKlass* method_klass = method->safeMethodHolder(); if (method_klass == nullptr) { return false; } + VMClassLoaderData* cld = method_klass->safeClassLoaderData(); + if (cld == nullptr) { + return false; + } + // java/lang/Object must be loaded by bootstrap class loader VMKlass* obj_klass = VMClasses::obj_klass(); assert(obj_klass != nullptr && "VMClasses not yet initialized"); - assert(method_klass->classLoaderData() != nullptr && "Method holder has no class loader data"); assert(obj_klass->classLoaderData() != nullptr && "Object class has no class loader data"); - return method_klass->classLoaderData() == obj_klass->classLoaderData(); + return cld == obj_klass->classLoaderData(); } #endif // _HOTSPOT_CLASSLOADER_INLINE_H diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index 9fa511538..cbe9dd599 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -103,7 +103,7 @@ static void fillFrameTypes(ASGCT_CallFrame *frames, int num_frames, VMNMethod *n jmethodID current_method_id = getMethodId(method); if (current_method_id == NULL) { - current_method_id = reinterpret_cast(method); + return; } // Mark current_method as COMPILED and frames above current_method as @@ -135,8 +135,8 @@ static void fillFrameTypes(ASGCT_CallFrame *frames, int num_frames, VMNMethod *n } static inline void fillFrame(ASGCT_CallFrame& frame, int bci, const VMMethod* method) { + assert(method != nullptr); frame.bci = FrameType::encode(FRAME_INTERPRETED_METHOD, bci); - assert(FrameType::decode(frame.bci) == FRAME_INTERPRETED_METHOD && "FrameType::encode/decode is not reversable"); frame.method = static_cast(method); } @@ -490,7 +490,13 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex Counters::increment(WALKVM_JAVA_FRAME_OK); int level = nm->level(); FrameTypeId type = details && level >= 1 && level <= 3 ? FRAME_C1_COMPILED : FRAME_JIT_COMPILED; - fillFrame(frames[depth++], type, 0, nm->method()->id()); + VMMethod* method = VMMethod::cast_or_null((const void*)frame.method()); + jmethodID method_id = getMethodId(method); + if (method_id != nullptr) { + fillFrame(frames[depth++], type, 0, nm->method()->id()); + } else if (VMClassLoader::isLoadedByBootstrapClassLoader(method)) { + fillFrame(frames[depth++], 0, method); + } if (nm->isFrameCompleteAt(pc)) { if (depth == 1 && frame.unwindEpilogue(nm, (uintptr_t&)pc, sp, fp)) { @@ -507,7 +513,13 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex type = scope_offset > 0 ? FRAME_INLINED : level >= 1 && level <= 3 ? FRAME_C1_COMPILED : FRAME_JIT_COMPILED; } - fillFrame(frames[depth++], type, scope.bci(), scope.method()->id()); + VMMethod* method = scope.method(); + jmethodID method_id = getMethodId(method); + if (method_id != nullptr) { + fillFrame(frames[depth++], type, scope.bci(), method_id); + } else if (VMClassLoader::isLoadedByBootstrapClassLoader(method)) { + fillFrame(frames[depth++], scope.bci(), scope.method()); + } } while (scope_offset > 0 && depth < max_depth); } @@ -619,6 +631,8 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex jmethodID method_id = getMethodId(method); if (method_id != NULL) { fillFrame(frames[depth++], FRAME_JIT_COMPILED, 0, method_id); + } else if (VMClassLoader::isLoadedByBootstrapClassLoader(method)) { + fillFrame(frames[depth++], 0, method); } } else if (mark == MARK_THREAD_ENTRY) { // Thread entry point detected via pre-computed mark - this is the root frame @@ -1240,12 +1254,18 @@ jmethodID HotspotSupport::resolve(const void* method) { return method_id; } - VMConstMethod* const_method = vm_method->constMethod(); + VMConstMethod* const_method = vm_method->safeConstMethod(); + if (const_method == nullptr) { + return nullptr; + } + VMSymbol* name_sym = const_method->name(); VMSymbol* sig_sym = const_method->signature(); - VMKlass* klass = vm_method->methodHolder(); + VMKlass* klass = vm_method->safeMethodHolder(); VMSymbol* klass_sym = klass->name(); + + char* method_name = (char*)malloc(name_sym->length() + 1); char* method_signature = (char*)malloc(sig_sym->length() + 1); int klass_name_len = klass_sym->length(); @@ -1262,20 +1282,18 @@ jmethodID HotspotSupport::resolve(const void* method) { jclass clz = jni->FindClass(klass_name); if (clz == nullptr) { jni->ExceptionClear(); - TEST_LOG("Failed to resolve class: %s", klass_name); } else { method_id = jni->GetMethodID(clz, method_name, method_signature); if (method_id == nullptr) { jni->ExceptionClear(); method_id = jni->GetStaticMethodID(clz, method_name, method_signature); if (method_id == nullptr) { - TEST_LOG("Failed to resolve method: %s %s", method_name, method_signature); jni->ExceptionClear(); } } } } -TEST_LOG("Resolved: %s: %s %s", klass_name, method_name, method_signature); +// TEST_LOG("Resolved: %s: %s %s", klass_name, method_name, method_signature); free(method_name); free(method_signature); diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h index 010a56268..55b768b2d 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h @@ -88,8 +88,10 @@ inline T* cast_or_null(const void* ptr) { static name * cast_raw(const void* ptr) { return (name *)ptr; } \ static name * load_then_cast(const void* ptr) { \ assert(ptr != nullptr); \ - return cast(*(const void**)ptr); } - + return cast(*(const void**)ptr); } \ + static name * safe_load_then_cast(const void* ptr) { \ + assert(ptr != nullptr); \ + return cast(SafeAccess::loadPtr((void**)ptr, nullptr)); } #define DECLARE_END }; /** @@ -696,11 +698,16 @@ DECLARE(VMKlass) return VMSymbol::load_then_cast(at(_klass_name_offset)); } - VMClassLoaderData* classLoaderData() { + VMClassLoaderData* classLoaderData() const { assert(_class_loader_data_offset >= 0); return VMClassLoaderData::load_then_cast(at(_class_loader_data_offset)); } + VMClassLoaderData* safeClassLoaderData() const { + assert(_class_loader_data_offset >= 0); + return VMClassLoaderData::safe_load_then_cast(at(_class_loader_data_offset)); + } + int methodCount() { assert(_methods_offset >= 0); int* methods = *(int**) at(_methods_offset); @@ -897,6 +904,8 @@ DECLARE_END DECLARE(VMConstantPool) public: inline VMKlass* holder() const; + inline VMKlass* safeHolder() const; + inline VMSymbol* symbolAt(u16 index) const; private: inline intptr_t* base() const; @@ -905,6 +914,7 @@ DECLARE_END DECLARE(VMConstMethod) public: inline VMConstantPool* constants() const; + inline VMConstantPool* safeConstants() const; inline u16 nameIndex() const; inline u16 signatureIndex() const; inline VMSymbol* name() const; @@ -935,8 +945,11 @@ DECLARE(VMMethod) } inline VMConstMethod* constMethod() const; + inline VMConstMethod* safeConstMethod() const; inline VMNMethod* code() const; inline VMKlass* methodHolder() const; + inline VMKlass* safeMethodHolder() const; + static bool check_jmethodID(jmethodID id); DECLARE_END diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h index 8dbc5956a..fe1ea499d 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h @@ -82,6 +82,11 @@ VMKlass* VMConstantPool::holder() const { return VMKlass::load_then_cast(at(_pool_holder_offset)); } +VMKlass* VMConstantPool::safeHolder() const { + assert(_pool_holder_offset >= 0); + return VMKlass::safe_load_then_cast(at(_pool_holder_offset)); +} + VMSymbol* VMConstantPool::symbolAt(u16 index) const { return VMSymbol::cast(*(void**)&base()[index]); } @@ -95,6 +100,10 @@ VMConstMethod* VMMethod::constMethod() const { return VMConstMethod::load_then_cast(at(_method_constmethod_offset)); } +VMConstMethod* VMMethod::safeConstMethod() const { + return VMConstMethod::safe_load_then_cast(at(_method_constmethod_offset)); +} + VMNMethod* VMMethod::code() const { assert(_method_code_offset >= 0); const void* code_ptr = *(const void**) at(_method_code_offset); @@ -102,17 +111,30 @@ VMNMethod* VMMethod::code() const { } VMKlass* VMMethod::methodHolder() const { -// return constMethod()->constants()->holder(); VMConstMethod* constMthd = constMethod(); VMConstantPool* pool = constMthd->constants(); VMKlass* holder = pool->holder(); return holder; } +VMKlass* VMMethod::safeMethodHolder() const { + VMConstMethod* constMthd = safeConstMethod(); + if (constMthd == nullptr) return nullptr; + + VMConstantPool* pool = constMthd->safeConstants(); + if (pool == nullptr) return nullptr; + + return pool->safeHolder(); +} + VMConstantPool* VMConstMethod::constants() const { return VMConstantPool::load_then_cast(at(_constmethod_constants_offset)); } +VMConstantPool* VMConstMethod::safeConstants() const { + return VMConstantPool::safe_load_then_cast(at(_constmethod_constants_offset)); +} + u16 VMConstMethod::nameIndex() const { assert(_constmethod_name_index_offset >= 0 && "Invalid name index"); return *(u16*)at(_constmethod_name_index_offset); diff --git a/ddprof-lib/src/main/cpp/lookup.cpp b/ddprof-lib/src/main/cpp/lookup.cpp index 2436aed46..203feb72e 100644 --- a/ddprof-lib/src/main/cpp/lookup.cpp +++ b/ddprof-lib/src/main/cpp/lookup.cpp @@ -147,7 +147,6 @@ void Lookup::fillJavaMethodInfo(MethodInfo *mi, jmethodID method, if (jvmti->GetMethodModifiers(method, &mi->_modifiers) != JVMTI_ERROR_NONE) { mi->_modifiers = 0; } - jvmtiError line_table_error = jvmti->GetLineNumberTable(method, &line_number_table_size, &line_number_table); // Defensive: if GetLineNumberTable failed, clean up any potentially allocated memory @@ -308,9 +307,8 @@ MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) { FrameTypeId frame_type = FrameType::decode(bci); jmethodID method_id = frame.method_id; - // Resolve method into jmethodID + // Resolve this frame into FRAME_INTERPRETED if (frame_type == FRAME_INTERPRETED_METHOD) { - // Resolve this frame into FRAME_INTERPRETED method_id = JVMSupport::resolve(frame.method); frame.bci = FrameType::encode(FRAME_INTERPRETED, frame.bci); frame.method_id = method_id; @@ -318,6 +316,7 @@ MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) { } if (method_id == nullptr) { + TEST_LOG("Unknown: frameType = %d, bci = %d", (int)frame_type, bci); key = MethodMap::makeKey(UNKNOWN); } else if (bci == BCI_ERROR || bci == BCI_NATIVE_FRAME) { key = MethodMap::makeKey(frame.native_function_name); From 40a8520b0cb1a235a573b244bf06e3e6e7912f46 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Fri, 1 May 2026 12:51:31 +0000 Subject: [PATCH 18/19] v10 --- ddprof-lib/src/main/cpp/hotspot/vmStructs.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h index 55b768b2d..1fb7417fb 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h @@ -87,10 +87,10 @@ inline T* cast_or_null(const void* ptr) { static name * cast_or_null(const void* ptr) { return ::cast_or_null(ptr); } \ static name * cast_raw(const void* ptr) { return (name *)ptr; } \ static name * load_then_cast(const void* ptr) { \ - assert(ptr != nullptr); \ + if (ptr == nullptr) return nullptr; \ return cast(*(const void**)ptr); } \ static name * safe_load_then_cast(const void* ptr) { \ - assert(ptr != nullptr); \ + if (ptr == nullptr) return nullptr; \ return cast(SafeAccess::loadPtr((void**)ptr, nullptr)); } #define DECLARE_END }; @@ -949,7 +949,7 @@ DECLARE(VMMethod) inline VMNMethod* code() const; inline VMKlass* methodHolder() const; inline VMKlass* safeMethodHolder() const; - + static bool check_jmethodID(jmethodID id); DECLARE_END From 52ff5c0e394bb566811cf2ca92180b4d26508876 Mon Sep 17 00:00:00 2001 From: Zhengyu Gu Date: Fri, 1 May 2026 15:45:09 -0400 Subject: [PATCH 19/19] v11 --- ddprof-lib/src/main/cpp/frame.h | 6 ++--- .../src/main/cpp/hotspot/classloader.inline.h | 4 ++-- .../src/main/cpp/hotspot/hotspotSupport.cpp | 21 +++++++++++++----- ddprof-lib/src/main/cpp/hotspot/vmStructs.h | 17 +++++++------- .../src/main/cpp/hotspot/vmStructs.inline.h | 22 +++++++++++-------- 5 files changed, 42 insertions(+), 28 deletions(-) diff --git a/ddprof-lib/src/main/cpp/frame.h b/ddprof-lib/src/main/cpp/frame.h index 914cf4c5d..6694dbaa2 100644 --- a/ddprof-lib/src/main/cpp/frame.h +++ b/ddprof-lib/src/main/cpp/frame.h @@ -12,9 +12,9 @@ enum FrameTypeId { FRAME_CPP = 4, FRAME_KERNEL = 5, FRAME_C1_COMPILED = 6, - FRAME_INTERPRETED_METHOD = 7, - FRAME_NATIVE_REMOTE = 8, // Native frame with remote symbolication (build-id + pc-offset) - FRAME_TYPE_MAX = FRAME_NATIVE_REMOTE // Maximum valid frame type + FRAME_NATIVE_REMOTE = 7, // Native frame with remote symbolication (build-id + pc-offset) + FRAME_INTERPRETED_METHOD = 8, + FRAME_TYPE_MAX = FRAME_INTERPRETED_METHOD // Maximum valid frame type }; class FrameType { diff --git a/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h b/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h index b043b1775..60db04d76 100644 --- a/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/classloader.inline.h @@ -16,12 +16,12 @@ bool VMClassLoader::isLoadedByBootstrapClassLoader(const VMMethod* method) { return false; } - VMKlass* method_klass = method->safeMethodHolder(); + VMKlass* method_klass = method->methodHolderSafe(); if (method_klass == nullptr) { return false; } - VMClassLoaderData* cld = method_klass->safeClassLoaderData(); + VMClassLoaderData* cld = method_klass->classLoaderDataSafe(); if (cld == nullptr) { return false; } diff --git a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp index cbe9dd599..35c9ee92e 100644 --- a/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp +++ b/ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp @@ -493,7 +493,7 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex VMMethod* method = VMMethod::cast_or_null((const void*)frame.method()); jmethodID method_id = getMethodId(method); if (method_id != nullptr) { - fillFrame(frames[depth++], type, 0, nm->method()->id()); + fillFrame(frames[depth++], type, 0, method_id); } else if (VMClassLoader::isLoadedByBootstrapClassLoader(method)) { fillFrame(frames[depth++], 0, method); } @@ -673,7 +673,7 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex // In HotSpot, lastJavaFP is non-zero only for interpreter frames; // compiled frames record FP=0 in the anchor. if (StackWalkValidation::isPlausibleInterpreterFrame(recovery_fp, recovery_sp, bcp_offset)) { - VMMethod* method = ((VMMethod**)recovery_fp)[InterpreterFrame::method_offset]; + VMMethod* method = VMMethod::cast_or_null(((VMMethod**)recovery_fp)[InterpreterFrame::method_offset]); jmethodID method_id = getMethodId(method); if (method_id != nullptr || VMClassLoader::isLoadedByBootstrapClassLoader(method)) { anchor = NULL; @@ -1236,6 +1236,9 @@ bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klas jvmti->Deallocate((unsigned char*)signature_ptr); } } + if (cl != nullptr) { + jni->DeleteLocalRef(cl); + } patchClassLoaderData(jni, klass); return JVMSupport::loadMethodIDsImpl(jvmti, jni, klass); } @@ -1254,17 +1257,23 @@ jmethodID HotspotSupport::resolve(const void* method) { return method_id; } - VMConstMethod* const_method = vm_method->safeConstMethod(); + VMConstMethod* const_method = vm_method->constMethodSafe(); if (const_method == nullptr) { return nullptr; } VMSymbol* name_sym = const_method->name(); VMSymbol* sig_sym = const_method->signature(); - VMKlass* klass = vm_method->safeMethodHolder(); - VMSymbol* klass_sym = klass->name(); + VMKlass* klass = vm_method->methodHolderSafe(); - + if (name_sym == nullptr || sig_sym == nullptr || klass == nullptr) { + return nullptr; + } + + VMSymbol* klass_sym = klass->name(); + if (klass_sym == nullptr) { + return nullptr; + } char* method_name = (char*)malloc(name_sym->length() + 1); char* method_signature = (char*)malloc(sig_sym->length() + 1); diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h index 1fb7417fb..10f4873dc 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.h @@ -218,7 +218,6 @@ typedef void* address; type_begin(VMConstMethod, MATCH_SYMBOLS("ConstMethod")) \ field(_constmethod_constants_offset, offset, MATCH_SYMBOLS("_constants")) \ field(_constmethod_idnum_offset, offset, MATCH_SYMBOLS("_method_idnum")) \ - field(_constmethod_code_size, offset, MATCH_SYMBOLS("_code_size")) \ field(_constmethod_name_index_offset, offset, MATCH_SYMBOLS("_name_index")) \ field(_constmethod_sig_index_offset, offset, MATCH_SYMBOLS("_signature_index")) \ type_end() \ @@ -703,7 +702,7 @@ DECLARE(VMKlass) return VMClassLoaderData::load_then_cast(at(_class_loader_data_offset)); } - VMClassLoaderData* safeClassLoaderData() const { + VMClassLoaderData* classLoaderDataSafe() const { assert(_class_loader_data_offset >= 0); return VMClassLoaderData::safe_load_then_cast(at(_class_loader_data_offset)); } @@ -904,9 +903,10 @@ DECLARE_END DECLARE(VMConstantPool) public: inline VMKlass* holder() const; - inline VMKlass* safeHolder() const; + inline VMKlass* holderSafe() const; inline VMSymbol* symbolAt(u16 index) const; + inline VMSymbol* symbolAtSafe(u16 index) const; private: inline intptr_t* base() const; DECLARE_END @@ -914,11 +914,12 @@ DECLARE_END DECLARE(VMConstMethod) public: inline VMConstantPool* constants() const; - inline VMConstantPool* safeConstants() const; - inline u16 nameIndex() const; - inline u16 signatureIndex() const; + inline VMConstantPool* constantsSafe() const; inline VMSymbol* name() const; inline VMSymbol* signature() const; +private: + inline u16 nameIndex() const; + inline u16 signatureIndex() const; DECLARE_END DECLARE(VMMethod) @@ -945,10 +946,10 @@ DECLARE(VMMethod) } inline VMConstMethod* constMethod() const; - inline VMConstMethod* safeConstMethod() const; + inline VMConstMethod* constMethodSafe() const; inline VMNMethod* code() const; inline VMKlass* methodHolder() const; - inline VMKlass* safeMethodHolder() const; + inline VMKlass* methodHolderSafe() const; static bool check_jmethodID(jmethodID id); DECLARE_END diff --git a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h index fe1ea499d..9c78c20a8 100644 --- a/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h +++ b/ddprof-lib/src/main/cpp/hotspot/vmStructs.inline.h @@ -82,7 +82,7 @@ VMKlass* VMConstantPool::holder() const { return VMKlass::load_then_cast(at(_pool_holder_offset)); } -VMKlass* VMConstantPool::safeHolder() const { +VMKlass* VMConstantPool::holderSafe() const { assert(_pool_holder_offset >= 0); return VMKlass::safe_load_then_cast(at(_pool_holder_offset)); } @@ -91,6 +91,10 @@ VMSymbol* VMConstantPool::symbolAt(u16 index) const { return VMSymbol::cast(*(void**)&base()[index]); } +VMSymbol* VMConstantPool::symbolAtSafe(u16 index) const { + return VMSymbol::cast_or_null(*(void**)&base()[index]); +} + intptr_t* VMConstantPool::base() const { assert(_VMConstantPool_size > 0); return (intptr_t*)(((char*)this) + _VMConstantPool_size); @@ -100,7 +104,7 @@ VMConstMethod* VMMethod::constMethod() const { return VMConstMethod::load_then_cast(at(_method_constmethod_offset)); } -VMConstMethod* VMMethod::safeConstMethod() const { +VMConstMethod* VMMethod::constMethodSafe() const { return VMConstMethod::safe_load_then_cast(at(_method_constmethod_offset)); } @@ -117,21 +121,21 @@ VMKlass* VMMethod::methodHolder() const { return holder; } -VMKlass* VMMethod::safeMethodHolder() const { - VMConstMethod* constMthd = safeConstMethod(); +VMKlass* VMMethod::methodHolderSafe() const { + VMConstMethod* constMthd = constMethodSafe(); if (constMthd == nullptr) return nullptr; - VMConstantPool* pool = constMthd->safeConstants(); + VMConstantPool* pool = constMthd->constantsSafe(); if (pool == nullptr) return nullptr; - return pool->safeHolder(); + return pool->holderSafe(); } VMConstantPool* VMConstMethod::constants() const { return VMConstantPool::load_then_cast(at(_constmethod_constants_offset)); } -VMConstantPool* VMConstMethod::safeConstants() const { +VMConstantPool* VMConstMethod::constantsSafe() const { return VMConstantPool::safe_load_then_cast(at(_constmethod_constants_offset)); } @@ -148,13 +152,13 @@ u16 VMConstMethod::signatureIndex() const { VMSymbol* VMConstMethod::name() const { VMConstantPool* cpool = constants(); u16 name_index = nameIndex(); - return cpool->symbolAt(name_index); + return cpool->symbolAtSafe(name_index); } VMSymbol* VMConstMethod::signature() const { VMConstantPool* cpool = constants(); u16 sig_index = signatureIndex(); - return cpool->symbolAt(sig_index); + return cpool->symbolAtSafe(sig_index); } VMKlass* VMClasses::obj_klass() {