436 lines
19 KiB
C++
436 lines
19 KiB
C++
/*
|
|
* 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 <array>
|
|
|
|
#include "events.h"
|
|
#include "jni_internal.h"
|
|
#include "nativehelper/ScopedLocalRef.h"
|
|
#include "ti_breakpoint.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<ArtJvmtiEvent>(e);
|
|
}
|
|
}
|
|
|
|
namespace impl {
|
|
|
|
// 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)
|
|
|
|
template <ArtJvmtiEvent kEvent>
|
|
struct EventFnType {
|
|
};
|
|
|
|
#define EVENT_FN_TYPE(name, enum_name) \
|
|
template <> \
|
|
struct EventFnType<enum_name> { \
|
|
using type = decltype(jvmtiEventCallbacks().name); \
|
|
};
|
|
|
|
FORALL_EVENT_TYPES(EVENT_FN_TYPE)
|
|
|
|
#undef EVENT_FN_TYPE
|
|
|
|
template <ArtJvmtiEvent kEvent>
|
|
ALWAYS_INLINE inline typename EventFnType<kEvent>::type GetCallback(ArtJvmTiEnv* env);
|
|
|
|
#define GET_CALLBACK(name, enum_name) \
|
|
template <> \
|
|
ALWAYS_INLINE inline EventFnType<enum_name>::type GetCallback<enum_name>( \
|
|
ArtJvmTiEnv* env) { \
|
|
if (env->event_callbacks == nullptr) { \
|
|
return nullptr; \
|
|
} \
|
|
return env->event_callbacks->name; \
|
|
}
|
|
|
|
FORALL_EVENT_TYPES(GET_CALLBACK)
|
|
|
|
#undef GET_CALLBACK
|
|
|
|
#undef FORALL_EVENT_TYPES
|
|
|
|
} // namespace impl
|
|
|
|
// 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.
|
|
// TODO Locking of some type!
|
|
template <ArtJvmtiEvent kEvent>
|
|
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 {
|
|
static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable ||
|
|
kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable, "Unsupported event");
|
|
DCHECK(*new_class_data == nullptr);
|
|
jint current_len = class_data_len;
|
|
unsigned char* current_class_data = const_cast<unsigned char*>(class_data);
|
|
ArtJvmTiEnv* last_env = nullptr;
|
|
for (ArtJvmTiEnv* env : envs) {
|
|
if (env == nullptr) {
|
|
continue;
|
|
}
|
|
if (ShouldDispatch<kEvent>(env, thread)) {
|
|
ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
|
|
jnienv->ExceptionClear();
|
|
jint new_len = 0;
|
|
unsigned char* new_data = nullptr;
|
|
auto callback = impl::GetCallback<kEvent>(env);
|
|
callback(env,
|
|
jnienv,
|
|
class_being_redefined,
|
|
loader,
|
|
name,
|
|
protection_domain,
|
|
current_len,
|
|
current_class_data,
|
|
&new_len,
|
|
&new_data);
|
|
if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
|
|
jnienv->Throw(thr.get());
|
|
}
|
|
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 = 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 <ArtJvmtiEvent kEvent, typename ...Args>
|
|
inline void EventHandler::DispatchEvent(art::Thread* thread, Args... args) const {
|
|
for (ArtJvmTiEnv* env : envs) {
|
|
if (env != nullptr) {
|
|
DispatchEvent<kEvent, Args...>(env, thread, args...);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
// TODO It would be nice to add the overwritten exceptions to the suppressed exceptions list of the
|
|
// newest exception.
|
|
template <ArtJvmtiEvent kEvent, typename ...Args>
|
|
inline void EventHandler::DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const {
|
|
for (ArtJvmTiEnv* env : envs) {
|
|
if (env != nullptr) {
|
|
ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
|
|
jnienv->ExceptionClear();
|
|
DispatchEvent<kEvent, JNIEnv*, Args...>(env, thread, jnienv, args...);
|
|
if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
|
|
jnienv->Throw(thr.get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <ArtJvmtiEvent kEvent, typename ...Args>
|
|
inline void EventHandler::DispatchEvent(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const {
|
|
using FnType = void(jvmtiEnv*, Args...);
|
|
if (ShouldDispatch<kEvent>(env, thread)) {
|
|
FnType* callback = impl::GetCallback<kEvent>(env);
|
|
if (callback != nullptr) {
|
|
(*callback)(env, args...);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Need to give custom specializations for Breakpoint since it needs to filter out which particular
|
|
// methods/dex_pcs agents get notified on.
|
|
template <>
|
|
inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kBreakpoint>(art::Thread* thread,
|
|
JNIEnv* jnienv,
|
|
jthread jni_thread,
|
|
jmethodID jmethod,
|
|
jlocation location) const {
|
|
art::ArtMethod* method = art::jni::DecodeArtMethod(jmethod);
|
|
for (ArtJvmTiEnv* env : envs) {
|
|
// Search for a breakpoint on this particular method and location.
|
|
if (env != nullptr &&
|
|
ShouldDispatch<ArtJvmtiEvent::kBreakpoint>(env, thread) &&
|
|
env->breakpoints.find({method, location}) != env->breakpoints.end()) {
|
|
// We temporarily clear any pending exceptions so the event can call back into java code.
|
|
ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
|
|
jnienv->ExceptionClear();
|
|
auto callback = impl::GetCallback<ArtJvmtiEvent::kBreakpoint>(env);
|
|
(*callback)(env, jnienv, jni_thread, jmethod, location);
|
|
if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
|
|
jnienv->Throw(thr.get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 void EventHandler::DispatchEvent<ArtJvmtiEvent::kFieldModification>(art::Thread* thread,
|
|
JNIEnv* jnienv,
|
|
jthread jni_thread,
|
|
jmethodID method,
|
|
jlocation location,
|
|
jclass field_klass,
|
|
jobject object,
|
|
jfieldID field,
|
|
char type_char,
|
|
jvalue val) const {
|
|
for (ArtJvmTiEnv* env : envs) {
|
|
if (env != nullptr &&
|
|
ShouldDispatch<ArtJvmtiEvent::kFieldModification>(env, thread) &&
|
|
env->modify_watched_fields.find(
|
|
art::jni::DecodeArtField(field)) != env->modify_watched_fields.end()) {
|
|
ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
|
|
jnienv->ExceptionClear();
|
|
auto callback = impl::GetCallback<ArtJvmtiEvent::kFieldModification>(env);
|
|
(*callback)(env,
|
|
jnienv,
|
|
jni_thread,
|
|
method,
|
|
location,
|
|
field_klass,
|
|
object,
|
|
field,
|
|
type_char,
|
|
val);
|
|
if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
|
|
jnienv->Throw(thr.get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <>
|
|
inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFieldAccess>(art::Thread* thread,
|
|
JNIEnv* jnienv,
|
|
jthread jni_thread,
|
|
jmethodID method,
|
|
jlocation location,
|
|
jclass field_klass,
|
|
jobject object,
|
|
jfieldID field) const {
|
|
for (ArtJvmTiEnv* env : envs) {
|
|
if (env != nullptr &&
|
|
ShouldDispatch<ArtJvmtiEvent::kFieldAccess>(env, thread) &&
|
|
env->access_watched_fields.find(
|
|
art::jni::DecodeArtField(field)) != env->access_watched_fields.end()) {
|
|
ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
|
|
jnienv->ExceptionClear();
|
|
auto callback = impl::GetCallback<ArtJvmtiEvent::kFieldAccess>(env);
|
|
(*callback)(env, jnienv, jni_thread, method, location, field_klass, object, field);
|
|
if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
|
|
jnienv->Throw(thr.get());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Need to give a custom specialization for NativeMethodBind since it has to deal with an out
|
|
// variable.
|
|
template <>
|
|
inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>(art::Thread* thread,
|
|
JNIEnv* jnienv,
|
|
jthread jni_thread,
|
|
jmethodID method,
|
|
void* cur_method,
|
|
void** new_method) const {
|
|
*new_method = cur_method;
|
|
for (ArtJvmTiEnv* env : envs) {
|
|
if (env != nullptr && ShouldDispatch<ArtJvmtiEvent::kNativeMethodBind>(env, thread)) {
|
|
auto callback = impl::GetCallback<ArtJvmtiEvent::kNativeMethodBind>(env);
|
|
(*callback)(env, jnienv, jni_thread, method, cur_method, new_method);
|
|
if (*new_method != nullptr) {
|
|
cur_method = *new_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<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
|
|
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<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
|
|
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<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>(
|
|
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<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>(
|
|
thread,
|
|
jnienv,
|
|
class_being_redefined,
|
|
loader,
|
|
name,
|
|
protection_domain,
|
|
class_data_len,
|
|
class_data,
|
|
new_class_data_len,
|
|
new_class_data);
|
|
}
|
|
|
|
template <ArtJvmtiEvent kEvent>
|
|
inline bool EventHandler::ShouldDispatch(ArtJvmTiEnv* env,
|
|
art::Thread* thread) {
|
|
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;
|
|
}
|
|
|
|
inline void EventHandler::RecalculateGlobalEventMask(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 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace openjdkjvmti
|
|
|
|
#endif // ART_RUNTIME_OPENJDKJVMTI_EVENTS_INL_H_
|