// TODO HasCodeItem == false means that the method is abstract (or native, but we check that // earlier). We should check what is returned by the RI in this situation since it's not clear // what the appropriate return value is from the spec.
art::CodeItemDebugInfoAccessor accessor(art_method->DexInstructionDebugInfo()); if (!accessor.HasCodeItem()) { return ERR(ABSENT_INFORMATION);
}
auto release = [&](jint* out_entry_count_ptr, jvmtiLocalVariableEntry** out_table_ptr) {
jlong table_size = sizeof(jvmtiLocalVariableEntry) * variables.size(); if (err != OK ||
(err = env->Allocate(table_size, reinterpret_cast<unsignedchar**>(out_table_ptr))) != OK) { for (jvmtiLocalVariableEntry& e : variables) {
env->Deallocate(reinterpret_cast<unsignedchar*>(e.name));
env->Deallocate(reinterpret_cast<unsignedchar*>(e.signature));
env->Deallocate(reinterpret_cast<unsignedchar*>(e.generic_signature));
} return err;
}
*out_entry_count_ptr = variables.size();
memcpy(*out_table_ptr, variables.data(), table_size); return OK;
};
// To avoid defining visitor in the same line as the `if`. We define the lambda and use std::move. auto visitor = [&](const art::DexFile::LocalInfo& entry) { if (err != OK) { return;
}
JvmtiUniquePtr<char[]> name_str = CopyString(env, entry.name_, &err); if (err != OK) { return;
}
JvmtiUniquePtr<char[]> sig_str = CopyString(env, entry.descriptor_, &err); if (err != OK) { return;
}
JvmtiUniquePtr<char[]> generic_sig_str = CopyString(env, entry.signature_, &err); if (err != OK) { return;
}
variables.push_back({
.start_location = static_cast<jlocation>(entry.start_address_),
.length = static_cast<jint>(entry.end_address_ - entry.start_address_),
.name = name_str.release(),
.signature = sig_str.release(),
.generic_signature = generic_sig_str.release(),
.slot = entry.reg_,
});
};
if (!accessor.DecodeDebugLocalInfo(
art_method->IsStatic(), art_method->GetDexMethodIndex(), std::move(visitor))) { // Something went wrong with decoding the debug information. It might as well not be there. return ERR(ABSENT_INFORMATION);
} return release(entry_count_ptr, table_ptr);
}
if (art_method->IsNative()) { return ERR(NATIVE_METHOD);
}
if (max_ptr == nullptr) { return ERR(NULL_POINTER);
}
art::ScopedObjectAccess soa(art::Thread::Current()); if (art_method->IsProxyMethod() || art_method->IsAbstract()) { // This isn't specified as an error case, so return 0.
*max_ptr = 0; return ERR(NONE);
}
art::ScopedObjectAccess soa(art::Thread::Current()); if (art_method->IsProxyMethod() || art_method->IsAbstract()) { // This isn't specified as an error case, so return -1/-1 as the RI does.
*start_location_ptr = -1;
*end_location_ptr = -1; return ERR(NONE);
}
jvmtiError MethodUtil::IsMethodNative(jvmtiEnv* env, jmethodID m, jboolean* is_native_ptr) { auto test = [](art::ArtMethod* method) { return method->IsNative();
}; return IsMethodT(env, m, test, is_native_ptr);
}
jvmtiError MethodUtil::IsMethodObsolete(jvmtiEnv* env, jmethodID m, jboolean* is_obsolete_ptr) { auto test = [](art::ArtMethod* method) { return method->IsObsolete();
}; return IsMethodT(env, m, test, is_obsolete_ptr);
}
jvmtiError MethodUtil::IsMethodSynthetic(jvmtiEnv* env, jmethodID m, jboolean* is_synthetic_ptr) { auto test = [](art::ArtMethod* method) { return method->IsSynthetic();
}; return IsMethodT(env, m, test, is_synthetic_ptr);
}
class CommonLocalVariableClosure : public art::Closure { public: // 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. enumclass VerifierPrimitiveType {
k32BitValue, // float, int, short, char, boolean, byte
k64BitValue, // double, long
kReferenceValue, // Object
kZeroValue, // null or zero constant. Might be either k32BitValue or kReferenceValue
};
using SlotType = std::variant<art::Primitive::Type, VerifierPrimitiveType>;
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(); // To avoid defining visitor in the same line as the `if`. We define the lambda and use std::move. 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(), std::move(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);
} elseif (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;
} elseif (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;
}
protected:
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;
} elseif (res != OK) {
JVMTI_LOG(INFO, jvmti_) << "Unable to get local variable in slot " << slot_ << ".";
} return res;
}
jvmtiError GetTypeErrorInner([[maybe_unused]] art::ArtMethod* method,
SlotType slot_type,
[[maybe_unused]] const std::string& descriptor)
REQUIRES_SHARED(art::Locks::mutator_lock_) { switch (type_) { case art::Primitive::kPrimFloat: 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);
} elseif (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 (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();
}
}
jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor)
override REQUIRES_SHARED(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));
obj_val_ = art::Runtime::Current()->GetJavaVM()->AddGlobalRef(art::Thread::Current(), 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_; // A global reference to the return value. We use the global reference to safely transfer the // value between threads.
jobject obj_val_;
};
jvmtiError MethodUtil::GetLocalVariableGeneric(jvmtiEnv* env,
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::Locks::thread_list_lock_->ExclusiveLock(self);
art::Thread* target = nullptr;
jvmtiError err = ERR(INTERNAL); if (!ThreadUtil::GetAliveNativeThread(thread, soa, &target, &err)) {
art::Locks::thread_list_lock_->ExclusiveUnlock(self); return err;
}
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);
} else { return c.GetResult();
}
}
jvmtiError MethodUtil::SetLocalVariableGeneric(jvmtiEnv* env,
jthread thread,
jint depth,
jint slot,
art::Primitive::Type type,
jvalue val) { if (depth < 0) { return ERR(ILLEGAL_ARGUMENT);
} // Make sure that we know not to do any OSR anymore. // TODO We should really keep track of this at the Frame granularity.
DeoptManager::Get()->SetLocalsUpdated();
art::Thread* self = art::Thread::Current();
art::ScopedObjectAccess soa(self);
art::Locks::thread_list_lock_->ExclusiveLock(self);
art::Thread* target = nullptr;
jvmtiError err = ERR(INTERNAL); if (!ThreadUtil::GetAliveNativeThread(thread, soa, &target, &err)) {
art::Locks::thread_list_lock_->ExclusiveUnlock(self); return err;
}
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);
} else { return c.GetResult();
}
}
class GetLocalInstanceClosure : public art::Closure { public: explicit GetLocalInstanceClosure(jint depth)
: result_(ERR(INTERNAL)),
depth_(depth),
val_(nullptr) {}
void Run(art::Thread* self) override REQUIRES(art::Locks::mutator_lock_) {
art::ScopedAssertNoThreadSuspension sants("GetLocalInstanceClosure::Run");
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;
}
result_ = OK;
val_ = art::GcRoot<art::mirror::Object>(visitor.GetThisObject());
}
jvmtiError MethodUtil::GetLocalInstance([[maybe_unused]] jvmtiEnv* env,
jthread thread,
jint depth,
jobject* data) { if (depth < 0) { return ERR(ILLEGAL_ARGUMENT);
}
art::Thread* self = art::Thread::Current();
art::ScopedObjectAccess soa(self);
art::Locks::thread_list_lock_->ExclusiveLock(self);
art::Thread* target = nullptr;
jvmtiError err = ERR(INTERNAL); if (!ThreadUtil::GetAliveNativeThread(thread, soa, &target, &err)) {
art::Locks::thread_list_lock_->ExclusiveUnlock(self); return err;
}
art::ScopedAssertNoThreadSuspension sants("Performing GetLocalInstance");
GetLocalInstanceClosure c(depth); // RequestSynchronousCheckpoint releases the thread_list_lock_ as a part of its execution. We // need to avoid suspending as we wait for the checkpoint to occur since we are (potentially) // transfering a GcRoot across threads. if (!target->RequestSynchronousCheckpoint(&c, art::ThreadState::kRunnable)) { return ERR(THREAD_NOT_ALIVE);
} else { return c.GetResult(data);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.