From 77708d9149b0a00247eb69ea4d5386cae4e40287 Mon Sep 17 00:00:00 2001 From: Andreas Gampe Date: Fri, 7 Oct 2016 11:48:21 -0700 Subject: [PATCH] ART: Add event callback support Add basic event callback support infrastructure. Actual users will follow. Bug: 31684920 Test: m test-art-host Change-Id: Ic496933ef3a94f9d27a2779b7f4fdc5b096eab22 --- runtime/openjdkjvmti/Android.bp | 3 +- runtime/openjdkjvmti/OpenjdkJvmTi.cc | 47 +++++++++- runtime/openjdkjvmti/art_jvmti.h | 11 +++ runtime/openjdkjvmti/events-inl.h | 121 ++++++++++++++++++++++++ runtime/openjdkjvmti/events.cc | 175 +++++++++++++++++++++++++++++++++++ runtime/openjdkjvmti/events.h | 100 ++++++++++++++++++++ 6 files changed, 454 insertions(+), 3 deletions(-) create mode 100644 runtime/openjdkjvmti/events-inl.h create mode 100644 runtime/openjdkjvmti/events.cc create mode 100644 runtime/openjdkjvmti/events.h diff --git a/runtime/openjdkjvmti/Android.bp b/runtime/openjdkjvmti/Android.bp index d7a6c0a86..e1f9ba2b9 100644 --- a/runtime/openjdkjvmti/Android.bp +++ b/runtime/openjdkjvmti/Android.bp @@ -17,7 +17,8 @@ cc_defaults { name: "libopenjdkjvmti_defaults", defaults: ["art_defaults"], host_supported: true, - srcs: ["OpenjdkJvmTi.cc", + srcs: ["events.cc", + "OpenjdkJvmTi.cc", "transform.cc"], include_dirs: ["art/runtime"], shared_libs: [ diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index bea40c878..6812a923b 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -37,12 +37,15 @@ #include "openjdkjvmti/jvmti.h" #include "art_jvmti.h" +#include "base/mutex.h" +#include "events-inl.h" #include "jni_env_ext-inl.h" #include "object_tagging.h" #include "obj_ptr-inl.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" #include "thread_list.h" +#include "thread-inl.h" #include "transform.h" // TODO Remove this at some point by annotating all the methods. It was put in to make the skeleton @@ -52,6 +55,7 @@ namespace openjdkjvmti { ObjectTagTable gObjectTagTable; +EventHandler gEventHandler; class JvmtiFunctions { private: @@ -731,10 +735,33 @@ class JvmtiFunctions { return ERR(NOT_IMPLEMENTED); } + // TODO: This will require locking, so that an agent can't remove callbacks when we're dispatching + // an event. static jvmtiError SetEventCallbacks(jvmtiEnv* env, const jvmtiEventCallbacks* callbacks, jint size_of_callbacks) { - return ERR(NOT_IMPLEMENTED); + if (env == nullptr) { + return ERR(NULL_POINTER); + } + if (size_of_callbacks < 0) { + return ERR(ILLEGAL_ARGUMENT); + } + + if (callbacks == nullptr) { + ArtJvmTiEnv::AsArtJvmTiEnv(env)->event_callbacks.reset(); + return ERR(NONE); + } + + std::unique_ptr tmp(new jvmtiEventCallbacks()); + memset(tmp.get(), 0, sizeof(jvmtiEventCallbacks)); + size_t copy_size = std::min(sizeof(jvmtiEventCallbacks), + static_cast(size_of_callbacks)); + copy_size = art::RoundDown(copy_size, sizeof(void*)); + memcpy(tmp.get(), callbacks, copy_size); + + ArtJvmTiEnv::AsArtJvmTiEnv(env)->event_callbacks = std::move(tmp); + + return ERR(NONE); } static jvmtiError SetEventNotificationMode(jvmtiEnv* env, @@ -742,7 +769,21 @@ class JvmtiFunctions { jvmtiEvent event_type, jthread event_thread, ...) { - return ERR(NOT_IMPLEMENTED); + art::Thread* art_thread = nullptr; + if (event_thread != nullptr) { + // TODO: Need non-aborting call here, to return JVMTI_ERROR_INVALID_THREAD. + art::ScopedObjectAccess soa(art::Thread::Current()); + art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_); + art_thread = art::Thread::FromManagedThread(soa, event_thread); + + if (art_thread == nullptr || // The thread hasn't been started or is already dead. + art_thread->IsStillStarting()) { + // TODO: We may want to let the EventHandler know, so it could clean up masks, potentially. + return ERR(THREAD_NOT_ALIVE); + } + } + + return gEventHandler.SetEvent(ArtJvmTiEnv::AsArtJvmTiEnv(env), art_thread, event_type, mode); } static jvmtiError GenerateEvents(jvmtiEnv* env, jvmtiEvent event_type) { @@ -1018,6 +1059,8 @@ static bool IsJvmtiVersion(jint version) { static void CreateArtJvmTiEnv(art::JavaVMExt* vm, /*out*/void** new_jvmtiEnv) { struct ArtJvmTiEnv* env = new ArtJvmTiEnv(vm); *new_jvmtiEnv = env; + + gEventHandler.RegisterArtJvmTiEnv(env); } // A hook that the runtime uses to allow plugins to handle GetEnv calls. It returns true and diff --git a/runtime/openjdkjvmti/art_jvmti.h b/runtime/openjdkjvmti/art_jvmti.h index a2c6882ac..66d093782 100644 --- a/runtime/openjdkjvmti/art_jvmti.h +++ b/runtime/openjdkjvmti/art_jvmti.h @@ -32,8 +32,12 @@ #ifndef ART_RUNTIME_OPENJDKJVMTI_ART_JVMTI_H_ #define ART_RUNTIME_OPENJDKJVMTI_ART_JVMTI_H_ +#include + #include +#include "base/casts.h" +#include "events.h" #include "java_vm_ext.h" #include "jni_env_ext.h" #include "jvmti.h" @@ -47,9 +51,16 @@ struct ArtJvmTiEnv : public jvmtiEnv { art::JavaVMExt* art_vm; void* local_data; + EventMasks event_masks; + std::unique_ptr event_callbacks; + explicit ArtJvmTiEnv(art::JavaVMExt* runtime) : art_vm(runtime), local_data(nullptr) { functions = &gJvmtiInterface; } + + static ArtJvmTiEnv* AsArtJvmTiEnv(jvmtiEnv* env) { + return art::down_cast(env); + } }; // Macro and constexpr to make error values less annoying to write. diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h new file mode 100644 index 000000000..b29830112 --- /dev/null +++ b/runtime/openjdkjvmti/events-inl.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 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. + */ + +#ifndef ART_RUNTIME_OPENJDKJVMTI_EVENTS_INL_H_ +#define ART_RUNTIME_OPENJDKJVMTI_EVENTS_INL_H_ + +#include "events.h" + +#include "art_jvmti.h" + +namespace openjdkjvmti { + +template +ALWAYS_INLINE static inline FnType* GetCallback(ArtJvmTiEnv* env, jvmtiEvent event) { + if (env->event_callbacks == nullptr) { + return nullptr; + } + + switch (event) { + case JVMTI_EVENT_VM_INIT: + return reinterpret_cast(env->event_callbacks->VMInit); + case JVMTI_EVENT_VM_DEATH: + return reinterpret_cast(env->event_callbacks->VMDeath); + case JVMTI_EVENT_THREAD_START: + return reinterpret_cast(env->event_callbacks->ThreadStart); + case JVMTI_EVENT_THREAD_END: + return reinterpret_cast(env->event_callbacks->ThreadEnd); + case JVMTI_EVENT_CLASS_FILE_LOAD_HOOK: + return reinterpret_cast(env->event_callbacks->ClassFileLoadHook); + case JVMTI_EVENT_CLASS_LOAD: + return reinterpret_cast(env->event_callbacks->ClassLoad); + case JVMTI_EVENT_CLASS_PREPARE: + return reinterpret_cast(env->event_callbacks->ClassPrepare); + case JVMTI_EVENT_VM_START: + return reinterpret_cast(env->event_callbacks->VMStart); + case JVMTI_EVENT_EXCEPTION: + return reinterpret_cast(env->event_callbacks->Exception); + case JVMTI_EVENT_EXCEPTION_CATCH: + return reinterpret_cast(env->event_callbacks->ExceptionCatch); + case JVMTI_EVENT_SINGLE_STEP: + return reinterpret_cast(env->event_callbacks->SingleStep); + case JVMTI_EVENT_FRAME_POP: + return reinterpret_cast(env->event_callbacks->FramePop); + case JVMTI_EVENT_BREAKPOINT: + return reinterpret_cast(env->event_callbacks->Breakpoint); + case JVMTI_EVENT_FIELD_ACCESS: + return reinterpret_cast(env->event_callbacks->FieldAccess); + case JVMTI_EVENT_FIELD_MODIFICATION: + return reinterpret_cast(env->event_callbacks->FieldModification); + case JVMTI_EVENT_METHOD_ENTRY: + return reinterpret_cast(env->event_callbacks->MethodEntry); + case JVMTI_EVENT_METHOD_EXIT: + return reinterpret_cast(env->event_callbacks->MethodExit); + case JVMTI_EVENT_NATIVE_METHOD_BIND: + return reinterpret_cast(env->event_callbacks->NativeMethodBind); + case JVMTI_EVENT_COMPILED_METHOD_LOAD: + return reinterpret_cast(env->event_callbacks->CompiledMethodLoad); + case JVMTI_EVENT_COMPILED_METHOD_UNLOAD: + return reinterpret_cast(env->event_callbacks->CompiledMethodUnload); + case JVMTI_EVENT_DYNAMIC_CODE_GENERATED: + return reinterpret_cast(env->event_callbacks->DynamicCodeGenerated); + case JVMTI_EVENT_DATA_DUMP_REQUEST: + return reinterpret_cast(env->event_callbacks->DataDumpRequest); + case JVMTI_EVENT_MONITOR_WAIT: + return reinterpret_cast(env->event_callbacks->MonitorWait); + case JVMTI_EVENT_MONITOR_WAITED: + return reinterpret_cast(env->event_callbacks->MonitorWaited); + case JVMTI_EVENT_MONITOR_CONTENDED_ENTER: + return reinterpret_cast(env->event_callbacks->MonitorContendedEnter); + case JVMTI_EVENT_MONITOR_CONTENDED_ENTERED: + return reinterpret_cast(env->event_callbacks->MonitorContendedEntered); + case JVMTI_EVENT_RESOURCE_EXHAUSTED: + return reinterpret_cast(env->event_callbacks->ResourceExhausted); + case JVMTI_EVENT_GARBAGE_COLLECTION_START: + return reinterpret_cast(env->event_callbacks->GarbageCollectionStart); + case JVMTI_EVENT_GARBAGE_COLLECTION_FINISH: + return reinterpret_cast(env->event_callbacks->GarbageCollectionFinish); + case JVMTI_EVENT_OBJECT_FREE: + return reinterpret_cast(env->event_callbacks->ObjectFree); + case JVMTI_EVENT_VM_OBJECT_ALLOC: + return reinterpret_cast(env->event_callbacks->VMObjectAlloc); + } + return nullptr; +} + +template +inline void EventHandler::DispatchEvent(art::Thread* thread, jvmtiEvent event, Args... args) { + using FnType = void(jvmtiEnv*, Args...); + for (ArtJvmTiEnv* env : envs) { + bool dispatch = env->event_masks.global_event_mask.Test(event); + + if (!dispatch && thread != nullptr && env->event_masks.unioned_thread_event_mask.Test(event)) { + EventMask* mask = env->event_masks.GetEventMaskOrNull(thread); + dispatch = mask != nullptr && mask->Test(event); + } + + if (dispatch) { + FnType* callback = GetCallback(env, event); + if (callback != nullptr) { + (*callback)(env, args...); + } + } + } +} + +} // namespace openjdkjvmti + +#endif // ART_RUNTIME_OPENJDKJVMTI_EVENTS_INL_H_ diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc new file mode 100644 index 000000000..48f3de437 --- /dev/null +++ b/runtime/openjdkjvmti/events.cc @@ -0,0 +1,175 @@ +/* Copyright (C) 2016 The Android Open Source Project + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This file implements interfaces from the file jvmti.h. This implementation + * is licensed under the same terms as the file jvmti.h. The + * copyright and license information for the file jvmti.h follows. + * + * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "events.h" + +#include "art_jvmti.h" + +namespace openjdkjvmti { + +EventMask& EventMasks::GetEventMask(art::Thread* thread) { + if (thread == nullptr) { + return global_event_mask; + } + + for (auto& pair : thread_event_masks) { + const UniqueThread& unique_thread = pair.first; + if (unique_thread.first == thread && + unique_thread.second == static_cast(thread->GetTid())) { + return pair.second; + } + } + + // TODO: Remove old UniqueThread with the same pointer, if exists. + + thread_event_masks.emplace_back(UniqueThread(thread, thread->GetTid()), EventMask()); + return thread_event_masks.back().second; +} + +EventMask* EventMasks::GetEventMaskOrNull(art::Thread* thread) { + if (thread == nullptr) { + return &global_event_mask; + } + + for (auto& pair : thread_event_masks) { + const UniqueThread& unique_thread = pair.first; + if (unique_thread.first == thread && + unique_thread.second == static_cast(thread->GetTid())) { + return &pair.second; + } + } + + return nullptr; +} + + +void EventMasks::EnableEvent(art::Thread* thread, jvmtiEvent event) { + DCHECK(EventMask::EventIsInRange(event)); + GetEventMask(thread).Set(event); + if (thread != nullptr) { + unioned_thread_event_mask.Set(event, true); + } +} + +void EventMasks::DisableEvent(art::Thread* thread, jvmtiEvent event) { + DCHECK(EventMask::EventIsInRange(event)); + GetEventMask(thread).Set(event, false); + if (thread != nullptr) { + // Regenerate union for the event. + bool union_value = false; + for (auto& pair : thread_event_masks) { + union_value |= pair.second.Test(event); + if (union_value) { + break; + } + } + unioned_thread_event_mask.Set(event, union_value); + } +} + +void EventHandler::RegisterArtJvmTiEnv(ArtJvmTiEnv* env) { + envs.push_back(env); +} + +static bool IsThreadControllable(jvmtiEvent event) { + switch (event) { + case JVMTI_EVENT_VM_INIT: + case JVMTI_EVENT_VM_START: + case JVMTI_EVENT_VM_DEATH: + case JVMTI_EVENT_THREAD_START: + case JVMTI_EVENT_COMPILED_METHOD_LOAD: + case JVMTI_EVENT_COMPILED_METHOD_UNLOAD: + case JVMTI_EVENT_DYNAMIC_CODE_GENERATED: + case JVMTI_EVENT_DATA_DUMP_REQUEST: + return false; + + default: + return true; + } +} + +// Handle special work for the given event type, if necessary. +static void HandleEventType(jvmtiEvent event ATTRIBUTE_UNUSED, bool enable ATTRIBUTE_UNUSED) { +} + +jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env, + art::Thread* thread, + jvmtiEvent event, + jvmtiEventMode mode) { + if (thread != nullptr) { + art::ThreadState state = thread->GetState(); + if (state == art::ThreadState::kStarting || + state == art::ThreadState::kTerminated || + thread->IsStillStarting()) { + return ERR(THREAD_NOT_ALIVE); + } + if (!IsThreadControllable(event)) { + return ERR(ILLEGAL_ARGUMENT); + } + } + + // TODO: Capability check. + + if (mode != JVMTI_ENABLE && mode != JVMTI_DISABLE) { + return ERR(ILLEGAL_ARGUMENT); + } + + if (!EventMask::EventIsInRange(event)) { + return ERR(INVALID_EVENT_TYPE); + } + + if (mode == JVMTI_ENABLE) { + env->event_masks.EnableEvent(thread, event); + global_mask.Set(event); + } else { + DCHECK_EQ(mode, JVMTI_DISABLE); + + env->event_masks.DisableEvent(thread, event); + + // Gotta recompute the global mask. + bool union_value = false; + for (const ArtJvmTiEnv* stored_env : envs) { + union_value |= stored_env->event_masks.global_event_mask.Test(event); + union_value |= stored_env->event_masks.unioned_thread_event_mask.Test(event); + if (union_value) { + break; + } + } + global_mask.Set(event, union_value); + } + + // Handle any special work required for the event type. + HandleEventType(event, mode == JVMTI_ENABLE); + + return ERR(NONE); +} + +} // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/events.h b/runtime/openjdkjvmti/events.h new file mode 100644 index 000000000..5716d0399 --- /dev/null +++ b/runtime/openjdkjvmti/events.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 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. + */ + +#ifndef ART_RUNTIME_OPENJDKJVMTI_EVENTS_H_ +#define ART_RUNTIME_OPENJDKJVMTI_EVENTS_H_ + +#include +#include + +#include "base/logging.h" +#include "jvmti.h" +#include "thread.h" + +namespace openjdkjvmti { + +struct ArtJvmTiEnv; + +struct EventMask { + static constexpr size_t kEventsSize = JVMTI_MAX_EVENT_TYPE_VAL - JVMTI_MIN_EVENT_TYPE_VAL + 1; + std::bitset bit_set; + + static bool EventIsInRange(jvmtiEvent event) { + return event >= JVMTI_MIN_EVENT_TYPE_VAL && event <= JVMTI_MAX_EVENT_TYPE_VAL; + } + + void Set(jvmtiEvent event, bool value = true) { + DCHECK(EventIsInRange(event)); + bit_set.set(event - JVMTI_MIN_EVENT_TYPE_VAL, value); + } + + bool Test(jvmtiEvent event) const { + DCHECK(EventIsInRange(event)); + return bit_set.test(event - JVMTI_MIN_EVENT_TYPE_VAL); + } +}; + +struct EventMasks { + // The globally enabled events. + EventMask global_event_mask; + + // The per-thread enabled events. + + // It is not enough to store a Thread pointer, as these may be reused. Use the pointer and the + // thread id. + // Note: We could just use the tid like tracing does. + using UniqueThread = std::pair; + // TODO: Native thread objects are immovable, so we can use them as keys in an (unordered) map, + // if necessary. + std::vector> thread_event_masks; + + // A union of the per-thread events, for fast-pathing. + EventMask unioned_thread_event_mask; + + EventMask& GetEventMask(art::Thread* thread); + EventMask* GetEventMaskOrNull(art::Thread* thread); + void EnableEvent(art::Thread* thread, jvmtiEvent event); + void DisableEvent(art::Thread* thread, jvmtiEvent event); +}; + +// Helper class for event handling. +struct EventHandler { + // List of all JvmTiEnv objects that have been created, in their creation order. + std::vector envs; + + // A union of all enabled events, anywhere. + EventMask global_mask; + + // Register an env. It is assumed that this happens on env creation, that is, no events are + // enabled, yet. + void RegisterArtJvmTiEnv(ArtJvmTiEnv* env); + + bool IsEventEnabledAnywhere(jvmtiEvent event) { + if (!EventMask::EventIsInRange(event)) { + return false; + } + return global_mask.Test(event); + } + + jvmtiError SetEvent(ArtJvmTiEnv* env, art::Thread* thread, jvmtiEvent event, jvmtiEventMode mode); + + template + ALWAYS_INLINE inline void DispatchEvent(art::Thread* thread, jvmtiEvent event, Args... args); +}; + +} // namespace openjdkjvmti + +#endif // ART_RUNTIME_OPENJDKJVMTI_EVENTS_H_ -- 2.11.0