Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions ddprof-lib/src/main/cpp/flightRecorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "incbin.h"
#include "jfrMetadata.h"
#include "jniHelper.h"
#include "jvmSupport.inline.h"
#include "os.h"
#include "profiler.h"
#include "signalSafety.h"
Expand Down Expand Up @@ -485,8 +486,13 @@ 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_id = frame.method_id;

jmethodID method = frame.method_id;
// Resolve native method
if (FrameType::isRawPointer(bci)) {
method_id = JVMSupport::resolve(frame.method);
}

// BCI_VTABLE_RECEIVER: method holds a VMSymbol* (see vmEntry.h). Resolve
// to a class_id via the per-dump cache once, then key MethodMap by the
Expand All @@ -495,10 +501,10 @@ MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) {
// row.
u32 vtable_class_id = 0;
if (bci == BCI_VTABLE_RECEIVER) {
vtable_class_id = resolveVTableReceiverCached((void *)method);
vtable_class_id = resolveVTableReceiverCached((void *)method_id);
}

if (method == nullptr) {
if (method_id == nullptr) {
key = MethodMap::makeKey(UNKNOWN);
} else if (bci == BCI_ERROR || bci == BCI_NATIVE_FRAME) {
key = MethodMap::makeKey(frame.native_function_name);
Expand All @@ -511,7 +517,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];
Expand All @@ -522,12 +528,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) {
Expand Down Expand Up @@ -575,7 +581,7 @@ MethodInfo *Lookup::resolveMethod(ASGCT_CallFrame &frame) {
mi->_type = FRAME_NATIVE;
mi->_is_entry = false;
} else {
fillJavaMethodInfo(mi, method, first_time);
fillJavaMethodInfo(mi, method_id, first_time);
}
}

Expand Down
15 changes: 12 additions & 3 deletions ddprof-lib/src/main/cpp/frame.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#ifndef _FRAME_H
#define _FRAME_H

#include <cassert>
#include "vmEntry.h"

enum FrameTypeId {
FRAME_INTERPRETED = 0,
FRAME_JIT_COMPILED = 1,
Expand All @@ -14,9 +17,11 @@ enum FrameTypeId {
};

class FrameType {
static constexpr int RAW_POIINTER_MASK = 1 << 30;
public:
static inline int encode(int type, int bci) {
return (1 << 24) | (type << 25) | (bci & 0xffffff);
static inline int encode(int type, int bci, bool rawPointer = false) {
assert((!rawPointer || VM::isHotspot()) && "Raw pointer is only valid for hotspot");
return (1 << 24) | (type << 25) | (bci & 0xffffff) | (rawPointer ? RAW_POIINTER_MASK : 0);
}

static inline FrameTypeId decode(int bci) {
Expand All @@ -25,9 +30,13 @@ class FrameType {
return FRAME_JIT_COMPILED;
}
// Clamp to valid FrameTypeId range to defend against corrupted values
int raw_type = bci >> 25;
int raw_type = (bci & ~ RAW_POIINTER_MASK) >> 25;
return (FrameTypeId)(raw_type <= FRAME_TYPE_MAX ? raw_type : FRAME_TYPE_MAX);
}

static inline bool isRawPointer(int bci) {
return bci > 0 && (bci & RAW_POIINTER_MASK) != 0;
}
};

#endif // _FRAME_H
181 changes: 170 additions & 11 deletions ddprof-lib/src/main/cpp/hotspot/hotspotSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
#include "hotspot/jitCodeCache.h"
#include "hotspot/vmStructs.inline.h"
#include "jvmSupport.h"
#include "profiler.h"
#include "guards.h"
#include "stackWalker.inline.h"
#include "frames.h"
Expand All @@ -36,7 +35,7 @@ static jmethodID getMethodId(VMMethod* method) {
&& SafeAccess::isReadableRange(method, VMMethod::type_size())) {
return method->validatedId();
}
return NULL;
return JMETHODID_NOT_WALKABLE;
}

/**
Expand Down Expand Up @@ -118,6 +117,12 @@ 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) {
assert(method != nullptr);
frame.bci = FrameType::encode(type, bci, true /*raw method pointer*/);
frame.method = static_cast<const void*>(method);
}

static ucontext_t empty_ucontext{};

