You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
737 lines
28 KiB
737 lines
28 KiB
// 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 {non_}jni_fun(name, event_num, args)
|
|
#define FOR_ALL_SUPPORTED_EVENTS_DIFFERENT(jni_fun, non_jni_fun) \
|
|
jni_fun(VMInit, EVENT(VM_INIT), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread), (jvmti, jni, jthreadContainer{.thread = thread})) \
|
|
jni_fun(VMDeath, EVENT(VM_DEATH), (jvmtiEnv* jvmti, JNIEnv* jni), (jvmti, jni)) \
|
|
jni_fun(ThreadStart, EVENT(THREAD_START), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread), (jvmti, jni, jthreadContainer{.thread = thread})) \
|
|
jni_fun(ThreadEnd, EVENT(THREAD_END), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread), (jvmti, jni, jthreadContainer{.thread = thread})) \
|
|
jni_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)) \
|
|
jni_fun(ClassLoad, EVENT(CLASS_LOAD), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jclass klass), (jvmti, jni, jthreadContainer{.thread = thread}, klass) ) \
|
|
jni_fun(ClassPrepare, EVENT(CLASS_PREPARE), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jclass klass), (jvmti, jni, jthreadContainer{.thread = thread}, klass)) \
|
|
jni_fun(VMStart, EVENT(VM_START), (jvmtiEnv* jvmti, JNIEnv* jni), (jvmti, jni)) \
|
|
jni_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)) \
|
|
jni_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)) \
|
|
jni_fun(SingleStep, EVENT(SINGLE_STEP), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID meth, jlocation loc), (jvmti, jni, jthreadContainer{.thread = thread}, meth, loc)) \
|
|
jni_fun(MethodEntry, EVENT(METHOD_ENTRY), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jmethodID meth), (jvmti, jni, jthreadContainer{.thread = thread}, meth)) \
|
|
jni_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)) \
|
|
jni_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)) \
|
|
non_jni_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)) \
|
|
non_jni_fun(CompiledMethodUnload, EVENT(COMPILED_METHOD_UNLOAD), (jvmtiEnv* jvmti, jmethodID meth, const void* cv1), (jvmti, meth, cv1)) \
|
|
non_jni_fun(DynamicCodeGenerated, EVENT(DYNAMIC_CODE_GENERATED), (jvmtiEnv* jvmti, const char* cc, const void* cv, jint i1), (jvmti, cc, cv, i1)) \
|
|
non_jni_fun(DataDumpRequest, EVENT(DATA_DUMP_REQUEST), (jvmtiEnv* jvmti), (jvmti)) \
|
|
jni_fun(MonitorWait, EVENT(MONITOR_WAIT), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jobject obj, jlong l1), (jvmti, jni, jthreadContainer{.thread = thread}, obj, jlongContainer{.val = l1})) \
|
|
jni_fun(MonitorWaited, EVENT(MONITOR_WAITED), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jobject obj, jboolean b1), (jvmti, jni, jthreadContainer{.thread = thread}, obj, b1)) \
|
|
jni_fun(MonitorContendedEnter, EVENT(MONITOR_CONTENDED_ENTER), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jobject obj), (jvmti, jni, jthreadContainer{.thread = thread}, obj)) \
|
|
jni_fun(MonitorContendedEntered, EVENT(MONITOR_CONTENDED_ENTERED), (jvmtiEnv* jvmti, JNIEnv* jni, jthread thread, jobject obj), (jvmti, jni, jthreadContainer{.thread = thread}, obj)) \
|
|
jni_fun(ResourceExhausted, EVENT(RESOURCE_EXHAUSTED), (jvmtiEnv* jvmti, JNIEnv* jni, jint i1, const void* cv, const char* cc), (jvmti, jni, i1, cv, cc)) \
|
|
non_jni_fun(GarbageCollectionStart, EVENT(GARBAGE_COLLECTION_START), (jvmtiEnv* jvmti), (jvmti)) \
|
|
non_jni_fun(GarbageCollectionFinish, EVENT(GARBAGE_COLLECTION_FINISH), (jvmtiEnv* jvmti), (jvmti)) \
|
|
jni_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_EVENTS(fun) \
|
|
FOR_ALL_SUPPORTED_EVENTS_DIFFERENT(fun, 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_EVENTS_DIFFERENT(GENERATE_LOG_FUNCTION_JNI, 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_EVENTS
|
|
#undef FOR_ALL_SUPPORTED_EVENTS_DIFFERENT
|
|
|
|
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
|
|
|