/*
* 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 "common_helper.h"
#include "jni.h"
#include "jvmti.h"
#include "jvmti_helper.h"
#include "scoped_local_ref.h"
#include "test_env.h"
namespace art {
namespace common_trace {
static bool IsInCallback(JNIEnv* env, jvmtiEnv *jvmti, jthread thr) {
void* data;
ScopedLocalRef<jthrowable> exc(env, env->ExceptionOccurred());
env->ExceptionClear();
jvmti->GetThreadLocalStorage(thr, &data);
if (exc.get() != nullptr) {
env->Throw(exc.get());
}
if (data == nullptr) {
return false;
} else {
return true;
}
}
static void SetInCallback(JNIEnv* env, jvmtiEnv *jvmti, jthread thr, bool val) {
ScopedLocalRef<jthrowable> exc(env, env->ExceptionOccurred());
env->ExceptionClear();
jvmti->SetThreadLocalStorage(thr, (val ? reinterpret_cast<void*>(0x1)
: reinterpret_cast<void*>(0x0)));
if (exc.get() != nullptr) {
env->Throw(exc.get());
}
}
class ScopedCallbackState {
public:
ScopedCallbackState(JNIEnv* jnienv, jvmtiEnv* env, jthread thr)
: jnienv_(jnienv), env_(env), thr_(thr) {
CHECK(!IsInCallback(jnienv_, env_, thr_));
SetInCallback(jnienv_, env_, thr_, true);
}
~ScopedCallbackState() {
CHECK(IsInCallback(jnienv_, env_, thr_));
SetInCallback(jnienv_, env_, thr_, false);
}
private:
JNIEnv* jnienv_;
jvmtiEnv* env_;
jthread thr_;
};
struct TraceData {
jclass test_klass;
jmethodID enter_method;
jmethodID exit_method;
jmethodID field_access;
jmethodID field_modify;
jmethodID single_step;
jmethodID thread_start;
jmethodID thread_end;
bool access_watch_on_load;
bool modify_watch_on_load;
jrawMonitorID trace_mon;
jclass GetTestClass(jvmtiEnv* jvmti, JNIEnv* env) {
if (JvmtiErrorToException(env, jvmti, jvmti->RawMonitorEnter(trace_mon))) {
return nullptr;
}
jclass out = reinterpret_cast<jclass>(env->NewLocalRef(test_klass));
if (JvmtiErrorToException(env, jvmti, jvmti->RawMonitorExit(trace_mon))) {
return nullptr;
}
return out;
}
};
static void threadStartCB(jvmtiEnv* jvmti,
JNIEnv* jnienv,
jthread thread) {
TraceData* data = nullptr;
if (JvmtiErrorToException(jnienv, jvmti,
jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
return;
}
ScopedLocalRef<jclass> klass(jnienv, data->GetTestClass(jvmti, jnienv));
if (klass.get() == nullptr) {
return;
}
CHECK(data->thread_start != nullptr);
jnienv->CallStaticVoidMethod(klass.get(), data->thread_start, thread);
}
static void threadEndCB(jvmtiEnv* jvmti,
JNIEnv* jnienv,
jthread thread) {
TraceData* data = nullptr;
if (JvmtiErrorToException(jnienv, jvmti,
jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
return;
}
ScopedLocalRef<jclass> klass(jnienv, data->GetTestClass(jvmti, jnienv));
if (klass.get() == nullptr) {
return;
}
CHECK(data->thread_end != nullptr);
jnienv->CallStaticVoidMethod(klass.get(), data->thread_end, thread);
}
static void singleStepCB(jvmtiEnv* jvmti,
JNIEnv* jnienv,
jthread thread,
jmethodID method,
jlocation location) {
TraceData* data = nullptr;
if (JvmtiErrorToException(jnienv, jvmti,
jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
return;
}
if (IsInCallback(jnienv, jvmti, thread)) {
return;
}
ScopedLocalRef<jclass> klass(jnienv, data->GetTestClass(jvmti, jnienv));
if (klass.get() == nullptr) {
return;
}
CHECK(data->single_step != nullptr);
ScopedCallbackState st(jnienv, jvmti, thread);
jobject method_arg = GetJavaMethod(jvmti, jnienv, method);
jnienv->CallStaticVoidMethod(klass.get(),
data->single_step,
thread,
method_arg,
static_cast<jlong>(location));
jnienv->DeleteLocalRef(method_arg);
}
static void fieldAccessCB(jvmtiEnv* jvmti,
JNIEnv* jnienv,
jthread thr,
jmethodID method,
jlocation location,
jclass field_klass,
jobject object,
jfieldID field) {
TraceData* data = nullptr;
if (JvmtiErrorToException(jnienv, jvmti,
jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
return;
}
if (IsInCallback(jnienv, jvmti, thr)) {
// Don't do callback for either of these to prevent an infinite loop.
return;
}
ScopedLocalRef<jclass> klass(jnienv, data->GetTestClass(jvmti, jnienv));
if (klass.get() == nullptr) {
return;
}
CHECK(data->field_access != nullptr);
ScopedCallbackState st(jnienv, jvmti, thr);
jobject method_arg = GetJavaMethod(jvmti, jnienv, method);
jobject field_arg = GetJavaField(jvmti, jnienv, field_klass, field);
jnienv->CallStaticVoidMethod(klass.get(),
data->field_access,
method_arg,
static_cast<jlong>(location),
field_klass,
object,
field_arg);
jnienv->DeleteLocalRef(method_arg);
jnienv->DeleteLocalRef(field_arg);
}
static void fieldModificationCB(jvmtiEnv* jvmti,
JNIEnv* jnienv,
jthread thr,
jmethodID method,
jlocation location,
jclass field_klass,
jobject object,
jfieldID field,
char type_char,
jvalue new_value) {
TraceData* data = nullptr;
if (JvmtiErrorToException(jnienv, jvmti,
jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
return;
}
if (IsInCallback(jnienv, jvmti, thr)) {
// Don't do callback recursively to prevent an infinite loop.
return;
}
ScopedLocalRef<jclass> klass(jnienv, data->GetTestClass(jvmti, jnienv));
if (klass.get() == nullptr) {
return;
}
CHECK(data->field_modify != nullptr);
ScopedCallbackState st(jnienv, jvmti, thr);
jobject method_arg = GetJavaMethod(jvmti, jnienv, method);
jobject field_arg = GetJavaField(jvmti, jnienv, field_klass, field);
jobject value = GetJavaValueByType(jnienv, type_char, new_value);
if (jnienv->ExceptionCheck()) {
jnienv->DeleteLocalRef(method_arg);
jnienv->DeleteLocalRef(field_arg);
return;
}
jnienv->CallStaticVoidMethod(klass.get(),
data->field_modify,
method_arg,
static_cast<jlong>(location),
field_klass,
object,
field_arg,
value);
jnienv->DeleteLocalRef(method_arg);
jnienv->DeleteLocalRef(field_arg);
}
static void methodExitCB(jvmtiEnv* jvmti,
JNIEnv* jnienv,
jthread thr,
jmethodID method,
jboolean was_popped_by_exception,
jvalue return_value) {
TraceData* data = nullptr;
if (JvmtiErrorToException(jnienv, jvmti,
jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
return;
}
if (method == data->exit_method ||
method == data->enter_method ||
IsInCallback(jnienv, jvmti, thr)) {
// Don't do callback for either of these to prevent an infinite loop.
return;
}
ScopedLocalRef<jclass> klass(jnienv, data->GetTestClass(jvmti, jnienv));
if (klass.get() == nullptr) {
return;
}
CHECK(data->exit_method != nullptr);
ScopedCallbackState st(jnienv, jvmti, thr);
jobject method_arg = GetJavaMethod(jvmti, jnienv, method);
jobject result =
was_popped_by_exception ? nullptr : GetJavaValue(jvmti, jnienv, method, return_value);
if (jnienv->ExceptionCheck()) {
return;
}
jnienv->CallStaticVoidMethod(klass.get(),
data->exit_method,
method_arg,
was_popped_by_exception,
result);
jnienv->DeleteLocalRef(method_arg);
}
static void methodEntryCB(jvmtiEnv* jvmti,
JNIEnv* jnienv,
jthread thr,
jmethodID method) {
TraceData* data = nullptr;
if (JvmtiErrorToException(jnienv, jvmti,
jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
return;
}
CHECK(data->enter_method != nullptr);
if (method == data->exit_method ||
method == data->enter_method ||
IsInCallback(jnienv, jvmti, thr)) {
// Don't do callback for either of these to prevent an infinite loop.
return;
}
ScopedLocalRef<jclass> klass(jnienv, data->GetTestClass(jvmti, jnienv));
if (klass.get() == nullptr) {
return;
}
ScopedCallbackState st(jnienv, jvmti, thr);
jobject method_arg = GetJavaMethod(jvmti, jnienv, method);
if (jnienv->ExceptionCheck()) {
return;
}
jnienv->CallStaticVoidMethod(klass.get(), data->enter_method, method_arg);
jnienv->DeleteLocalRef(method_arg);
}
static void classPrepareCB(jvmtiEnv* jvmti,
JNIEnv* jnienv,
jthread thr ATTRIBUTE_UNUSED,
jclass klass) {
TraceData* data = nullptr;
if (JvmtiErrorToException(jnienv, jvmti,
jvmti->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
return;
}
if (data->access_watch_on_load || data->modify_watch_on_load) {
jint nfields;
jfieldID* fields;
if (JvmtiErrorToException(jnienv, jvmti, jvmti->GetClassFields(klass, &nfields, &fields))) {
return;
}
for (jint i = 0; i < nfields; i++) {
jfieldID f = fields[i];
// Ignore errors
if (data->access_watch_on_load) {
jvmti->SetFieldAccessWatch(klass, f);
}
if (data->modify_watch_on_load) {
jvmti->SetFieldModificationWatch(klass, f);
}
}
jvmti->Deallocate(reinterpret_cast<unsigned char*>(fields));
}
}
extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchAllFieldAccesses(JNIEnv* env) {
TraceData* data = nullptr;
if (JvmtiErrorToException(
env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
return;
}
data->access_watch_on_load = true;
// We need the classPrepareCB to watch new fields as the classes are loaded/prepared.
if (JvmtiErrorToException(env,
jvmti_env,
jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
JVMTI_EVENT_CLASS_PREPARE,
nullptr))) {
return;
}
jint nklasses;
jclass* klasses;
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLoadedClasses(&nklasses, &klasses))) {
return;
}
for (jint i = 0; i < nklasses; i++) {
jclass k = klasses[i];
jint nfields;
jfieldID* fields;
jvmtiError err = jvmti_env->GetClassFields(k, &nfields, &fields);
if (err == JVMTI_ERROR_CLASS_NOT_PREPARED) {
continue;
} else if (JvmtiErrorToException(env, jvmti_env, err)) {
jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses));
return;
}
for (jint j = 0; j < nfields; j++) {
jvmti_env->SetFieldAccessWatch(k, fields[j]);
}
jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(fields));
}
jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses));
}
extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchAllFieldModifications(JNIEnv* env) {
TraceData* data = nullptr;
if (JvmtiErrorToException(
env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
return;
}
data->modify_watch_on_load = true;
// We need the classPrepareCB to watch new fields as the classes are loaded/prepared.
if (JvmtiErrorToException(env,
jvmti_env,
jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
JVMTI_EVENT_CLASS_PREPARE,
nullptr))) {
return;
}
jint nklasses;
jclass* klasses;
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->GetLoadedClasses(&nklasses, &klasses))) {
return;
}
for (jint i = 0; i < nklasses; i++) {
jclass k = klasses[i];
jint nfields;
jfieldID* fields;
jvmtiError err = jvmti_env->GetClassFields(k, &nfields, &fields);
if (err == JVMTI_ERROR_CLASS_NOT_PREPARED) {
continue;
} else if (JvmtiErrorToException(env, jvmti_env, err)) {
jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses));
return;
}
for (jint j = 0; j < nfields; j++) {
jvmti_env->SetFieldModificationWatch(k, fields[j]);
}
jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(fields));
}
jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(klasses));
}
static bool GetFieldAndClass(JNIEnv* env,
jobject ref_field,
jclass* out_klass,
jfieldID* out_field) {
*out_field = env->FromReflectedField(ref_field);
if (env->ExceptionCheck()) {
return false;
}
jclass field_klass = env->FindClass("java/lang/reflect/Field");
if (env->ExceptionCheck()) {
return false;
}
jmethodID get_declaring_class_method =
env->GetMethodID(field_klass, "getDeclaringClass", "()Ljava/lang/Class;");
if (env->ExceptionCheck()) {
env->DeleteLocalRef(field_klass);
return false;
}
*out_klass = static_cast<jclass>(env->CallObjectMethod(ref_field, get_declaring_class_method));
if (env->ExceptionCheck()) {
*out_klass = nullptr;
env->DeleteLocalRef(field_klass);
return false;
}
env->DeleteLocalRef(field_klass);
return true;
}
extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchFieldModification(
JNIEnv* env,
jclass trace ATTRIBUTE_UNUSED,
jobject field_obj) {
jfieldID field;
jclass klass;
if (!GetFieldAndClass(env, field_obj, &klass, &field)) {
return;
}
JvmtiErrorToException(env, jvmti_env, jvmti_env->SetFieldModificationWatch(klass, field));
env->DeleteLocalRef(klass);
}
extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchFieldAccess(
JNIEnv* env,
jclass trace ATTRIBUTE_UNUSED,
jobject field_obj) {
jfieldID field;
jclass klass;
if (!GetFieldAndClass(env, field_obj, &klass, &field)) {
return;
}
JvmtiErrorToException(env, jvmti_env, jvmti_env->SetFieldAccessWatch(klass, field));
env->DeleteLocalRef(klass);
}
extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing2(
JNIEnv* env,
jclass trace ATTRIBUTE_UNUSED,
jclass klass,
jobject enter,
jobject exit,
jobject field_access,
jobject field_modify,
jobject single_step,
jobject thread_start,
jobject thread_end,
jthread thr) {
TraceData* data = nullptr;
if (JvmtiErrorToException(env,
jvmti_env,
jvmti_env->Allocate(sizeof(TraceData),
reinterpret_cast<unsigned char**>(&data)))) {
return;
}
memset(data, 0, sizeof(TraceData));
if (JvmtiErrorToException(env, jvmti_env,
jvmti_env->CreateRawMonitor("Trace monitor", &data->trace_mon))) {
return;
}
data->test_klass = reinterpret_cast<jclass>(env->NewGlobalRef(klass));
data->enter_method = enter != nullptr ? env->FromReflectedMethod(enter) : nullptr;
data->exit_method = exit != nullptr ? env->FromReflectedMethod(exit) : nullptr;
data->field_access = field_access != nullptr ? env->FromReflectedMethod(field_access) : nullptr;
data->field_modify = field_modify != nullptr ? env->FromReflectedMethod(field_modify) : nullptr;
data->single_step = single_step != nullptr ? env->FromReflectedMethod(single_step) : nullptr;
data->thread_start = thread_start != nullptr ? env->FromReflectedMethod(thread_start) : nullptr;
data->thread_end = thread_end != nullptr ? env->FromReflectedMethod(thread_end) : nullptr;
TraceData* old_data = nullptr;
if (JvmtiErrorToException(env, jvmti_env,
jvmti_env->GetEnvironmentLocalStorage(
reinterpret_cast<void**>(&old_data)))) {
return;
} else if (old_data != nullptr && old_data->test_klass != nullptr) {
ScopedLocalRef<jclass> rt_exception(env, env->FindClass("java/lang/RuntimeException"));
env->ThrowNew(rt_exception.get(), "Environment already has local storage set!");
return;
}
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEnvironmentLocalStorage(data))) {
return;
}
current_callbacks.MethodEntry = methodEntryCB;
current_callbacks.MethodExit = methodExitCB;
current_callbacks.FieldAccess = fieldAccessCB;
current_callbacks.FieldModification = fieldModificationCB;
current_callbacks.ClassPrepare = classPrepareCB;
current_callbacks.SingleStep = singleStepCB;
current_callbacks.ThreadStart = threadStartCB;
current_callbacks.ThreadEnd = threadEndCB;
if (JvmtiErrorToException(env,
jvmti_env,
jvmti_env->SetEventCallbacks(¤t_callbacks,
sizeof(current_callbacks)))) {
return;
}
if (enter != nullptr &&
JvmtiErrorToException(env,
jvmti_env,
jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
JVMTI_EVENT_METHOD_ENTRY,
thr))) {
return;
}
if (exit != nullptr &&
JvmtiErrorToException(env,
jvmti_env,
jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
JVMTI_EVENT_METHOD_EXIT,
thr))) {
return;
}
if (field_access != nullptr &&
JvmtiErrorToException(env,
jvmti_env,
jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
JVMTI_EVENT_FIELD_ACCESS,
thr))) {
return;
}
if (field_modify != nullptr &&
JvmtiErrorToException(env,
jvmti_env,
jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
JVMTI_EVENT_FIELD_MODIFICATION,
thr))) {
return;
}
if (single_step != nullptr &&
JvmtiErrorToException(env,
jvmti_env,
jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
JVMTI_EVENT_SINGLE_STEP,
thr))) {
return;
}
if (thread_start != nullptr &&
JvmtiErrorToException(env,
jvmti_env,
jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
JVMTI_EVENT_THREAD_START,
thr))) {
return;
}
if (thread_end != nullptr &&
JvmtiErrorToException(env,
jvmti_env,
jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
JVMTI_EVENT_THREAD_END,
thr))) {
return;
}
}
extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing(
JNIEnv* env,
jclass trace,
jclass klass,
jobject enter,
jobject exit,
jobject field_access,
jobject field_modify,
jobject single_step,
jthread thr) {
Java_art_Trace_enableTracing2(env,
trace,
klass,
enter,
exit,
field_access,
field_modify,
single_step,
/* thread_start */ nullptr,
/* thread_end */ nullptr,
thr);
return;
}
extern "C" JNIEXPORT void JNICALL Java_art_Trace_disableTracing(
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) {
TraceData* data = nullptr;
if (JvmtiErrorToException(
env, jvmti_env, jvmti_env->GetEnvironmentLocalStorage(reinterpret_cast<void**>(&data)))) {
return;
}
// If data is null then we haven't ever enabled tracing so we don't need to do anything.
if (data == nullptr || data->test_klass == nullptr) {
return;
}
ScopedLocalRef<jthrowable> err(env, nullptr);
// First disable all the events.
if (JvmtiErrorToException(env, jvmti_env,
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
JVMTI_EVENT_FIELD_ACCESS,
thr))) {
env->ExceptionDescribe();
err.reset(env->ExceptionOccurred());
env->ExceptionClear();
}
if (JvmtiErrorToException(env, jvmti_env,
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
JVMTI_EVENT_FIELD_MODIFICATION,
thr))) {
env->ExceptionDescribe();
err.reset(env->ExceptionOccurred());
env->ExceptionClear();
}
if (JvmtiErrorToException(env, jvmti_env,
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
JVMTI_EVENT_METHOD_ENTRY,
thr))) {
env->ExceptionDescribe();
err.reset(env->ExceptionOccurred());
env->ExceptionClear();
}
if (JvmtiErrorToException(env, jvmti_env,
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
JVMTI_EVENT_METHOD_EXIT,
thr))) {
env->ExceptionDescribe();
err.reset(env->ExceptionOccurred());
env->ExceptionClear();
}
if (JvmtiErrorToException(env, jvmti_env,
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
JVMTI_EVENT_SINGLE_STEP,
thr))) {
env->ExceptionDescribe();
err.reset(env->ExceptionOccurred());
env->ExceptionClear();
}
if (JvmtiErrorToException(env, jvmti_env,
jvmti_env->RawMonitorEnter(data->trace_mon))) {
return;
}
// Clear test_klass so we know this isn't being used
env->DeleteGlobalRef(data->test_klass);
data->test_klass = nullptr;
if (JvmtiErrorToException(env,
jvmti_env,
jvmti_env->RawMonitorExit(data->trace_mon))) {
return;
}
if (err.get() != nullptr) {
env->Throw(err.get());
}
}
} // namespace common_trace
} // namespace art