#ifdef NDEBUG
Expand Down Expand Up @@ -410,13 +415,16 @@ __attribute__((no_sanitize("address"))) int HotspotSupport::walkVM(void* ucontex
if (is_plausible_interpreter_frame) {
VMMethod* method = ((VMMethod**)fp)[InterpreterFrame::method_offset];
jmethodID method_id = getMethodId(method);
if (method_id != NULL) {
if (method_id != JMETHODID_NOT_WALKABLE) {
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, bci, method);
}
sp = ((uintptr_t*)fp)[InterpreterFrame::sender_sp_offset];
pc = stripPointer(((void**)fp)[FRAME_PC_SLOT]);
fp = *(uintptr_t*)fp;
Expand All @@ -427,10 +435,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 != JMETHODID_NOT_WALKABLE) {
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();
Expand Down Expand Up @@ -465,7 +476,14 @@ __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 = nm->method();
jmethodID method_id = method->id();
if (method_id != JMETHODID_NOT_WALKABLE && method_id != nullptr) {
fillFrame(frames[depth++], type, 0, nm->method()->id());
} else {
fillFrame(frames[depth++], type, 0, method);
}

if (nm->isFrameCompleteAt(pc)) {
if (depth == 1 && frame.unwindEpilogue(nm, (uintptr_t&)pc, sp, fp)) {
Expand All @@ -482,7 +500,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 = method->id();
if (method_id != JMETHODID_NOT_WALKABLE && method_id != nullptr) {
fillFrame(frames[depth++], type, scope.bci(), method_id);
} else {
fillFrame(frames[depth++], type, scope.bci(), method);
}
} while (scope_offset > 0 && depth < max_depth);
}

Expand Down Expand Up @@ -812,7 +836,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 != JMETHODID_NOT_WALKABLE && method_id != nullptr) {
fillFrame(frames[depth++], FRAME_INTERPRETED, bci, method_id);
} else {
fillFrame(frames[depth++], FRAME_INTERPRETED, bci, method);
}
sp = ((uintptr_t*)anchor_fp)[InterpreterFrame::sender_sp_offset];
pc = stripPointer(((void**)anchor_fp)[FRAME_PC_SLOT]);
fp = *(uintptr_t*)anchor_fp;
Expand Down Expand Up @@ -1150,3 +1178,134 @@ 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) {
// 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();
}
}
}
}

constexpr const char* LAMBDA_PREFIX = "Ljava/lang/invoke/LambdaForm$";
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;
}

bool HotspotSupport::loadMethodIDsImpl(jvmtiEnv *jvmti, JNIEnv *jni, jclass klass) {
jobject cl;
// 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 = nullptr;
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);
}
}
if (cl != nullptr) {
jni->DeleteLocalRef(cl);
}
patchClassLoaderData(jni, klass);
return JVMSupport::loadMethodIDsImpl(jvmti, jni, klass);
}

jmethodID HotspotSupport::resolve(const void* method) {
assert(VM::isHotspot());
assert(method != nullptr);
VMMethod* vm_method = VMMethod::cast_or_null(method);
if (vm_method == nullptr) {
return nullptr;
}

// May have been populated by following code or JMETHODID_NOT_WALKABLE
jmethodID method_id = vm_method->validatedId();
if (method_id != nullptr && method_id != JMETHODID_NOT_WALKABLE) {
return method_id;
}

VMConstMethod* const_method = vm_method->constMethod_or_null();
if (const_method == nullptr) {
return nullptr;
}

VMConstantPool* const_pool = const_method->constants_or_null();
if (const_pool == nullptr) {
return nullptr;
}

VMSymbol* name_sym = const_method->name();
VMSymbol* sig_sym = const_method->signature();
VMKlass* klass = const_pool->holder_or_null();
if (klass == nullptr) {
return nullptr;
}

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);
int klass_name_len = klass_sym->length();
char* klass_name = (char*)malloc(klass_name_len + 1);
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);

return method_id;
}
9 changes: 9 additions & 0 deletions ddprof-lib/src/main/cpp/hotspot/hotspotSupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@

#include "hotspot/hotspotStackFrame.h"
#include "hotspot/jitCodeCache.h"
#include "profiler.h"
#include "stackFrame.h"
#include "stackWalker.h"

#include <jni.h>
#include <jvmti.h>

class ProfiledThread;

class HotspotSupport {
friend class JVMSupport;

private:
static int walkVM(void* ucontext, ASGCT_CallFrame* frames, int max_depth,
StackWalkFeatures features, EventType event_type,
Expand All @@ -27,6 +33,7 @@ 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);
Expand All @@ -37,6 +44,8 @@ class HotspotSupport {
static inline bool isJitCode(const void* p) {
return JitCodeCache::isJitCode(p);
}

static jmethodID resolve(const void* method);
};

#endif // _HOTSPOT_HOTSPOTSUPPORT_H
Loading
Loading