diff options
author | 2017-07-25 14:05:52 -0700 | |
---|---|---|
committer | 2017-07-26 17:10:00 -0700 | |
commit | bebd7bd02a073056851cc1b0942c53ce0d2ee206 (patch) | |
tree | c2af3c94c89a2dcee1dd742d9f8806af8025df06 | |
parent | ce68cc6023ef929578bcff250dff150edd0ca5e9 (diff) |
JVMTI set & get local variables
Basic implementation of the JVMTI can_access_local_variables
capability. This implements the functions and behaviors required for
this capability.
Currently enabling this capability immediately forces all threads to
use the interpreter exclusively. This behavior should be removed
eventually.
Tests follow in next CL.
Test: ./test.py --host -j50
Bug: 34414073
Bug: 36892980
Change-Id: I11a4d3cb2b945955cca270efdee2fbfd2601e0ba
-rw-r--r-- | runtime/instrumentation.h | 4 | ||||
-rw-r--r-- | runtime/openjdkjvmti/OpenjdkJvmTi.cc | 108 | ||||
-rw-r--r-- | runtime/openjdkjvmti/events-inl.h | 10 | ||||
-rw-r--r-- | runtime/openjdkjvmti/events.cc | 15 | ||||
-rw-r--r-- | runtime/openjdkjvmti/events.h | 1 | ||||
-rw-r--r-- | runtime/openjdkjvmti/ti_method.cc | 543 | ||||
-rw-r--r-- | runtime/openjdkjvmti/ti_method.h | 23 | ||||
-rw-r--r-- | runtime/openjdkjvmti/ti_thread.cc | 5 | ||||
-rw-r--r-- | runtime/openjdkjvmti/ti_thread.h | 6 |
9 files changed, 655 insertions, 60 deletions
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h index 90b5def9fe..b8ea59725d 100644 --- a/runtime/instrumentation.h +++ b/runtime/instrumentation.h @@ -196,6 +196,10 @@ class Instrumentation { } bool ShouldNotifyMethodEnterExitEvents() const REQUIRES_SHARED(Locks::mutator_lock_); + bool CanDeoptimize() { + return deoptimization_enabled_; + } + // Executes everything with interpreter. void DeoptimizeEverything(const char* key) REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_) diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc index aff2ea4c57..af770724ab 100644 --- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc +++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc @@ -503,112 +503,112 @@ class JvmtiFunctions { } static jvmtiError GetLocalObject(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jobject* value_ptr ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jobject* value_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr); } static jvmtiError GetLocalInstance(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jobject* value_ptr ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jobject* value_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::GetLocalInstance(env, thread, depth, value_ptr); } static jvmtiError GetLocalInt(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jint* value_ptr ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jint* value_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr); } static jvmtiError GetLocalLong(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jlong* value_ptr ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jlong* value_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr); } static jvmtiError GetLocalFloat(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jfloat* value_ptr ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jfloat* value_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr); } static jvmtiError GetLocalDouble(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jdouble* value_ptr ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jdouble* value_ptr) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr); } static jvmtiError SetLocalObject(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jobject value ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jobject value) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::SetLocalVariable(env, thread, depth, slot, value); } static jvmtiError SetLocalInt(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jint value ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jint value) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::SetLocalVariable(env, thread, depth, slot, value); } static jvmtiError SetLocalLong(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jlong value ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jlong value) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::SetLocalVariable(env, thread, depth, slot, value); } static jvmtiError SetLocalFloat(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jfloat value ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jfloat value) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::SetLocalVariable(env, thread, depth, slot, value); } static jvmtiError SetLocalDouble(jvmtiEnv* env, - jthread thread ATTRIBUTE_UNUSED, - jint depth ATTRIBUTE_UNUSED, - jint slot ATTRIBUTE_UNUSED, - jdouble value ATTRIBUTE_UNUSED) { + jthread thread, + jint depth, + jint slot, + jdouble value) { ENSURE_VALID_ENV(env); ENSURE_HAS_CAP(env, can_access_local_variables); - return ERR(NOT_IMPLEMENTED); + return MethodUtil::SetLocalVariable(env, thread, depth, slot, value); } diff --git a/runtime/openjdkjvmti/events-inl.h b/runtime/openjdkjvmti/events-inl.h index 43177ab845..91e40553f0 100644 --- a/runtime/openjdkjvmti/events-inl.h +++ b/runtime/openjdkjvmti/events-inl.h @@ -414,9 +414,10 @@ inline bool EventHandler::NeedsEventUpdate(ArtJvmTiEnv* env, bool added) { ArtJvmtiEvent event = added ? ArtJvmtiEvent::kClassFileLoadHookNonRetransformable : ArtJvmtiEvent::kClassFileLoadHookRetransformable; - return caps.can_retransform_classes == 1 && - IsEventEnabledAnywhere(event) && - env->event_masks.IsEnabledAnywhere(event); + return (added && caps.can_access_local_variables == 1) || + (caps.can_retransform_classes == 1 && + IsEventEnabledAnywhere(event) && + env->event_masks.IsEnabledAnywhere(event)); } inline void EventHandler::HandleChangedCapabilities(ArtJvmTiEnv* env, @@ -428,6 +429,9 @@ inline void EventHandler::HandleChangedCapabilities(ArtJvmTiEnv* env, RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookRetransformable); RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookNonRetransformable); } + if (added && caps.can_access_local_variables == 1) { + HandleLocalAccessCapabilityAdded(); + } } } diff --git a/runtime/openjdkjvmti/events.cc b/runtime/openjdkjvmti/events.cc index 7a930d4163..2944a453e0 100644 --- a/runtime/openjdkjvmti/events.cc +++ b/runtime/openjdkjvmti/events.cc @@ -610,6 +610,21 @@ static void SetupTraceListener(JvmtiMethodTraceListener* listener, } } +void EventHandler::HandleLocalAccessCapabilityAdded() { + art::ScopedThreadStateChange stsc(art::Thread::Current(), art::ThreadState::kNative); + art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation(); + art::gc::ScopedGCCriticalSection gcs(art::Thread::Current(), + art::gc::kGcCauseInstrumentation, + art::gc::kCollectorTypeInstrumentation); + art::ScopedSuspendAll ssa("Deoptimize everything for local variable access", true); + // TODO This should be disabled when there are no environments using it. + if (!instr->CanDeoptimize()) { + instr->EnableDeoptimization(); + } + // TODO We should be able to support can_access_local_variables without this. + instr->DeoptimizeEverything("jvmti-local-variable-access"); +} + // Handle special work for the given event type, if necessary. void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) { switch (event) { diff --git a/runtime/openjdkjvmti/events.h b/runtime/openjdkjvmti/events.h index 5f37dcf0a7..617519eaa3 100644 --- a/runtime/openjdkjvmti/events.h +++ b/runtime/openjdkjvmti/events.h @@ -210,6 +210,7 @@ class EventHandler { unsigned char** new_class_data) const; void HandleEventType(ArtJvmtiEvent event, bool enable); + void HandleLocalAccessCapabilityAdded(); // List of all JvmTiEnv objects that have been created, in their creation order. // NB Some elements might be null representing envs that have been deleted. They should be skipped diff --git a/runtime/openjdkjvmti/ti_method.cc b/runtime/openjdkjvmti/ti_method.cc index ab25319732..ed5645a1ab 100644 --- a/runtime/openjdkjvmti/ti_method.cc +++ b/runtime/openjdkjvmti/ti_method.cc @@ -34,16 +34,22 @@ #include "art_jvmti.h" #include "art_method-inl.h" #include "base/enums.h" +#include "base/mutex-inl.h" #include "dex_file_annotations.h" #include "events-inl.h" #include "jni_internal.h" +#include "mirror/class-inl.h" +#include "mirror/class_loader.h" +#include "mirror/object-inl.h" #include "mirror/object_array-inl.h" #include "modifiers.h" #include "nativehelper/ScopedLocalRef.h" #include "runtime_callbacks.h" #include "scoped_thread_state_change-inl.h" +#include "stack.h" #include "thread-current-inl.h" #include "thread_list.h" +#include "ti_thread.h" #include "ti_phase.h" namespace openjdkjvmti { @@ -525,4 +531,541 @@ jvmtiError MethodUtil::IsMethodSynthetic(jvmtiEnv* env, jmethodID m, jboolean* i return IsMethodT(env, m, test, is_synthetic_ptr); } +struct FindFrameAtDepthVisitor : art::StackVisitor { + public: + FindFrameAtDepthVisitor(art::Thread* target, art::Context* ctx, jint depth) + REQUIRES_SHARED(art::Locks::mutator_lock_) + : art::StackVisitor(target, ctx, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames), + found_frame_(false), + cnt_(0), + depth_(static_cast<size_t>(depth)) { } + + bool FoundFrame() { + return found_frame_; + } + + bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS { + if (GetMethod()->IsRuntimeMethod()) { + return true; + } + if (cnt_ == depth_) { + // We found our frame, exit. + found_frame_ = true; + return false; + } else { + cnt_++; + return true; + } + } + + private: + bool found_frame_; + size_t cnt_; + size_t depth_; +}; + +class CommonLocalVariableClosure : public art::Closure { + public: + CommonLocalVariableClosure(art::Thread* caller, + jint depth, + jint slot) + : result_(ERR(INTERNAL)), caller_(caller), depth_(depth), slot_(slot) {} + + void Run(art::Thread* self) OVERRIDE REQUIRES(art::Locks::mutator_lock_) { + art::Locks::mutator_lock_->AssertSharedHeld(art::Thread::Current()); + std::unique_ptr<art::Context> context(art::Context::Create()); + FindFrameAtDepthVisitor visitor(self, context.get(), depth_); + visitor.WalkStack(); + if (!visitor.FoundFrame()) { + // Must have been a bad depth. + result_ = ERR(NO_MORE_FRAMES); + return; + } + art::ArtMethod* method = visitor.GetMethod(); + if (method->IsNative() || !visitor.IsShadowFrame()) { + // TODO We really should support get/set for non-shadow frames. + result_ = ERR(OPAQUE_FRAME); + return; + } else if (method->GetCodeItem()->registers_size_ <= slot_) { + result_ = ERR(INVALID_SLOT); + return; + } + uint32_t pc = visitor.GetDexPc(/*abort_on_failure*/ false); + if (pc == art::DexFile::kDexNoIndex) { + // Cannot figure out current PC. + result_ = ERR(OPAQUE_FRAME); + return; + } + std::string descriptor; + art::Primitive::Type slot_type = art::Primitive::kPrimVoid; + jvmtiError err = GetSlotType(method, pc, &descriptor, &slot_type); + if (err != OK) { + result_ = err; + return; + } + + err = GetTypeError(method, slot_type, descriptor); + if (err != OK) { + result_ = err; + return; + } + result_ = Execute(method, visitor); + } + + jvmtiError GetResult() const { + return result_; + } + + protected: + virtual jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor) + REQUIRES(art::Locks::mutator_lock_) = 0; + virtual jvmtiError GetTypeError(art::ArtMethod* method, + art::Primitive::Type type, + const std::string& descriptor) + REQUIRES(art::Locks::mutator_lock_) = 0; + + jvmtiError GetSlotType(art::ArtMethod* method, + uint32_t dex_pc, + /*out*/std::string* descriptor, + /*out*/art::Primitive::Type* type) + REQUIRES(art::Locks::mutator_lock_) { + const art::DexFile* dex_file = method->GetDexFile(); + const art::DexFile::CodeItem* code_item = method->GetCodeItem(); + if (dex_file == nullptr || code_item == nullptr) { + return ERR(OPAQUE_FRAME); + } + + struct GetLocalVariableInfoContext { + explicit GetLocalVariableInfoContext(jint slot, + uint32_t pc, + std::string* out_descriptor, + art::Primitive::Type* out_type) + : found_(false), jslot_(slot), pc_(pc), descriptor_(out_descriptor), type_(out_type) { + *descriptor_ = ""; + *type_ = art::Primitive::kPrimVoid; + } + + static void Callback(void* raw_ctx, const art::DexFile::LocalInfo& entry) { + reinterpret_cast<GetLocalVariableInfoContext*>(raw_ctx)->Handle(entry); + } + + void Handle(const art::DexFile::LocalInfo& entry) { + if (found_) { + return; + } else if (entry.start_address_ <= pc_ && + entry.end_address_ > pc_ && + entry.reg_ == jslot_) { + found_ = true; + *type_ = art::Primitive::GetType(entry.descriptor_[0]); + *descriptor_ = entry.descriptor_; + } + return; + } + + bool found_; + jint jslot_; + uint32_t pc_; + std::string* descriptor_; + art::Primitive::Type* type_; + }; + + GetLocalVariableInfoContext context(slot_, dex_pc, descriptor, type); + if (!dex_file->DecodeDebugLocalInfo(code_item, + method->IsStatic(), + method->GetDexMethodIndex(), + GetLocalVariableInfoContext::Callback, + &context) || !context.found_) { + // Something went wrong with decoding the debug information. It might as well not be there. + return ERR(INVALID_SLOT); + } else { + return OK; + } + } + + jvmtiError result_; + art::Thread* caller_; + jint depth_; + jint slot_; +}; + +class GetLocalVariableClosure : public CommonLocalVariableClosure { + public: + GetLocalVariableClosure(art::Thread* caller, + jint depth, + jint slot, + art::Primitive::Type type, + jvalue* val) + : CommonLocalVariableClosure(caller, depth, slot), type_(type), val_(val) {} + + protected: + jvmtiError GetTypeError(art::ArtMethod* method ATTRIBUTE_UNUSED, + art::Primitive::Type slot_type, + const std::string& descriptor ATTRIBUTE_UNUSED) + OVERRIDE REQUIRES(art::Locks::mutator_lock_) { + switch (slot_type) { + case art::Primitive::kPrimByte: + case art::Primitive::kPrimChar: + case art::Primitive::kPrimInt: + case art::Primitive::kPrimShort: + case art::Primitive::kPrimBoolean: + return type_ == art::Primitive::kPrimInt ? OK : ERR(TYPE_MISMATCH); + case art::Primitive::kPrimLong: + case art::Primitive::kPrimFloat: + case art::Primitive::kPrimDouble: + case art::Primitive::kPrimNot: + return type_ == slot_type ? OK : ERR(TYPE_MISMATCH); + case art::Primitive::kPrimVoid: + LOG(FATAL) << "Unexpected primitive type " << slot_type; + UNREACHABLE(); + } + } + + jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor) + OVERRIDE REQUIRES(art::Locks::mutator_lock_) { + switch (type_) { + case art::Primitive::kPrimNot: { + uint32_t ptr_val; + if (!visitor.GetVReg(method, + static_cast<uint16_t>(slot_), + art::kReferenceVReg, + &ptr_val)) { + return ERR(OPAQUE_FRAME); + } + art::ObjPtr<art::mirror::Object> obj(reinterpret_cast<art::mirror::Object*>(ptr_val)); + val_->l = obj.IsNull() ? nullptr : caller_->GetJniEnv()->AddLocalReference<jobject>(obj); + break; + } + case art::Primitive::kPrimInt: + case art::Primitive::kPrimFloat: { + if (!visitor.GetVReg(method, + static_cast<uint16_t>(slot_), + type_ == art::Primitive::kPrimFloat ? art::kFloatVReg : art::kIntVReg, + reinterpret_cast<uint32_t*>(&val_->i))) { + return ERR(OPAQUE_FRAME); + } + break; + } + case art::Primitive::kPrimDouble: + case art::Primitive::kPrimLong: { + auto lo_type = type_ == art::Primitive::kPrimLong ? art::kLongLoVReg : art::kDoubleLoVReg; + auto high_type = type_ == art::Primitive::kPrimLong ? art::kLongHiVReg : art::kDoubleHiVReg; + if (!visitor.GetVRegPair(method, + static_cast<uint16_t>(slot_), + lo_type, + high_type, + reinterpret_cast<uint64_t*>(&val_->j))) { + return ERR(OPAQUE_FRAME); + } + break; + } + default: { + LOG(FATAL) << "unexpected register type " << type_; + UNREACHABLE(); + } + } + return OK; + } + + private: + art::Primitive::Type type_; + jvalue* val_; +}; + +jvmtiError MethodUtil::GetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED, + jthread thread, + jint depth, + jint slot, + art::Primitive::Type type, + jvalue* val) { + if (depth < 0) { + return ERR(ILLEGAL_ARGUMENT); + } + art::Thread* self = art::Thread::Current(); + art::ScopedObjectAccess soa(self); + art::Thread* target = ThreadUtil::GetNativeThread(thread, soa); + if (target == nullptr && thread == nullptr) { + return ERR(INVALID_THREAD); + } + if (target == nullptr) { + return ERR(THREAD_NOT_ALIVE); + } + GetLocalVariableClosure c(self, depth, slot, type, val); + art::MutexLock mu(self, *art::Locks::thread_list_lock_); + if (!target->RequestSynchronousCheckpoint(&c)) { + return ERR(THREAD_NOT_ALIVE); + } else { + return c.GetResult(); + } +} + +class SetLocalVariableClosure : public CommonLocalVariableClosure { + public: + SetLocalVariableClosure(art::Thread* caller, + jint depth, + jint slot, + art::Primitive::Type type, + jvalue val) + : CommonLocalVariableClosure(caller, depth, slot), type_(type), val_(val) {} + + protected: + jvmtiError GetTypeError(art::ArtMethod* method, + art::Primitive::Type slot_type, + const std::string& descriptor) + OVERRIDE REQUIRES(art::Locks::mutator_lock_) { + switch (slot_type) { + case art::Primitive::kPrimNot: { + if (type_ != art::Primitive::kPrimNot) { + return ERR(TYPE_MISMATCH); + } else if (val_.l == nullptr) { + return OK; + } else { + art::ClassLinker* cl = art::Runtime::Current()->GetClassLinker(); + art::ObjPtr<art::mirror::Class> set_class = + caller_->DecodeJObject(val_.l)->GetClass(); + art::ObjPtr<art::mirror::ClassLoader> loader = + method->GetDeclaringClass()->GetClassLoader(); + art::ObjPtr<art::mirror::Class> slot_class = + cl->LookupClass(caller_, descriptor.c_str(), loader); + DCHECK(!slot_class.IsNull()); + return slot_class->IsAssignableFrom(set_class) ? OK : ERR(TYPE_MISMATCH); + } + } + case art::Primitive::kPrimByte: + case art::Primitive::kPrimChar: + case art::Primitive::kPrimInt: + case art::Primitive::kPrimShort: + case art::Primitive::kPrimBoolean: + return type_ == art::Primitive::kPrimInt ? OK : ERR(TYPE_MISMATCH); + case art::Primitive::kPrimLong: + case art::Primitive::kPrimFloat: + case art::Primitive::kPrimDouble: + return type_ == slot_type ? OK : ERR(TYPE_MISMATCH); + case art::Primitive::kPrimVoid: + LOG(FATAL) << "Unexpected primitive type " << slot_type; + UNREACHABLE(); + } + } + + jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor) + OVERRIDE REQUIRES(art::Locks::mutator_lock_) { + switch (type_) { + case art::Primitive::kPrimNot: { + uint32_t ptr_val; + art::ObjPtr<art::mirror::Object> obj(caller_->DecodeJObject(val_.l)); + ptr_val = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(obj.Ptr())); + if (!visitor.SetVReg(method, + static_cast<uint16_t>(slot_), + ptr_val, + art::kReferenceVReg)) { + return ERR(OPAQUE_FRAME); + } + break; + } + case art::Primitive::kPrimInt: + case art::Primitive::kPrimFloat: { + if (!visitor.SetVReg(method, + static_cast<uint16_t>(slot_), + static_cast<uint32_t>(val_.i), + type_ == art::Primitive::kPrimFloat ? art::kFloatVReg + : art::kIntVReg)) { + return ERR(OPAQUE_FRAME); + } + break; + } + case art::Primitive::kPrimDouble: + case art::Primitive::kPrimLong: { + auto lo_type = type_ == art::Primitive::kPrimLong ? art::kLongLoVReg : art::kDoubleLoVReg; + auto high_type = type_ == art::Primitive::kPrimLong ? art::kLongHiVReg : art::kDoubleHiVReg; + if (!visitor.SetVRegPair(method, + static_cast<uint16_t>(slot_), + static_cast<uint64_t>(val_.j), + lo_type, + high_type)) { + return ERR(OPAQUE_FRAME); + } + break; + } + default: { + LOG(FATAL) << "unexpected register type " << type_; + UNREACHABLE(); + } + } + return OK; + } + + private: + art::Primitive::Type type_; + jvalue val_; +}; + +jvmtiError MethodUtil::SetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED, + jthread thread, + jint depth, + jint slot, + art::Primitive::Type type, + jvalue val) { + if (depth < 0) { + return ERR(ILLEGAL_ARGUMENT); + } + art::Thread* self = art::Thread::Current(); + art::ScopedObjectAccess soa(self); + art::Thread* target = ThreadUtil::GetNativeThread(thread, soa); + if (target == nullptr && thread == nullptr) { + return ERR(INVALID_THREAD); + } + if (target == nullptr) { + return ERR(THREAD_NOT_ALIVE); + } + SetLocalVariableClosure c(self, depth, slot, type, val); + art::MutexLock mu(self, *art::Locks::thread_list_lock_); + if (!target->RequestSynchronousCheckpoint(&c)) { + return ERR(THREAD_NOT_ALIVE); + } else { + return c.GetResult(); + } +} + +class GetLocalInstanceClosure : public art::Closure { + public: + GetLocalInstanceClosure(art::Thread* caller, jint depth, jobject* val) + : result_(ERR(INTERNAL)), + caller_(caller), + depth_(depth), + val_(val) {} + + void Run(art::Thread* self) OVERRIDE REQUIRES(art::Locks::mutator_lock_) { + art::Locks::mutator_lock_->AssertSharedHeld(art::Thread::Current()); + std::unique_ptr<art::Context> context(art::Context::Create()); + FindFrameAtDepthVisitor visitor(self, context.get(), depth_); + visitor.WalkStack(); + if (!visitor.FoundFrame()) { + // Must have been a bad depth. + result_ = ERR(NO_MORE_FRAMES); + return; + } + art::ArtMethod* method = visitor.GetMethod(); + if (!visitor.IsShadowFrame() && !method->IsNative() && !method->IsProxyMethod()) { + // TODO We really should support get/set for non-shadow frames. + result_ = ERR(OPAQUE_FRAME); + return; + } + result_ = OK; + art::ObjPtr<art::mirror::Object> obj = visitor.GetThisObject(); + *val_ = obj.IsNull() ? nullptr : caller_->GetJniEnv()->AddLocalReference<jobject>(obj); + } + + jvmtiError GetResult() const { + return result_; + } + + private: + jvmtiError result_; + art::Thread* caller_; + jint depth_; + jobject* val_; +}; + +jvmtiError MethodUtil::GetLocalInstance(jvmtiEnv* env ATTRIBUTE_UNUSED, + jthread thread, + jint depth, + jobject* data) { + if (depth < 0) { + return ERR(ILLEGAL_ARGUMENT); + } + art::Thread* self = art::Thread::Current(); + art::ScopedObjectAccess soa(self); + art::Thread* target = ThreadUtil::GetNativeThread(thread, soa); + if (target == nullptr && thread == nullptr) { + return ERR(INVALID_THREAD); + } + if (target == nullptr) { + return ERR(THREAD_NOT_ALIVE); + } + GetLocalInstanceClosure c(self, depth, data); + art::MutexLock mu(self, *art::Locks::thread_list_lock_); + if (!target->RequestSynchronousCheckpoint(&c)) { + return ERR(THREAD_NOT_ALIVE); + } else { + return c.GetResult(); + } +} + +#define FOR_JVMTI_JVALUE_TYPES(fn) \ + fn(jint, art::Primitive::kPrimInt, i) \ + fn(jlong, art::Primitive::kPrimLong, j) \ + fn(jfloat, art::Primitive::kPrimFloat, f) \ + fn(jdouble, art::Primitive::kPrimDouble, d) \ + fn(jobject, art::Primitive::kPrimNot, l) + +namespace impl { + +template<typename T> void WriteJvalue(T, jvalue*); +template<typename T> void ReadJvalue(jvalue, T*); +template<typename T> art::Primitive::Type GetJNIType(); + +#define JNI_TYPE_CHAR(type, prim, id) \ +template<> art::Primitive::Type GetJNIType<type>() { \ + return prim; \ +} + +FOR_JVMTI_JVALUE_TYPES(JNI_TYPE_CHAR); + +#undef JNI_TYPE_CHAR + +#define RW_JVALUE(type, prim, id) \ + template<> void ReadJvalue<type>(jvalue in, type* out) { \ + *out = in.id; \ + } \ + template<> void WriteJvalue<type>(type in, jvalue* out) { \ + out->id = in; \ + } + +FOR_JVMTI_JVALUE_TYPES(RW_JVALUE); + +#undef RW_JVALUE + +} // namespace impl + +template<typename T> +jvmtiError MethodUtil::SetLocalVariable(jvmtiEnv* env, + jthread thread, + jint depth, + jint slot, + T data) { + jvalue v = {.j = 0}; + art::Primitive::Type type = impl::GetJNIType<T>(); + impl::WriteJvalue(data, &v); + return SetLocalVariableGeneric(env, thread, depth, slot, type, v); +} + +template<typename T> +jvmtiError MethodUtil::GetLocalVariable(jvmtiEnv* env, + jthread thread, + jint depth, + jint slot, + T* data) { + if (data == nullptr) { + return ERR(NULL_POINTER); + } + jvalue v = {.j = 0}; + art::Primitive::Type type = impl::GetJNIType<T>(); + jvmtiError err = GetLocalVariableGeneric(env, thread, depth, slot, type, &v); + if (err != OK) { + return err; + } else { + impl::ReadJvalue(v, data); + return OK; + } +} + +#define GET_SET_LV(type, prim, id) \ + template jvmtiError MethodUtil::GetLocalVariable<type>(jvmtiEnv*, jthread, jint, jint, type*); \ + template jvmtiError MethodUtil::SetLocalVariable<type>(jvmtiEnv*, jthread, jint, jint, type); + +FOR_JVMTI_JVALUE_TYPES(GET_SET_LV); + +#undef GET_SET_LV + +#undef FOR_JVMTI_JVALUE_TYPES + } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_method.h b/runtime/openjdkjvmti/ti_method.h index f506594c64..aabaedb932 100644 --- a/runtime/openjdkjvmti/ti_method.h +++ b/runtime/openjdkjvmti/ti_method.h @@ -34,6 +34,7 @@ #include "jni.h" #include "jvmti.h" +#include "primitive.h" namespace openjdkjvmti { @@ -84,6 +85,28 @@ class MethodUtil { jmethodID method, jint* entry_count_ptr, jvmtiLocalVariableEntry** table_ptr); + + template<typename T> + static jvmtiError SetLocalVariable(jvmtiEnv* env, jthread thread, jint depth, jint slot, T data); + + template<typename T> + static jvmtiError GetLocalVariable(jvmtiEnv* env, jthread thread, jint depth, jint slot, T* data); + + static jvmtiError GetLocalInstance(jvmtiEnv* env, jthread thread, jint depth, jobject* data); + + private: + static jvmtiError SetLocalVariableGeneric(jvmtiEnv* env, + jthread thread, + jint depth, + jint slot, + art::Primitive::Type type, + jvalue value); + static jvmtiError GetLocalVariableGeneric(jvmtiEnv* env, + jthread thread, + jint depth, + jint slot, + art::Primitive::Type type, + jvalue* value); }; } // namespace openjdkjvmti diff --git a/runtime/openjdkjvmti/ti_thread.cc b/runtime/openjdkjvmti/ti_thread.cc index 9acea2a288..7d42879055 100644 --- a/runtime/openjdkjvmti/ti_thread.cc +++ b/runtime/openjdkjvmti/ti_thread.cc @@ -171,9 +171,8 @@ static art::Thread* GetNativeThreadLocked(jthread thread, } // Get the native thread. The spec says a null object denotes the current thread. -static art::Thread* GetNativeThread(jthread thread, - const art::ScopedObjectAccessAlreadyRunnable& soa) - REQUIRES_SHARED(art::Locks::mutator_lock_) { +art::Thread* ThreadUtil::GetNativeThread(jthread thread, + const art::ScopedObjectAccessAlreadyRunnable& soa) { if (thread == nullptr) { return art::Thread::Current(); } diff --git a/runtime/openjdkjvmti/ti_thread.h b/runtime/openjdkjvmti/ti_thread.h index 0f7e8379fd..083bf8d7a5 100644 --- a/runtime/openjdkjvmti/ti_thread.h +++ b/runtime/openjdkjvmti/ti_thread.h @@ -35,10 +35,12 @@ #include "jni.h" #include "jvmti.h" +#include "base/macros.h" #include "base/mutex.h" namespace art { class ArtField; +class ScopedObjectAccessAlreadyRunnable; class Thread; } // namespace art @@ -86,6 +88,10 @@ class ThreadUtil { const jthread* threads, jvmtiError* results); + static art::Thread* GetNativeThread(jthread thread, + const art::ScopedObjectAccessAlreadyRunnable& soa) + REQUIRES_SHARED(art::Locks::mutator_lock_); + private: // We need to make sure only one thread tries to suspend threads at a time so we can get the // 'suspend-only-once' behavior the spec requires. Internally, ART considers suspension to be a |