804 lines
27 KiB
C++
804 lines
27 KiB
C++
|
/*
|
||
|
* Copyright (C) 2019 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 "suspend_event_helper.h"
|
||
|
|
||
|
#include <inttypes.h>
|
||
|
|
||
|
#include <cstdio>
|
||
|
#include <memory>
|
||
|
#include <string>
|
||
|
#include <vector>
|
||
|
|
||
|
#include "android-base/logging.h"
|
||
|
#include "android-base/stringprintf.h"
|
||
|
|
||
|
#include "jni.h"
|
||
|
#include "jvmti.h"
|
||
|
#include "scoped_local_ref.h"
|
||
|
#include "scoped_utf_chars.h"
|
||
|
|
||
|
// Test infrastructure
|
||
|
#include "jni_binder.h"
|
||
|
#include "jni_helper.h"
|
||
|
#include "jvmti_helper.h"
|
||
|
#include "test_env.h"
|
||
|
#include "ti_macros.h"
|
||
|
|
||
|
namespace art {
|
||
|
namespace common_suspend_event {
|
||
|
|
||
|
struct TestData {
|
||
|
jlocation target_loc;
|
||
|
jmethodID target_method;
|
||
|
jclass target_klass;
|
||
|
jfieldID target_field;
|
||
|
jrawMonitorID notify_monitor;
|
||
|
jint frame_pop_offset;
|
||
|
jmethodID frame_pop_setup_method;
|
||
|
std::vector<std::string> interesting_classes;
|
||
|
bool hit_location;
|
||
|
|
||
|
TestData(jvmtiEnv* jvmti,
|
||
|
JNIEnv* env,
|
||
|
jlocation loc,
|
||
|
jobject meth,
|
||
|
jclass klass,
|
||
|
jobject field,
|
||
|
jobject setup_meth,
|
||
|
jint pop_offset,
|
||
|
const std::vector<std::string>&& interesting)
|
||
|
: target_loc(loc), target_method(meth != nullptr ? env->FromReflectedMethod(meth) : nullptr),
|
||
|
target_klass(reinterpret_cast<jclass>(env->NewGlobalRef(klass))),
|
||
|
target_field(field != nullptr ? env->FromReflectedField(field) : nullptr),
|
||
|
frame_pop_offset(pop_offset),
|
||
|
frame_pop_setup_method(setup_meth != nullptr ? env->FromReflectedMethod(setup_meth)
|
||
|
: nullptr),
|
||
|
interesting_classes(interesting), hit_location(false) {
|
||
|
JvmtiErrorToException(
|
||
|
env, jvmti, jvmti->CreateRawMonitor("SuspendStopMonitor", ¬ify_monitor));
|
||
|
}
|
||
|
|
||
|
void PerformSuspend(jvmtiEnv* jvmti, JNIEnv* env) {
|
||
|
// Wake up the waiting thread.
|
||
|
JvmtiErrorToException(env, jvmti, jvmti->RawMonitorEnter(notify_monitor));
|
||
|
hit_location = true;
|
||
|
JvmtiErrorToException(env, jvmti, jvmti->RawMonitorNotifyAll(notify_monitor));
|
||
|
JvmtiErrorToException(env, jvmti, jvmti->RawMonitorExit(notify_monitor));
|
||
|
// Suspend ourself
|
||
|
jvmti->SuspendThread(nullptr);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void PerformSuspension(jvmtiEnv* jvmti, JNIEnv* env) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti->GetThreadLocalStorage(/* thread */ nullptr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
data->PerformSuspend(jvmti, env);
|
||
|
}
|
||
|
|
||
|
void JNICALL
|
||
|
cbSingleStep(jvmtiEnv* jvmti, JNIEnv* env, jthread thr, jmethodID meth, jlocation loc) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti, jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (meth != data->target_method || loc != data->target_loc) {
|
||
|
return;
|
||
|
}
|
||
|
data->PerformSuspend(jvmti, env);
|
||
|
}
|
||
|
|
||
|
void JNICALL cbExceptionCatch(jvmtiEnv* jvmti,
|
||
|
JNIEnv* env,
|
||
|
jthread thr,
|
||
|
jmethodID method,
|
||
|
jlocation location ATTRIBUTE_UNUSED,
|
||
|
jobject exception ATTRIBUTE_UNUSED) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti, jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (method != data->target_method) {
|
||
|
return;
|
||
|
}
|
||
|
data->PerformSuspend(jvmti, env);
|
||
|
}
|
||
|
|
||
|
void JNICALL cbException(jvmtiEnv* jvmti,
|
||
|
JNIEnv* env,
|
||
|
jthread thr,
|
||
|
jmethodID method,
|
||
|
jlocation location ATTRIBUTE_UNUSED,
|
||
|
jobject exception ATTRIBUTE_UNUSED,
|
||
|
jmethodID catch_method ATTRIBUTE_UNUSED,
|
||
|
jlocation catch_location ATTRIBUTE_UNUSED) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti, jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (method != data->target_method) {
|
||
|
return;
|
||
|
}
|
||
|
data->PerformSuspend(jvmti, env);
|
||
|
}
|
||
|
|
||
|
void JNICALL cbMethodEntry(jvmtiEnv* jvmti, JNIEnv* env, jthread thr, jmethodID method) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti, jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (method != data->target_method) {
|
||
|
return;
|
||
|
}
|
||
|
data->PerformSuspend(jvmti, env);
|
||
|
}
|
||
|
|
||
|
void JNICALL cbMethodExit(jvmtiEnv* jvmti,
|
||
|
JNIEnv* env,
|
||
|
jthread thr,
|
||
|
jmethodID method,
|
||
|
jboolean was_popped_by_exception ATTRIBUTE_UNUSED,
|
||
|
jvalue return_value ATTRIBUTE_UNUSED) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti, jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (method != data->target_method) {
|
||
|
return;
|
||
|
}
|
||
|
data->PerformSuspend(jvmti, env);
|
||
|
}
|
||
|
|
||
|
void JNICALL cbFieldModification(jvmtiEnv* jvmti,
|
||
|
JNIEnv* env,
|
||
|
jthread thr,
|
||
|
jmethodID method ATTRIBUTE_UNUSED,
|
||
|
jlocation location ATTRIBUTE_UNUSED,
|
||
|
jclass field_klass ATTRIBUTE_UNUSED,
|
||
|
jobject object ATTRIBUTE_UNUSED,
|
||
|
jfieldID field,
|
||
|
char signature_type ATTRIBUTE_UNUSED,
|
||
|
jvalue new_value ATTRIBUTE_UNUSED) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti, jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (field != data->target_field) {
|
||
|
// TODO What to do here.
|
||
|
LOG(FATAL) << "Strange, shouldn't get here!";
|
||
|
}
|
||
|
data->PerformSuspend(jvmti, env);
|
||
|
}
|
||
|
|
||
|
void JNICALL cbFieldAccess(jvmtiEnv* jvmti,
|
||
|
JNIEnv* env,
|
||
|
jthread thr,
|
||
|
jmethodID method ATTRIBUTE_UNUSED,
|
||
|
jlocation location ATTRIBUTE_UNUSED,
|
||
|
jclass field_klass,
|
||
|
jobject object ATTRIBUTE_UNUSED,
|
||
|
jfieldID field) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti, jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (field != data->target_field || !env->IsSameObject(field_klass, data->target_klass)) {
|
||
|
// TODO What to do here.
|
||
|
LOG(FATAL) << "Strange, shouldn't get here!";
|
||
|
}
|
||
|
data->PerformSuspend(jvmti, env);
|
||
|
}
|
||
|
|
||
|
void JNICALL
|
||
|
cbBreakpointHit(jvmtiEnv* jvmti, JNIEnv* env, jthread thr, jmethodID method, jlocation loc) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti, jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (data->frame_pop_setup_method == method) {
|
||
|
CHECK(loc == 0) << "We should have stopped at location 0";
|
||
|
if (JvmtiErrorToException(env, jvmti, jvmti->NotifyFramePop(thr, data->frame_pop_offset))) {
|
||
|
return;
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
if (method != data->target_method || loc != data->target_loc) {
|
||
|
// TODO What to do here.
|
||
|
LOG(FATAL) << "Strange, shouldn't get here!";
|
||
|
}
|
||
|
data->PerformSuspend(jvmti, env);
|
||
|
}
|
||
|
|
||
|
void JNICALL cbFramePop(jvmtiEnv* jvmti,
|
||
|
JNIEnv* env,
|
||
|
jthread thr,
|
||
|
jmethodID method ATTRIBUTE_UNUSED,
|
||
|
jboolean was_popped_by_exception ATTRIBUTE_UNUSED) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti, jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
data->PerformSuspend(jvmti, env);
|
||
|
}
|
||
|
|
||
|
void JNICALL cbClassLoadOrPrepare(jvmtiEnv* jvmti, JNIEnv* env, jthread thr, jclass klass) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti, jvmti->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
char* name;
|
||
|
if (JvmtiErrorToException(env, jvmti, jvmti->GetClassSignature(klass, &name, nullptr))) {
|
||
|
return;
|
||
|
}
|
||
|
std::string name_str(name);
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti, jvmti->Deallocate(reinterpret_cast<unsigned char*>(name)))) {
|
||
|
return;
|
||
|
}
|
||
|
if (std::find(data->interesting_classes.cbegin(), data->interesting_classes.cend(), name_str) !=
|
||
|
data->interesting_classes.cend()) {
|
||
|
data->PerformSuspend(jvmti, env);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL Java_art_SuspendEvents_setupTest(JNIEnv* env,
|
||
|
jclass klass ATTRIBUTE_UNUSED) {
|
||
|
jvmtiCapabilities caps;
|
||
|
memset(&caps, 0, sizeof(caps));
|
||
|
// Most of these will already be there but might as well be complete.
|
||
|
caps.can_pop_frame = 1;
|
||
|
caps.can_force_early_return = 1;
|
||
|
caps.can_generate_single_step_events = 1;
|
||
|
caps.can_generate_breakpoint_events = 1;
|
||
|
caps.can_suspend = 1;
|
||
|
caps.can_generate_method_entry_events = 1;
|
||
|
caps.can_generate_method_exit_events = 1;
|
||
|
caps.can_generate_monitor_events = 1;
|
||
|
caps.can_generate_exception_events = 1;
|
||
|
caps.can_generate_frame_pop_events = 1;
|
||
|
caps.can_generate_field_access_events = 1;
|
||
|
caps.can_generate_field_modification_events = 1;
|
||
|
caps.can_redefine_classes = 1;
|
||
|
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps))) {
|
||
|
return;
|
||
|
}
|
||
|
jvmtiEventCallbacks cb;
|
||
|
memset(&cb, 0, sizeof(cb));
|
||
|
// TODO Add the rest of these.
|
||
|
cb.Breakpoint = cbBreakpointHit;
|
||
|
cb.SingleStep = cbSingleStep;
|
||
|
cb.FieldAccess = cbFieldAccess;
|
||
|
cb.FieldModification = cbFieldModification;
|
||
|
cb.MethodEntry = cbMethodEntry;
|
||
|
cb.MethodExit = cbMethodExit;
|
||
|
cb.Exception = cbException;
|
||
|
cb.ExceptionCatch = cbExceptionCatch;
|
||
|
cb.FramePop = cbFramePop;
|
||
|
cb.ClassLoad = cbClassLoadOrPrepare;
|
||
|
cb.ClassPrepare = cbClassLoadOrPrepare;
|
||
|
JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb)));
|
||
|
}
|
||
|
|
||
|
static bool DeleteTestData(JNIEnv* env, jthread thr, TestData* data) {
|
||
|
env->DeleteGlobalRef(data->target_klass);
|
||
|
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetThreadLocalStorage(thr, nullptr))) {
|
||
|
return false;
|
||
|
}
|
||
|
return JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->Deallocate(reinterpret_cast<uint8_t*>(data)));
|
||
|
}
|
||
|
|
||
|
static TestData* SetupTestData(JNIEnv* env,
|
||
|
jobject meth,
|
||
|
jlocation loc,
|
||
|
jclass target_klass,
|
||
|
jobject field,
|
||
|
jobject setup_meth,
|
||
|
jint pop_offset,
|
||
|
const std::vector<std::string>&& interesting_names) {
|
||
|
void* data_ptr;
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->Allocate(sizeof(TestData), reinterpret_cast<uint8_t**>(&data_ptr)))) {
|
||
|
return nullptr;
|
||
|
}
|
||
|
data = new (data_ptr) TestData(jvmti_env,
|
||
|
env,
|
||
|
loc,
|
||
|
meth,
|
||
|
target_klass,
|
||
|
field,
|
||
|
setup_meth,
|
||
|
pop_offset,
|
||
|
std::move(interesting_names));
|
||
|
if (env->ExceptionCheck()) {
|
||
|
env->DeleteGlobalRef(data->target_klass);
|
||
|
jvmti_env->Deallocate(reinterpret_cast<uint8_t*>(data));
|
||
|
return nullptr;
|
||
|
}
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
static TestData* SetupTestData(JNIEnv* env,
|
||
|
jobject meth,
|
||
|
jlocation loc,
|
||
|
jclass target_klass,
|
||
|
jobject field,
|
||
|
jobject setup_meth,
|
||
|
jint pop_offset) {
|
||
|
std::vector<std::string> empty;
|
||
|
return SetupTestData(
|
||
|
env, meth, loc, target_klass, field, setup_meth, pop_offset, std::move(empty));
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL
|
||
|
Java_art_SuspendEvents_setupSuspendClassEvent(JNIEnv* env,
|
||
|
jclass klass ATTRIBUTE_UNUSED,
|
||
|
jint event_num,
|
||
|
jobjectArray interesting_names,
|
||
|
jthread thr) {
|
||
|
CHECK(event_num == JVMTI_EVENT_CLASS_LOAD || event_num == JVMTI_EVENT_CLASS_PREPARE);
|
||
|
std::vector<std::string> names;
|
||
|
jint cnt = env->GetArrayLength(interesting_names);
|
||
|
for (jint i = 0; i < cnt; i++) {
|
||
|
env->PushLocalFrame(1);
|
||
|
jstring name_obj = reinterpret_cast<jstring>(env->GetObjectArrayElement(interesting_names, i));
|
||
|
const char* name_chr = env->GetStringUTFChars(name_obj, nullptr);
|
||
|
names.push_back(std::string(name_chr));
|
||
|
env->ReleaseStringUTFChars(name_obj, name_chr);
|
||
|
env->PopLocalFrame(nullptr);
|
||
|
}
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data == nullptr) << "Data was not cleared!";
|
||
|
data = SetupTestData(env, nullptr, 0, nullptr, nullptr, nullptr, 0, std::move(names));
|
||
|
if (data == nullptr) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetThreadLocalStorage(thr, data))) {
|
||
|
return;
|
||
|
}
|
||
|
JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, static_cast<jvmtiEvent>(event_num), thr));
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL Java_art_SuspendEvents_clearSuspendClassEvent(
|
||
|
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_CLASS_LOAD, thr))) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_CLASS_PREPARE, thr))) {
|
||
|
return;
|
||
|
}
|
||
|
DeleteTestData(env, thr, data);
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL Java_art_SuspendEvents_setupSuspendSingleStepAt(
|
||
|
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject meth, jlocation loc, jthread thr) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data == nullptr) << "Data was not cleared!";
|
||
|
data = SetupTestData(env, meth, loc, nullptr, nullptr, nullptr, 0);
|
||
|
if (data == nullptr) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetThreadLocalStorage(thr, data))) {
|
||
|
return;
|
||
|
}
|
||
|
JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_SINGLE_STEP, thr));
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL Java_art_SuspendEvents_clearSuspendSingleStepFor(
|
||
|
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_SINGLE_STEP, thr))) {
|
||
|
return;
|
||
|
}
|
||
|
DeleteTestData(env, thr, data);
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL Java_art_SuspendEvents_setupSuspendPopFrameEvent(
|
||
|
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jint offset, jobject breakpoint_func, jthread thr) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data == nullptr) << "Data was not cleared!";
|
||
|
data = SetupTestData(env, nullptr, 0, nullptr, nullptr, breakpoint_func, offset);
|
||
|
CHECK(data != nullptr);
|
||
|
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetThreadLocalStorage(thr, data))) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_FRAME_POP, thr))) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_BREAKPOINT, thr))) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->SetBreakpoint(data->frame_pop_setup_method, 0))) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL Java_art_SuspendEvents_clearSuspendPopFrameEvent(
|
||
|
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_FRAME_POP, thr))) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_BREAKPOINT, thr))) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->ClearBreakpoint(data->frame_pop_setup_method, 0))) {
|
||
|
return;
|
||
|
}
|
||
|
DeleteTestData(env, thr, data);
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL Java_art_SuspendEvents_setupSuspendBreakpointFor(
|
||
|
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject meth, jlocation loc, jthread thr) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data == nullptr) << "Data was not cleared!";
|
||
|
data = SetupTestData(env, meth, loc, nullptr, nullptr, nullptr, 0);
|
||
|
if (data == nullptr) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetThreadLocalStorage(thr, data))) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_BREAKPOINT, thr))) {
|
||
|
return;
|
||
|
}
|
||
|
JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->SetBreakpoint(data->target_method, data->target_loc));
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL Java_art_SuspendEvents_clearSuspendBreakpointFor(
|
||
|
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_BREAKPOINT, thr))) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->ClearBreakpoint(data->target_method, data->target_loc))) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetThreadLocalStorage(thr, nullptr))) {
|
||
|
return;
|
||
|
}
|
||
|
DeleteTestData(env, thr, data);
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL Java_art_SuspendEvents_setupSuspendExceptionEvent(
|
||
|
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject method, jboolean is_catch, jthread thr) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data == nullptr) << "Data was not cleared!";
|
||
|
data = SetupTestData(env, method, 0, nullptr, nullptr, nullptr, 0);
|
||
|
if (data == nullptr) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetThreadLocalStorage(thr, data))) {
|
||
|
return;
|
||
|
}
|
||
|
JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(
|
||
|
JVMTI_ENABLE, is_catch ? JVMTI_EVENT_EXCEPTION_CATCH : JVMTI_EVENT_EXCEPTION, thr));
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL Java_art_SuspendEvents_clearSuspendExceptionEvent(
|
||
|
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_EXCEPTION_CATCH, thr))) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_EXCEPTION, thr))) {
|
||
|
return;
|
||
|
}
|
||
|
DeleteTestData(env, thr, data);
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL Java_art_SuspendEvents_setupSuspendMethodEvent(
|
||
|
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject method, jboolean enter, jthread thr) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data == nullptr) << "Data was not cleared!";
|
||
|
data = SetupTestData(env, method, 0, nullptr, nullptr, nullptr, 0);
|
||
|
if (data == nullptr) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetThreadLocalStorage(thr, data))) {
|
||
|
return;
|
||
|
}
|
||
|
JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(
|
||
|
JVMTI_ENABLE, enter ? JVMTI_EVENT_METHOD_ENTRY : JVMTI_EVENT_METHOD_EXIT, thr));
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL Java_art_SuspendEvents_clearSuspendMethodEvent(
|
||
|
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_METHOD_EXIT, thr))) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_METHOD_ENTRY, thr))) {
|
||
|
return;
|
||
|
}
|
||
|
DeleteTestData(env, thr, data);
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL
|
||
|
Java_art_SuspendEvents_setupFieldSuspendFor(JNIEnv* env,
|
||
|
jclass klass ATTRIBUTE_UNUSED,
|
||
|
jclass target_klass,
|
||
|
jobject field,
|
||
|
jboolean access,
|
||
|
jthread thr) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data == nullptr) << "Data was not cleared!";
|
||
|
data = SetupTestData(env, nullptr, 0, target_klass, field, nullptr, 0);
|
||
|
if (data == nullptr) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetThreadLocalStorage(thr, data))) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(
|
||
|
JVMTI_ENABLE,
|
||
|
access ? JVMTI_EVENT_FIELD_ACCESS : JVMTI_EVENT_FIELD_MODIFICATION,
|
||
|
thr))) {
|
||
|
return;
|
||
|
}
|
||
|
if (access) {
|
||
|
JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->SetFieldAccessWatch(data->target_klass, data->target_field));
|
||
|
} else {
|
||
|
JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetFieldModificationWatch(data->target_klass, data->target_field));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL Java_art_SuspendEvents_clearFieldSuspendFor(
|
||
|
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(JVMTI_DISABLE, JVMTI_EVENT_FIELD_ACCESS, thr))) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->SetEventNotificationMode(
|
||
|
JVMTI_DISABLE, JVMTI_EVENT_FIELD_MODIFICATION, thr))) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->ClearFieldModificationWatch(data->target_klass, data->target_field)) &&
|
||
|
JvmtiErrorToException(
|
||
|
env,
|
||
|
jvmti_env,
|
||
|
jvmti_env->ClearFieldAccessWatch(data->target_klass, data->target_field))) {
|
||
|
return;
|
||
|
} else {
|
||
|
env->ExceptionClear();
|
||
|
}
|
||
|
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetThreadLocalStorage(thr, nullptr))) {
|
||
|
return;
|
||
|
}
|
||
|
DeleteTestData(env, thr, data);
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL Java_art_SuspendEvents_setupWaitForNativeCall(
|
||
|
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data == nullptr) << "Data was not cleared!";
|
||
|
data = SetupTestData(env, nullptr, 0, nullptr, nullptr, nullptr, 0);
|
||
|
if (data == nullptr) {
|
||
|
return;
|
||
|
}
|
||
|
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetThreadLocalStorage(thr, data))) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL Java_art_SuspendEvents_clearWaitForNativeCall(
|
||
|
JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetThreadLocalStorage(thr, nullptr))) {
|
||
|
return;
|
||
|
}
|
||
|
DeleteTestData(env, thr, data);
|
||
|
}
|
||
|
|
||
|
extern "C" JNIEXPORT void JNICALL
|
||
|
Java_art_SuspendEvents_waitForSuspendHit(JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr) {
|
||
|
TestData* data;
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->GetThreadLocalStorage(thr, reinterpret_cast<void**>(&data)))) {
|
||
|
return;
|
||
|
}
|
||
|
CHECK(data != nullptr);
|
||
|
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorEnter(data->notify_monitor))) {
|
||
|
return;
|
||
|
}
|
||
|
while (!data->hit_location) {
|
||
|
if (JvmtiErrorToException(
|
||
|
env, jvmti_env, jvmti_env->RawMonitorWait(data->notify_monitor, -1))) {
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
if (JvmtiErrorToException(env, jvmti_env, jvmti_env->RawMonitorExit(data->notify_monitor))) {
|
||
|
return;
|
||
|
}
|
||
|
jint state = 0;
|
||
|
while (!JvmtiErrorToException(env, jvmti_env, jvmti_env->GetThreadState(thr, &state)) &&
|
||
|
(state & JVMTI_THREAD_STATE_SUSPENDED) == 0) {
|
||
|
}
|
||
|
}
|
||
|
} // namespace common_suspend_event
|
||
|
} // namespace art
|