| /* |
| * 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 |