/* * 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_OPENJDKJVMTI_EVENTS_INL_H_ #define ART_OPENJDKJVMTI_EVENTS_INL_H_ #include #include #include #include "base/mutex-inl.h" #include "events.h" #include "jni/jni_internal.h" #include "nativehelper/scoped_local_ref.h" #include "runtime-inl.h" #include "scoped_thread_state_change-inl.h" #include "stack.h" #include "ti_breakpoint.h" #include "ti_thread.h" #include "art_jvmti.h" namespace openjdkjvmti { static inline ArtJvmtiEvent GetArtJvmtiEvent(ArtJvmTiEnv* env, jvmtiEvent e) { if (UNLIKELY(e == JVMTI_EVENT_CLASS_FILE_LOAD_HOOK)) { if (env->capabilities.can_retransform_classes) { return ArtJvmtiEvent::kClassFileLoadHookRetransformable; } else { return ArtJvmtiEvent::kClassFileLoadHookNonRetransformable; } } else { return static_cast(e); } } namespace impl { // Helper for ensuring that the dispatch environment is sane. Events with JNIEnvs need to stash // pending exceptions since they can cause new ones to be thrown. In accordance with the JVMTI // specification we allow exceptions originating from events to overwrite the current exception, // including exceptions originating from earlier events. class ScopedEventDispatchEnvironment final : public art::ValueObject { public: ScopedEventDispatchEnvironment() : env_(nullptr), throw_(nullptr, nullptr) { DCHECK_EQ(art::Thread::Current()->GetState(), art::ThreadState::kNative); } explicit ScopedEventDispatchEnvironment(JNIEnv* env) : env_(env), throw_(env_, env_->ExceptionOccurred()) { DCHECK_EQ(art::Thread::Current()->GetState(), art::ThreadState::kNative); // The spec doesn't say how much local data should be there, so we just give 128 which seems // likely to be enough for most cases. env_->PushLocalFrame(128); env_->ExceptionClear(); } ~ScopedEventDispatchEnvironment() { if (env_ != nullptr) { if (throw_.get() != nullptr && !env_->ExceptionCheck()) { // TODO It would be nice to add the overwritten exceptions to the suppressed exceptions list // of the newest exception. env_->Throw(throw_.get()); } env_->PopLocalFrame(nullptr); } DCHECK_EQ(art::Thread::Current()->GetState(), art::ThreadState::kNative); } private: JNIEnv* env_; ScopedLocalRef throw_; DISALLOW_COPY_AND_ASSIGN(ScopedEventDispatchEnvironment); }; // Infrastructure to achieve type safety for event dispatch. #define FORALL_EVENT_TYPES(fn) \ fn(VMInit, ArtJvmtiEvent::kVmInit) \ fn(VMDeath, ArtJvmtiEvent::kVmDeath) \ fn(ThreadStart, ArtJvmtiEvent::kThreadStart) \ fn(ThreadEnd, ArtJvmtiEvent::kThreadEnd) \ fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookRetransformable) \ fn(ClassFileLoadHook, ArtJvmtiEvent::kClassFileLoadHookNonRetransformable) \ fn(ClassLoad, ArtJvmtiEvent::kClassLoad) \ fn(ClassPrepare, ArtJvmtiEvent::kClassPrepare) \ fn(VMStart, ArtJvmtiEvent::kVmStart) \ fn(Exception, ArtJvmtiEvent::kException) \ fn(ExceptionCatch, ArtJvmtiEvent::kExceptionCatch) \ fn(SingleStep, ArtJvmtiEvent::kSingleStep) \ fn(FramePop, ArtJvmtiEvent::kFramePop) \ fn(Breakpoint, ArtJvmtiEvent::kBreakpoint) \ fn(FieldAccess, ArtJvmtiEvent::kFieldAccess) \ fn(FieldModification, ArtJvmtiEvent::kFieldModification) \ fn(MethodEntry, ArtJvmtiEvent::kMethodEntry) \ fn(MethodExit, ArtJvmtiEvent::kMethodExit) \ fn(NativeMethodBind, ArtJvmtiEvent::kNativeMethodBind) \ fn(CompiledMethodLoad, ArtJvmtiEvent::kCompiledMethodLoad) \ fn(CompiledMethodUnload, ArtJvmtiEvent::kCompiledMethodUnload) \ fn(DynamicCodeGenerated, ArtJvmtiEvent::kDynamicCodeGenerated) \ fn(DataDumpRequest, ArtJvmtiEvent::kDataDumpRequest) \ fn(MonitorWait, ArtJvmtiEvent::kMonitorWait) \ fn(MonitorWaited, ArtJvmtiEvent::kMonitorWaited) \ fn(MonitorContendedEnter, ArtJvmtiEvent::kMonitorContendedEnter) \ fn(MonitorContendedEntered, ArtJvmtiEvent::kMonitorContendedEntered) \ fn(ResourceExhausted, ArtJvmtiEvent::kResourceExhausted) \ fn(GarbageCollectionStart, ArtJvmtiEvent::kGarbageCollectionStart) \ fn(GarbageCollectionFinish, ArtJvmtiEvent::kGarbageCollectionFinish) \ fn(ObjectFree, ArtJvmtiEvent::kObjectFree) \ fn(VMObjectAlloc, ArtJvmtiEvent::kVmObjectAlloc) \ fn(DdmPublishChunk, ArtJvmtiEvent::kDdmPublishChunk) \ fn(ObsoleteObjectCreated, ArtJvmtiEvent::kObsoleteObjectCreated) \ fn(StructuralDexFileLoadHook, ArtJvmtiEvent::kStructuralDexFileLoadHook) template struct EventFnType { }; #define EVENT_FN_TYPE(name, enum_name) \ template <> \ struct EventFnType { \ using type = decltype(ArtJvmtiEventCallbacks().name); \ }; FORALL_EVENT_TYPES(EVENT_FN_TYPE) #undef EVENT_FN_TYPE #define MAKE_EVENT_HANDLER_FUNC(name, enum_name) \ template<> \ struct EventHandlerFunc { \ using EventFnType = typename impl::EventFnType::type; \ explicit EventHandlerFunc(ArtJvmTiEnv* env) \ : env_(env), \ fn_(env_->event_callbacks == nullptr ? nullptr : env_->event_callbacks->name) { } \ \ template \ ALWAYS_INLINE \ void ExecuteCallback(JNIEnv* jnienv, Args... args) const { \ if (fn_ != nullptr) { \ ScopedEventDispatchEnvironment sede(jnienv); \ DoExecute(jnienv, args...); \ } \ } \ \ template \ ALWAYS_INLINE \ void ExecuteCallback(Args... args) const { \ if (fn_ != nullptr) { \ ScopedEventDispatchEnvironment sede; \ DoExecute(args...); \ } \ } \ \ private: \ template \ ALWAYS_INLINE \ inline void DoExecute(Args... args) const { \ static_assert(std::is_same::value, \ "Unexpected different type of ExecuteCallback"); \ fn_(env_, args...); \ } \ \ public: \ ArtJvmTiEnv* env_; \ EventFnType fn_; \ }; FORALL_EVENT_TYPES(MAKE_EVENT_HANDLER_FUNC) #undef MAKE_EVENT_HANDLER_FUNC #undef FORALL_EVENT_TYPES } // namespace impl template inline std::vector> EventHandler::CollectEvents(art::Thread* thread, Args... args) const { art::ReaderMutexLock mu(thread, envs_lock_); std::vector> handlers; for (ArtJvmTiEnv* env : envs) { if (ShouldDispatch(env, thread, args...)) { impl::EventHandlerFunc h(env); handlers.push_back(h); } } return handlers; } // C++ does not allow partial template function specialization. The dispatch for our separated // ClassFileLoadHook event types is the same, so use this helper for code deduplication. template inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread, JNIEnv* jnienv, jclass class_being_redefined, jobject loader, const char* name, jobject protection_domain, jint class_data_len, const unsigned char* class_data, jint* new_class_data_len, unsigned char** new_class_data) const { art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable || kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable || kEvent == ArtJvmtiEvent::kStructuralDexFileLoadHook, "Unsupported event"); DCHECK(*new_class_data == nullptr); jint current_len = class_data_len; unsigned char* current_class_data = const_cast(class_data); std::vector> handlers = CollectEvents(thread, jnienv, class_being_redefined, loader, name, protection_domain, class_data_len, class_data, new_class_data_len, new_class_data); ArtJvmTiEnv* last_env = nullptr; for (const impl::EventHandlerFunc& event : handlers) { jint new_len = 0; unsigned char* new_data = nullptr; ExecuteCallback(event, jnienv, class_being_redefined, loader, name, protection_domain, current_len, static_cast(current_class_data), &new_len, &new_data); if (new_data != nullptr && new_data != current_class_data) { // Destroy the data the last transformer made. We skip this if the previous state was the // initial one since we don't know here which jvmtiEnv allocated it. // NB Currently this doesn't matter since all allocations just go to malloc but in the // future we might have jvmtiEnv's keep track of their allocations for leak-checking. if (last_env != nullptr) { last_env->Deallocate(current_class_data); } last_env = event.env_; current_class_data = new_data; current_len = new_len; } } if (last_env != nullptr) { *new_class_data_len = current_len; *new_class_data = current_class_data; } } // Our goal for DispatchEvent: Do not allow implicit type conversion. Types of ...args must match // exactly the argument types of the corresponding Jvmti kEvent function pointer. template inline void EventHandler::DispatchEvent(art::Thread* thread, Args... args) const { art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); static_assert(!std::is_same>>>::value, "Should be calling DispatchEvent with explicit JNIEnv* argument!"); DCHECK(thread == nullptr || !thread->IsExceptionPending()); std::vector> events = CollectEvents(thread, args...); for (auto event : events) { ExecuteCallback(event, args...); } } template inline void EventHandler::DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const { art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); std::vector> events = CollectEvents(thread, jnienv, args...); for (auto event : events) { ExecuteCallback(event, jnienv, args...); } } template inline void EventHandler::DispatchEventOnEnv( ArtJvmTiEnv* env, art::Thread* thread, JNIEnv* jnienv, Args... args) const { DCHECK(env != nullptr); if (ShouldDispatch(env, thread, jnienv, args...)) { art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); impl::EventHandlerFunc func(env); ExecuteCallback(func, jnienv, args...); } } template inline void EventHandler::DispatchEventOnEnv( ArtJvmTiEnv* env, art::Thread* thread, Args... args) const { static_assert(!std::is_same>>>::value, "Should be calling DispatchEventOnEnv with explicit JNIEnv* argument!"); DCHECK(env != nullptr); if (ShouldDispatch(env, thread, args...)) { art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); impl::EventHandlerFunc func(env); ExecuteCallback(func, args...); } } template <> inline void EventHandler::DispatchEventOnEnv( ArtJvmTiEnv* env, art::Thread* thread, jlong* obsolete_tag, jlong* new_tag) const { static constexpr ArtJvmtiEvent kEvent = ArtJvmtiEvent::kObsoleteObjectCreated; DCHECK(env != nullptr); if (ShouldDispatch(env, thread, obsolete_tag, new_tag)) { art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); impl::EventHandlerFunc func(env); ExecuteCallback(func, obsolete_tag, new_tag); } else { // Unlike most others this has a default action to make sure that agents without knowledge of // this extension get reasonable behavior. jlong temp = *obsolete_tag; *obsolete_tag = *new_tag; *new_tag = temp; } } template inline void EventHandler::ExecuteCallback(impl::EventHandlerFunc handler, Args... args) { handler.ExecuteCallback(args...); } template inline void EventHandler::ExecuteCallback(impl::EventHandlerFunc handler, JNIEnv* jnienv, Args... args) { handler.ExecuteCallback(jnienv, args...); } // Events that need custom logic for if we send the event but are otherwise normal. This includes // the kBreakpoint, kFramePop, kFieldAccess, and kFieldModification events. // Need to give custom specializations for Breakpoint since it needs to filter out which particular // methods/dex_pcs agents get notified on. template <> inline bool EventHandler::ShouldDispatch( ArtJvmTiEnv* env, art::Thread* thread, JNIEnv* jnienv ATTRIBUTE_UNUSED, jthread jni_thread ATTRIBUTE_UNUSED, jmethodID jmethod, jlocation location) const { art::ReaderMutexLock lk(art::Thread::Current(), env->event_info_mutex_); art::ArtMethod* method = art::jni::DecodeArtMethod(jmethod); return ShouldDispatchOnThread(env, thread) && env->breakpoints.find({method, location}) != env->breakpoints.end(); } template <> inline bool EventHandler::ShouldDispatch( ArtJvmTiEnv* env, art::Thread* thread, JNIEnv* jnienv ATTRIBUTE_UNUSED, jthread jni_thread ATTRIBUTE_UNUSED, jmethodID jmethod ATTRIBUTE_UNUSED, jboolean is_exception ATTRIBUTE_UNUSED, const art::ShadowFrame* frame) const { // Search for the frame. Do this before checking if we need to send the event so that we don't // have to deal with use-after-free or the frames being reallocated later. art::WriterMutexLock lk(art::Thread::Current(), env->event_info_mutex_); return env->notify_frames.erase(frame) != 0 && !frame->GetSkipMethodExitEvents() && ShouldDispatchOnThread(env, thread); } // Need to give custom specializations for FieldAccess and FieldModification since they need to // filter out which particular fields agents want to get notified on. // TODO The spec allows us to do shortcuts like only allow one agent to ever set these watches. This // could make the system more performant. template <> inline bool EventHandler::ShouldDispatch( ArtJvmTiEnv* env, art::Thread* thread, JNIEnv* jnienv ATTRIBUTE_UNUSED, jthread jni_thread ATTRIBUTE_UNUSED, jmethodID method ATTRIBUTE_UNUSED, jlocation location ATTRIBUTE_UNUSED, jclass field_klass ATTRIBUTE_UNUSED, jobject object ATTRIBUTE_UNUSED, jfieldID field, char type_char ATTRIBUTE_UNUSED, jvalue val ATTRIBUTE_UNUSED) const { art::ReaderMutexLock lk(art::Thread::Current(), env->event_info_mutex_); return ShouldDispatchOnThread(env, thread) && env->modify_watched_fields.find( art::jni::DecodeArtField(field)) != env->modify_watched_fields.end(); } template <> inline bool EventHandler::ShouldDispatch( ArtJvmTiEnv* env, art::Thread* thread, JNIEnv* jnienv ATTRIBUTE_UNUSED, jthread jni_thread ATTRIBUTE_UNUSED, jmethodID method ATTRIBUTE_UNUSED, jlocation location ATTRIBUTE_UNUSED, jclass field_klass ATTRIBUTE_UNUSED, jobject object ATTRIBUTE_UNUSED, jfieldID field) const { art::ReaderMutexLock lk(art::Thread::Current(), env->event_info_mutex_); return ShouldDispatchOnThread(env, thread) && env->access_watched_fields.find( art::jni::DecodeArtField(field)) != env->access_watched_fields.end(); } // Need to give custom specializations for FramePop since it needs to filter out which particular // agents get the event. This specialization gets an extra argument so we can determine which (if // any) environments have the frame pop. // TODO It might be useful to use more template magic to have this only define ShouldDispatch or // something. template <> inline void EventHandler::ExecuteCallback( impl::EventHandlerFunc event, JNIEnv* jnienv, jthread jni_thread, jmethodID jmethod, jboolean is_exception, const art::ShadowFrame* frame ATTRIBUTE_UNUSED) { ExecuteCallback(event, jnienv, jni_thread, jmethod, is_exception); } struct ScopedDisablePopFrame { public: explicit ScopedDisablePopFrame(art::Thread* thread) : thread_(thread) { art::Locks::mutator_lock_->AssertSharedHeld(thread_); art::MutexLock mu(thread_, *art::Locks::thread_list_lock_); JvmtiGlobalTLSData* data = ThreadUtil::GetOrCreateGlobalTLSData(thread_); current_top_frame_ = art::StackVisitor::ComputeNumFrames( thread_, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames); old_disable_frame_pop_depth_ = data->disable_pop_frame_depth; data->disable_pop_frame_depth = current_top_frame_; // Check that we cleaned up any old disables. This should only increase (or be equals if we do // another ClassLoad/Prepare recursively). DCHECK(old_disable_frame_pop_depth_ == JvmtiGlobalTLSData::kNoDisallowedPopFrame || current_top_frame_ >= old_disable_frame_pop_depth_) << "old: " << old_disable_frame_pop_depth_ << " current: " << current_top_frame_; } ~ScopedDisablePopFrame() { art::Locks::mutator_lock_->AssertSharedHeld(thread_); art::MutexLock mu(thread_, *art::Locks::thread_list_lock_); JvmtiGlobalTLSData* data = ThreadUtil::GetGlobalTLSData(thread_); DCHECK_EQ(data->disable_pop_frame_depth, current_top_frame_); data->disable_pop_frame_depth = old_disable_frame_pop_depth_; } private: art::Thread* thread_; size_t current_top_frame_; size_t old_disable_frame_pop_depth_; }; // We want to prevent the use of PopFrame when reporting either of these events. template inline void EventHandler::DispatchClassLoadOrPrepareEvent(art::Thread* thread, JNIEnv* jnienv, jthread jni_thread, jclass klass) const { ScopedDisablePopFrame sdpf(thread); art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); std::vector> events = CollectEvents(thread, jnienv, jni_thread, klass); for (auto event : events) { ExecuteCallback(event, jnienv, jni_thread, klass); } } template <> inline void EventHandler::DispatchEvent(art::Thread* thread, JNIEnv* jnienv, jthread jni_thread, jclass klass) const { DispatchClassLoadOrPrepareEvent(thread, jnienv, jni_thread, klass); } template <> inline void EventHandler::DispatchEvent(art::Thread* thread, JNIEnv* jnienv, jthread jni_thread, jclass klass) const { DispatchClassLoadOrPrepareEvent(thread, jnienv, jni_thread, klass); } // Need to give a custom specialization for NativeMethodBind since it has to deal with an out // variable. template <> inline void EventHandler::DispatchEvent(art::Thread* thread, JNIEnv* jnienv, jthread jni_thread, jmethodID method, void* cur_method, void** new_method) const { art::ScopedThreadStateChange stsc(thread, art::ThreadState::kNative); std::vector> events = CollectEvents(thread, jnienv, jni_thread, method, cur_method, new_method); *new_method = cur_method; for (auto event : events) { *new_method = cur_method; ExecuteCallback(event, jnienv, jni_thread, method, cur_method, new_method); if (*new_method != nullptr) { cur_method = *new_method; } } *new_method = cur_method; } // C++ does not allow partial template function specialization. The dispatch for our separated // ClassFileLoadHook event types is the same, and in the DispatchClassFileLoadHookEvent helper. // The following two DispatchEvent specializations dispatch to it. template <> inline void EventHandler::DispatchEvent( art::Thread* thread, JNIEnv* jnienv, jclass class_being_redefined, jobject loader, const char* name, jobject protection_domain, jint class_data_len, const unsigned char* class_data, jint* new_class_data_len, unsigned char** new_class_data) const { return DispatchClassFileLoadHookEvent( thread, jnienv, class_being_redefined, loader, name, protection_domain, class_data_len, class_data, new_class_data_len, new_class_data); } template <> inline void EventHandler::DispatchEvent( art::Thread* thread, JNIEnv* jnienv, jclass class_being_redefined, jobject loader, const char* name, jobject protection_domain, jint class_data_len, const unsigned char* class_data, jint* new_class_data_len, unsigned char** new_class_data) const { return DispatchClassFileLoadHookEvent( thread, jnienv, class_being_redefined, loader, name, protection_domain, class_data_len, class_data, new_class_data_len, new_class_data); } template <> inline void EventHandler::DispatchEvent( art::Thread* thread, JNIEnv* jnienv, jclass class_being_redefined, jobject loader, const char* name, jobject protection_domain, jint class_data_len, const unsigned char* class_data, jint* new_class_data_len, unsigned char** new_class_data) const { return DispatchClassFileLoadHookEvent( thread, jnienv, class_being_redefined, loader, name, protection_domain, class_data_len, class_data, new_class_data_len, new_class_data); } template inline bool EventHandler::ShouldDispatchOnThread(ArtJvmTiEnv* env, art::Thread* thread) const { bool dispatch = env->event_masks.global_event_mask.Test(kEvent); if (!dispatch && thread != nullptr && env->event_masks.unioned_thread_event_mask.Test(kEvent)) { EventMask* mask = env->event_masks.GetEventMaskOrNull(thread); dispatch = mask != nullptr && mask->Test(kEvent); } return dispatch; } template inline bool EventHandler::ShouldDispatch(ArtJvmTiEnv* env, art::Thread* thread, Args... args ATTRIBUTE_UNUSED) const { static_assert(std::is_same::type, void(*)(jvmtiEnv*, Args...)>::value, "Unexpected different type of shouldDispatch"); return ShouldDispatchOnThread(env, thread); } inline void EventHandler::RecalculateGlobalEventMask(ArtJvmtiEvent event) { art::WriterMutexLock mu(art::Thread::Current(), envs_lock_); RecalculateGlobalEventMaskLocked(event); } inline void EventHandler::RecalculateGlobalEventMaskLocked(ArtJvmtiEvent event) { bool union_value = false; for (const ArtJvmTiEnv* stored_env : envs) { if (stored_env == nullptr) { continue; } 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); } inline bool EventHandler::NeedsEventUpdate(ArtJvmTiEnv* env, const jvmtiCapabilities& caps, bool added) { ArtJvmtiEvent event = added ? ArtJvmtiEvent::kClassFileLoadHookNonRetransformable : ArtJvmtiEvent::kClassFileLoadHookRetransformable; return (added && caps.can_access_local_variables == 1) || caps.can_generate_breakpoint_events == 1 || caps.can_pop_frame == 1 || caps.can_force_early_return == 1 || (caps.can_retransform_classes == 1 && IsEventEnabledAnywhere(event) && env->event_masks.IsEnabledAnywhere(event)); } inline void EventHandler::HandleChangedCapabilities(ArtJvmTiEnv* env, const jvmtiCapabilities& caps, bool added) { if (UNLIKELY(NeedsEventUpdate(env, caps, added))) { env->event_masks.HandleChangedCapabilities(caps, added); if (caps.can_retransform_classes == 1) { RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookRetransformable); RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookNonRetransformable); } if (added && caps.can_access_local_variables == 1) { HandleLocalAccessCapabilityAdded(); } if (caps.can_generate_breakpoint_events == 1) { HandleBreakpointEventsChanged(added); } if ((caps.can_pop_frame == 1 || caps.can_force_early_return == 1) && added) { // TODO We should keep track of how many of these have been enabled and remove it if there are // no more possible users. This isn't expected to be too common. art::Runtime::Current()->SetNonStandardExitsEnabled(); } } } } // namespace openjdkjvmti #endif // ART_OPENJDKJVMTI_EVENTS_INL_H_