blob: 7b4f39ae23e2fd41336a54e2d50ef73876e66fd1 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "jvmti_helper.h"
#include <iomanip>
#include <sstream>
#include "scoped_local_ref.h"
#include "stdlib.h"
#include "utils/device_info.h"
#include "utils/log.h"
namespace profiler {
jvmtiEnv* CreateJvmtiEnv(JavaVM* vm) {
jvmtiEnv* jvmti_env;
jint jvmti_flag = JVMTI_VERSION_1_2;
if (!DeviceInfo::is_user_build() &&
DeviceInfo::api_level() >= DeviceInfo::P) {
// On non-user-build devices (such as userdebug build), we use flag
// |kArtTiVersion| as defined in //art/openjdkjvmti/art_jvmti.h to support
// non-debuggable apps. The flag was introduced in Android P (API 28).
jvmti_flag = JVMTI_VERSION_1_2 | 0x40000000;
}
jint result = vm->GetEnv((void**)&jvmti_env, jvmti_flag);
if (result != JNI_OK) {
Log::E("Error creating jvmti environment.");
return nullptr;
}
return jvmti_env;
}
bool CheckJvmtiError(jvmtiEnv* jvmti, jvmtiError err_num,
const std::string& message) {
if (err_num == JVMTI_ERROR_NONE) {
return false;
}
char* error = nullptr;
jvmti->GetErrorName(err_num, &error);
Log::E("JVMTI error: %d(%s) %s", err_num,
error == nullptr ? "Unknown" : error, message.c_str());
Deallocate(jvmti, error);
return true;
}
void SetAllCapabilities(jvmtiEnv* jvmti) {
jvmtiCapabilities caps;
jvmtiError error;
error = jvmti->GetPotentialCapabilities(&caps);
CheckJvmtiError(jvmti, error);
error = jvmti->AddCapabilities(&caps);
CheckJvmtiError(jvmti, error);
}
void SetEventNotification(jvmtiEnv* jvmti, jvmtiEventMode mode,
jvmtiEvent event_type) {
jvmtiError err = jvmti->SetEventNotificationMode(mode, event_type, nullptr);
CheckJvmtiError(jvmti, err);
}
JNIEnv* GetThreadLocalJNI(JavaVM* vm) {
JNIEnv* jni;
jint result =
vm->GetEnv((void**)&jni, JNI_VERSION_1_6); // ndk is only up to 1.6.
if (result == JNI_EDETACHED) {
Log::V("JNIEnv not attached");
#ifdef __ANDROID__
if (vm->AttachCurrentThread(&jni, nullptr) != 0) {
#else
// TODO get rid of this. Currently bazel built with the jdk's jni headers
// which has a slightly different signature. Once bazel has switched to
// platform-dependent headers we will remove this.
if (vm->AttachCurrentThread((void**)&jni, nullptr) != 0) {
#endif
Log::V("Failed to attach JNIEnv");
return nullptr;
}
}
return jni;
}
jthread AllocateJavaThread(jvmtiEnv* jvmti, JNIEnv* jni) {
ScopedLocalRef<jclass> klass(jni, jni->FindClass("java/lang/Thread"));
if (klass.get() == nullptr) {
Log::E("Failed to find Thread class.");
}
jmethodID method = jni->GetMethodID(klass.get(), "<init>", "()V");
if (method == nullptr) {
Log::E("Failed to find Thread.<init> method.");
}
jthread result = jni->NewObject(klass.get(), method);
if (result == nullptr) {
Log::E("Failed to create new Thread object.");
}
return result;
}
int32_t GetClassLoaderId(jvmtiEnv* jvmti, JNIEnv* jni, jclass klass) {
jint klass_loader_id = -1;
jobject klass_loader;
jvmtiError error = jvmti->GetClassLoader(klass, &klass_loader);
CheckJvmtiError(jvmti, error);
if (klass_loader != nullptr) {
ScopedLocalRef<jobject> scoped_klass_loader(jni, klass_loader);
error =
jvmti->GetObjectHashCode(scoped_klass_loader.get(), &klass_loader_id);
CheckJvmtiError(jvmti, error);
}
return klass_loader_id;
}
void* Allocate(jvmtiEnv* jvmti, jlong size) {
unsigned char* alloc = nullptr;
jvmtiError err = jvmti->Allocate(size, &alloc);
CheckJvmtiError(jvmti, err);
return (void*)alloc;
}
void Deallocate(jvmtiEnv* jvmti, void* ptr) {
if (ptr == nullptr) {
return;
}
jvmtiError err = jvmti->Deallocate((unsigned char*)ptr);
CheckJvmtiError(jvmti, err);
}
std::string GetMangledName(const char* klass_signature,
const char* method_name) {
std::string klass_string(klass_signature);
std::string method_string(method_name);
std::string mangled("Java_");
mangled.append(MangleForJni(klass_string));
mangled.append("_");
mangled.append(MangleForJni(method_string));
return mangled;
}
std::string MangleForJni(const std::string& mutf8) {
std::stringstream ss;
const char* char_ptr = &mutf8[0];
const char* end = char_ptr + mutf8.length();
while (char_ptr < end) {
uint16_t ch = GetUtf16FromMutf8(&char_ptr);
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') ||
(ch >= '0' && ch <= '9')) {
ss << (char)ch;
} else if (ch == '.' || ch == '/') {
ss << "_";
} else if (ch == '_') {
ss << "_1";
} else if (ch == ';') {
ss << "_2";
} else if (ch == '[') {
ss << "_3";
} else {
ss << "_0" << std::setfill('0') << std::setw(4) << std::hex << ch;
}
}
return ss.str();
}
uint16_t GetUtf16FromMutf8(const char** mutf8_data) {
const uint8_t one = *(*mutf8_data)++;
if ((one & 0x80) == 0) {
// one-byte encoding
return one;
}
const uint8_t two = *(*mutf8_data)++;
if ((one & 0x20) == 0) {
// two-byte encoding
return ((one & 0x1f) << 6) | (two & 0x3f);
}
const uint8_t three = *(*mutf8_data)++;
if ((one & 0x10) == 0) {
// three-byte encoding
return ((one & 0x0f) << 12) | ((two & 0x3f) << 6) | (three & 0x3f);
}
// TODO: Handle 6-byte encoding (high/low surrogate pairs)
// In practice, we most likely will not need this as we don't have any method
// names using anything outside the basic multilingual plane.
*mutf8_data += 3;
return 0;
}
} // namespace profiler