blob: 72f6a5e019b575ab360d6c0eaa16a52ca5cfbbe0 [file] [log] [blame]
/*
* Copyright (C) 2019 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 "app_inspection_service.h"
#include <functional>
#include <unordered_map>
#include "agent/jvmti_helper.h"
#include "app_inspection_transform.h"
#include "slicer/reader.h"
#include "slicer/writer.h"
#include "utils/device_info.h"
#include "utils/log.h"
using namespace profiler;
namespace app_inspection {
AppInspectionService* AppInspectionService::create(JNIEnv* env) {
JavaVM* vm;
int error = env->GetJavaVM(&vm);
if (error != 0) {
Log::E(Log::Tag::APPINSPECT,
"Failed to get JavaVM instance for AppInspectionService with error "
"code: %d",
error);
return nullptr;
}
// This will attach the current thread to the vm, otherwise
// CreateJvmtiEnv(vm) below will return JNI_EDETACHED error code.
GetThreadLocalJNI(vm);
// Create a stand-alone jvmtiEnv to avoid any callback conflicts
// with other profilers' agents.
jvmtiEnv* jvmti = CreateJvmtiEnv(vm);
if (jvmti == nullptr) {
Log::E(Log::Tag::APPINSPECT,
"Failed to initialize JVMTI env for AppInspectionService");
return nullptr;
}
auto service = new AppInspectionService(jvmti);
service->Initialize();
return service;
}
AppInspectionService::AppInspectionService(jvmtiEnv* jvmti)
: jvmti_(jvmti), next_tag_(1) {}
// Used on devices with API level < 29
// Names of HeapIterationCallback and HeapObjectCallback are unfortunately
// similar, but they mirror names in jvmti APIs
static jint JNICALL HeapIterationCallback(jlong class_tag, jlong size,
jlong* tag_ptr, jint length,
void* user_data) {
jlong tag = *(reinterpret_cast<jlong*>(user_data));
*tag_ptr = tag;
return 0;
}
bool AppInspectionService::tagClassInstancesO(JNIEnv* jni, jclass clazz,
jlong tag) {
jvmtiHeapCallbacks heap_callbacks;
jint count;
jclass* classes;
if (CheckJvmtiError(jvmti_, jvmti_->GetLoadedClasses(&count, &classes))) {
return true;
}
memset(&heap_callbacks, 0, sizeof(heap_callbacks));
heap_callbacks.heap_iteration_callback =
reinterpret_cast<decltype(heap_callbacks.heap_iteration_callback)>(
HeapIterationCallback);
// unlike IterateOverInstancesOfClass, that is available only on Q and newer,
// IterateThroughHeap doesn't include subclasses
// of the specfied class, so have to manually search for subclasses.
bool error = false;
for (int i = 0; (i < count) && !error; ++i) {
if (jni->IsAssignableFrom(classes[i], clazz)) {
error = CheckJvmtiError(
jvmti_,
jvmti_->IterateThroughHeap(0, classes[i], &heap_callbacks, &tag));
}
}
jvmti_->Deallocate((unsigned char*)classes);
return error;
}
// Used on devices with API level >= 29
// Names of HeapIterationCallback and HeapObjectCallback are unfortunately
// similar, but they mirror names in jvmti APIs
static jvmtiIterationControl JNICALL HeapObjectCallback(jlong class_tag,
jlong size,
jlong* tag_ptr,
void* user_data) {
jlong tag = *(reinterpret_cast<jlong*>(user_data));
*tag_ptr = tag;
return JVMTI_ITERATION_CONTINUE;
}
bool AppInspectionService::tagClassInstancesQ(jclass clazz, jlong tag) {
return CheckJvmtiError(
jvmti_, jvmti_->IterateOverInstancesOfClass(
clazz, JVMTI_HEAP_OBJECT_EITHER, HeapObjectCallback, &tag));
}
jobjectArray AppInspectionService::FindInstances(JNIEnv* jni, jclass clazz) {
if (jni->IsSameObject(clazz, jni->FindClass("java/lang/Class"))) {
// Special-case handling for the Class object. Internally, ART creates many
// dummy Class objects that we don't care about. Calling GetLoadedClasses
// returns us the list of the real Class instances.
jint count;
jclass* classes;
if (CheckJvmtiError(jvmti_, jvmti_->GetLoadedClasses(&count, &classes))) {
return jni->NewObjectArray(0, clazz, NULL);
}
auto result = jni->NewObjectArray(count, clazz, NULL);
for (int i = 0; i < count; ++i) {
jni->SetObjectArrayElement(result, i, (jobject)classes[i]);
}
jvmti_->Deallocate((unsigned char*)classes);
return result;
}
jlong tag = next_tag_++;
bool error = DeviceInfo::feature_level() < DeviceInfo::Q
? tagClassInstancesO(jni, clazz, tag)
: tagClassInstancesQ(clazz, tag);
if (error) {
return jni->NewObjectArray(0, clazz, NULL);
}
jint count;
jobject* instances;
if (CheckJvmtiError(jvmti_, jvmti_->GetObjectsWithTags(1, &tag, &count,
&instances, NULL))) {
return jni->NewObjectArray(0, clazz, NULL);
}
auto result = jni->NewObjectArray(count, clazz, NULL);
for (int i = 0; i < count; ++i) {
jni->SetObjectArrayElement(result, i, instances[i]);
}
jvmti_->Deallocate((unsigned char*)instances);
return result;
}
class JvmtiAllocator : public dex::Writer::Allocator {
public:
JvmtiAllocator(jvmtiEnv* jvmti_env) : jvmti_env_(jvmti_env) {}
virtual void* Allocate(size_t size) {
return profiler::Allocate(jvmti_env_, size);
}
virtual void Free(void* ptr) { profiler::Deallocate(jvmti_env_, ptr); }
private:
jvmtiEnv* jvmti_env_;
};
std::unordered_map<std::string, AppInspectionTransform*>*
GetAppInspectionTransforms() {
static auto* transformations =
new std::unordered_map<std::string, AppInspectionTransform*>();
return transformations;
}
// ClassPrepare event callback to invoke transformation of selected classes.
// In pre-P, this saves expensive OnClassFileLoaded calls for other classes.
void AppInspectionService::OnClassPrepare(jvmtiEnv* jvmti_env, JNIEnv* jni_env,
jthread thread, jclass klass) {
char* sig_mutf8;
jvmti_env->GetClassSignature(klass, &sig_mutf8, nullptr);
auto class_transforms = GetAppInspectionTransforms();
if (class_transforms->find(sig_mutf8) != class_transforms->end()) {
CheckJvmtiError(
jvmti_env, jvmti_env->SetEventNotificationMode(
JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, thread));
CheckJvmtiError(jvmti_env, jvmti_env->RetransformClasses(1, &klass));
CheckJvmtiError(jvmti_env, jvmti_env->SetEventNotificationMode(
JVMTI_DISABLE,
JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, thread));
}
if (sig_mutf8 != nullptr) {
jvmti_env->Deallocate((unsigned char*)sig_mutf8);
}
}
void AppInspectionService::OnClassFileLoaded(
jvmtiEnv* jvmti_env, JNIEnv* jni_env, jclass class_being_redefined,
jobject loader, const char* name, jobject protection_domain,
jint class_data_len, const unsigned char* class_data,
jint* new_class_data_len, unsigned char** new_class_data) {
// The tooling interface will specify class names like "java/net/URL"
// however, in .dex these classes are stored using the "Ljava/net/URL;"
// format.
std::string desc = "L" + std::string(name) + ";";
auto class_transforms = GetAppInspectionTransforms();
auto transform = class_transforms->find(desc);
if (transform == class_transforms->end()) return;
dex::Reader reader(class_data, class_data_len);
auto class_index = reader.FindClassIndex(desc.c_str());
if (class_index == dex::kNoIndex) {
Log::V(Log::Tag::APPINSPECT, "Could not find class index for %s", name);
return;
}
reader.CreateClassIr(class_index);
auto dex_ir = reader.GetIr();
transform->second->Apply(dex_ir);
size_t new_image_size = 0;
dex::u1* new_image = nullptr;
dex::Writer writer(dex_ir);
JvmtiAllocator allocator(jvmti_env);
new_image = writer.CreateImage(&allocator, &new_image_size);
*new_class_data_len = new_image_size;
*new_class_data = new_image;
}
void AppInspectionService::Initialize() {
SetAllCapabilities(jvmti_);
jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.ClassFileLoadHook = OnClassFileLoaded;
callbacks.ClassPrepare = OnClassPrepare;
CheckJvmtiError(jvmti_,
jvmti_->SetEventCallbacks(&callbacks, sizeof(callbacks)));
// Before P ClassFileLoadHook has significant performance overhead so we
// only enable the hook during retransformation (on agent attach and class
// prepare). For P+ we want to keep the hook events always on to support
// multiple retransforming agents (and therefore don't need to perform
// retransformation on class prepare).
bool filter_class_load_hook = DeviceInfo::feature_level() < DeviceInfo::P;
SetEventNotification(jvmti_,
filter_class_load_hook ? JVMTI_ENABLE : JVMTI_DISABLE,
JVMTI_EVENT_CLASS_PREPARE);
SetEventNotification(jvmti_,
filter_class_load_hook ? JVMTI_DISABLE : JVMTI_ENABLE,
JVMTI_EVENT_CLASS_FILE_LOAD_HOOK);
}
void AppInspectionService::AddTransform(JNIEnv* jni,
const std::string& class_name,
const std::string& method_name,
const std::string& signature,
bool is_entry) {
auto class_transforms = GetAppInspectionTransforms();
auto transform_iter = class_transforms->find(class_name);
AppInspectionTransform* app_transform;
if (transform_iter == class_transforms->end()) {
app_transform = new AppInspectionTransform(class_name.c_str());
class_transforms->insert({class_name, app_transform});
} else {
app_transform = transform_iter->second;
}
app_transform->AddTransform(class_name.c_str(), method_name.c_str(),
signature.c_str(), is_entry);
jclass found_class = nullptr;
jint class_count;
jclass* loaded_classes;
char* sig_mutf8;
jvmti_->GetLoadedClasses(&class_count, &loaded_classes);
for (int i = 0; i < class_count; ++i) {
jvmti_->GetClassSignature(loaded_classes[i], &sig_mutf8, nullptr);
if (sig_mutf8 == class_name) {
found_class = loaded_classes[i];
}
if (sig_mutf8 != nullptr) {
jvmti_->Deallocate((unsigned char*)sig_mutf8);
}
}
if (found_class != nullptr) {
jthread thread = nullptr;
jvmti_->GetCurrentThread(&thread);
// Class file load hooks are automatically managed on P (and newer) devices
bool manually_toggle_load_hook =
DeviceInfo::feature_level() < DeviceInfo::P;
if (manually_toggle_load_hook) {
CheckJvmtiError(
jvmti_, jvmti_->SetEventNotificationMode(
JVMTI_ENABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, thread));
}
CheckJvmtiError(jvmti_, jvmti_->RetransformClasses(1, &found_class));
if (manually_toggle_load_hook) {
CheckJvmtiError(
jvmti_, jvmti_->SetEventNotificationMode(
JVMTI_DISABLE, JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, thread));
}
if (thread != nullptr) {
jni->DeleteLocalRef(thread);
}
}
for (int i = 0; i < class_count; ++i) {
jni->DeleteLocalRef(loaded_classes[i]);
}
jvmti_->Deallocate(reinterpret_cast<unsigned char*>(loaded_classes));
}
} // namespace app_inspection