diff options
-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 |