diff options
-rw-r--r-- | openjdkjvmti/deopt_manager.cc | 9 | ||||
-rw-r--r-- | openjdkjvmti/events.cc | 111 | ||||
-rw-r--r-- | openjdkjvmti/events.h | 18 | ||||
-rw-r--r-- | runtime/instrumentation.cc | 33 | ||||
-rw-r--r-- | runtime/instrumentation.h | 21 | ||||
-rw-r--r-- | runtime/instrumentation_test.cc | 9 | ||||
-rw-r--r-- | test/1962-multi-thread-events/expected.txt | 4 | ||||
-rw-r--r-- | test/1962-multi-thread-events/info.txt | 5 | ||||
-rw-r--r-- | test/1962-multi-thread-events/multi_thread_events.cc | 91 | ||||
-rwxr-xr-x | test/1962-multi-thread-events/run | 17 | ||||
-rw-r--r-- | test/1962-multi-thread-events/src/Main.java | 21 | ||||
-rw-r--r-- | test/1962-multi-thread-events/src/art/Test1962.java | 83 | ||||
-rw-r--r-- | test/Android.bp | 2 |
13 files changed, 385 insertions, 39 deletions
diff --git a/openjdkjvmti/deopt_manager.cc b/openjdkjvmti/deopt_manager.cc index a7feba81e1..3b04ed8be8 100644 --- a/openjdkjvmti/deopt_manager.cc +++ b/openjdkjvmti/deopt_manager.cc @@ -45,6 +45,7 @@ #include "gc/collector_type.h" #include "gc/heap.h" #include "gc/scoped_gc_critical_section.h" +#include "instrumentation.h" #include "jit/jit.h" #include "jni/jni_internal.h" #include "mirror/class-inl.h" @@ -472,8 +473,12 @@ void DeoptManager::AddDeoptimizationRequester() { deopter_count_++; if (deopter_count_ == 1) { ScopedDeoptimizationContext sdc(self, this); - art::Runtime::Current()->GetInstrumentation()->EnableDeoptimization(); - return; + art::instrumentation::Instrumentation* instrumentation = + art::Runtime::Current()->GetInstrumentation(); + // Enable deoptimization + instrumentation->EnableDeoptimization(); + // Tell instrumentation we will be deopting single threads. + instrumentation->EnableSingleThreadDeopt(); } else { deoptimization_status_lock_.ExclusiveUnlock(self); } diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc index 16abf41372..40e8b801c0 100644 --- a/openjdkjvmti/events.cc +++ b/openjdkjvmti/events.cc @@ -974,6 +974,8 @@ static uint32_t GetInstrumentationEventsFor(ArtJvmtiEvent event) { } enum class DeoptRequirement { + // No deoptimization work required. + kNone, // Limited/no deopt required. kLimited, // A single thread must be put into interpret only. @@ -998,19 +1000,38 @@ static DeoptRequirement GetDeoptRequirement(ArtJvmtiEvent event, jthread thread) case ArtJvmtiEvent::kSingleStep: case ArtJvmtiEvent::kFramePop: return thread == nullptr ? DeoptRequirement::kFull : DeoptRequirement::kThread; - default: - LOG(FATAL) << "Unexpected event type!"; - UNREACHABLE(); + case ArtJvmtiEvent::kVmInit: + case ArtJvmtiEvent::kVmDeath: + case ArtJvmtiEvent::kThreadStart: + case ArtJvmtiEvent::kThreadEnd: + case ArtJvmtiEvent::kClassFileLoadHookNonRetransformable: + case ArtJvmtiEvent::kClassLoad: + case ArtJvmtiEvent::kClassPrepare: + case ArtJvmtiEvent::kVmStart: + case ArtJvmtiEvent::kNativeMethodBind: + case ArtJvmtiEvent::kCompiledMethodLoad: + case ArtJvmtiEvent::kCompiledMethodUnload: + case ArtJvmtiEvent::kDynamicCodeGenerated: + case ArtJvmtiEvent::kDataDumpRequest: + case ArtJvmtiEvent::kMonitorWait: + case ArtJvmtiEvent::kMonitorWaited: + case ArtJvmtiEvent::kMonitorContendedEnter: + case ArtJvmtiEvent::kMonitorContendedEntered: + case ArtJvmtiEvent::kResourceExhausted: + case ArtJvmtiEvent::kGarbageCollectionStart: + case ArtJvmtiEvent::kGarbageCollectionFinish: + case ArtJvmtiEvent::kObjectFree: + case ArtJvmtiEvent::kVmObjectAlloc: + case ArtJvmtiEvent::kClassFileLoadHookRetransformable: + case ArtJvmtiEvent::kDdmPublishChunk: + return DeoptRequirement::kNone; } } -jvmtiError EventHandler::SetupTraceListener(JvmtiMethodTraceListener* listener, - ArtJvmtiEvent event, - jthread thread, - bool enable) { +jvmtiError EventHandler::HandleEventDeopt(ArtJvmtiEvent event, jthread thread, bool enable) { DeoptRequirement deopt_req = GetDeoptRequirement(event, thread); // Make sure we can deopt. - { + if (deopt_req != DeoptRequirement::kNone) { art::ScopedObjectAccess soa(art::Thread::Current()); DeoptManager* deopt_manager = DeoptManager::Get(); jvmtiError err = OK; @@ -1047,7 +1068,12 @@ jvmtiError EventHandler::SetupTraceListener(JvmtiMethodTraceListener* listener, } } } + return OK; +} +void EventHandler::SetupTraceListener(JvmtiMethodTraceListener* listener, + ArtJvmtiEvent event, + bool enable) { // Add the actual listeners. uint32_t new_events = GetInstrumentationEventsFor(event); if (new_events == art::instrumentation::Instrumentation::kDexPcMoved) { @@ -1060,7 +1086,7 @@ jvmtiError EventHandler::SetupTraceListener(JvmtiMethodTraceListener* listener, if (IsEventEnabledAnywhere(other)) { // The event needs to be kept around/is already enabled by the other jvmti event that uses the // same instrumentation event. - return OK; + return; } } art::ScopedThreadStateChange stsc(art::Thread::Current(), art::ThreadState::kNative); @@ -1071,7 +1097,7 @@ jvmtiError EventHandler::SetupTraceListener(JvmtiMethodTraceListener* listener, } else { instr->RemoveListener(listener, new_events); } - return OK; + return; } // Makes sure that all compiled methods are AsyncDeoptimizable so we can deoptimize (and force to @@ -1127,11 +1153,10 @@ bool EventHandler::OtherMonitorEventsEnabledAnywhere(ArtJvmtiEvent event) { return false; } -jvmtiError EventHandler::SetupFramePopTraceListener(jthread thread, bool enable) { +void EventHandler::SetupFramePopTraceListener(bool enable) { if (enable) { frame_pop_enabled = true; - return SetupTraceListener( - method_trace_listener_.get(), ArtJvmtiEvent::kFramePop, thread, enable); + SetupTraceListener(method_trace_listener_.get(), ArtJvmtiEvent::kFramePop, enable); } else { // remove the listener if we have no outstanding frames. { @@ -1140,38 +1165,37 @@ jvmtiError EventHandler::SetupFramePopTraceListener(jthread thread, bool enable) art::ReaderMutexLock event_mu(art::Thread::Current(), env->event_info_mutex_); if (!env->notify_frames.empty()) { // Leaving FramePop listener since there are unsent FramePop events. - return OK; + return; } } frame_pop_enabled = false; } - return SetupTraceListener( - method_trace_listener_.get(), ArtJvmtiEvent::kFramePop, thread, enable); + SetupTraceListener(method_trace_listener_.get(), ArtJvmtiEvent::kFramePop, enable); } } // Handle special work for the given event type, if necessary. -jvmtiError EventHandler::HandleEventType(ArtJvmtiEvent event, jthread thread, bool enable) { +void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { switch (event) { case ArtJvmtiEvent::kDdmPublishChunk: SetupDdmTracking(ddm_listener_.get(), enable); - return OK; + return; case ArtJvmtiEvent::kVmObjectAlloc: SetupObjectAllocationTracking(alloc_listener_.get(), enable); - return OK; + return; case ArtJvmtiEvent::kGarbageCollectionStart: case ArtJvmtiEvent::kGarbageCollectionFinish: SetupGcPauseTracking(gc_pause_listener_.get(), event, enable); - return OK; + return; // FramePop can never be disabled once it's been turned on if it was turned off with outstanding // pop-events since we would either need to deal with dangling pointers or have missed events. case ArtJvmtiEvent::kFramePop: if (enable && frame_pop_enabled) { // The frame-pop event was held on by pending events so we don't need to do anything. - break; } else { - return SetupFramePopTraceListener(thread, enable); + SetupFramePopTraceListener(enable); } + return; case ArtJvmtiEvent::kMethodEntry: case ArtJvmtiEvent::kMethodExit: case ArtJvmtiEvent::kFieldAccess: @@ -1180,7 +1204,8 @@ jvmtiError EventHandler::HandleEventType(ArtJvmtiEvent event, jthread thread, bo case ArtJvmtiEvent::kExceptionCatch: case ArtJvmtiEvent::kBreakpoint: case ArtJvmtiEvent::kSingleStep: - return SetupTraceListener(method_trace_listener_.get(), event, thread, enable); + SetupTraceListener(method_trace_listener_.get(), event, enable); + return; case ArtJvmtiEvent::kMonitorContendedEnter: case ArtJvmtiEvent::kMonitorContendedEntered: case ArtJvmtiEvent::kMonitorWait: @@ -1188,11 +1213,11 @@ jvmtiError EventHandler::HandleEventType(ArtJvmtiEvent event, jthread thread, bo if (!OtherMonitorEventsEnabledAnywhere(event)) { SetupMonitorListener(monitor_listener_.get(), park_listener_.get(), enable); } - return OK; + return; default: break; } - return OK; + return; } // Checks to see if the env has the capabilities associated with the given event. @@ -1276,8 +1301,15 @@ jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env, art::Thread* self = art::Thread::Current(); art::Thread* target = nullptr; ScopedNoUserCodeSuspension snucs(self); + // The overall state across all threads and jvmtiEnvs. This is used to control the state of the + // instrumentation handlers since we only want each added once. bool old_state; bool new_state; + // The state for just the current 'thread' (including null) across all jvmtiEnvs. This is used to + // control the deoptimization state since we do refcounting for that and need to perform different + // actions depending on if the event is limited to a single thread or global. + bool old_thread_state; + bool new_thread_state; { // From now on we know we cannot get suspended by user-code. // NB This does a SuspendCheck (during thread state change) so we need to @@ -1296,28 +1328,55 @@ jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env, } } + art::WriterMutexLock ei_mu(self, env->event_info_mutex_); + old_thread_state = GetThreadEventState(event, target); old_state = global_mask.Test(event); if (mode == JVMTI_ENABLE) { env->event_masks.EnableEvent(env, target, event); global_mask.Set(event); new_state = true; + new_thread_state = true; + DCHECK(GetThreadEventState(event, target)); } else { DCHECK_EQ(mode, JVMTI_DISABLE); env->event_masks.DisableEvent(env, target, event); RecalculateGlobalEventMaskLocked(event); new_state = global_mask.Test(event); + new_thread_state = GetThreadEventState(event, target); + DCHECK(new_state || !new_thread_state); } } // Handle any special work required for the event type. We still have the // user_code_suspend_count_lock_ so there won't be any interleaving here. if (new_state != old_state) { - return HandleEventType(event, thread, mode == JVMTI_ENABLE); + HandleEventType(event, mode == JVMTI_ENABLE); + } + if (old_thread_state != new_thread_state) { + return HandleEventDeopt(event, thread, new_thread_state); } return OK; } +bool EventHandler::GetThreadEventState(ArtJvmtiEvent event, art::Thread* thread) { + for (ArtJvmTiEnv* stored_env : envs) { + if (stored_env == nullptr) { + continue; + } + auto& masks = stored_env->event_masks; + if (thread == nullptr && masks.global_event_mask.Test(event)) { + return true; + } else if (thread != nullptr) { + EventMask* mask = masks.GetEventMaskOrNull(thread); + if (mask != nullptr && mask->Test(event)) { + return true; + } + } + } + return false; +} + void EventHandler::HandleBreakpointEventsChanged(bool added) { if (added) { DeoptManager::Get()->AddDeoptimizationRequester(); diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h index 2c3c7a0ba5..d54c87aceb 100644 --- a/openjdkjvmti/events.h +++ b/openjdkjvmti/events.h @@ -247,13 +247,10 @@ class EventHandler { REQUIRES(!envs_lock_); private: - jvmtiError SetupTraceListener(JvmtiMethodTraceListener* listener, - ArtJvmtiEvent event, - jthread thread, - bool enable); + void SetupTraceListener(JvmtiMethodTraceListener* listener, ArtJvmtiEvent event, bool enable); // Specifically handle the FramePop event which it might not always be possible to turn off. - jvmtiError SetupFramePopTraceListener(jthread thread, bool enable); + void SetupFramePopTraceListener(bool enable); template <ArtJvmtiEvent kEvent, typename ...Args> ALWAYS_INLINE @@ -293,6 +290,11 @@ class EventHandler { ALWAYS_INLINE inline void RecalculateGlobalEventMaskLocked(ArtJvmtiEvent event) REQUIRES_SHARED(envs_lock_); + // Returns whether there are any active requests for the given event on the given thread. This + // should only be used while modifying the events for a thread. + bool GetThreadEventState(ArtJvmtiEvent event, art::Thread* thread) + REQUIRES(envs_lock_, art::Locks::thread_list_lock_); + template <ArtJvmtiEvent kEvent> ALWAYS_INLINE inline void DispatchClassFileLoadHookEvent(art::Thread* thread, JNIEnv* jnienv, @@ -313,7 +315,11 @@ class EventHandler { jclass klass) const REQUIRES(!envs_lock_); - jvmtiError HandleEventType(ArtJvmtiEvent event, jthread thread, bool enable); + // Sets up the global state needed for the first/last enable of an event across all threads + void HandleEventType(ArtJvmtiEvent event, bool enable); + // Perform deopts required for enabling the event on the given thread. Null thread indicates + // global event enabled. + jvmtiError HandleEventDeopt(ArtJvmtiEvent event, jthread thread, bool enable); void HandleLocalAccessCapabilityAdded(); void HandleBreakpointEventsChanged(bool enable); diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc index 7d572b6719..57f79487aa 100644 --- a/runtime/instrumentation.cc +++ b/runtime/instrumentation.cc @@ -169,7 +169,8 @@ Instrumentation::Instrumentation() deoptimization_enabled_(false), interpreter_handler_table_(kMainHandlerTable), quick_alloc_entry_points_instrumentation_counter_(0), - alloc_entrypoints_instrumented_(false) { + alloc_entrypoints_instrumented_(false), + can_use_instrumentation_trampolines_(true) { } void Instrumentation::InstallStubsForClass(ObjPtr<mirror::Class> klass) { @@ -698,6 +699,19 @@ bool Instrumentation::RequiresInstrumentationInstallation(InstrumentationLevel n return GetCurrentInstrumentationLevel() != new_level; } +void Instrumentation::UpdateInstrumentationLevels(InstrumentationLevel level) { + if (level == InstrumentationLevel::kInstrumentWithInterpreter) { + can_use_instrumentation_trampolines_ = false; + } + if (UNLIKELY(!can_use_instrumentation_trampolines_)) { + for (auto& p : requested_instrumentation_levels_) { + if (p.second == InstrumentationLevel::kInstrumentWithInstrumentationStubs) { + p.second = InstrumentationLevel::kInstrumentWithInterpreter; + } + } + } +} + void Instrumentation::ConfigureStubs(const char* key, InstrumentationLevel desired_level) { // Store the instrumentation level for this key or remove it. if (desired_level == InstrumentationLevel::kInstrumentNothing) { @@ -708,12 +722,29 @@ void Instrumentation::ConfigureStubs(const char* key, InstrumentationLevel desir requested_instrumentation_levels_.Overwrite(key, desired_level); } + UpdateInstrumentationLevels(desired_level); + UpdateStubs(); +} + +void Instrumentation::EnableSingleThreadDeopt() { + // Single-thread deopt only uses interpreter. + can_use_instrumentation_trampolines_ = false; + UpdateInstrumentationLevels(InstrumentationLevel::kInstrumentWithInterpreter); + UpdateStubs(); +} + +void Instrumentation::UpdateStubs() { // Look for the highest required instrumentation level. InstrumentationLevel requested_level = InstrumentationLevel::kInstrumentNothing; for (const auto& v : requested_instrumentation_levels_) { requested_level = std::max(requested_level, v.second); } + DCHECK(can_use_instrumentation_trampolines_ || + requested_level != InstrumentationLevel::kInstrumentWithInstrumentationStubs) + << "Use trampolines: " << can_use_instrumentation_trampolines_ << " level " + << requested_level; + interpret_only_ = (requested_level == InstrumentationLevel::kInstrumentWithInterpreter) || forced_interpret_only_; diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h index c8ee2a8bfb..fc64c49f7a 100644 --- a/runtime/instrumentation.h +++ b/runtime/instrumentation.h @@ -512,6 +512,13 @@ class Instrumentation { void InstallStubsForMethod(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock()); + // Sets up instrumentation to allow single thread deoptimization using ForceInterpreterCount. + void EnableSingleThreadDeopt() + REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_) + REQUIRES(!Locks::thread_list_lock_, + !Locks::classlinker_classes_lock_, + !GetDeoptimizedMethodsLock()); + // Install instrumentation exit stub on every method of the stack of the given thread. // This is used by the debugger to cause a deoptimization of the thread's stack after updating // local variable(s). @@ -546,6 +553,15 @@ class Instrumentation { REQUIRES(!GetDeoptimizedMethodsLock(), !Locks::thread_list_lock_, !Locks::classlinker_classes_lock_); + void UpdateStubs() REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_) + REQUIRES(!GetDeoptimizedMethodsLock(), + !Locks::thread_list_lock_, + !Locks::classlinker_classes_lock_); + void UpdateInstrumentationLevels(InstrumentationLevel level) + REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_) + REQUIRES(!GetDeoptimizedMethodsLock(), + !Locks::thread_list_lock_, + !Locks::classlinker_classes_lock_); void UpdateInterpreterHandlerTable() REQUIRES(Locks::mutator_lock_) { /* @@ -710,6 +726,11 @@ class Instrumentation { // alloc_entrypoints_instrumented_ change during suspend points. bool alloc_entrypoints_instrumented_; + // If we can use instrumentation trampolines. After the first time we instrument something with + // the interpreter we can no longer use trampolines because it can lead to stack corruption. + // TODO Figure out a way to remove the need for this. + bool can_use_instrumentation_trampolines_; + friend class InstrumentationTest; // For GetCurrentInstrumentationLevel and ConfigureStubs. friend class InstrumentationStackPopper; // For popping instrumentation frames. diff --git a/runtime/instrumentation_test.cc b/runtime/instrumentation_test.cc index d97368931a..cf5d3ed139 100644 --- a/runtime/instrumentation_test.cc +++ b/runtime/instrumentation_test.cc @@ -840,7 +840,8 @@ TEST_F(InstrumentationTest, ConfigureStubs_InterpreterToInstrumentationStubs) { // Configure stubs with instrumentation stubs. CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); - CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, + // Make sure we are still interpreter since going from interpreter->instrumentation is dangerous. + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); // Check we can disable instrumentation. @@ -866,7 +867,7 @@ TEST_F(InstrumentationTest, // Configure stubs with instrumentation stubs again. CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs); - CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); // Check we can disable instrumentation. @@ -970,9 +971,9 @@ TEST_F(InstrumentationTest, MultiConfigureStubs_InterpreterThenInstrumentationSt CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 2U); // 1st client requests instrumentation deactivation but 2nd client still needs - // instrumentation stubs. + // instrumentation stubs. Since we already got interpreter stubs we need to stay there. CheckConfigureStubs(kClientOneKey, Instrumentation::InstrumentationLevel::kInstrumentNothing); - CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInstrumentationStubs, + CHECK_INSTRUMENTATION(Instrumentation::InstrumentationLevel::kInstrumentWithInterpreter, 1U); // 2nd client requests instrumentation deactivation diff --git a/test/1962-multi-thread-events/expected.txt b/test/1962-multi-thread-events/expected.txt new file mode 100644 index 0000000000..841eb8b8ae --- /dev/null +++ b/test/1962-multi-thread-events/expected.txt @@ -0,0 +1,4 @@ +Events on thread 1: + Hit event on T1 Thread +Events on thread 2: + Hit event on T2 Thread diff --git a/test/1962-multi-thread-events/info.txt b/test/1962-multi-thread-events/info.txt new file mode 100644 index 0000000000..75a47b8aec --- /dev/null +++ b/test/1962-multi-thread-events/info.txt @@ -0,0 +1,5 @@ +Tests b/131865028 + +Due to a mistake in the single-thread deopt CL we would incorrectly only update a threads +deoptimization count if it was the first thread of a particular event type to be activated. This +could cause events to be missed. diff --git a/test/1962-multi-thread-events/multi_thread_events.cc b/test/1962-multi-thread-events/multi_thread_events.cc new file mode 100644 index 0000000000..aeb15b0c8c --- /dev/null +++ b/test/1962-multi-thread-events/multi_thread_events.cc @@ -0,0 +1,91 @@ +/* + * 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 <stdio.h> + +#include "android-base/macros.h" + +#include "jni.h" +#include "jvmti.h" +#include "scoped_local_ref.h" + +// Test infrastructure +#include "jni_helper.h" +#include "jvmti_helper.h" +#include "test_env.h" + +namespace art { +namespace Test1962MultiThreadEvents { + +struct BreakpointData { + jobject events; + jmethodID target; +}; +void cbMethodEntry(jvmtiEnv* jvmti, + JNIEnv* env, + jthread thread, + jmethodID method, + jboolean was_exception ATTRIBUTE_UNUSED, + jvalue val ATTRIBUTE_UNUSED) { + BreakpointData* data = nullptr; + if (JvmtiErrorToException( + env, jvmti, jvmti->GetThreadLocalStorage(thread, reinterpret_cast<void**>(&data)))) { + return; + } + if (data->target != method) { + return; + } + jclass klass = env->FindClass("art/Test1962"); + jmethodID handler = + env->GetStaticMethodID(klass, "HandleEvent", "(Ljava/lang/Thread;Ljava/util/List;)V"); + CHECK(data != nullptr); + env->CallStaticVoidMethod(klass, handler, thread, data->events); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test1962_setupTest(JNIEnv* env, + jclass klass ATTRIBUTE_UNUSED) { + jvmtiCapabilities caps{ + .can_generate_method_exit_events = 1, + }; + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps))) { + return; + } + jvmtiEventCallbacks cb{ + .MethodExit = cbMethodEntry, + }; + JvmtiErrorToException(env, jvmti_env, jvmti_env->SetEventCallbacks(&cb, sizeof(cb))); +} + +extern "C" JNIEXPORT void JNICALL Java_art_Test1962_setupThread( + JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thr, jobject events, jobject target) { + BreakpointData* data = nullptr; + if (JvmtiErrorToException( + env, jvmti_env, jvmti_env->Allocate(sizeof(*data), reinterpret_cast<uint8_t**>(&data)))) { + return; + } + data->events = env->NewGlobalRef(events); + data->target = env->FromReflectedMethod(target); + if (JvmtiErrorToException(env, jvmti_env, jvmti_env->SetThreadLocalStorage(thr, data))) { + return; + } + JvmtiErrorToException( + env, + jvmti_env, + jvmti_env->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT, thr)); +} + +} // namespace Test1962MultiThreadEvents +} // namespace art diff --git a/test/1962-multi-thread-events/run b/test/1962-multi-thread-events/run new file mode 100755 index 0000000000..c6e62ae6cd --- /dev/null +++ b/test/1962-multi-thread-events/run @@ -0,0 +1,17 @@ +#!/bin/bash +# +# Copyright 2016 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. + +./default-run "$@" --jvmti diff --git a/test/1962-multi-thread-events/src/Main.java b/test/1962-multi-thread-events/src/Main.java new file mode 100644 index 0000000000..7669e21e4c --- /dev/null +++ b/test/1962-multi-thread-events/src/Main.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +public class Main { + public static void main(String[] args) throws Exception { + art.Test1962.run(); + } +} diff --git a/test/1962-multi-thread-events/src/art/Test1962.java b/test/1962-multi-thread-events/src/art/Test1962.java new file mode 100644 index 0000000000..b5c2992823 --- /dev/null +++ b/test/1962-multi-thread-events/src/art/Test1962.java @@ -0,0 +1,83 @@ +/* + * 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. + */ + +package art; + +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +public class Test1962 { + public static void doNothing() { + // We set a breakpoint here! + } + public static void run() throws Exception { + doTest(); + } + + public static void HandleEvent(Thread t, List<String> events) { + events.add("Hit event on " + t.getName()); + } + + public static void doTest() throws Exception { + setupTest(); + + final int NUM_THREADS = 2; + List<String> t1Events = new ArrayList<>(); + List<String> t2Events = new ArrayList<>(); + final CountDownLatch threadResumeLatch = new CountDownLatch(1); + final CountDownLatch threadStartLatch = new CountDownLatch(NUM_THREADS); + Runnable threadRun = () -> { + try { + threadStartLatch.countDown(); + threadResumeLatch.await(); + doNothing(); + } catch (Exception e) { + throw new Error("Failed at something", e); + } + }; + Thread T1 = new Thread(threadRun, "T1 Thread"); + Thread T2 = new Thread(threadRun, "T2 Thread"); + T1.start(); + T2.start(); + // Wait for both threads to have started. + threadStartLatch.await(); + // Tell the threads to notify us when the doNothing method exits + Method target = Test1962.class.getDeclaredMethod("doNothing"); + setupThread(T1, t1Events, target); + setupThread(T2, t2Events, target); + // Let the threads go. + threadResumeLatch.countDown(); + // Wait for the threads to finish. + T1.join(); + T2.join(); + // Print out the events each of the threads performed. + System.out.println("Events on thread 1:"); + for (String s : t1Events) { + System.out.println("\t" + s); + } + System.out.println("Events on thread 2:"); + for (String s : t2Events) { + System.out.println("\t" + s); + } + } + + public static native void setupTest(); + public static native void setupThread(Thread t, List<String> events, Method target); +} diff --git a/test/Android.bp b/test/Android.bp index 8b40d3c483..380a25db03 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -293,6 +293,7 @@ art_cc_defaults { "1951-monitor-enter-no-suspend/raw_monitor.cc", "1953-pop-frame/pop_frame.cc", "1957-error-ext/lasterror.cc", + "1962-multi-thread-events/multi_thread_events.cc", ], // Use NDK-compatible headers for ctstiagent. header_libs: [ @@ -682,6 +683,7 @@ filegroup { "1943-suspend-raw-monitor-wait/src/art/Test1943.java", "1953-pop-frame/src/art/Test1953.java", "1958-transform-try-jit/src/art/Test1958.java", + "1962-multi-thread-events/src/art/Test1962.java", ], } |