diff options
Diffstat (limited to 'openjdkjvmti/ti_method.cc')
-rw-r--r-- | openjdkjvmti/ti_method.cc | 398 |
1 files changed, 325 insertions, 73 deletions
diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc index 408ce6901f..4c6ea219f4 100644 --- a/openjdkjvmti/ti_method.cc +++ b/openjdkjvmti/ti_method.cc @@ -31,22 +31,33 @@ #include "ti_method.h" +#include <initializer_list> #include <type_traits> +#include <variant> +#include "android-base/macros.h" #include "arch/context.h" #include "art_jvmti.h" #include "art_method-inl.h" #include "base/enums.h" +#include "base/globals.h" +#include "base/macros.h" #include "base/mutex-inl.h" #include "deopt_manager.h" #include "dex/code_item_accessors-inl.h" +#include "dex/code_item_accessors.h" #include "dex/dex_file_annotations.h" #include "dex/dex_file_types.h" +#include "dex/dex_instruction.h" +#include "dex/dex_instruction_iterator.h" #include "dex/modifiers.h" +#include "dex/primitive.h" #include "events-inl.h" #include "gc_root-inl.h" +#include "handle.h" #include "jit/jit.h" #include "jni/jni_internal.h" +#include "jvmti.h" #include "mirror/class-inl.h" #include "mirror/class_loader.h" #include "mirror/object-inl.h" @@ -56,13 +67,18 @@ #include "obj_ptr.h" #include "runtime_callbacks.h" #include "scoped_thread_state_change-inl.h" +#include "scoped_thread_state_change.h" #include "stack.h" #include "thread-current-inl.h" #include "thread.h" #include "thread_list.h" +#include "ti_logging.h" #include "ti_stack.h" #include "ti_thread.h" #include "ti_phase.h" +#include "verifier/register_line-inl.h" +#include "verifier/reg_type-inl.h" +#include "verifier/method_verifier-inl.h" namespace openjdkjvmti { @@ -526,10 +542,21 @@ jvmtiError MethodUtil::IsMethodSynthetic(jvmtiEnv* env, jmethodID m, jboolean* i class CommonLocalVariableClosure : public art::Closure { public: - CommonLocalVariableClosure(jint depth, jint slot) - : result_(ERR(INTERNAL)), depth_(depth), slot_(slot) {} + // The verifier isn't always able to be as specific as the local-variable-table. We can only get + // 32-bit, 64-bit or reference. + enum class VerifierPrimitiveType { + k32BitValue, // float, int, short, char, boolean, byte + k64BitValue, // double, long + kReferenceValue, // Object + kZeroValue, // null or zero constant. Might be either k32BitValue or kReferenceValue + }; - void Run(art::Thread* self) override REQUIRES(art::Locks::mutator_lock_) { + using SlotType = std::variant<art::Primitive::Type, VerifierPrimitiveType>; + + CommonLocalVariableClosure(jvmtiEnv* jvmti, jint depth, jint slot) + : jvmti_(jvmti), result_(ERR(INTERNAL)), depth_(depth), slot_(slot) {} + + void Run(art::Thread* self) override REQUIRES_SHARED(art::Locks::mutator_lock_) { art::Locks::mutator_lock_->AssertSharedHeld(art::Thread::Current()); bool needs_instrument; { @@ -560,7 +587,7 @@ class CommonLocalVariableClosure : public art::Closure { return; } std::string descriptor; - art::Primitive::Type slot_type = art::Primitive::kPrimVoid; + SlotType slot_type{ art::Primitive::kPrimVoid }; jvmtiError err = GetSlotType(method, pc, &descriptor, &slot_type); if (err != OK) { result_ = err; @@ -587,56 +614,190 @@ class CommonLocalVariableClosure : public art::Closure { virtual jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor) REQUIRES_SHARED(art::Locks::mutator_lock_) = 0; virtual jvmtiError GetTypeError(art::ArtMethod* method, - art::Primitive::Type type, + SlotType type, const std::string& descriptor) REQUIRES_SHARED(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(); - if (dex_file == nullptr) { - return ERR(OPAQUE_FRAME); + /*out*/SlotType* type) + REQUIRES_SHARED(art::Locks::mutator_lock_); + + jvmtiError InferSlotTypeFromVerifier(art::ArtMethod* method, + uint32_t dex_pc, + /*out*/ std::string* descriptor, + /*out*/ SlotType* type) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + art::Thread* self = art::Thread::Current(); + art::StackHandleScope<2> hs(self); + std::unique_ptr<art::verifier::MethodVerifier> verifier( + art::verifier::MethodVerifier::CalculateVerificationInfo( + self, + method, + hs.NewHandle(method->GetDexCache()), + hs.NewHandle(method->GetDeclaringClass()->GetClassLoader()))); + if (verifier == nullptr) { + JVMTI_LOG(WARNING, jvmti_) << "Unable to extract verification information from " + << method->PrettyMethod() << " due to hard verification failures! " + << "How did this method even get loaded!"; + return ERR(INTERNAL); } - art::CodeItemDebugInfoAccessor accessor(method->DexInstructionDebugInfo()); - if (!accessor.HasCodeItem()) { + art::verifier::RegisterLine* line = verifier->GetRegLine(dex_pc); + if (line == nullptr) { + JVMTI_LOG(WARNING, jvmti_) << "Unable to determine register line at dex-pc " << dex_pc + << " for method " << method->PrettyMethod(); return ERR(OPAQUE_FRAME); } - bool found = false; - *type = art::Primitive::kPrimVoid; - descriptor->clear(); - auto visitor = [&](const art::DexFile::LocalInfo& entry) { - if (!found && - entry.start_address_ <= dex_pc && - entry.end_address_ > dex_pc && - entry.reg_ == slot_) { - found = true; - *type = art::Primitive::GetType(entry.descriptor_[0]); - *descriptor = entry.descriptor_; - } - }; - if (!accessor.DecodeDebugLocalInfo(method->IsStatic(), method->GetDexMethodIndex(), visitor) || - !found) { - // Something went wrong with decoding the debug information. It might as well not be there. + const art::verifier::RegType& rt = line->GetRegisterType(verifier.get(), slot_); + if (rt.IsUndefined()) { + return ERR(INVALID_SLOT); + } else if (rt.IsNonZeroReferenceTypes() || rt.IsNull()) { + *descriptor = (rt.HasClass() ? rt.GetDescriptor() : "Ljava/lang/Object;"); + *type = VerifierPrimitiveType::kReferenceValue; + return OK; + } else if (rt.IsZero()) { + *descriptor = "I"; + *type = VerifierPrimitiveType::kZeroValue; + return OK; + } else if (rt.IsCategory1Types()) { + *descriptor = "I"; + *type = VerifierPrimitiveType::k32BitValue; + return OK; + } else if (rt.IsCategory2Types() && rt.IsLowHalf()) { + *descriptor = "J"; + *type = VerifierPrimitiveType::k64BitValue; + return OK; + } else { + // The slot doesn't have a type. Must not be valid here. return ERR(INVALID_SLOT); } - return OK; } + constexpr VerifierPrimitiveType SquashType(SlotType t) { + if (std::holds_alternative<art::Primitive::Type>(t)) { + switch (std::get<art::Primitive::Type>(t)) { + // 32-bit primitives + case art::Primitive::kPrimByte: + case art::Primitive::kPrimChar: + case art::Primitive::kPrimInt: + case art::Primitive::kPrimShort: + case art::Primitive::kPrimBoolean: + case art::Primitive::kPrimFloat: + return VerifierPrimitiveType::k32BitValue; + // 64-bit primitives + case art::Primitive::kPrimLong: + case art::Primitive::kPrimDouble: + return VerifierPrimitiveType::k64BitValue; + case art::Primitive::kPrimNot: + return VerifierPrimitiveType::kReferenceValue; + case art::Primitive::kPrimVoid: + LOG(FATAL) << "Got kPrimVoid"; + UNREACHABLE(); + } + } else { + return std::get<VerifierPrimitiveType>(t); + } + } + + jvmtiEnv* jvmti_; jvmtiError result_; jint depth_; jint slot_; + + private: + DISALLOW_COPY_AND_ASSIGN(CommonLocalVariableClosure); }; +std::ostream& operator<<(std::ostream& os, + CommonLocalVariableClosure::VerifierPrimitiveType state) { + switch (state) { + case CommonLocalVariableClosure::VerifierPrimitiveType::k32BitValue: + return os << "32BitValue"; + case CommonLocalVariableClosure::VerifierPrimitiveType::k64BitValue: + return os << "64BitValue"; + case CommonLocalVariableClosure::VerifierPrimitiveType::kReferenceValue: + return os << "ReferenceValue"; + case CommonLocalVariableClosure::VerifierPrimitiveType::kZeroValue: + return os << "ZeroValue"; + } +} + +std::ostream& operator<<(std::ostream& os, CommonLocalVariableClosure::SlotType state) { + if (std::holds_alternative<art::Primitive::Type>(state)) { + return os << "Primitive::Type[" << std::get<art::Primitive::Type>(state) << "]"; + } else { + return os << "VerifierPrimitiveType[" + << std::get<CommonLocalVariableClosure::VerifierPrimitiveType>(state) << "]"; + } +} + +jvmtiError CommonLocalVariableClosure::GetSlotType(art::ArtMethod* method, + uint32_t dex_pc, + /*out*/ std::string* descriptor, + /*out*/ SlotType* type) { + const art::DexFile* dex_file = method->GetDexFile(); + if (dex_file == nullptr) { + return ERR(OPAQUE_FRAME); + } + art::CodeItemDebugInfoAccessor accessor(method->DexInstructionDebugInfo()); + if (!accessor.HasCodeItem()) { + return ERR(OPAQUE_FRAME); + } + bool found = false; + *type = art::Primitive::kPrimVoid; + descriptor->clear(); + auto visitor = [&](const art::DexFile::LocalInfo& entry) { + if (!found && entry.start_address_ <= dex_pc && entry.end_address_ > dex_pc && + entry.reg_ == slot_) { + found = true; + *type = art::Primitive::GetType(entry.descriptor_[0]); + *descriptor = entry.descriptor_; + } + }; + if (!accessor.DecodeDebugLocalInfo(method->IsStatic(), method->GetDexMethodIndex(), visitor) || + !found) { + // Something went wrong with decoding the debug information. It might as well not be there. + // Try to find the type with the verifier. + // TODO This is very slow. + return InferSlotTypeFromVerifier(method, dex_pc, descriptor, type); + } else if (art::kIsDebugBuild) { + std::string type_unused; + SlotType verifier_type{ art::Primitive::kPrimVoid }; + DCHECK_EQ(InferSlotTypeFromVerifier(method, dex_pc, &type_unused, &verifier_type), OK) + << method->PrettyMethod() << " failed to verify!"; + if (*type == SlotType{ art::Primitive::kPrimNot }) { + // We cannot distinguish between a constant 0 and a null reference so we return that it is a + // 32bit value (Due to the way references are read by the interpreter this is safe even if + // it's modified, the value will remain null). This is not ideal since it prevents modifying + // locals in some circumstances but generally is not a big deal (since one can just modify it + // later once it's been determined to be a reference by a later instruction). + DCHECK(verifier_type == SlotType { VerifierPrimitiveType::kZeroValue } || + verifier_type == SlotType { VerifierPrimitiveType::kReferenceValue }) + << "Verifier disagrees on type of slot! debug: " << *type + << " verifier: " << verifier_type; + } else if (verifier_type == SlotType { VerifierPrimitiveType::kZeroValue }) { + DCHECK(VerifierPrimitiveType::k32BitValue == SquashType(*type) || + VerifierPrimitiveType::kReferenceValue == SquashType(*type)) + << "Verifier disagrees on type of slot! debug: " << *type + << " verifier: " << verifier_type; + } else { + DCHECK_EQ(SquashType(verifier_type), SquashType(*type)) + << "Verifier disagrees on type of slot! debug: " << *type + << " verifier: " << verifier_type; + } + } + return OK; +} + class GetLocalVariableClosure : public CommonLocalVariableClosure { public: - GetLocalVariableClosure(jint depth, + GetLocalVariableClosure(jvmtiEnv* jvmti, + jint depth, jint slot, art::Primitive::Type type, jvalue* val) - : CommonLocalVariableClosure(depth, slot), + : CommonLocalVariableClosure(jvmti, depth, slot), type_(type), val_(val), obj_val_(nullptr) {} @@ -656,22 +817,61 @@ class GetLocalVariableClosure : public CommonLocalVariableClosure { } protected: - jvmtiError GetTypeError(art::ArtMethod* method ATTRIBUTE_UNUSED, - art::Primitive::Type slot_type, - const std::string& descriptor ATTRIBUTE_UNUSED) - override REQUIRES_SHARED(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: + jvmtiError + GetTypeError(art::ArtMethod* method, SlotType slot_type, const std::string& descriptor) override + REQUIRES_SHARED(art::Locks::mutator_lock_) { + jvmtiError res = GetTypeErrorInner(method, slot_type, descriptor); + if (res == ERR(TYPE_MISMATCH)) { + JVMTI_LOG(INFO, jvmti_) << "Unable to Get local variable in slot " << slot_ << ". Expected" + << " slot to be of type compatible with " << SlotType { type_ } + << " but slot is " << slot_type; + } else if (res != OK) { + JVMTI_LOG(INFO, jvmti_) << "Unable to get local variable in slot " << slot_ << "."; + } + return res; + } + + jvmtiError GetTypeErrorInner(art::ArtMethod* method ATTRIBUTE_UNUSED, + SlotType slot_type, + const std::string& descriptor ATTRIBUTE_UNUSED) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + switch (type_) { case art::Primitive::kPrimFloat: - case art::Primitive::kPrimDouble: + case art::Primitive::kPrimInt: { + if (std::holds_alternative<VerifierPrimitiveType>(slot_type)) { + return (slot_type == SlotType { VerifierPrimitiveType::k32BitValue } || + slot_type == SlotType { VerifierPrimitiveType::kZeroValue }) + ? OK + : ERR(TYPE_MISMATCH); + } else if (type_ == art::Primitive::kPrimFloat || + slot_type == SlotType { art::Primitive::kPrimFloat }) { + // Check that we are actually a float. + return (SlotType { type_ } == slot_type) ? OK : ERR(TYPE_MISMATCH); + } else { + // Some smaller int type. + return SquashType(slot_type) == SquashType(SlotType { type_ }) ? OK : ERR(TYPE_MISMATCH); + } + } + case art::Primitive::kPrimLong: + case art::Primitive::kPrimDouble: { + // todo + if (std::holds_alternative<VerifierPrimitiveType>(slot_type)) { + return (slot_type == SlotType { VerifierPrimitiveType::k64BitValue }) + ? OK + : ERR(TYPE_MISMATCH); + } else { + return slot_type == SlotType { type_ } ? OK : ERR(TYPE_MISMATCH); + } + } case art::Primitive::kPrimNot: - return type_ == slot_type ? OK : ERR(TYPE_MISMATCH); + return (SquashType(slot_type) == VerifierPrimitiveType::kReferenceValue || + SquashType(slot_type) == VerifierPrimitiveType::kZeroValue) + ? OK + : ERR(TYPE_MISMATCH); + case art::Primitive::kPrimShort: + case art::Primitive::kPrimChar: + case art::Primitive::kPrimByte: + case art::Primitive::kPrimBoolean: case art::Primitive::kPrimVoid: LOG(FATAL) << "Unexpected primitive type " << slot_type; UNREACHABLE(); @@ -735,7 +935,7 @@ class GetLocalVariableClosure : public CommonLocalVariableClosure { jobject obj_val_; }; -jvmtiError MethodUtil::GetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED, +jvmtiError MethodUtil::GetLocalVariableGeneric(jvmtiEnv* env, jthread thread, jint depth, jint slot, @@ -753,7 +953,7 @@ jvmtiError MethodUtil::GetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED, art::Locks::thread_list_lock_->ExclusiveUnlock(self); return err; } - GetLocalVariableClosure c(depth, slot, type, val); + GetLocalVariableClosure c(env, depth, slot, type, val); // RequestSynchronousCheckpoint releases the thread_list_lock_ as a part of its execution. if (!target->RequestSynchronousCheckpoint(&c)) { return ERR(THREAD_NOT_ALIVE); @@ -764,49 +964,100 @@ jvmtiError MethodUtil::GetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED, class SetLocalVariableClosure : public CommonLocalVariableClosure { public: - SetLocalVariableClosure(art::Thread* caller, + SetLocalVariableClosure(jvmtiEnv* jvmti, + art::Thread* caller, jint depth, jint slot, art::Primitive::Type type, jvalue val) - : CommonLocalVariableClosure(depth, slot), caller_(caller), type_(type), val_(val) {} + : CommonLocalVariableClosure(jvmti, depth, slot), caller_(caller), type_(type), val_(val) {} protected: - jvmtiError GetTypeError(art::ArtMethod* method, - art::Primitive::Type slot_type, - const std::string& descriptor) - override REQUIRES_SHARED(art::Locks::mutator_lock_) { - switch (slot_type) { - case art::Primitive::kPrimNot: { - if (type_ != art::Primitive::kPrimNot) { + jvmtiError + GetTypeError(art::ArtMethod* method, SlotType slot_type, const std::string& descriptor) override + REQUIRES_SHARED(art::Locks::mutator_lock_) { + jvmtiError res = GetTypeErrorInner(method, slot_type, descriptor); + if (res != OK) { + if (res == ERR(TYPE_MISMATCH)) { + std::ostringstream desc_exp; + std::ostringstream desc_set; + if (type_ == art::Primitive::kPrimNot) { + desc_exp << " (type: " << descriptor << ")"; + art::ObjPtr<art::mirror::Object> new_val(art::Thread::Current()->DecodeJObject(val_.l)); + desc_set << " (type: " + << (new_val.IsNull() ? "NULL" : new_val->GetClass()->PrettyDescriptor()) << ")"; + } + JVMTI_LOG(INFO, jvmti_) << "Unable to Set local variable in slot " << slot_ << ". Expected" + << " slot to be of type compatible with " << SlotType{ type_ } + << desc_set.str() << " but slot is " << slot_type << desc_exp.str(); + } else { + JVMTI_LOG(INFO, jvmti_) << "Unable to set local variable in slot " << slot_ << ". " + << err_.str(); + } + } + return res; + } + + jvmtiError + GetTypeErrorInner(art::ArtMethod* method, SlotType slot_type, const std::string& descriptor) + REQUIRES_SHARED(art::Locks::mutator_lock_) { + switch (SquashType(SlotType{ type_ })) { + case VerifierPrimitiveType::k32BitValue: { + if (slot_type == SlotType{ VerifierPrimitiveType::kZeroValue }) { + if (val_.i == 0) { + return OK; + } else { + err_ << "Cannot determine if slot " << slot_ << " is a null reference or 32bit " + << "constant. Cannot allow writing to slot."; + return ERR(INTERNAL); + } + } else if (SquashType(slot_type) != VerifierPrimitiveType::k32BitValue) { + return ERR(TYPE_MISMATCH); + } else if (slot_type == SlotType { VerifierPrimitiveType::k32BitValue } || + slot_type == SlotType { type_ }) { + return OK; + } else if (type_ == art::Primitive::kPrimFloat || + slot_type == SlotType { art::Primitive::kPrimFloat }) { + // we should have hit the get == type_ above + return ERR(TYPE_MISMATCH); + } else { + // Some smaller type then int. + return OK; + } + } + case VerifierPrimitiveType::k64BitValue: { + if (slot_type == SlotType { VerifierPrimitiveType::k64BitValue } || + slot_type == SlotType { type_ }) { + return OK; + } else { + return ERR(TYPE_MISMATCH); + } + } + case VerifierPrimitiveType::kReferenceValue: { + if (SquashType(slot_type) != VerifierPrimitiveType::kReferenceValue && + SquashType(slot_type) != VerifierPrimitiveType::kZeroValue) { return ERR(TYPE_MISMATCH); } else if (val_.l == nullptr) { return OK; + } else if (slot_type == SlotType { VerifierPrimitiveType::kZeroValue }) { + err_ << "Cannot determine if slot " << slot_ << " is a null " + << "reference or 32bit constant. Cannot allow writing to slot."; + return ERR(INTERNAL); } else { art::ClassLinker* cl = art::Runtime::Current()->GetClassLinker(); - art::ObjPtr<art::mirror::Class> set_class = - caller_->DecodeJObject(val_.l)->GetClass(); + 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()); + DCHECK(!slot_class.IsNull()) << descriptor << " slot: " << slot_type; 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; + case VerifierPrimitiveType::kZeroValue: { + LOG(FATAL) << "Illegal result from SquashType of art::Primitive::Type " << type_; UNREACHABLE(); + } } } @@ -857,9 +1108,10 @@ class SetLocalVariableClosure : public CommonLocalVariableClosure { art::Thread* caller_; art::Primitive::Type type_; jvalue val_; + std::ostringstream err_; }; -jvmtiError MethodUtil::SetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED, +jvmtiError MethodUtil::SetLocalVariableGeneric(jvmtiEnv* env, jthread thread, jint depth, jint slot, @@ -880,7 +1132,7 @@ jvmtiError MethodUtil::SetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED, art::Locks::thread_list_lock_->ExclusiveUnlock(self); return err; } - SetLocalVariableClosure c(self, depth, slot, type, val); + SetLocalVariableClosure c(env, self, depth, slot, type, val); // RequestSynchronousCheckpoint releases the thread_list_lock_ as a part of its execution. if (!target->RequestSynchronousCheckpoint(&c)) { return ERR(THREAD_NOT_ALIVE); |