// Copyright (C) 2020 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "android-base/unique_fd.h" #include "nativehelper/scoped_local_ref.h" namespace simple_profile { static constexpr jint kArtTiVersion = JVMTI_VERSION_1_2 | 0x40000000; #define CHECK_JVMTI(a) CHECK_EQ(JVMTI_ERROR_NONE, a) struct DataDefinition { std::string_view class_name; std::string_view method_name; std::string_view method_descriptor; uint64_t count; }; std::ostream& operator<<(std::ostream& os, const DataDefinition& dd) { return os << "{\"class_name\":\"" << dd.class_name << "\",\"method_name\":\"" << dd.method_name << "\",\"method_descriptor\":\"" << dd.method_descriptor << "\",\"count\":" << dd.count << "}"; } class SimpleProfileData { public: SimpleProfileData( jvmtiEnv* env, std::string out_fd_name, int fd, bool dump_on_shutdown, bool dump_on_main_stop) : dump_id_(0), out_fd_name_(out_fd_name), out_fd_(fd), shutdown_(false), dump_on_shutdown_(dump_on_shutdown || dump_on_main_stop), dump_on_main_stop_(dump_on_main_stop) { CHECK_JVMTI(env->CreateRawMonitor("simple_profile_mon", &mon_)); method_counts_.reserve(10000); } void Dump(jvmtiEnv* jvmti); void Enter(jvmtiEnv* jvmti, JNIEnv* env, jmethodID meth); void RunDumpLoop(jvmtiEnv* jvmti, JNIEnv* env); static SimpleProfileData* GetProfileData(jvmtiEnv* env) { void* data; CHECK_JVMTI(env->GetEnvironmentLocalStorage(&data)); return static_cast(data); } void FinishInitialization(jvmtiEnv* jvmti, JNIEnv* jni, jthread cur); void Shutdown(jvmtiEnv* jvmti, JNIEnv* jni); private: void DoDump(jvmtiEnv* jvmti, JNIEnv* jni, std::unordered_map copy); jlong dump_id_; jrawMonitorID mon_; std::string out_fd_name_; int out_fd_; std::unordered_map method_counts_; bool shutdown_; bool dump_on_shutdown_; bool dump_on_main_stop_; }; struct ScopedJvmtiMonitor { public: ScopedJvmtiMonitor(jvmtiEnv* env, jrawMonitorID mon) : jvmti_(env), mon_(mon) { CHECK_JVMTI(jvmti_->RawMonitorEnter(mon_)); } ~ScopedJvmtiMonitor() { CHECK_JVMTI(jvmti_->RawMonitorExit(mon_)); } void Notify() { CHECK_JVMTI(jvmti_->RawMonitorNotifyAll(mon_)); } void Wait() { CHECK_JVMTI(jvmti_->RawMonitorWait(mon_, 0)); } private: jvmtiEnv* jvmti_; jrawMonitorID mon_; }; void SimpleProfileData::Enter(jvmtiEnv* jvmti, JNIEnv* env, jmethodID meth) { ScopedJvmtiMonitor sjm(jvmti, mon_); // Keep all classes from being unloaded to allow us to know we can get the method info later. jclass tmp; CHECK_JVMTI(jvmti->GetMethodDeclaringClass(meth, &tmp)); ScopedLocalRef klass(env, tmp); jlong tag; CHECK_JVMTI(jvmti->GetTag(klass.get(), &tag)); if (tag == 0) { CHECK_JVMTI(jvmti->SetTag(klass.get(), 1u)); env->NewGlobalRef(klass.get()); } method_counts_.insert({ meth, 0u }).first->second++; } void SimpleProfileData::Dump(jvmtiEnv* jvmti) { ScopedJvmtiMonitor sjm(jvmti, mon_); dump_id_++; sjm.Notify(); } void SimpleProfileData::RunDumpLoop(jvmtiEnv* jvmti, JNIEnv* env) { jlong current_id = 0; do { std::unordered_map copy; { ScopedJvmtiMonitor sjm(jvmti, mon_); while (!shutdown_ && current_id == dump_id_) { sjm.Wait(); } if (shutdown_) { break; } current_id = dump_id_; copy = method_counts_; } DoDump(jvmti, env, std::move(copy)); } while (true); } void SimpleProfileData::Shutdown(jvmtiEnv* jvmti, JNIEnv* jni) { std::unordered_map copy; { ScopedJvmtiMonitor sjm(jvmti, mon_); if (shutdown_) { return; } shutdown_ = true; copy = method_counts_; sjm.Notify(); } if (dump_on_shutdown_) { DoDump(jvmti, jni, std::move(copy)); } } void SimpleProfileData::FinishInitialization(jvmtiEnv* jvmti, JNIEnv* env, jthread cur) { // Finish up startup. // Create a Thread object. std::string name = std::string("profile dump Thread: ") + this->out_fd_name_; ScopedLocalRef thread_name(env, env->NewStringUTF(name.c_str())); CHECK_NE(thread_name.get(), nullptr); ScopedLocalRef thread_klass(env, env->FindClass("java/lang/Thread")); CHECK_NE(thread_klass.get(), nullptr); ScopedLocalRef thread(env, env->AllocObject(thread_klass.get())); CHECK_NE(thread.get(), nullptr); jmethodID initID = env->GetMethodID(thread_klass.get(), "", "(Ljava/lang/String;)V"); jmethodID setDaemonId = env->GetMethodID(thread_klass.get(), "setDaemon", "(Z)V"); CHECK_NE(initID, nullptr); CHECK_NE(setDaemonId, nullptr); env->CallNonvirtualVoidMethod(thread.get(), thread_klass.get(), initID, thread_name.get()); env->CallVoidMethod(thread.get(), setDaemonId, JNI_TRUE); CHECK(!env->ExceptionCheck()); CHECK_JVMTI(jvmti->RunAgentThread( thread.get(), [](jvmtiEnv* jvmti, JNIEnv* jni, void* unused_data ATTRIBUTE_UNUSED) { SimpleProfileData* data = SimpleProfileData::GetProfileData(jvmti); data->RunDumpLoop(jvmti, jni); }, nullptr, JVMTI_THREAD_NORM_PRIORITY)); CHECK_JVMTI(jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, nullptr)); CHECK_JVMTI( jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_DATA_DUMP_REQUEST, nullptr)); if (dump_on_main_stop_) { CHECK_JVMTI(jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_THREAD_END, cur)); } CHECK_JVMTI(jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, nullptr)); } class ScopedClassInfo { public: ScopedClassInfo(jvmtiEnv* jvmti_env, jclass c) : jvmti_env_(jvmti_env), class_(c), name_(nullptr), generic_(nullptr) {} ~ScopedClassInfo() { if (class_ != nullptr) { jvmti_env_->Deallocate(reinterpret_cast(name_)); jvmti_env_->Deallocate(reinterpret_cast(generic_)); } } bool Init() { if (class_ == nullptr) { name_ = const_cast(""); generic_ = const_cast(""); return true; } else { return jvmti_env_->GetClassSignature(class_, &name_, &generic_) == JVMTI_ERROR_NONE; } } jclass GetClass() const { return class_; } const char* GetName() const { return name_; } // Generic type parameters, whatever is in the <> for a class const char* GetGeneric() const { return generic_; } private: jvmtiEnv* jvmti_env_; jclass class_; char* name_; char* generic_; }; class ScopedMethodInfo { public: ScopedMethodInfo(jvmtiEnv* jvmti_env, JNIEnv* env, jmethodID method) : jvmti_env_(jvmti_env), env_(env), method_(method), declaring_class_(nullptr), class_info_(nullptr), name_(nullptr), signature_(nullptr), generic_(nullptr) {} ~ScopedMethodInfo() { env_->DeleteLocalRef(declaring_class_); jvmti_env_->Deallocate(reinterpret_cast(name_)); jvmti_env_->Deallocate(reinterpret_cast(signature_)); jvmti_env_->Deallocate(reinterpret_cast(generic_)); } bool Init() { if (jvmti_env_->GetMethodDeclaringClass(method_, &declaring_class_) != JVMTI_ERROR_NONE) { LOG(INFO) << "No decl"; return false; } class_info_.reset(new ScopedClassInfo(jvmti_env_, declaring_class_)); return class_info_->Init() && (jvmti_env_->GetMethodName(method_, &name_, &signature_, &generic_) == JVMTI_ERROR_NONE); } const ScopedClassInfo& GetDeclaringClassInfo() const { return *class_info_; } jclass GetDeclaringClass() const { return declaring_class_; } const char* GetName() const { return name_; } const char* GetSignature() const { return signature_; } const char* GetGeneric() const { return generic_; } private: jvmtiEnv* jvmti_env_; JNIEnv* env_; jmethodID method_; jclass declaring_class_; std::unique_ptr class_info_; char* name_; char* signature_; char* generic_; friend std::ostream& operator<<(std::ostream& os, ScopedMethodInfo const& method); }; std::ostream& operator<<(std::ostream& os, const ScopedMethodInfo* method) { return os << *method; } std::ostream& operator<<(std::ostream& os, ScopedMethodInfo const& method) { return os << method.GetDeclaringClassInfo().GetName() << "->" << method.GetName() << method.GetSignature(); } void SimpleProfileData::DoDump(jvmtiEnv* jvmti, JNIEnv* jni, std::unordered_map copy) { std::ostringstream oss; oss << "["; bool is_first = true; for (auto [meth, cnt] : copy) { ScopedMethodInfo smi(jvmti, jni, meth); if (!smi.Init()) { continue; } if (!is_first) { oss << "," << std::endl; } is_first = false; oss << DataDefinition { .class_name = smi.GetDeclaringClassInfo().GetName(), .method_name = smi.GetName(), .method_descriptor = smi.GetSignature(), .count = cnt, }; } oss << "]"; CHECK_GE(TEMP_FAILURE_RETRY(write(out_fd_, oss.str().c_str(), oss.str().size())), 0) << strerror(errno) << out_fd_ << " " << out_fd_name_; fsync(out_fd_); } static void DataDumpCb(jvmtiEnv* jvmti_env) { SimpleProfileData* data = SimpleProfileData::GetProfileData(jvmti_env); data->Dump(jvmti_env); } static void MethodEntryCB(jvmtiEnv* jvmti_env, JNIEnv* env, jthread thread ATTRIBUTE_UNUSED, jmethodID method) { SimpleProfileData* data = SimpleProfileData::GetProfileData(jvmti_env); data->Enter(jvmti_env, env, method); } static void VMInitCB(jvmtiEnv* jvmti, JNIEnv* env, jthread thr) { SimpleProfileData* data = SimpleProfileData::GetProfileData(jvmti); data->FinishInitialization(jvmti, env, thr); } static void VMDeathCB(jvmtiEnv* jvmti, JNIEnv* env) { SimpleProfileData* data = SimpleProfileData::GetProfileData(jvmti); data->Shutdown(jvmti, env); } // Fills targets with the breakpoints to add. // Lname/of/Klass;->methodName(Lsig/of/Method)Lreturn/Type;@location,<...> static bool ParseArgs(const std::string& start_options, /*out*/ std::string* fd_name, /*out*/ int* fd, /*out*/ bool* dump_on_shutdown, /*out*/ bool* dump_on_main_stop) { std::istringstream iss(start_options); std::string item; *dump_on_main_stop = false; *dump_on_shutdown = false; bool has_fd = false; while (std::getline(iss, item, ',')) { if (item == "dump_on_shutdown") { *dump_on_shutdown = true; } else if (item == "dump_on_main_stop") { *dump_on_main_stop = true; } else if (has_fd) { LOG(ERROR) << "Too many args!"; return false; } else { has_fd = true; *fd_name = item; *fd = TEMP_FAILURE_RETRY(open(fd_name->c_str(), O_WRONLY | O_CLOEXEC | O_CREAT, 00666)); CHECK_GE(*fd, 0) << strerror(errno); } } return has_fd; } enum class StartType { OnAttach, OnLoad, }; static jint SetupJvmtiEnv(JavaVM* vm, jvmtiEnv** jvmti) { jint res = 0; res = vm->GetEnv(reinterpret_cast(jvmti), JVMTI_VERSION_1_1); if (res != JNI_OK || *jvmti == nullptr) { LOG(ERROR) << "Unable to access JVMTI, error code " << res; return vm->GetEnv(reinterpret_cast(jvmti), kArtTiVersion); } return res; } static jint AgentStart(StartType start, JavaVM* vm, const char* options, void* reserved ATTRIBUTE_UNUSED) { if (options == nullptr) { options = ""; } jvmtiEnv* jvmti = nullptr; jvmtiError error = JVMTI_ERROR_NONE; { jint res = 0; res = SetupJvmtiEnv(vm, &jvmti); if (res != JNI_OK || jvmti == nullptr) { LOG(ERROR) << "Unable to access JVMTI, error code " << res; return JNI_ERR; } } int fd; std::string fd_name; bool dump_on_shutdown; bool dump_on_main_stop; if (!ParseArgs(options, /*out*/ &fd_name, /*out*/ &fd, /*out*/ &dump_on_shutdown, /*out*/ &dump_on_main_stop)) { LOG(ERROR) << "failed to get output file from " << options << "!"; return JNI_ERR; } void* data_mem = nullptr; error = jvmti->Allocate(sizeof(SimpleProfileData), reinterpret_cast(&data_mem)); if (error != JVMTI_ERROR_NONE) { LOG(ERROR) << "Unable to alloc memory for breakpoint target data"; return JNI_ERR; } SimpleProfileData* data = new (data_mem) SimpleProfileData(jvmti, fd_name, fd, dump_on_shutdown, dump_on_main_stop); error = jvmti->SetEnvironmentLocalStorage(data); if (error != JVMTI_ERROR_NONE) { LOG(ERROR) << "Unable to set local storage"; return JNI_ERR; } jvmtiCapabilities caps {}; caps.can_generate_method_entry_events = JNI_TRUE; caps.can_tag_objects = JNI_TRUE; error = jvmti->AddCapabilities(&caps); if (error != JVMTI_ERROR_NONE) { LOG(ERROR) << "Unable to set caps"; return JNI_ERR; } jvmtiEventCallbacks callbacks {}; callbacks.MethodEntry = &MethodEntryCB; callbacks.VMInit = &VMInitCB; callbacks.DataDumpRequest = &DataDumpCb; callbacks.VMDeath = &VMDeathCB; callbacks.ThreadEnd = [](jvmtiEnv* env, JNIEnv* jni, jthread thr ATTRIBUTE_UNUSED) { VMDeathCB(env, jni); }; error = jvmti->SetEventCallbacks(&callbacks, static_cast(sizeof(callbacks))); if (error != JVMTI_ERROR_NONE) { LOG(ERROR) << "Unable to set event callbacks."; return JNI_ERR; } if (start == StartType::OnAttach) { JNIEnv* env = nullptr; jint res = 0; res = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_2); if (res != JNI_OK || env == nullptr) { LOG(ERROR) << "Unable to get jnienv"; return JNI_ERR; } jthread temp; ScopedLocalRef cur(env, nullptr); CHECK_JVMTI(jvmti->GetCurrentThread(&temp)); cur.reset(temp); VMInitCB(jvmti, env, cur.get()); } else { error = jvmti->SetEventNotificationMode( JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, nullptr /* all threads */); if (error != JVMTI_ERROR_NONE) { LOG(ERROR) << "Unable to set event vminit"; return JNI_ERR; } } return JNI_OK; } // Late attachment (e.g. 'am attach-agent'). extern "C" JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char* options, void* reserved) { return AgentStart(StartType::OnAttach, vm, options, reserved); } // Early attachment extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) { return AgentStart(StartType::OnLoad, jvm, options, reserved); } } // namespace simple_profile