| // 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 <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})) \ |
| |
| #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 ATTRIBUTE_UNUSED, Args... args) { |
| stream << ", jvalue[<UNION>]"; |
| 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 |
| |