blob: e6c7f8693c6b4eb99efc1c166eaf4088952b58a1 [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.h"
#include <dlfcn.h>
#include <unistd.h>
#include <algorithm>
#include <cassert>
#include <string>
#include <unordered_map>
#include "agent/agent.h"
#include "jvmti_helper.h"
#include "memory/memory_tracking_env.h"
#include "scoped_local_ref.h"
#include "utils/config.h"
#include "utils/log.h"
#include "slicer/reader.h"
#include "slicer/writer.h"
#include "transform/android_activitythread_transform.h"
#include "transform/android_alarmmanager_listenerwrapper_transform.h"
#include "transform/android_alarmmanager_transform.h"
#include "transform/android_debug_transform.h"
#include "transform/android_fragment_transform.h"
#include "transform/android_instrumentation_transform.h"
#include "transform/android_intentservice_transform.h"
#include "transform/android_jobschedulerimpl_transform.h"
#include "transform/android_jobservice_transform.h"
#include "transform/android_jobserviceengine_jobhandler_transform.h"
#include "transform/android_locationmanager_listenertransport_transform.h"
#include "transform/android_locationmanager_transform.h"
#include "transform/android_pendingintent_transform.h"
#include "transform/android_powermanager_transform.h"
#include "transform/android_powermanager_wakelock_transform.h"
#include "transform/androidx_fragment_transform.h"
#include "transform/gms_fusedlocationproviderclient_transform.h"
#include "transform/java_url_transform.h"
#include "transform/okhttp3_okhttpclient_transform.h"
#include "transform/okhttp_okhttpclient_transform.h"
#include "transform/transform.h"
using profiler::Agent;
using profiler::Log;
using profiler::MemoryTrackingEnv;
using profiler::ScopedLocalRef;
using profiler::proto::AgentConfig;
namespace profiler {
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, Transform*>* GetClassTransforms() {
static auto* transformations =
new std::unordered_map<std::string, Transform*>();
return transformations;
}
// Retrieve the app's data directory path
static std::string GetAppDataPath() {
Dl_info dl_info;
dladdr((void*)Agent_OnAttach, &dl_info);
std::string so_path(dl_info.dli_fname);
return so_path.substr(0, so_path.find_last_of('/') + 1);
}
// ClassPrepare event callback to invoke transformation of selected classes.
// In pre-P, this saves expensive OnClassFileLoaded calls for other classes.
void JNICALL OnClassPrepare(jvmtiEnv* jvmti_env, JNIEnv* jni_env,
jthread thread, jclass klass) {
char* sig_mutf8;
jvmti_env->GetClassSignature(klass, &sig_mutf8, nullptr);
auto class_transforms = GetClassTransforms();
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 JNICALL 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 = GetClassTransforms();
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("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;
Log::V("Transformed class: %s", name);
} // namespace profiler
void LoadDex(jvmtiEnv* jvmti, JNIEnv* jni) {
// Load in perfa.jar which should be in to data/data.
std::string agent_lib_path(GetAppDataPath());
agent_lib_path.append("perfa.jar");
jvmti->AddToBootstrapClassLoaderSearch(agent_lib_path.c_str());
}
// Populate the map of transforms we want to apply to different classes.
void RegisterTransforms(
const proto::AgentConfig& config,
std::unordered_map<std::string, Transform*>* transforms) {
transforms->insert({"Ljava/net/URL;", new JavaUrlTransform()});
transforms->insert({"Lokhttp3/OkHttpClient;", new Okhttp3ClientTransform()});
transforms->insert(
{"Lcom/squareup/okhttp/OkHttpClient;", new OkhttpClientTransform()});
if (config.cpu_api_tracing_enabled()) {
transforms->insert({"Landroid/os/Debug;", new AndroidDebugTransform()});
}
transforms->insert(
{"Landroid/support/v4/app/Fragment;", new AndroidFragmentTransform()});
transforms->insert(
{"Landroidx/fragment/app/Fragment;", new AndroidXFragmentTransform()});
if (config.energy_profiler_enabled()) {
transforms->insert({"Landroid/app/Instrumentation;",
new AndroidInstrumentationTransform()});
transforms->insert(
{"Landroid/app/ActivityThread;", new AndroidActivityThreadTransform()});
transforms->insert(
{"Landroid/app/AlarmManager;", new AndroidAlarmManagerTransform()});
transforms->insert({"Landroid/app/AlarmManager$ListenerWrapper;",
new AndroidAlarmManagerListenerWrapperTransform()});
transforms->insert(
{"Landroid/app/IntentService;", new AndroidIntentServiceTransform()});
transforms->insert({"Landroid/app/JobSchedulerImpl;",
new AndroidJobSchedulerImplTransform()});
transforms->insert(
{"Landroid/app/job/JobService;", new AndroidJobServiceTransform()});
transforms->insert({"Landroid/app/job/JobServiceEngine$JobHandler;",
new AndroidJobServiceEngineJobHandlerTransform()});
transforms->insert(
{"Landroid/app/PendingIntent;", new AndroidPendingIntentTransform()});
transforms->insert({"Landroid/location/LocationManager;",
new AndroidLocationManagerTransform()});
transforms->insert(
{"Landroid/location/LocationManager$ListenerTransport;",
new AndroidLocationManagerListenerTransportTransform()});
transforms->insert(
{"Landroid/os/PowerManager;", new AndroidPowerManagerTransform()});
transforms->insert({"Landroid/os/PowerManager$WakeLock;",
new AndroidPowerManagerWakeLockTransform()});
transforms->insert(
{"Lcom/google/android/gms/location/FusedLocationProviderClient;",
new GmsFusedLocationProviderClientTransform()});
}
}
void ProfilerInitializationWorker(jvmtiEnv* jvmti, JNIEnv* jni, void* ptr) {
proto::AgentConfig* config = static_cast<proto::AgentConfig*>(ptr);
jclass service =
jni->FindClass("com/android/tools/profiler/support/ProfilerService");
jmethodID initialize = jni->GetStaticMethodID(service, "initialize", "(Z)V");
bool log_live_alloc_count = config->mem_config().use_live_alloc();
jni->CallStaticVoidMethod(service, initialize, !log_live_alloc_count);
}
extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options,
void* reserved) {
jvmtiEnv* jvmti_env = CreateJvmtiEnv(vm);
if (jvmti_env == nullptr) {
return JNI_ERR;
}
if (options == nullptr) {
Log::E("Config file parameter was not specified");
return JNI_ERR;
}
SetAllCapabilities(jvmti_env);
// TODO: Update options to support more than one argument if needed.
static const auto* const config = new profiler::Config(options);
auto const& agent_config = config->GetAgentConfig();
Agent::Instance(config);
JNIEnv* jni_env = GetThreadLocalJNI(vm);
LoadDex(jvmti_env, jni_env);
auto class_transforms = GetClassTransforms();
RegisterTransforms(agent_config, class_transforms);
jvmtiEventCallbacks callbacks;
memset(&callbacks, 0, sizeof(callbacks));
callbacks.ClassFileLoadHook = OnClassFileLoaded;
callbacks.ClassPrepare = OnClassPrepare;
CheckJvmtiError(jvmti_env,
jvmti_env->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 = agent_config.android_feature_level() <= 27;
SetEventNotification(jvmti_env,
filter_class_load_hook ? JVMTI_ENABLE : JVMTI_DISABLE,
JVMTI_EVENT_CLASS_PREPARE);
SetEventNotification(jvmti_env,
filter_class_load_hook ? JVMTI_DISABLE : JVMTI_ENABLE,
JVMTI_EVENT_CLASS_FILE_LOAD_HOOK);
// Sample instrumentation
std::vector<jclass> classes;
jint class_count;
jclass* loaded_classes;
char* sig_mutf8;
jvmti_env->GetLoadedClasses(&class_count, &loaded_classes);
for (int i = 0; i < class_count; ++i) {
jvmti_env->GetClassSignature(loaded_classes[i], &sig_mutf8, nullptr);
if (class_transforms->find(sig_mutf8) != class_transforms->end()) {
classes.push_back(loaded_classes[i]);
}
if (sig_mutf8 != nullptr) {
jvmti_env->Deallocate((unsigned char*)sig_mutf8);
}
}
if (classes.size() > 0) {
jthread thread = nullptr;
jvmti_env->GetCurrentThread(&thread);
if (filter_class_load_hook) {
CheckJvmtiError(jvmti_env, jvmti_env->SetEventNotificationMode(
JVMTI_ENABLE,
JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, thread));
}
CheckJvmtiError(jvmti_env,
jvmti_env->RetransformClasses(classes.size(), &classes[0]));
if (filter_class_load_hook) {
CheckJvmtiError(jvmti_env, jvmti_env->SetEventNotificationMode(
JVMTI_DISABLE,
JVMTI_EVENT_CLASS_FILE_LOAD_HOOK, thread));
}
if (thread != nullptr) {
jni_env->DeleteLocalRef(thread);
}
}
for (int i = 0; i < class_count; ++i) {
jni_env->DeleteLocalRef(loaded_classes[i]);
}
jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(loaded_classes));
Agent::Instance().AddPerfdConnectedCallback([vm, &agent_config] {
// MemoryTackingEnv needs a connection to perfd, which may not be always the
// case. If we don't postpone until there is a connection, MemoryTackingEnv
// is going to busy-wait, so not allowing the application to finish
// initialization. This callback will be called each time perfd connects.
MemoryTrackingEnv::Instance(vm, agent_config.mem_config());
// Starts the heartbeat thread after MemoryTrackingEnv is fully initialized
// and has opened a grpc stream perfd. The order is important as a heartbeat
// will trigger Studio to start live allocation tracking.
Agent::Instance().StartHeartbeat();
// Perf-test currently waits on this message to determine that perfa is
// connected to perfd.
Log::V("Perfa connected to Perfd.");
});
// ProfilerService#Initialize depends on JNI native methods being auto-binded
// after the agent finishes attaching. Therefore we call initialize after
// the VM is unpaused to make sure the runtime can auto-find the JNI methods.
jvmti_env->RunAgentThread(AllocateJavaThread(jvmti_env, jni_env),
&ProfilerInitializationWorker, &agent_config,
JVMTI_THREAD_NORM_PRIORITY);
return JNI_OK;
}
} // namespace profiler