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
 |