// Copyright (C) 2018 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 <android-base/logging.h> #include <atomic> #include <iostream> #include <istream> #include <iomanip> #include <jni.h> #include <jvmti.h> #include <limits> #include <memory> #include <string> #include <sstream> #include <vector> namespace tifast { #define EVENT(x) JVMTI_EVENT_ ## x namespace { // Special art ti-version number. We will use this as a fallback if we cannot get a regular JVMTI // env. static constexpr jint kArtTiVersion = JVMTI_VERSION_1_2 | 0x40000000; template <typename ...Args> static void Unused(Args... args ATTRIBUTE_UNUSED) {} // jthread is a typedef of jobject so we use this to allow the templates to distinguish them. struct jthreadContainer { jthread thread; }; // jlocation is a typedef of jlong so use this to distinguish the less common jlong. struct jlongContainer { jlong val; }; static void AddCapsForEvent(jvmtiEvent event, jvmtiCapabilities* caps) { switch (event) { #define DO_CASE(name, cap_name) \ case EVENT(name): \ caps->cap_name = 1; \ break DO_CASE(SINGLE_STEP, can_generate_single_step_events); DO_CASE(METHOD_ENTRY, can_generate_method_entry_events); DO_CASE(METHOD_EXIT, can_generate_method_exit_events); DO_CASE(NATIVE_METHOD_BIND, can_generate_native_method_bind_events); DO_CASE(EXCEPTION, can_generate_exception_events); DO_CASE(EXCEPTION_CATCH, can_generate_exception_events); DO_CASE(COMPILED_METHOD_LOAD, can_generate_compiled_method_load_events); DO_CASE(COMPILED_METHOD_UNLOAD, can_generate_compiled_method_load_events); DO_CASE(MONITOR_CONTENDED_ENTER, can_generate_monitor_events); DO_CASE(MONITOR_CONTENDED_ENTERED, can_generate_monitor_events); DO_CASE(MONITOR_WAIT, can_generate_monitor_events); DO_CASE(MONITOR_WAITED, can_generate_monitor_events); DO_CASE(VM_OBJECT_ALLOC, can_generate_vm_object_alloc_events); DO_CASE(GARBAGE_COLLECTION_START, can_generate_garbage_collection_events); DO_CASE(GARBAGE_COLLECTION_FINISH, can_generate_garbage_collection_events); #undef DO_CASE default: break; } } // Setup for all supported events. Give a macro with fun(name, event_num, args) #define FOR_ALL_SUPPORTED_JNI_EVENTS(fun) \ fun(SingleStep, EVENT(SINGLE_STEP), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID meth, jlocation loc), (jvmti, jni, jthreadContainer{.thread = thread}, meth, loc)) \ fun(MethodEntry, EVENT(METHOD_ENTRY), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID meth), (jvmti, jni, jthreadContainer{.thread = thread}, meth)) \ fun(MethodExit, EVENT(METHOD_EXIT), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID meth, jboolean jb, jvalue jv), (jvmti, jni, jthreadContainer{.thread = thread}, meth, jb, jv)) \ fun(NativeMethodBind, EVENT(NATIVE_METHOD_BIND), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID meth, void* v1, void** v2), (jvmti, jni, jthreadContainer{.thread = thread}, meth, v1, v2)) \ fun(Exception, EVENT(EXCEPTION), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID meth1, jlocation loc1, jobject obj, jmethodID meth2, jlocation loc2), (jvmti, jni, jthreadContainer{.thread = thread}, meth1, loc1, obj, meth2, loc2)) \ fun(ExceptionCatch, EVENT(EXCEPTION_CATCH), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID meth, jlocation loc, jobject obj), (jvmti, jni, jthreadContainer{.thread = thread}, meth, loc, obj)) \ fun(ThreadStart, EVENT(THREAD_START), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread), (jvmti, jni, jthreadContainer{.thread = thread})) \ fun(ThreadEnd, EVENT(THREAD_END), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread), (jvmti, jni, jthreadContainer{.thread = thread})) \ fun(ClassLoad, EVENT(CLASS_LOAD), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jclass klass), (jvmti, jni, jthreadContainer{.thread = thread}, klass) ) \ fun(ClassPrepare, EVENT(CLASS_PREPARE), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jclass klass), (jvmti, jni, jthreadContainer{.thread = thread}, klass)) \ fun(ClassFileLoadHook, EVENT(CLASS_FILE_LOAD_HOOK), (jvmtiEnv* jvmti, JNIEnv* jni, jclass klass, jobject obj1, const char* c1, jobject obj2, jint i1, const unsigned char* c2, jint* ip1, unsigned char** cp1), (jvmti, jni, klass, obj1, c1, obj2, i1, c2, ip1, cp1)) \ fun(MonitorContendedEnter, EVENT(MONITOR_CONTENDED_ENTER), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jobject obj), (jvmti, jni, jthreadContainer{.thread = thread}, obj)) \ fun(MonitorContendedEntered, EVENT(MONITOR_CONTENDED_ENTERED), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jobject obj), (jvmti, jni, jthreadContainer{.thread = thread}, obj)) \ fun(MonitorWait, EVENT(MONITOR_WAIT), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jobject obj, jlong l1), (jvmti, jni, jthreadContainer{.thread = thread}, obj, jlongContainer{.val = l1})) \ fun(MonitorWaited, EVENT(MONITOR_WAITED), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jobject obj, jboolean b1), (jvmti, jni, jthreadContainer{.thread = thread}, obj, b1)) \ fun(ResourceExhausted, EVENT(RESOURCE_EXHAUSTED), (jvmtiEnv* jvmti, JNIEnv* jni, jint i1, const void* cv, const char* cc), (jvmti, jni, i1, cv, cc)) \ fun(VMObjectAlloc, EVENT(VM_OBJECT_ALLOC), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jobject obj, jclass klass, jlong l1), (jvmti, jni, jthreadContainer{.thread = thread}, obj, klass, jlongContainer{.val = l1})) \ fun(VMInit, EVENT(VM_INIT), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread), (jvmti, jni, jthreadContainer{.thread = thread})) \ fun(VMStart, EVENT(VM_START), (jvmtiEnv* jvmti, JNIEnv* jni), (jvmti, jni)) \ fun(VMDeath, EVENT(VM_DEATH), (jvmtiEnv* jvmti, JNIEnv* jni), (jvmti, jni)) \ #define FOR_ALL_SUPPORTED_NO_JNI_EVENTS(fun) \ fun(CompiledMethodLoad, EVENT(COMPILED_METHOD_LOAD), (jvmtiEnv* jvmti, jmethodID meth, jint i1, const void* cv1, jint i2, const jvmtiAddrLocationMap* alm, const void* cv2), (jvmti, meth, i1, cv1, i2, alm, cv2)) \ fun(CompiledMethodUnload, EVENT(COMPILED_METHOD_UNLOAD), (jvmtiEnv* jvmti, jmethodID meth, const void* cv1), (jvmti, meth, cv1)) \ fun(DynamicCodeGenerated, EVENT(DYNAMIC_CODE_GENERATED), (jvmtiEnv* jvmti, const char* cc, const void* cv, jint i1), (jvmti, cc, cv, i1)) \ fun(DataDumpRequest, EVENT(DATA_DUMP_REQUEST), (jvmtiEnv* jvmti), (jvmti)) \ fun(GarbageCollectionStart, EVENT(GARBAGE_COLLECTION_START), (jvmtiEnv* jvmti), (jvmti)) \ fun(GarbageCollectionFinish, EVENT(GARBAGE_COLLECTION_FINISH), (jvmtiEnv* jvmti), (jvmti)) #define FOR_ALL_SUPPORTED_EVENTS(fun) \ FOR_ALL_SUPPORTED_JNI_EVENTS(fun) \ FOR_ALL_SUPPORTED_NO_JNI_EVENTS(fun) static const jvmtiEvent kAllEvents[] = { #define GET_EVENT(a, event, b, c) event, FOR_ALL_SUPPORTED_EVENTS(GET_EVENT) #undef GET_EVENT }; #define GENERATE_EMPTY_FUNCTION(name, number, args, argnames) \ static void JNICALL empty ## name args { Unused argnames ; } FOR_ALL_SUPPORTED_EVENTS(GENERATE_EMPTY_FUNCTION) #undef GENERATE_EMPTY_FUNCTION static jvmtiEventCallbacks kEmptyCallbacks { #define CREATE_EMPTY_EVENT_CALLBACKS(name, num, args, argnames) \ .name = empty ## name, FOR_ALL_SUPPORTED_EVENTS(CREATE_EMPTY_EVENT_CALLBACKS) #undef CREATE_EMPTY_EVENT_CALLBACKS }; static void DeleteLocalRef(JNIEnv* env, jobject obj) { if (obj != nullptr && env != nullptr) { env->DeleteLocalRef(obj); } } class ScopedThreadInfo { public: ScopedThreadInfo(jvmtiEnv* jvmtienv, JNIEnv* env, jthread thread) : jvmtienv_(jvmtienv), env_(env), free_name_(false) { if (thread == nullptr) { info_.name = const_cast<char*>("<NULLPTR>"); } else if (jvmtienv->GetThreadInfo(thread, &info_) != JVMTI_ERROR_NONE) { info_.name = const_cast<char*>("<UNKNOWN THREAD>"); } else { free_name_ = true; } } ~ScopedThreadInfo() { if (free_name_) { jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(info_.name)); } DeleteLocalRef(env_, info_.thread_group); DeleteLocalRef(env_, info_.context_class_loader); } const char* GetName() const { return info_.name; } private: jvmtiEnv* jvmtienv_; JNIEnv* env_; bool free_name_; jvmtiThreadInfo info_{}; }; class ScopedClassInfo { public: ScopedClassInfo(jvmtiEnv* jvmtienv, jclass c) : jvmtienv_(jvmtienv), class_(c) {} ~ScopedClassInfo() { if (class_ != nullptr) { jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_)); jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_)); jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(file_)); jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(debug_ext_)); } } bool Init(bool get_generic = true) { if (class_ == nullptr) { name_ = const_cast<char*>("<NONE>"); generic_ = const_cast<char*>("<NONE>"); return true; } else { jvmtiError ret1 = jvmtienv_->GetSourceFileName(class_, &file_); jvmtiError ret2 = jvmtienv_->GetSourceDebugExtension(class_, &debug_ext_); char** gen_ptr = &generic_; if (!get_generic) { generic_ = nullptr; gen_ptr = nullptr; } return jvmtienv_->GetClassSignature(class_, &name_, gen_ptr) == JVMTI_ERROR_NONE && ret1 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY && ret1 != JVMTI_ERROR_INVALID_CLASS && ret2 != JVMTI_ERROR_MUST_POSSESS_CAPABILITY && ret2 != JVMTI_ERROR_INVALID_CLASS; } } jclass GetClass() const { return class_; } const char* GetName() const { return name_; } const char* GetGeneric() const { return generic_; } const char* GetSourceDebugExtension() const { if (debug_ext_ == nullptr) { return "<UNKNOWN_SOURCE_DEBUG_EXTENSION>"; } else { return debug_ext_; } } const char* GetSourceFileName() const { if (file_ == nullptr) { return "<UNKNOWN_FILE>"; } else { return file_; } } private: jvmtiEnv* jvmtienv_; jclass class_; char* name_ = nullptr; char* generic_ = nullptr; char* file_ = nullptr; char* debug_ext_ = nullptr; friend std::ostream& operator<<(std::ostream &os, ScopedClassInfo const& m); }; class ScopedMethodInfo { public: ScopedMethodInfo(jvmtiEnv* jvmtienv, JNIEnv* env, jmethodID m) : jvmtienv_(jvmtienv), env_(env), method_(m) {} ~ScopedMethodInfo() { DeleteLocalRef(env_, declaring_class_); jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(name_)); jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(signature_)); jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(generic_)); } bool Init(bool get_generic = true) { if (jvmtienv_->GetMethodDeclaringClass(method_, &declaring_class_) != JVMTI_ERROR_NONE) { return false; } class_info_.reset(new ScopedClassInfo(jvmtienv_, declaring_class_)); jint nlines; jvmtiLineNumberEntry* lines; jvmtiError err = jvmtienv_->GetLineNumberTable(method_, &nlines, &lines); if (err == JVMTI_ERROR_NONE) { if (nlines > 0) { first_line_ = lines[0].line_number; } jvmtienv_->Deallocate(reinterpret_cast<unsigned char*>(lines)); } else if (err != JVMTI_ERROR_ABSENT_INFORMATION && err != JVMTI_ERROR_NATIVE_METHOD) { return false; } return class_info_->Init(get_generic) && (jvmtienv_->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_; } jint GetFirstLine() const { return first_line_; } private: jvmtiEnv* jvmtienv_; JNIEnv* env_; jmethodID method_; jclass declaring_class_ = nullptr; std::unique_ptr<ScopedClassInfo> class_info_; char* name_ = nullptr; char* signature_ = nullptr; char* generic_ = nullptr; jint first_line_ = -1; friend std::ostream& operator<<(std::ostream &os, ScopedMethodInfo const& m); }; std::ostream& operator<<(std::ostream &os, ScopedClassInfo const& c) { const char* generic = c.GetGeneric(); if (generic != nullptr) { return os << c.GetName() << "<" << generic << ">" << " file: " << c.GetSourceFileName(); } else { return os << c.GetName() << " file: " << c.GetSourceFileName(); } } std::ostream& operator<<(std::ostream &os, ScopedMethodInfo const& m) { return os << m.GetDeclaringClassInfo().GetName() << "->" << m.GetName() << m.GetSignature() << " (source: " << m.GetDeclaringClassInfo().GetSourceFileName() << ":" << m.GetFirstLine() << ")"; } class LogPrinter { public: explicit LogPrinter(jvmtiEvent event) : event_(event) {} template <typename ...Args> void PrintRestNoJNI(jvmtiEnv* jvmti, Args... args) { PrintRest(jvmti, static_cast<JNIEnv*>(nullptr), args...); } template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, jlongContainer l, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, jthreadContainer thr, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, jboolean i, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, jint i, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, jclass klass, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, jmethodID meth, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, jlocation loc, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, jint* ip, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, const void* loc, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, void* loc, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, void** loc, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, unsigned char** v, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, const unsigned char* v, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, const char* v, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, const jvmtiAddrLocationMap* v, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, jvalue v, Args... args); template <typename ...Args> void PrintRest(jvmtiEnv* jvmti, JNIEnv* env, jobject v, Args... args); std::string GetResult() { std::string out_str = stream.str(); return start_args + out_str; } private: jvmtiEvent event_; std::string start_args; std::ostringstream stream; }; // Base case template<> void LogPrinter::PrintRest(jvmtiEnv* jvmti ATTRIBUTE_UNUSED, JNIEnv* jni) { if (jni == nullptr) { start_args = "jvmtiEnv*"; } else { start_args = "jvmtiEnv*, JNIEnv*"; } } template<typename ...Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, const jvmtiAddrLocationMap* v, Args... args) { if (v != nullptr) { stream << ", const jvmtiAddrLocationMap*[start_address: " << v->start_address << ", location: " << v->location << "]"; } else { stream << ", const jvmtiAddrLocationMap*[nullptr]"; } PrintRest(jvmti, jni, args...); } template<typename ...Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, jint* v, Args... args) { stream << ", jint*[" << static_cast<const void*>(v) << "]"; PrintRest(jvmti, jni, args...); } template<typename ...Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, const void* v, Args... args) { stream << ", const void*[" << v << "]"; PrintRest(jvmti, jni, args...); } template<typename ...Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, unsigned char** v, Args... args) { stream << ", unsigned char**[" << static_cast<const void*>(v) << "]"; PrintRest(jvmti, jni, args...); } template<typename ...Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, const unsigned char* v, Args... args) { stream << ", const unsigned char*[" << static_cast<const void*>(v) << "]"; PrintRest(jvmti, jni, args...); } template<typename ...Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, const char* v, Args... args) { stream << ", const char*[" << v << "]"; PrintRest(jvmti, jni, args...); } template<typename... Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, jvalue v, Args... args) { std::ostringstream hex; hex << std::hex << v.j; std::ostringstream char_val; if (std::isprint(v.c) && v.c < std::numeric_limits<unsigned char>::max()) { char_val << "'" << static_cast<unsigned char>(v.c) << "'"; } else { char_val << "0x" << std::hex << reinterpret_cast<uint16_t>(v.c); } stream << ", jvalue[{<hex: 0x" << hex.str() << ">" << ", .z=" << (v.z ? "true" : "false") << ", .b=" << static_cast<int32_t>(v.b) << ", .c=" << char_val.str() << ", .s=" << static_cast<int32_t>(v.s) << ", .i=" << v.i << ", .j=" << v.j << ", .f=" << v.f << ", .d=" << v.d << ", .l=" << v.l << "}]"; PrintRest(jvmti, jni, args...); } template<typename ...Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, void** v, Args... args) { stream << ", void**[" << v << "]"; PrintRest(jvmti, jni, args...); } template<typename ...Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, void* v, Args... args) { stream << ", void*[" << v << "]"; PrintRest(jvmti, jni, args...); } template<typename ...Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, jlongContainer l, Args... args) { stream << ", jlong[" << l.val << ", hex: 0x" << std::hex << l.val << "]"; PrintRest(jvmti, jni, args...); } template<typename ...Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, jlocation l, Args... args) { stream << ", jlocation[" << l << ", hex: 0x" << std::hex << l << "]"; PrintRest(jvmti, jni, args...); } template<typename ...Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, jboolean b, Args... args) { stream << ", jboolean[" << (b ? "true" : "false") << "]"; PrintRest(jvmti, jni, args...); } template<typename ...Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, jint i, Args... args) { stream << ", jint[" << i << ", hex: 0x" << std::hex << i << "]"; PrintRest(jvmti, jni, args...); } template<typename ...Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, jobject obj, Args... args) { if (obj == nullptr) { stream << ", jobject[nullptr]"; } else { jni->PushLocalFrame(1); jclass klass = jni->GetObjectClass(obj); ScopedClassInfo sci(jvmti, klass); if (sci.Init(event_ != JVMTI_EVENT_VM_OBJECT_ALLOC)) { stream << ", jobject[type: " << sci << "]"; } else { stream << ", jobject[type: TYPE UNKNOWN]"; } jni->PopLocalFrame(nullptr); } PrintRest(jvmti, jni, args...); } template<typename ...Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, jthreadContainer thr, Args... args) { ScopedThreadInfo sti(jvmti, jni, thr.thread); stream << ", jthread[" << sti.GetName() << "]"; PrintRest(jvmti, jni, args...); } template<typename ...Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, jclass klass, Args... args) { ScopedClassInfo sci(jvmti, klass); if (sci.Init(/*get_generic=*/event_ != JVMTI_EVENT_VM_OBJECT_ALLOC)) { stream << ", jclass[" << sci << "]"; } else { stream << ", jclass[TYPE UNKNOWN]"; } PrintRest(jvmti, jni, args...); } template<typename ...Args> void LogPrinter::PrintRest(jvmtiEnv* jvmti, JNIEnv* jni, jmethodID meth, Args... args) { ScopedMethodInfo smi(jvmti, jni, meth); if (smi.Init()) { stream << ", jmethodID[" << smi << "]"; } else { stream << ", jmethodID[METHOD UNKNOWN]"; } PrintRest(jvmti, jni, args...); } #define GENERATE_LOG_FUNCTION_JNI(name, event, args, argnames) \ static void JNICALL log ## name args { \ LogPrinter printer(event); \ printer.PrintRest argnames; \ LOG(INFO) << "Got event " << #name << "(" << printer.GetResult() << ")"; \ } \ #define GENERATE_LOG_FUNCTION_NO_JNI(name, event, args, argnames) \ static void JNICALL log ## name args { \ LogPrinter printer(event); \ printer.PrintRestNoJNI argnames; \ LOG(INFO) << "Got event " << #name << "(" << printer.GetResult() << ")"; \ } \ FOR_ALL_SUPPORTED_JNI_EVENTS(GENERATE_LOG_FUNCTION_JNI) FOR_ALL_SUPPORTED_NO_JNI_EVENTS(GENERATE_LOG_FUNCTION_NO_JNI) #undef GENERATE_LOG_FUNCTION static jvmtiEventCallbacks kLogCallbacks { #define CREATE_LOG_EVENT_CALLBACK(name, num, args, argnames) \ .name = log ## name, FOR_ALL_SUPPORTED_EVENTS(CREATE_LOG_EVENT_CALLBACK) #undef CREATE_LOG_EVENT_CALLBACK }; static std::string EventToName(jvmtiEvent desired_event) { #define CHECK_NAME(name, event, args, argnames) \ if (desired_event == (event)) { \ return #name; \ } FOR_ALL_SUPPORTED_EVENTS(CHECK_NAME); LOG(FATAL) << "Unknown event " << desired_event; __builtin_unreachable(); #undef CHECK_NAME } static jvmtiEvent NameToEvent(const std::string& desired_name) { #define CHECK_NAME(name, event, args, argnames) \ if (desired_name == #name) { \ return event; \ } FOR_ALL_SUPPORTED_EVENTS(CHECK_NAME); LOG(FATAL) << "Unknown event " << desired_name; __builtin_unreachable(); #undef CHECK_NAME } #undef FOR_ALL_SUPPORTED_JNI_EVENTS #undef FOR_ALL_SUPPORTED_NO_JNI_EVENTS #undef FOR_ALL_SUPPORTED_EVENTS static std::vector<jvmtiEvent> GetAllAvailableEvents(jvmtiEnv* jvmti) { std::vector<jvmtiEvent> out; jvmtiCapabilities caps{}; jvmti->GetPotentialCapabilities(&caps); uint8_t caps_bytes[sizeof(caps)]; memcpy(caps_bytes, &caps, sizeof(caps)); for (jvmtiEvent e : kAllEvents) { jvmtiCapabilities req{}; AddCapsForEvent(e, &req); uint8_t req_bytes[sizeof(req)]; memcpy(req_bytes, &req, sizeof(req)); bool good = true; for (size_t i = 0; i < sizeof(caps); i++) { if ((req_bytes[i] & caps_bytes[i]) != req_bytes[i]) { good = false; break; } } if (good) { out.push_back(e); } else { LOG(WARNING) << "Unable to get capabilities for event " << EventToName(e); } } return out; } static std::vector<jvmtiEvent> GetRequestedEventList(jvmtiEnv* jvmti, const std::string& args) { std::vector<jvmtiEvent> res; std::stringstream args_stream(args); std::string item; while (std::getline(args_stream, item, ',')) { if (item == "") { continue; } else if (item == "all") { return GetAllAvailableEvents(jvmti); } res.push_back(NameToEvent(item)); } return res; } static jint SetupJvmtiEnv(JavaVM* vm, jvmtiEnv** jvmti) { jint res = 0; res = vm->GetEnv(reinterpret_cast<void**>(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<void**>(jvmti), kArtTiVersion); } return res; } } // namespace static jint AgentStart(JavaVM* vm, char* options, void* reserved ATTRIBUTE_UNUSED) { jvmtiEnv* jvmti = nullptr; jvmtiError error = JVMTI_ERROR_NONE; if (SetupJvmtiEnv(vm, &jvmti) != JNI_OK) { LOG(ERROR) << "Could not get JVMTI env or ArtTiEnv!"; return JNI_ERR; } std::string args(options); bool is_log = false; if (args.compare(0, 3, "log") == 0) { is_log = true; args = args.substr(3); } std::vector<jvmtiEvent> events = GetRequestedEventList(jvmti, args); jvmtiCapabilities caps{}; for (jvmtiEvent e : events) { AddCapsForEvent(e, &caps); } if (is_log) { caps.can_get_line_numbers = 1; caps.can_get_source_file_name = 1; caps.can_get_source_debug_extension = 1; } error = jvmti->AddCapabilities(&caps); if (error != JVMTI_ERROR_NONE) { LOG(ERROR) << "Unable to set caps"; return JNI_ERR; } if (is_log) { error = jvmti->SetEventCallbacks(&kLogCallbacks, static_cast<jint>(sizeof(kLogCallbacks))); } else { error = jvmti->SetEventCallbacks(&kEmptyCallbacks, static_cast<jint>(sizeof(kEmptyCallbacks))); } if (error != JVMTI_ERROR_NONE) { LOG(ERROR) << "Unable to set event callbacks."; return JNI_ERR; } for (jvmtiEvent e : events) { error = jvmti->SetEventNotificationMode(JVMTI_ENABLE, e, nullptr /* all threads */); if (error != JVMTI_ERROR_NONE) { LOG(ERROR) << "Unable to enable event " << e; 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(vm, options, reserved); } // Early attachment extern "C" JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* jvm, char* options, void* reserved) { return AgentStart(jvm, options, reserved); } } // namespace tifast