| /* |
| * Copyright (C) 2008 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. |
| */ |
| |
| #include "debugger.h" |
| |
| #include <sys/uio.h> |
| |
| #include <set> |
| |
| #include "arch/context.h" |
| #include "class_linker.h" |
| #include "class_linker-inl.h" |
| #include "dex_file-inl.h" |
| #include "dex_instruction.h" |
| #include "gc/accounting/card_table-inl.h" |
| #include "gc/space/large_object_space.h" |
| #include "gc/space/space-inl.h" |
| #include "handle_scope.h" |
| #include "jdwp/object_registry.h" |
| #include "mirror/art_field-inl.h" |
| #include "mirror/art_method-inl.h" |
| #include "mirror/class.h" |
| #include "mirror/class-inl.h" |
| #include "mirror/class_loader.h" |
| #include "mirror/object-inl.h" |
| #include "mirror/object_array-inl.h" |
| #include "mirror/string-inl.h" |
| #include "mirror/throwable.h" |
| #include "quick/inline_method_analyser.h" |
| #include "reflection.h" |
| #include "safe_map.h" |
| #include "scoped_thread_state_change.h" |
| #include "ScopedLocalRef.h" |
| #include "ScopedPrimitiveArray.h" |
| #include "handle_scope-inl.h" |
| #include "thread_list.h" |
| #include "utf.h" |
| #include "verifier/method_verifier-inl.h" |
| #include "well_known_classes.h" |
| |
| #ifdef HAVE_ANDROID_OS |
| #include "cutils/properties.h" |
| #endif |
| |
| namespace art { |
| |
| static const size_t kMaxAllocRecordStackDepth = 16; // Max 255. |
| static const size_t kDefaultNumAllocRecords = 64*1024; // Must be a power of 2. 2BE can hold 64k-1. |
| |
| // Limit alloc_record_count to the 2BE value that is the limit of the current protocol. |
| static uint16_t CappedAllocRecordCount(size_t alloc_record_count) { |
| if (alloc_record_count > 0xffff) { |
| return 0xffff; |
| } |
| return alloc_record_count; |
| } |
| |
| class AllocRecordStackTraceElement { |
| public: |
| AllocRecordStackTraceElement() : method_(nullptr), dex_pc_(0) { |
| } |
| |
| int32_t LineNumber() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| mirror::ArtMethod* method = Method(); |
| DCHECK(method != nullptr); |
| return method->GetLineNumFromDexPC(DexPc()); |
| } |
| |
| mirror::ArtMethod* Method() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| return soa.DecodeMethod(method_); |
| } |
| |
| void SetMethod(mirror::ArtMethod* m) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| method_ = soa.EncodeMethod(m); |
| } |
| |
| uint32_t DexPc() const { |
| return dex_pc_; |
| } |
| |
| void SetDexPc(uint32_t pc) { |
| dex_pc_ = pc; |
| } |
| |
| private: |
| jmethodID method_; |
| uint32_t dex_pc_; |
| }; |
| |
| jobject Dbg::TypeCache::Add(mirror::Class* t) { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| JNIEnv* const env = soa.Env(); |
| ScopedLocalRef<jobject> local_ref(soa.Env(), soa.AddLocalReference<jobject>(t)); |
| const int32_t hash_code = soa.Decode<mirror::Class*>(local_ref.get())->IdentityHashCode(); |
| auto range = objects_.equal_range(hash_code); |
| for (auto it = range.first; it != range.second; ++it) { |
| if (soa.Decode<mirror::Class*>(it->second) == soa.Decode<mirror::Class*>(local_ref.get())) { |
| // Found a matching weak global, return it. |
| return it->second; |
| } |
| } |
| const jobject weak_global = env->NewWeakGlobalRef(local_ref.get()); |
| objects_.insert(std::make_pair(hash_code, weak_global)); |
| return weak_global; |
| } |
| |
| void Dbg::TypeCache::Clear() { |
| JavaVMExt* vm = Runtime::Current()->GetJavaVM(); |
| Thread* self = Thread::Current(); |
| for (const auto& p : objects_) { |
| vm->DeleteWeakGlobalRef(self, p.second); |
| } |
| objects_.clear(); |
| } |
| |
| class AllocRecord { |
| public: |
| AllocRecord() : type_(nullptr), byte_count_(0), thin_lock_id_(0) {} |
| |
| mirror::Class* Type() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| return down_cast<mirror::Class*>(Thread::Current()->DecodeJObject(type_)); |
| } |
| |
| void SetType(mirror::Class* t) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_, |
| Locks::alloc_tracker_lock_) { |
| type_ = Dbg::type_cache_.Add(t); |
| } |
| |
| size_t GetDepth() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| size_t depth = 0; |
| while (depth < kMaxAllocRecordStackDepth && stack_[depth].Method() != nullptr) { |
| ++depth; |
| } |
| return depth; |
| } |
| |
| size_t ByteCount() const { |
| return byte_count_; |
| } |
| |
| void SetByteCount(size_t count) { |
| byte_count_ = count; |
| } |
| |
| uint16_t ThinLockId() const { |
| return thin_lock_id_; |
| } |
| |
| void SetThinLockId(uint16_t id) { |
| thin_lock_id_ = id; |
| } |
| |
| AllocRecordStackTraceElement* StackElement(size_t index) { |
| DCHECK_LT(index, kMaxAllocRecordStackDepth); |
| return &stack_[index]; |
| } |
| |
| private: |
| jobject type_; // This is a weak global. |
| size_t byte_count_; |
| uint16_t thin_lock_id_; |
| AllocRecordStackTraceElement stack_[kMaxAllocRecordStackDepth]; // Unused entries have nullptr method. |
| }; |
| |
| class Breakpoint { |
| public: |
| Breakpoint(mirror::ArtMethod* method, uint32_t dex_pc, |
| DeoptimizationRequest::Kind deoptimization_kind) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) |
| : method_(nullptr), dex_pc_(dex_pc), deoptimization_kind_(deoptimization_kind) { |
| CHECK(deoptimization_kind_ == DeoptimizationRequest::kNothing || |
| deoptimization_kind_ == DeoptimizationRequest::kSelectiveDeoptimization || |
| deoptimization_kind_ == DeoptimizationRequest::kFullDeoptimization); |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| method_ = soa.EncodeMethod(method); |
| } |
| |
| Breakpoint(const Breakpoint& other) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) |
| : method_(nullptr), dex_pc_(other.dex_pc_), |
| deoptimization_kind_(other.deoptimization_kind_) { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| method_ = soa.EncodeMethod(other.Method()); |
| } |
| |
| mirror::ArtMethod* Method() const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| return soa.DecodeMethod(method_); |
| } |
| |
| uint32_t DexPc() const { |
| return dex_pc_; |
| } |
| |
| DeoptimizationRequest::Kind GetDeoptimizationKind() const { |
| return deoptimization_kind_; |
| } |
| |
| private: |
| // The location of this breakpoint. |
| jmethodID method_; |
| uint32_t dex_pc_; |
| |
| // Indicates whether breakpoint needs full deoptimization or selective deoptimization. |
| DeoptimizationRequest::Kind deoptimization_kind_; |
| }; |
| |
| static std::ostream& operator<<(std::ostream& os, const Breakpoint& rhs) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| os << StringPrintf("Breakpoint[%s @%#x]", PrettyMethod(rhs.Method()).c_str(), rhs.DexPc()); |
| return os; |
| } |
| |
| class DebugInstrumentationListener FINAL : public instrumentation::InstrumentationListener { |
| public: |
| DebugInstrumentationListener() {} |
| virtual ~DebugInstrumentationListener() {} |
| |
| void MethodEntered(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method, |
| uint32_t dex_pc ATTRIBUTE_UNUSED) |
| OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| if (method->IsNative()) { |
| // TODO: post location events is a suspension point and native method entry stubs aren't. |
| return; |
| } |
| Dbg::UpdateDebugger(thread, this_object, method, 0, Dbg::kMethodEntry, nullptr); |
| } |
| |
| void MethodExited(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method, |
| uint32_t dex_pc, const JValue& return_value) |
| OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| if (method->IsNative()) { |
| // TODO: post location events is a suspension point and native method entry stubs aren't. |
| return; |
| } |
| Dbg::UpdateDebugger(thread, this_object, method, dex_pc, Dbg::kMethodExit, &return_value); |
| } |
| |
| void MethodUnwind(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method, |
| uint32_t dex_pc) |
| OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| // We're not recorded to listen to this kind of event, so complain. |
| UNUSED(thread, this_object, method, dex_pc); |
| LOG(ERROR) << "Unexpected method unwind event in debugger " << PrettyMethod(method) |
| << " " << dex_pc; |
| } |
| |
| void DexPcMoved(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method, |
| uint32_t new_dex_pc) |
| OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| Dbg::UpdateDebugger(thread, this_object, method, new_dex_pc, 0, nullptr); |
| } |
| |
| void FieldRead(Thread* thread, mirror::Object* this_object, mirror::ArtMethod* method, |
| uint32_t dex_pc, mirror::ArtField* field) |
| OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| UNUSED(thread); |
| Dbg::PostFieldAccessEvent(method, dex_pc, this_object, field); |
| } |
| |
| void FieldWritten(Thread* thread ATTRIBUTE_UNUSED, mirror::Object* this_object, |
| mirror::ArtMethod* method, uint32_t dex_pc, mirror::ArtField* field, |
| const JValue& field_value) |
| OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| Dbg::PostFieldModificationEvent(method, dex_pc, this_object, field, &field_value); |
| } |
| |
| void ExceptionCaught(Thread* thread ATTRIBUTE_UNUSED, mirror::Throwable* exception_object) |
| OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| Dbg::PostException(exception_object); |
| } |
| |
| // We only care about how many backward branches were executed in the Jit. |
| void BackwardBranch(Thread* /*thread*/, mirror::ArtMethod* method, int32_t dex_pc_offset) |
| OVERRIDE SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| LOG(ERROR) << "Unexpected backward branch event in debugger " << PrettyMethod(method) |
| << " " << dex_pc_offset; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(DebugInstrumentationListener); |
| } gDebugInstrumentationListener; |
| |
| // JDWP is allowed unless the Zygote forbids it. |
| static bool gJdwpAllowed = true; |
| |
| // Was there a -Xrunjdwp or -agentlib:jdwp= argument on the command line? |
| static bool gJdwpConfigured = false; |
| |
| // JDWP options for debugging. Only valid if IsJdwpConfigured() is true. |
| static JDWP::JdwpOptions gJdwpOptions; |
| |
| // Runtime JDWP state. |
| static JDWP::JdwpState* gJdwpState = nullptr; |
| static bool gDebuggerConnected; // debugger or DDMS is connected. |
| static bool gDebuggerActive; // debugger is making requests. |
| static bool gDisposed; // debugger called VirtualMachine.Dispose, so we should drop the connection. |
| |
| static bool gDdmThreadNotification = false; |
| |
| // DDMS GC-related settings. |
| static Dbg::HpifWhen gDdmHpifWhen = Dbg::HPIF_WHEN_NEVER; |
| static Dbg::HpsgWhen gDdmHpsgWhen = Dbg::HPSG_WHEN_NEVER; |
| static Dbg::HpsgWhat gDdmHpsgWhat; |
| static Dbg::HpsgWhen gDdmNhsgWhen = Dbg::HPSG_WHEN_NEVER; |
| static Dbg::HpsgWhat gDdmNhsgWhat; |
| |
| ObjectRegistry* Dbg::gRegistry = nullptr; |
| |
| // Recent allocation tracking. |
| AllocRecord* Dbg::recent_allocation_records_ = nullptr; // TODO: CircularBuffer<AllocRecord> |
| size_t Dbg::alloc_record_max_ = 0; |
| size_t Dbg::alloc_record_head_ = 0; |
| size_t Dbg::alloc_record_count_ = 0; |
| Dbg::TypeCache Dbg::type_cache_; |
| |
| // Deoptimization support. |
| std::vector<DeoptimizationRequest> Dbg::deoptimization_requests_; |
| size_t Dbg::full_deoptimization_event_count_ = 0; |
| size_t Dbg::delayed_full_undeoptimization_count_ = 0; |
| |
| // Instrumentation event reference counters. |
| size_t Dbg::dex_pc_change_event_ref_count_ = 0; |
| size_t Dbg::method_enter_event_ref_count_ = 0; |
| size_t Dbg::method_exit_event_ref_count_ = 0; |
| size_t Dbg::field_read_event_ref_count_ = 0; |
| size_t Dbg::field_write_event_ref_count_ = 0; |
| size_t Dbg::exception_catch_event_ref_count_ = 0; |
| uint32_t Dbg::instrumentation_events_ = 0; |
| |
| // Breakpoints. |
| static std::vector<Breakpoint> gBreakpoints GUARDED_BY(Locks::breakpoint_lock_); |
| |
| void DebugInvokeReq::VisitRoots(RootCallback* callback, void* arg, const RootInfo& root_info) { |
| receiver.VisitRootIfNonNull(callback, arg, root_info); // null for static method call. |
| klass.VisitRoot(callback, arg, root_info); |
| method.VisitRoot(callback, arg, root_info); |
| } |
| |
| void SingleStepControl::VisitRoots(RootCallback* callback, void* arg, const RootInfo& root_info) { |
| if (method_ != nullptr) { |
| callback(reinterpret_cast<mirror::Object**>(&method_), arg, root_info); |
| } |
| } |
| |
| void SingleStepControl::AddDexPc(uint32_t dex_pc) { |
| dex_pcs_.insert(dex_pc); |
| } |
| |
| bool SingleStepControl::ContainsDexPc(uint32_t dex_pc) const { |
| return dex_pcs_.find(dex_pc) == dex_pcs_.end(); |
| } |
| |
| static bool IsBreakpoint(const mirror::ArtMethod* m, uint32_t dex_pc) |
| LOCKS_EXCLUDED(Locks::breakpoint_lock_) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| ReaderMutexLock mu(Thread::Current(), *Locks::breakpoint_lock_); |
| for (size_t i = 0, e = gBreakpoints.size(); i < e; ++i) { |
| if (gBreakpoints[i].DexPc() == dex_pc && gBreakpoints[i].Method() == m) { |
| VLOG(jdwp) << "Hit breakpoint #" << i << ": " << gBreakpoints[i]; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static bool IsSuspendedForDebugger(ScopedObjectAccessUnchecked& soa, Thread* thread) |
| LOCKS_EXCLUDED(Locks::thread_suspend_count_lock_) { |
| MutexLock mu(soa.Self(), *Locks::thread_suspend_count_lock_); |
| // A thread may be suspended for GC; in this code, we really want to know whether |
| // there's a debugger suspension active. |
| return thread->IsSuspended() && thread->GetDebugSuspendCount() > 0; |
| } |
| |
| static mirror::Array* DecodeNonNullArray(JDWP::RefTypeId id, JDWP::JdwpError* error) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| mirror::Object* o = Dbg::GetObjectRegistry()->Get<mirror::Object*>(id, error); |
| if (o == nullptr) { |
| *error = JDWP::ERR_INVALID_OBJECT; |
| return nullptr; |
| } |
| if (!o->IsArrayInstance()) { |
| *error = JDWP::ERR_INVALID_ARRAY; |
| return nullptr; |
| } |
| *error = JDWP::ERR_NONE; |
| return o->AsArray(); |
| } |
| |
| static mirror::Class* DecodeClass(JDWP::RefTypeId id, JDWP::JdwpError* error) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| mirror::Object* o = Dbg::GetObjectRegistry()->Get<mirror::Object*>(id, error); |
| if (o == nullptr) { |
| *error = JDWP::ERR_INVALID_OBJECT; |
| return nullptr; |
| } |
| if (!o->IsClass()) { |
| *error = JDWP::ERR_INVALID_CLASS; |
| return nullptr; |
| } |
| *error = JDWP::ERR_NONE; |
| return o->AsClass(); |
| } |
| |
| static Thread* DecodeThread(ScopedObjectAccessUnchecked& soa, JDWP::ObjectId thread_id, |
| JDWP::JdwpError* error) |
| EXCLUSIVE_LOCKS_REQUIRED(Locks::thread_list_lock_) |
| LOCKS_EXCLUDED(Locks::thread_suspend_count_lock_) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| mirror::Object* thread_peer = Dbg::GetObjectRegistry()->Get<mirror::Object*>(thread_id, error); |
| if (thread_peer == nullptr) { |
| // This isn't even an object. |
| *error = JDWP::ERR_INVALID_OBJECT; |
| return nullptr; |
| } |
| |
| mirror::Class* java_lang_Thread = soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_Thread); |
| if (!java_lang_Thread->IsAssignableFrom(thread_peer->GetClass())) { |
| // This isn't a thread. |
| *error = JDWP::ERR_INVALID_THREAD; |
| return nullptr; |
| } |
| |
| Thread* thread = Thread::FromManagedThread(soa, thread_peer); |
| // If thread is null then this a java.lang.Thread without a Thread*. Must be a un-started or a |
| // zombie. |
| *error = (thread == nullptr) ? JDWP::ERR_THREAD_NOT_ALIVE : JDWP::ERR_NONE; |
| return thread; |
| } |
| |
| static JDWP::JdwpTag BasicTagFromDescriptor(const char* descriptor) { |
| // JDWP deliberately uses the descriptor characters' ASCII values for its enum. |
| // Note that by "basic" we mean that we don't get more specific than JT_OBJECT. |
| return static_cast<JDWP::JdwpTag>(descriptor[0]); |
| } |
| |
| static JDWP::JdwpTag BasicTagFromClass(mirror::Class* klass) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| std::string temp; |
| const char* descriptor = klass->GetDescriptor(&temp); |
| return BasicTagFromDescriptor(descriptor); |
| } |
| |
| static JDWP::JdwpTag TagFromClass(const ScopedObjectAccessUnchecked& soa, mirror::Class* c) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| CHECK(c != nullptr); |
| if (c->IsArrayClass()) { |
| return JDWP::JT_ARRAY; |
| } |
| if (c->IsStringClass()) { |
| return JDWP::JT_STRING; |
| } |
| if (c->IsClassClass()) { |
| return JDWP::JT_CLASS_OBJECT; |
| } |
| { |
| mirror::Class* thread_class = soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_Thread); |
| if (thread_class->IsAssignableFrom(c)) { |
| return JDWP::JT_THREAD; |
| } |
| } |
| { |
| mirror::Class* thread_group_class = |
| soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_ThreadGroup); |
| if (thread_group_class->IsAssignableFrom(c)) { |
| return JDWP::JT_THREAD_GROUP; |
| } |
| } |
| { |
| mirror::Class* class_loader_class = |
| soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_ClassLoader); |
| if (class_loader_class->IsAssignableFrom(c)) { |
| return JDWP::JT_CLASS_LOADER; |
| } |
| } |
| return JDWP::JT_OBJECT; |
| } |
| |
| /* |
| * Objects declared to hold Object might actually hold a more specific |
| * type. The debugger may take a special interest in these (e.g. it |
| * wants to display the contents of Strings), so we want to return an |
| * appropriate tag. |
| * |
| * Null objects are tagged JT_OBJECT. |
| */ |
| JDWP::JdwpTag Dbg::TagFromObject(const ScopedObjectAccessUnchecked& soa, mirror::Object* o) { |
| return (o == nullptr) ? JDWP::JT_OBJECT : TagFromClass(soa, o->GetClass()); |
| } |
| |
| static bool IsPrimitiveTag(JDWP::JdwpTag tag) { |
| switch (tag) { |
| case JDWP::JT_BOOLEAN: |
| case JDWP::JT_BYTE: |
| case JDWP::JT_CHAR: |
| case JDWP::JT_FLOAT: |
| case JDWP::JT_DOUBLE: |
| case JDWP::JT_INT: |
| case JDWP::JT_LONG: |
| case JDWP::JT_SHORT: |
| case JDWP::JT_VOID: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| void Dbg::StartJdwp() { |
| if (!gJdwpAllowed || !IsJdwpConfigured()) { |
| // No JDWP for you! |
| return; |
| } |
| |
| CHECK(gRegistry == nullptr); |
| gRegistry = new ObjectRegistry; |
| |
| // Init JDWP if the debugger is enabled. This may connect out to a |
| // debugger, passively listen for a debugger, or block waiting for a |
| // debugger. |
| gJdwpState = JDWP::JdwpState::Create(&gJdwpOptions); |
| if (gJdwpState == nullptr) { |
| // We probably failed because some other process has the port already, which means that |
| // if we don't abort the user is likely to think they're talking to us when they're actually |
| // talking to that other process. |
| LOG(FATAL) << "Debugger thread failed to initialize"; |
| } |
| |
| // If a debugger has already attached, send the "welcome" message. |
| // This may cause us to suspend all threads. |
| if (gJdwpState->IsActive()) { |
| ScopedObjectAccess soa(Thread::Current()); |
| gJdwpState->PostVMStart(); |
| } |
| } |
| |
| void Dbg::StopJdwp() { |
| // Post VM_DEATH event before the JDWP connection is closed (either by the JDWP thread or the |
| // destruction of gJdwpState). |
| if (gJdwpState != nullptr && gJdwpState->IsActive()) { |
| gJdwpState->PostVMDeath(); |
| } |
| // Prevent the JDWP thread from processing JDWP incoming packets after we close the connection. |
| Disposed(); |
| delete gJdwpState; |
| gJdwpState = nullptr; |
| delete gRegistry; |
| gRegistry = nullptr; |
| } |
| |
| void Dbg::GcDidFinish() { |
| if (gDdmHpifWhen != HPIF_WHEN_NEVER) { |
| ScopedObjectAccess soa(Thread::Current()); |
| VLOG(jdwp) << "Sending heap info to DDM"; |
| DdmSendHeapInfo(gDdmHpifWhen); |
| } |
| if (gDdmHpsgWhen != HPSG_WHEN_NEVER) { |
| ScopedObjectAccess soa(Thread::Current()); |
| VLOG(jdwp) << "Dumping heap to DDM"; |
| DdmSendHeapSegments(false); |
| } |
| if (gDdmNhsgWhen != HPSG_WHEN_NEVER) { |
| ScopedObjectAccess soa(Thread::Current()); |
| VLOG(jdwp) << "Dumping native heap to DDM"; |
| DdmSendHeapSegments(true); |
| } |
| } |
| |
| void Dbg::SetJdwpAllowed(bool allowed) { |
| gJdwpAllowed = allowed; |
| } |
| |
| DebugInvokeReq* Dbg::GetInvokeReq() { |
| return Thread::Current()->GetInvokeReq(); |
| } |
| |
| Thread* Dbg::GetDebugThread() { |
| return (gJdwpState != nullptr) ? gJdwpState->GetDebugThread() : nullptr; |
| } |
| |
| void Dbg::ClearWaitForEventThread() { |
| gJdwpState->ReleaseJdwpTokenForEvent(); |
| } |
| |
| void Dbg::Connected() { |
| CHECK(!gDebuggerConnected); |
| VLOG(jdwp) << "JDWP has attached"; |
| gDebuggerConnected = true; |
| gDisposed = false; |
| } |
| |
| void Dbg::Disposed() { |
| gDisposed = true; |
| } |
| |
| bool Dbg::IsDisposed() { |
| return gDisposed; |
| } |
| |
| bool Dbg::RequiresDeoptimization() { |
| // We don't need deoptimization if everything runs with interpreter after |
| // enabling -Xint mode. |
| return !Runtime::Current()->GetInstrumentation()->IsForcedInterpretOnly(); |
| } |
| |
| void Dbg::GoActive() { |
| // Enable all debugging features, including scans for breakpoints. |
| // This is a no-op if we're already active. |
| // Only called from the JDWP handler thread. |
| if (gDebuggerActive) { |
| return; |
| } |
| |
| { |
| // TODO: dalvik only warned if there were breakpoints left over. clear in Dbg::Disconnected? |
| ReaderMutexLock mu(Thread::Current(), *Locks::breakpoint_lock_); |
| CHECK_EQ(gBreakpoints.size(), 0U); |
| } |
| |
| { |
| MutexLock mu(Thread::Current(), *Locks::deoptimization_lock_); |
| CHECK_EQ(deoptimization_requests_.size(), 0U); |
| CHECK_EQ(full_deoptimization_event_count_, 0U); |
| CHECK_EQ(delayed_full_undeoptimization_count_, 0U); |
| CHECK_EQ(dex_pc_change_event_ref_count_, 0U); |
| CHECK_EQ(method_enter_event_ref_count_, 0U); |
| CHECK_EQ(method_exit_event_ref_count_, 0U); |
| CHECK_EQ(field_read_event_ref_count_, 0U); |
| CHECK_EQ(field_write_event_ref_count_, 0U); |
| CHECK_EQ(exception_catch_event_ref_count_, 0U); |
| } |
| |
| Runtime* runtime = Runtime::Current(); |
| runtime->GetThreadList()->SuspendAll(__FUNCTION__); |
| Thread* self = Thread::Current(); |
| ThreadState old_state = self->SetStateUnsafe(kRunnable); |
| CHECK_NE(old_state, kRunnable); |
| if (RequiresDeoptimization()) { |
| runtime->GetInstrumentation()->EnableDeoptimization(); |
| } |
| instrumentation_events_ = 0; |
| gDebuggerActive = true; |
| CHECK_EQ(self->SetStateUnsafe(old_state), kRunnable); |
| runtime->GetThreadList()->ResumeAll(); |
| |
| LOG(INFO) << "Debugger is active"; |
| } |
| |
| void Dbg::Disconnected() { |
| CHECK(gDebuggerConnected); |
| |
| LOG(INFO) << "Debugger is no longer active"; |
| |
| // Suspend all threads and exclusively acquire the mutator lock. Set the state of the thread |
| // to kRunnable to avoid scoped object access transitions. Remove the debugger as a listener |
| // and clear the object registry. |
| Runtime* runtime = Runtime::Current(); |
| runtime->GetThreadList()->SuspendAll(__FUNCTION__); |
| Thread* self = Thread::Current(); |
| ThreadState old_state = self->SetStateUnsafe(kRunnable); |
| |
| // Debugger may not be active at this point. |
| if (gDebuggerActive) { |
| { |
| // Since we're going to disable deoptimization, we clear the deoptimization requests queue. |
| // This prevents us from having any pending deoptimization request when the debugger attaches |
| // to us again while no event has been requested yet. |
| MutexLock mu(Thread::Current(), *Locks::deoptimization_lock_); |
| deoptimization_requests_.clear(); |
| full_deoptimization_event_count_ = 0U; |
| delayed_full_undeoptimization_count_ = 0U; |
| } |
| if (instrumentation_events_ != 0) { |
| runtime->GetInstrumentation()->RemoveListener(&gDebugInstrumentationListener, |
| instrumentation_events_); |
| instrumentation_events_ = 0; |
| } |
| if (RequiresDeoptimization()) { |
| runtime->GetInstrumentation()->DisableDeoptimization(); |
| } |
| gDebuggerActive = false; |
| } |
| CHECK_EQ(self->SetStateUnsafe(old_state), kRunnable); |
| runtime->GetThreadList()->ResumeAll(); |
| |
| { |
| ScopedObjectAccess soa(self); |
| gRegistry->Clear(); |
| } |
| |
| gDebuggerConnected = false; |
| } |
| |
| bool Dbg::IsDebuggerActive() { |
| return gDebuggerActive; |
| } |
| |
| void Dbg::ConfigureJdwp(const JDWP::JdwpOptions& jdwp_options) { |
| CHECK_NE(jdwp_options.transport, JDWP::kJdwpTransportUnknown); |
| gJdwpOptions = jdwp_options; |
| gJdwpConfigured = true; |
| } |
| |
| bool Dbg::IsJdwpConfigured() { |
| return gJdwpConfigured; |
| } |
| |
| int64_t Dbg::LastDebuggerActivity() { |
| return gJdwpState->LastDebuggerActivity(); |
| } |
| |
| void Dbg::UndoDebuggerSuspensions() { |
| Runtime::Current()->GetThreadList()->UndoDebuggerSuspensions(); |
| } |
| |
| std::string Dbg::GetClassName(JDWP::RefTypeId class_id) { |
| JDWP::JdwpError error; |
| mirror::Object* o = gRegistry->Get<mirror::Object*>(class_id, &error); |
| if (o == nullptr) { |
| if (error == JDWP::ERR_NONE) { |
| return "NULL"; |
| } else { |
| return StringPrintf("invalid object %p", reinterpret_cast<void*>(class_id)); |
| } |
| } |
| if (!o->IsClass()) { |
| return StringPrintf("non-class %p", o); // This is only used for debugging output anyway. |
| } |
| return GetClassName(o->AsClass()); |
| } |
| |
| std::string Dbg::GetClassName(mirror::Class* klass) { |
| if (klass == nullptr) { |
| return "NULL"; |
| } |
| std::string temp; |
| return DescriptorToName(klass->GetDescriptor(&temp)); |
| } |
| |
| JDWP::JdwpError Dbg::GetClassObject(JDWP::RefTypeId id, JDWP::ObjectId* class_object_id) { |
| JDWP::JdwpError status; |
| mirror::Class* c = DecodeClass(id, &status); |
| if (c == nullptr) { |
| *class_object_id = 0; |
| return status; |
| } |
| *class_object_id = gRegistry->Add(c); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetSuperclass(JDWP::RefTypeId id, JDWP::RefTypeId* superclass_id) { |
| JDWP::JdwpError status; |
| mirror::Class* c = DecodeClass(id, &status); |
| if (c == nullptr) { |
| *superclass_id = 0; |
| return status; |
| } |
| if (c->IsInterface()) { |
| // http://code.google.com/p/android/issues/detail?id=20856 |
| *superclass_id = 0; |
| } else { |
| *superclass_id = gRegistry->Add(c->GetSuperClass()); |
| } |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetClassLoader(JDWP::RefTypeId id, JDWP::ExpandBuf* pReply) { |
| JDWP::JdwpError error; |
| mirror::Object* o = gRegistry->Get<mirror::Object*>(id, &error); |
| if (o == nullptr) { |
| return JDWP::ERR_INVALID_OBJECT; |
| } |
| expandBufAddObjectId(pReply, gRegistry->Add(o->GetClass()->GetClassLoader())); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetModifiers(JDWP::RefTypeId id, JDWP::ExpandBuf* pReply) { |
| JDWP::JdwpError error; |
| mirror::Class* c = DecodeClass(id, &error); |
| if (c == nullptr) { |
| return error; |
| } |
| |
| uint32_t access_flags = c->GetAccessFlags() & kAccJavaFlagsMask; |
| |
| // Set ACC_SUPER. Dex files don't contain this flag but only classes are supposed to have it set, |
| // not interfaces. |
| // Class.getModifiers doesn't return it, but JDWP does, so we set it here. |
| if ((access_flags & kAccInterface) == 0) { |
| access_flags |= kAccSuper; |
| } |
| |
| expandBufAdd4BE(pReply, access_flags); |
| |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetMonitorInfo(JDWP::ObjectId object_id, JDWP::ExpandBuf* reply) { |
| JDWP::JdwpError error; |
| mirror::Object* o = gRegistry->Get<mirror::Object*>(object_id, &error); |
| if (o == nullptr) { |
| return JDWP::ERR_INVALID_OBJECT; |
| } |
| |
| // Ensure all threads are suspended while we read objects' lock words. |
| Thread* self = Thread::Current(); |
| CHECK_EQ(self->GetState(), kRunnable); |
| self->TransitionFromRunnableToSuspended(kSuspended); |
| Runtime::Current()->GetThreadList()->SuspendAll(__FUNCTION__); |
| |
| MonitorInfo monitor_info(o); |
| |
| Runtime::Current()->GetThreadList()->ResumeAll(); |
| self->TransitionFromSuspendedToRunnable(); |
| |
| if (monitor_info.owner_ != nullptr) { |
| expandBufAddObjectId(reply, gRegistry->Add(monitor_info.owner_->GetPeer())); |
| } else { |
| expandBufAddObjectId(reply, gRegistry->Add(nullptr)); |
| } |
| expandBufAdd4BE(reply, monitor_info.entry_count_); |
| expandBufAdd4BE(reply, monitor_info.waiters_.size()); |
| for (size_t i = 0; i < monitor_info.waiters_.size(); ++i) { |
| expandBufAddObjectId(reply, gRegistry->Add(monitor_info.waiters_[i]->GetPeer())); |
| } |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetOwnedMonitors(JDWP::ObjectId thread_id, |
| std::vector<JDWP::ObjectId>* monitors, |
| std::vector<uint32_t>* stack_depths) { |
| struct OwnedMonitorVisitor : public StackVisitor { |
| OwnedMonitorVisitor(Thread* thread, Context* context, |
| std::vector<JDWP::ObjectId>* monitor_vector, |
| std::vector<uint32_t>* stack_depth_vector) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) |
| : StackVisitor(thread, context), current_stack_depth(0), |
| monitors(monitor_vector), stack_depths(stack_depth_vector) {} |
| |
| // TODO: Enable annotalysis. We know lock is held in constructor, but abstraction confuses |
| // annotalysis. |
| bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS { |
| if (!GetMethod()->IsRuntimeMethod()) { |
| Monitor::VisitLocks(this, AppendOwnedMonitors, this); |
| ++current_stack_depth; |
| } |
| return true; |
| } |
| |
| static void AppendOwnedMonitors(mirror::Object* owned_monitor, void* arg) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| OwnedMonitorVisitor* visitor = reinterpret_cast<OwnedMonitorVisitor*>(arg); |
| visitor->monitors->push_back(gRegistry->Add(owned_monitor)); |
| visitor->stack_depths->push_back(visitor->current_stack_depth); |
| } |
| |
| size_t current_stack_depth; |
| std::vector<JDWP::ObjectId>* const monitors; |
| std::vector<uint32_t>* const stack_depths; |
| }; |
| |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| Thread* thread; |
| { |
| MutexLock mu(soa.Self(), *Locks::thread_list_lock_); |
| JDWP::JdwpError error; |
| thread = DecodeThread(soa, thread_id, &error); |
| if (thread == nullptr) { |
| return error; |
| } |
| if (!IsSuspendedForDebugger(soa, thread)) { |
| return JDWP::ERR_THREAD_NOT_SUSPENDED; |
| } |
| } |
| std::unique_ptr<Context> context(Context::Create()); |
| OwnedMonitorVisitor visitor(thread, context.get(), monitors, stack_depths); |
| visitor.WalkStack(); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetContendedMonitor(JDWP::ObjectId thread_id, |
| JDWP::ObjectId* contended_monitor) { |
| mirror::Object* contended_monitor_obj; |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| *contended_monitor = 0; |
| { |
| MutexLock mu(soa.Self(), *Locks::thread_list_lock_); |
| JDWP::JdwpError error; |
| Thread* thread = DecodeThread(soa, thread_id, &error); |
| if (thread == nullptr) { |
| return error; |
| } |
| if (!IsSuspendedForDebugger(soa, thread)) { |
| return JDWP::ERR_THREAD_NOT_SUSPENDED; |
| } |
| contended_monitor_obj = Monitor::GetContendedMonitor(thread); |
| } |
| // Add() requires the thread_list_lock_ not held to avoid the lock |
| // level violation. |
| *contended_monitor = gRegistry->Add(contended_monitor_obj); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetInstanceCounts(const std::vector<JDWP::RefTypeId>& class_ids, |
| std::vector<uint64_t>* counts) { |
| gc::Heap* heap = Runtime::Current()->GetHeap(); |
| heap->CollectGarbage(false); |
| std::vector<mirror::Class*> classes; |
| counts->clear(); |
| for (size_t i = 0; i < class_ids.size(); ++i) { |
| JDWP::JdwpError error; |
| mirror::Class* c = DecodeClass(class_ids[i], &error); |
| if (c == nullptr) { |
| return error; |
| } |
| classes.push_back(c); |
| counts->push_back(0); |
| } |
| heap->CountInstances(classes, false, &(*counts)[0]); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetInstances(JDWP::RefTypeId class_id, int32_t max_count, |
| std::vector<JDWP::ObjectId>* instances) { |
| gc::Heap* heap = Runtime::Current()->GetHeap(); |
| // We only want reachable instances, so do a GC. |
| heap->CollectGarbage(false); |
| JDWP::JdwpError error; |
| mirror::Class* c = DecodeClass(class_id, &error); |
| if (c == nullptr) { |
| return error; |
| } |
| std::vector<mirror::Object*> raw_instances; |
| Runtime::Current()->GetHeap()->GetInstances(c, max_count, raw_instances); |
| for (size_t i = 0; i < raw_instances.size(); ++i) { |
| instances->push_back(gRegistry->Add(raw_instances[i])); |
| } |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetReferringObjects(JDWP::ObjectId object_id, int32_t max_count, |
| std::vector<JDWP::ObjectId>* referring_objects) { |
| gc::Heap* heap = Runtime::Current()->GetHeap(); |
| heap->CollectGarbage(false); |
| JDWP::JdwpError error; |
| mirror::Object* o = gRegistry->Get<mirror::Object*>(object_id, &error); |
| if (o == nullptr) { |
| return JDWP::ERR_INVALID_OBJECT; |
| } |
| std::vector<mirror::Object*> raw_instances; |
| heap->GetReferringObjects(o, max_count, raw_instances); |
| for (size_t i = 0; i < raw_instances.size(); ++i) { |
| referring_objects->push_back(gRegistry->Add(raw_instances[i])); |
| } |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::DisableCollection(JDWP::ObjectId object_id) { |
| JDWP::JdwpError error; |
| mirror::Object* o = gRegistry->Get<mirror::Object*>(object_id, &error); |
| if (o == nullptr) { |
| return JDWP::ERR_INVALID_OBJECT; |
| } |
| gRegistry->DisableCollection(object_id); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::EnableCollection(JDWP::ObjectId object_id) { |
| JDWP::JdwpError error; |
| mirror::Object* o = gRegistry->Get<mirror::Object*>(object_id, &error); |
| // Unlike DisableCollection, JDWP specs do not state an invalid object causes an error. The RI |
| // also ignores these cases and never return an error. However it's not obvious why this command |
| // should behave differently from DisableCollection and IsCollected commands. So let's be more |
| // strict and return an error if this happens. |
| if (o == nullptr) { |
| return JDWP::ERR_INVALID_OBJECT; |
| } |
| gRegistry->EnableCollection(object_id); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::IsCollected(JDWP::ObjectId object_id, bool* is_collected) { |
| *is_collected = true; |
| if (object_id == 0) { |
| // Null object id is invalid. |
| return JDWP::ERR_INVALID_OBJECT; |
| } |
| // JDWP specs state an INVALID_OBJECT error is returned if the object ID is not valid. However |
| // the RI seems to ignore this and assume object has been collected. |
| JDWP::JdwpError error; |
| mirror::Object* o = gRegistry->Get<mirror::Object*>(object_id, &error); |
| if (o != nullptr) { |
| *is_collected = gRegistry->IsCollected(object_id); |
| } |
| return JDWP::ERR_NONE; |
| } |
| |
| void Dbg::DisposeObject(JDWP::ObjectId object_id, uint32_t reference_count) { |
| gRegistry->DisposeObject(object_id, reference_count); |
| } |
| |
| JDWP::JdwpTypeTag Dbg::GetTypeTag(mirror::Class* klass) { |
| DCHECK(klass != nullptr); |
| if (klass->IsArrayClass()) { |
| return JDWP::TT_ARRAY; |
| } else if (klass->IsInterface()) { |
| return JDWP::TT_INTERFACE; |
| } else { |
| return JDWP::TT_CLASS; |
| } |
| } |
| |
| JDWP::JdwpError Dbg::GetReflectedType(JDWP::RefTypeId class_id, JDWP::ExpandBuf* pReply) { |
| JDWP::JdwpError error; |
| mirror::Class* c = DecodeClass(class_id, &error); |
| if (c == nullptr) { |
| return error; |
| } |
| |
| JDWP::JdwpTypeTag type_tag = GetTypeTag(c); |
| expandBufAdd1(pReply, type_tag); |
| expandBufAddRefTypeId(pReply, class_id); |
| return JDWP::ERR_NONE; |
| } |
| |
| void Dbg::GetClassList(std::vector<JDWP::RefTypeId>* classes) { |
| // Get the complete list of reference classes (i.e. all classes except |
| // the primitive types). |
| // Returns a newly-allocated buffer full of RefTypeId values. |
| struct ClassListCreator { |
| explicit ClassListCreator(std::vector<JDWP::RefTypeId>* classes_in) : classes(classes_in) { |
| } |
| |
| static bool Visit(mirror::Class* c, void* arg) { |
| return reinterpret_cast<ClassListCreator*>(arg)->Visit(c); |
| } |
| |
| // TODO: Enable annotalysis. We know lock is held in constructor, but abstraction confuses |
| // annotalysis. |
| bool Visit(mirror::Class* c) NO_THREAD_SAFETY_ANALYSIS { |
| if (!c->IsPrimitive()) { |
| classes->push_back(gRegistry->AddRefType(c)); |
| } |
| return true; |
| } |
| |
| std::vector<JDWP::RefTypeId>* const classes; |
| }; |
| |
| ClassListCreator clc(classes); |
| Runtime::Current()->GetClassLinker()->VisitClassesWithoutClassesLock(ClassListCreator::Visit, |
| &clc); |
| } |
| |
| JDWP::JdwpError Dbg::GetClassInfo(JDWP::RefTypeId class_id, JDWP::JdwpTypeTag* pTypeTag, |
| uint32_t* pStatus, std::string* pDescriptor) { |
| JDWP::JdwpError error; |
| mirror::Class* c = DecodeClass(class_id, &error); |
| if (c == nullptr) { |
| return error; |
| } |
| |
| if (c->IsArrayClass()) { |
| *pStatus = JDWP::CS_VERIFIED | JDWP::CS_PREPARED; |
| *pTypeTag = JDWP::TT_ARRAY; |
| } else { |
| if (c->IsErroneous()) { |
| *pStatus = JDWP::CS_ERROR; |
| } else { |
| *pStatus = JDWP::CS_VERIFIED | JDWP::CS_PREPARED | JDWP::CS_INITIALIZED; |
| } |
| *pTypeTag = c->IsInterface() ? JDWP::TT_INTERFACE : JDWP::TT_CLASS; |
| } |
| |
| if (pDescriptor != nullptr) { |
| std::string temp; |
| *pDescriptor = c->GetDescriptor(&temp); |
| } |
| return JDWP::ERR_NONE; |
| } |
| |
| void Dbg::FindLoadedClassBySignature(const char* descriptor, std::vector<JDWP::RefTypeId>* ids) { |
| std::vector<mirror::Class*> classes; |
| Runtime::Current()->GetClassLinker()->LookupClasses(descriptor, classes); |
| ids->clear(); |
| for (size_t i = 0; i < classes.size(); ++i) { |
| ids->push_back(gRegistry->Add(classes[i])); |
| } |
| } |
| |
| JDWP::JdwpError Dbg::GetReferenceType(JDWP::ObjectId object_id, JDWP::ExpandBuf* pReply) { |
| JDWP::JdwpError error; |
| mirror::Object* o = gRegistry->Get<mirror::Object*>(object_id, &error); |
| if (o == nullptr) { |
| return JDWP::ERR_INVALID_OBJECT; |
| } |
| |
| JDWP::JdwpTypeTag type_tag = GetTypeTag(o->GetClass()); |
| JDWP::RefTypeId type_id = gRegistry->AddRefType(o->GetClass()); |
| |
| expandBufAdd1(pReply, type_tag); |
| expandBufAddRefTypeId(pReply, type_id); |
| |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetSignature(JDWP::RefTypeId class_id, std::string* signature) { |
| JDWP::JdwpError error; |
| mirror::Class* c = DecodeClass(class_id, &error); |
| if (c == nullptr) { |
| return error; |
| } |
| std::string temp; |
| *signature = c->GetDescriptor(&temp); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetSourceFile(JDWP::RefTypeId class_id, std::string* result) { |
| JDWP::JdwpError error; |
| mirror::Class* c = DecodeClass(class_id, &error); |
| if (c == nullptr) { |
| return error; |
| } |
| const char* source_file = c->GetSourceFile(); |
| if (source_file == nullptr) { |
| return JDWP::ERR_ABSENT_INFORMATION; |
| } |
| *result = source_file; |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetObjectTag(JDWP::ObjectId object_id, uint8_t* tag) { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| JDWP::JdwpError error; |
| mirror::Object* o = gRegistry->Get<mirror::Object*>(object_id, &error); |
| if (error != JDWP::ERR_NONE) { |
| *tag = JDWP::JT_VOID; |
| return error; |
| } |
| *tag = TagFromObject(soa, o); |
| return JDWP::ERR_NONE; |
| } |
| |
| size_t Dbg::GetTagWidth(JDWP::JdwpTag tag) { |
| switch (tag) { |
| case JDWP::JT_VOID: |
| return 0; |
| case JDWP::JT_BYTE: |
| case JDWP::JT_BOOLEAN: |
| return 1; |
| case JDWP::JT_CHAR: |
| case JDWP::JT_SHORT: |
| return 2; |
| case JDWP::JT_FLOAT: |
| case JDWP::JT_INT: |
| return 4; |
| case JDWP::JT_ARRAY: |
| case JDWP::JT_OBJECT: |
| case JDWP::JT_STRING: |
| case JDWP::JT_THREAD: |
| case JDWP::JT_THREAD_GROUP: |
| case JDWP::JT_CLASS_LOADER: |
| case JDWP::JT_CLASS_OBJECT: |
| return sizeof(JDWP::ObjectId); |
| case JDWP::JT_DOUBLE: |
| case JDWP::JT_LONG: |
| return 8; |
| default: |
| LOG(FATAL) << "Unknown tag " << tag; |
| return -1; |
| } |
| } |
| |
| JDWP::JdwpError Dbg::GetArrayLength(JDWP::ObjectId array_id, int32_t* length) { |
| JDWP::JdwpError error; |
| mirror::Array* a = DecodeNonNullArray(array_id, &error); |
| if (a == nullptr) { |
| return error; |
| } |
| *length = a->GetLength(); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::OutputArray(JDWP::ObjectId array_id, int offset, int count, JDWP::ExpandBuf* pReply) { |
| JDWP::JdwpError error; |
| mirror::Array* a = DecodeNonNullArray(array_id, &error); |
| if (a == nullptr) { |
| return error; |
| } |
| |
| if (offset < 0 || count < 0 || offset > a->GetLength() || a->GetLength() - offset < count) { |
| LOG(WARNING) << __FUNCTION__ << " access out of bounds: offset=" << offset << "; count=" << count; |
| return JDWP::ERR_INVALID_LENGTH; |
| } |
| JDWP::JdwpTag element_tag = BasicTagFromClass(a->GetClass()->GetComponentType()); |
| expandBufAdd1(pReply, element_tag); |
| expandBufAdd4BE(pReply, count); |
| |
| if (IsPrimitiveTag(element_tag)) { |
| size_t width = GetTagWidth(element_tag); |
| uint8_t* dst = expandBufAddSpace(pReply, count * width); |
| if (width == 8) { |
| const uint64_t* src8 = reinterpret_cast<uint64_t*>(a->GetRawData(sizeof(uint64_t), 0)); |
| for (int i = 0; i < count; ++i) JDWP::Write8BE(&dst, src8[offset + i]); |
| } else if (width == 4) { |
| const uint32_t* src4 = reinterpret_cast<uint32_t*>(a->GetRawData(sizeof(uint32_t), 0)); |
| for (int i = 0; i < count; ++i) JDWP::Write4BE(&dst, src4[offset + i]); |
| } else if (width == 2) { |
| const uint16_t* src2 = reinterpret_cast<uint16_t*>(a->GetRawData(sizeof(uint16_t), 0)); |
| for (int i = 0; i < count; ++i) JDWP::Write2BE(&dst, src2[offset + i]); |
| } else { |
| const uint8_t* src = reinterpret_cast<uint8_t*>(a->GetRawData(sizeof(uint8_t), 0)); |
| memcpy(dst, &src[offset * width], count * width); |
| } |
| } else { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| mirror::ObjectArray<mirror::Object>* oa = a->AsObjectArray<mirror::Object>(); |
| for (int i = 0; i < count; ++i) { |
| mirror::Object* element = oa->Get(offset + i); |
| JDWP::JdwpTag specific_tag = (element != nullptr) ? TagFromObject(soa, element) |
| : element_tag; |
| expandBufAdd1(pReply, specific_tag); |
| expandBufAddObjectId(pReply, gRegistry->Add(element)); |
| } |
| } |
| |
| return JDWP::ERR_NONE; |
| } |
| |
| template <typename T> |
| static void CopyArrayData(mirror::Array* a, JDWP::Request* src, int offset, int count) |
| NO_THREAD_SAFETY_ANALYSIS { |
| // TODO: fix when annotalysis correctly handles non-member functions. |
| DCHECK(a->GetClass()->IsPrimitiveArray()); |
| |
| T* dst = reinterpret_cast<T*>(a->GetRawData(sizeof(T), offset)); |
| for (int i = 0; i < count; ++i) { |
| *dst++ = src->ReadValue(sizeof(T)); |
| } |
| } |
| |
| JDWP::JdwpError Dbg::SetArrayElements(JDWP::ObjectId array_id, int offset, int count, |
| JDWP::Request* request) { |
| JDWP::JdwpError error; |
| mirror::Array* dst = DecodeNonNullArray(array_id, &error); |
| if (dst == nullptr) { |
| return error; |
| } |
| |
| if (offset < 0 || count < 0 || offset > dst->GetLength() || dst->GetLength() - offset < count) { |
| LOG(WARNING) << __FUNCTION__ << " access out of bounds: offset=" << offset << "; count=" << count; |
| return JDWP::ERR_INVALID_LENGTH; |
| } |
| JDWP::JdwpTag element_tag = BasicTagFromClass(dst->GetClass()->GetComponentType()); |
| |
| if (IsPrimitiveTag(element_tag)) { |
| size_t width = GetTagWidth(element_tag); |
| if (width == 8) { |
| CopyArrayData<uint64_t>(dst, request, offset, count); |
| } else if (width == 4) { |
| CopyArrayData<uint32_t>(dst, request, offset, count); |
| } else if (width == 2) { |
| CopyArrayData<uint16_t>(dst, request, offset, count); |
| } else { |
| CopyArrayData<uint8_t>(dst, request, offset, count); |
| } |
| } else { |
| mirror::ObjectArray<mirror::Object>* oa = dst->AsObjectArray<mirror::Object>(); |
| for (int i = 0; i < count; ++i) { |
| JDWP::ObjectId id = request->ReadObjectId(); |
| mirror::Object* o = gRegistry->Get<mirror::Object*>(id, &error); |
| if (error != JDWP::ERR_NONE) { |
| return error; |
| } |
| oa->Set<false>(offset + i, o); |
| } |
| } |
| |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::ObjectId Dbg::CreateString(const std::string& str) { |
| return gRegistry->Add(mirror::String::AllocFromModifiedUtf8(Thread::Current(), str.c_str())); |
| } |
| |
| JDWP::JdwpError Dbg::CreateObject(JDWP::RefTypeId class_id, JDWP::ObjectId* new_object) { |
| JDWP::JdwpError error; |
| mirror::Class* c = DecodeClass(class_id, &error); |
| if (c == nullptr) { |
| *new_object = 0; |
| return error; |
| } |
| *new_object = gRegistry->Add(c->AllocObject(Thread::Current())); |
| return JDWP::ERR_NONE; |
| } |
| |
| /* |
| * Used by Eclipse's "Display" view to evaluate "new byte[5]" to get "(byte[]) [0, 0, 0, 0, 0]". |
| */ |
| JDWP::JdwpError Dbg::CreateArrayObject(JDWP::RefTypeId array_class_id, uint32_t length, |
| JDWP::ObjectId* new_array) { |
| JDWP::JdwpError error; |
| mirror::Class* c = DecodeClass(array_class_id, &error); |
| if (c == nullptr) { |
| *new_array = 0; |
| return error; |
| } |
| *new_array = gRegistry->Add(mirror::Array::Alloc<true>(Thread::Current(), c, length, |
| c->GetComponentSizeShift(), |
| Runtime::Current()->GetHeap()->GetCurrentAllocator())); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::FieldId Dbg::ToFieldId(const mirror::ArtField* f) { |
| CHECK(!kMovingFields); |
| return static_cast<JDWP::FieldId>(reinterpret_cast<uintptr_t>(f)); |
| } |
| |
| static JDWP::MethodId ToMethodId(const mirror::ArtMethod* m) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| CHECK(!kMovingMethods); |
| return static_cast<JDWP::MethodId>(reinterpret_cast<uintptr_t>(m)); |
| } |
| |
| static mirror::ArtField* FromFieldId(JDWP::FieldId fid) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| CHECK(!kMovingFields); |
| return reinterpret_cast<mirror::ArtField*>(static_cast<uintptr_t>(fid)); |
| } |
| |
| static mirror::ArtMethod* FromMethodId(JDWP::MethodId mid) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| CHECK(!kMovingMethods); |
| return reinterpret_cast<mirror::ArtMethod*>(static_cast<uintptr_t>(mid)); |
| } |
| |
| bool Dbg::MatchThread(JDWP::ObjectId expected_thread_id, Thread* event_thread) { |
| CHECK(event_thread != nullptr); |
| JDWP::JdwpError error; |
| mirror::Object* expected_thread_peer = gRegistry->Get<mirror::Object*>(expected_thread_id, |
| &error); |
| return expected_thread_peer == event_thread->GetPeer(); |
| } |
| |
| bool Dbg::MatchLocation(const JDWP::JdwpLocation& expected_location, |
| const JDWP::EventLocation& event_location) { |
| if (expected_location.dex_pc != event_location.dex_pc) { |
| return false; |
| } |
| mirror::ArtMethod* m = FromMethodId(expected_location.method_id); |
| return m == event_location.method; |
| } |
| |
| bool Dbg::MatchType(mirror::Class* event_class, JDWP::RefTypeId class_id) { |
| if (event_class == nullptr) { |
| return false; |
| } |
| JDWP::JdwpError error; |
| mirror::Class* expected_class = DecodeClass(class_id, &error); |
| CHECK(expected_class != nullptr); |
| return expected_class->IsAssignableFrom(event_class); |
| } |
| |
| bool Dbg::MatchField(JDWP::RefTypeId expected_type_id, JDWP::FieldId expected_field_id, |
| mirror::ArtField* event_field) { |
| mirror::ArtField* expected_field = FromFieldId(expected_field_id); |
| if (expected_field != event_field) { |
| return false; |
| } |
| return Dbg::MatchType(event_field->GetDeclaringClass(), expected_type_id); |
| } |
| |
| bool Dbg::MatchInstance(JDWP::ObjectId expected_instance_id, mirror::Object* event_instance) { |
| JDWP::JdwpError error; |
| mirror::Object* modifier_instance = gRegistry->Get<mirror::Object*>(expected_instance_id, &error); |
| return modifier_instance == event_instance; |
| } |
| |
| void Dbg::SetJdwpLocation(JDWP::JdwpLocation* location, mirror::ArtMethod* m, uint32_t dex_pc) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| if (m == nullptr) { |
| memset(location, 0, sizeof(*location)); |
| } else { |
| mirror::Class* c = m->GetDeclaringClass(); |
| location->type_tag = GetTypeTag(c); |
| location->class_id = gRegistry->AddRefType(c); |
| location->method_id = ToMethodId(m); |
| location->dex_pc = (m->IsNative() || m->IsProxyMethod()) ? static_cast<uint64_t>(-1) : dex_pc; |
| } |
| } |
| |
| std::string Dbg::GetMethodName(JDWP::MethodId method_id) { |
| mirror::ArtMethod* m = FromMethodId(method_id); |
| if (m == nullptr) { |
| return "NULL"; |
| } |
| return m->GetName(); |
| } |
| |
| std::string Dbg::GetFieldName(JDWP::FieldId field_id) { |
| mirror::ArtField* f = FromFieldId(field_id); |
| if (f == nullptr) { |
| return "NULL"; |
| } |
| return f->GetName(); |
| } |
| |
| /* |
| * Augment the access flags for synthetic methods and fields by setting |
| * the (as described by the spec) "0xf0000000 bit". Also, strip out any |
| * flags not specified by the Java programming language. |
| */ |
| static uint32_t MangleAccessFlags(uint32_t accessFlags) { |
| accessFlags &= kAccJavaFlagsMask; |
| if ((accessFlags & kAccSynthetic) != 0) { |
| accessFlags |= 0xf0000000; |
| } |
| return accessFlags; |
| } |
| |
| /* |
| * Circularly shifts registers so that arguments come first. Debuggers |
| * expect slots to begin with arguments, but dex code places them at |
| * the end. |
| */ |
| static uint16_t MangleSlot(uint16_t slot, mirror::ArtMethod* m) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| const DexFile::CodeItem* code_item = m->GetCodeItem(); |
| if (code_item == nullptr) { |
| // We should not get here for a method without code (native, proxy or abstract). Log it and |
| // return the slot as is since all registers are arguments. |
| LOG(WARNING) << "Trying to mangle slot for method without code " << PrettyMethod(m); |
| return slot; |
| } |
| uint16_t ins_size = code_item->ins_size_; |
| uint16_t locals_size = code_item->registers_size_ - ins_size; |
| if (slot >= locals_size) { |
| return slot - locals_size; |
| } else { |
| return slot + ins_size; |
| } |
| } |
| |
| /* |
| * Circularly shifts registers so that arguments come last. Reverts |
| * slots to dex style argument placement. |
| */ |
| static uint16_t DemangleSlot(uint16_t slot, mirror::ArtMethod* m) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| const DexFile::CodeItem* code_item = m->GetCodeItem(); |
| if (code_item == nullptr) { |
| // We should not get here for a method without code (native, proxy or abstract). Log it and |
| // return the slot as is since all registers are arguments. |
| LOG(WARNING) << "Trying to demangle slot for method without code " << PrettyMethod(m); |
| return slot; |
| } |
| uint16_t ins_size = code_item->ins_size_; |
| uint16_t locals_size = code_item->registers_size_ - ins_size; |
| if (slot < ins_size) { |
| return slot + locals_size; |
| } else { |
| return slot - ins_size; |
| } |
| } |
| |
| JDWP::JdwpError Dbg::OutputDeclaredFields(JDWP::RefTypeId class_id, bool with_generic, JDWP::ExpandBuf* pReply) { |
| JDWP::JdwpError error; |
| mirror::Class* c = DecodeClass(class_id, &error); |
| if (c == nullptr) { |
| return error; |
| } |
| |
| size_t instance_field_count = c->NumInstanceFields(); |
| size_t static_field_count = c->NumStaticFields(); |
| |
| expandBufAdd4BE(pReply, instance_field_count + static_field_count); |
| |
| for (size_t i = 0; i < instance_field_count + static_field_count; ++i) { |
| mirror::ArtField* f = (i < instance_field_count) ? c->GetInstanceField(i) : c->GetStaticField(i - instance_field_count); |
| expandBufAddFieldId(pReply, ToFieldId(f)); |
| expandBufAddUtf8String(pReply, f->GetName()); |
| expandBufAddUtf8String(pReply, f->GetTypeDescriptor()); |
| if (with_generic) { |
| static const char genericSignature[1] = ""; |
| expandBufAddUtf8String(pReply, genericSignature); |
| } |
| expandBufAdd4BE(pReply, MangleAccessFlags(f->GetAccessFlags())); |
| } |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::OutputDeclaredMethods(JDWP::RefTypeId class_id, bool with_generic, |
| JDWP::ExpandBuf* pReply) { |
| JDWP::JdwpError error; |
| mirror::Class* c = DecodeClass(class_id, &error); |
| if (c == nullptr) { |
| return error; |
| } |
| |
| size_t direct_method_count = c->NumDirectMethods(); |
| size_t virtual_method_count = c->NumVirtualMethods(); |
| |
| expandBufAdd4BE(pReply, direct_method_count + virtual_method_count); |
| |
| for (size_t i = 0; i < direct_method_count + virtual_method_count; ++i) { |
| mirror::ArtMethod* m = (i < direct_method_count) ? c->GetDirectMethod(i) : c->GetVirtualMethod(i - direct_method_count); |
| expandBufAddMethodId(pReply, ToMethodId(m)); |
| expandBufAddUtf8String(pReply, m->GetName()); |
| expandBufAddUtf8String(pReply, m->GetSignature().ToString()); |
| if (with_generic) { |
| static const char genericSignature[1] = ""; |
| expandBufAddUtf8String(pReply, genericSignature); |
| } |
| expandBufAdd4BE(pReply, MangleAccessFlags(m->GetAccessFlags())); |
| } |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::OutputDeclaredInterfaces(JDWP::RefTypeId class_id, JDWP::ExpandBuf* pReply) { |
| JDWP::JdwpError error; |
| Thread* self = Thread::Current(); |
| StackHandleScope<1> hs(self); |
| Handle<mirror::Class> c(hs.NewHandle(DecodeClass(class_id, &error))); |
| if (c.Get() == nullptr) { |
| return error; |
| } |
| size_t interface_count = c->NumDirectInterfaces(); |
| expandBufAdd4BE(pReply, interface_count); |
| for (size_t i = 0; i < interface_count; ++i) { |
| expandBufAddRefTypeId(pReply, |
| gRegistry->AddRefType(mirror::Class::GetDirectInterface(self, c, i))); |
| } |
| return JDWP::ERR_NONE; |
| } |
| |
| void Dbg::OutputLineTable(JDWP::RefTypeId, JDWP::MethodId method_id, JDWP::ExpandBuf* pReply) { |
| struct DebugCallbackContext { |
| int numItems; |
| JDWP::ExpandBuf* pReply; |
| |
| static bool Callback(void* context, uint32_t address, uint32_t line_number) { |
| DebugCallbackContext* pContext = reinterpret_cast<DebugCallbackContext*>(context); |
| expandBufAdd8BE(pContext->pReply, address); |
| expandBufAdd4BE(pContext->pReply, line_number); |
| pContext->numItems++; |
| return false; |
| } |
| }; |
| mirror::ArtMethod* m = FromMethodId(method_id); |
| const DexFile::CodeItem* code_item = m->GetCodeItem(); |
| uint64_t start, end; |
| if (code_item == nullptr) { |
| DCHECK(m->IsNative() || m->IsProxyMethod()); |
| start = -1; |
| end = -1; |
| } else { |
| start = 0; |
| // Return the index of the last instruction |
| end = code_item->insns_size_in_code_units_ - 1; |
| } |
| |
| expandBufAdd8BE(pReply, start); |
| expandBufAdd8BE(pReply, end); |
| |
| // Add numLines later |
| size_t numLinesOffset = expandBufGetLength(pReply); |
| expandBufAdd4BE(pReply, 0); |
| |
| DebugCallbackContext context; |
| context.numItems = 0; |
| context.pReply = pReply; |
| |
| if (code_item != nullptr) { |
| m->GetDexFile()->DecodeDebugInfo(code_item, m->IsStatic(), m->GetDexMethodIndex(), |
| DebugCallbackContext::Callback, nullptr, &context); |
| } |
| |
| JDWP::Set4BE(expandBufGetBuffer(pReply) + numLinesOffset, context.numItems); |
| } |
| |
| void Dbg::OutputVariableTable(JDWP::RefTypeId, JDWP::MethodId method_id, bool with_generic, |
| JDWP::ExpandBuf* pReply) { |
| struct DebugCallbackContext { |
| mirror::ArtMethod* method; |
| JDWP::ExpandBuf* pReply; |
| size_t variable_count; |
| bool with_generic; |
| |
| static void Callback(void* context, uint16_t slot, uint32_t startAddress, uint32_t endAddress, |
| const char* name, const char* descriptor, const char* signature) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| DebugCallbackContext* pContext = reinterpret_cast<DebugCallbackContext*>(context); |
| |
| VLOG(jdwp) << StringPrintf(" %2zd: %d(%d) '%s' '%s' '%s' actual slot=%d mangled slot=%d", |
| pContext->variable_count, startAddress, endAddress - startAddress, |
| name, descriptor, signature, slot, |
| MangleSlot(slot, pContext->method)); |
| |
| slot = MangleSlot(slot, pContext->method); |
| |
| expandBufAdd8BE(pContext->pReply, startAddress); |
| expandBufAddUtf8String(pContext->pReply, name); |
| expandBufAddUtf8String(pContext->pReply, descriptor); |
| if (pContext->with_generic) { |
| expandBufAddUtf8String(pContext->pReply, signature); |
| } |
| expandBufAdd4BE(pContext->pReply, endAddress - startAddress); |
| expandBufAdd4BE(pContext->pReply, slot); |
| |
| ++pContext->variable_count; |
| } |
| }; |
| mirror::ArtMethod* m = FromMethodId(method_id); |
| |
| // arg_count considers doubles and longs to take 2 units. |
| // variable_count considers everything to take 1 unit. |
| std::string shorty(m->GetShorty()); |
| expandBufAdd4BE(pReply, mirror::ArtMethod::NumArgRegisters(shorty)); |
| |
| // We don't know the total number of variables yet, so leave a blank and update it later. |
| size_t variable_count_offset = expandBufGetLength(pReply); |
| expandBufAdd4BE(pReply, 0); |
| |
| DebugCallbackContext context; |
| context.method = m; |
| context.pReply = pReply; |
| context.variable_count = 0; |
| context.with_generic = with_generic; |
| |
| const DexFile::CodeItem* code_item = m->GetCodeItem(); |
| if (code_item != nullptr) { |
| m->GetDexFile()->DecodeDebugInfo( |
| code_item, m->IsStatic(), m->GetDexMethodIndex(), nullptr, DebugCallbackContext::Callback, |
| &context); |
| } |
| |
| JDWP::Set4BE(expandBufGetBuffer(pReply) + variable_count_offset, context.variable_count); |
| } |
| |
| void Dbg::OutputMethodReturnValue(JDWP::MethodId method_id, const JValue* return_value, |
| JDWP::ExpandBuf* pReply) { |
| mirror::ArtMethod* m = FromMethodId(method_id); |
| JDWP::JdwpTag tag = BasicTagFromDescriptor(m->GetShorty()); |
| OutputJValue(tag, return_value, pReply); |
| } |
| |
| void Dbg::OutputFieldValue(JDWP::FieldId field_id, const JValue* field_value, |
| JDWP::ExpandBuf* pReply) { |
| mirror::ArtField* f = FromFieldId(field_id); |
| JDWP::JdwpTag tag = BasicTagFromDescriptor(f->GetTypeDescriptor()); |
| OutputJValue(tag, field_value, pReply); |
| } |
| |
| JDWP::JdwpError Dbg::GetBytecodes(JDWP::RefTypeId, JDWP::MethodId method_id, |
| std::vector<uint8_t>* bytecodes) { |
| mirror::ArtMethod* m = FromMethodId(method_id); |
| if (m == nullptr) { |
| return JDWP::ERR_INVALID_METHODID; |
| } |
| const DexFile::CodeItem* code_item = m->GetCodeItem(); |
| size_t byte_count = code_item->insns_size_in_code_units_ * 2; |
| const uint8_t* begin = reinterpret_cast<const uint8_t*>(code_item->insns_); |
| const uint8_t* end = begin + byte_count; |
| for (const uint8_t* p = begin; p != end; ++p) { |
| bytecodes->push_back(*p); |
| } |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpTag Dbg::GetFieldBasicTag(JDWP::FieldId field_id) { |
| return BasicTagFromDescriptor(FromFieldId(field_id)->GetTypeDescriptor()); |
| } |
| |
| JDWP::JdwpTag Dbg::GetStaticFieldBasicTag(JDWP::FieldId field_id) { |
| return BasicTagFromDescriptor(FromFieldId(field_id)->GetTypeDescriptor()); |
| } |
| |
| static JDWP::JdwpError GetFieldValueImpl(JDWP::RefTypeId ref_type_id, JDWP::ObjectId object_id, |
| JDWP::FieldId field_id, JDWP::ExpandBuf* pReply, |
| bool is_static) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| JDWP::JdwpError error; |
| mirror::Class* c = DecodeClass(ref_type_id, &error); |
| if (ref_type_id != 0 && c == nullptr) { |
| return error; |
| } |
| |
| mirror::Object* o = Dbg::GetObjectRegistry()->Get<mirror::Object*>(object_id, &error); |
| if ((!is_static && o == nullptr) || error != JDWP::ERR_NONE) { |
| return JDWP::ERR_INVALID_OBJECT; |
| } |
| mirror::ArtField* f = FromFieldId(field_id); |
| |
| mirror::Class* receiver_class = c; |
| if (receiver_class == nullptr && o != nullptr) { |
| receiver_class = o->GetClass(); |
| } |
| // TODO: should we give up now if receiver_class is nullptr? |
| if (receiver_class != nullptr && !f->GetDeclaringClass()->IsAssignableFrom(receiver_class)) { |
| LOG(INFO) << "ERR_INVALID_FIELDID: " << PrettyField(f) << " " << PrettyClass(receiver_class); |
| return JDWP::ERR_INVALID_FIELDID; |
| } |
| |
| // The RI only enforces the static/non-static mismatch in one direction. |
| // TODO: should we change the tests and check both? |
| if (is_static) { |
| if (!f->IsStatic()) { |
| return JDWP::ERR_INVALID_FIELDID; |
| } |
| } else { |
| if (f->IsStatic()) { |
| LOG(WARNING) << "Ignoring non-nullptr receiver for ObjectReference.SetValues on static field " |
| << PrettyField(f); |
| } |
| } |
| if (f->IsStatic()) { |
| o = f->GetDeclaringClass(); |
| } |
| |
| JDWP::JdwpTag tag = BasicTagFromDescriptor(f->GetTypeDescriptor()); |
| JValue field_value; |
| if (tag == JDWP::JT_VOID) { |
| LOG(FATAL) << "Unknown tag: " << tag; |
| } else if (!IsPrimitiveTag(tag)) { |
| field_value.SetL(f->GetObject(o)); |
| } else if (tag == JDWP::JT_DOUBLE || tag == JDWP::JT_LONG) { |
| field_value.SetJ(f->Get64(o)); |
| } else { |
| field_value.SetI(f->Get32(o)); |
| } |
| Dbg::OutputJValue(tag, &field_value, pReply); |
| |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetFieldValue(JDWP::ObjectId object_id, JDWP::FieldId field_id, |
| JDWP::ExpandBuf* pReply) { |
| return GetFieldValueImpl(0, object_id, field_id, pReply, false); |
| } |
| |
| JDWP::JdwpError Dbg::GetStaticFieldValue(JDWP::RefTypeId ref_type_id, JDWP::FieldId field_id, |
| JDWP::ExpandBuf* pReply) { |
| return GetFieldValueImpl(ref_type_id, 0, field_id, pReply, true); |
| } |
| |
| static JDWP::JdwpError SetFieldValueImpl(JDWP::ObjectId object_id, JDWP::FieldId field_id, |
| uint64_t value, int width, bool is_static) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| JDWP::JdwpError error; |
| mirror::Object* o = Dbg::GetObjectRegistry()->Get<mirror::Object*>(object_id, &error); |
| if ((!is_static && o == nullptr) || error != JDWP::ERR_NONE) { |
| return JDWP::ERR_INVALID_OBJECT; |
| } |
| mirror::ArtField* f = FromFieldId(field_id); |
| |
| // The RI only enforces the static/non-static mismatch in one direction. |
| // TODO: should we change the tests and check both? |
| if (is_static) { |
| if (!f->IsStatic()) { |
| return JDWP::ERR_INVALID_FIELDID; |
| } |
| } else { |
| if (f->IsStatic()) { |
| LOG(WARNING) << "Ignoring non-nullptr receiver for ObjectReference.SetValues on static field " << PrettyField(f); |
| } |
| } |
| if (f->IsStatic()) { |
| o = f->GetDeclaringClass(); |
| } |
| |
| JDWP::JdwpTag tag = BasicTagFromDescriptor(f->GetTypeDescriptor()); |
| |
| if (IsPrimitiveTag(tag)) { |
| if (tag == JDWP::JT_DOUBLE || tag == JDWP::JT_LONG) { |
| CHECK_EQ(width, 8); |
| // Debugging can't use transactional mode (runtime only). |
| f->Set64<false>(o, value); |
| } else { |
| CHECK_LE(width, 4); |
| // Debugging can't use transactional mode (runtime only). |
| f->Set32<false>(o, value); |
| } |
| } else { |
| mirror::Object* v = Dbg::GetObjectRegistry()->Get<mirror::Object*>(value, &error); |
| if (error != JDWP::ERR_NONE) { |
| return JDWP::ERR_INVALID_OBJECT; |
| } |
| if (v != nullptr) { |
| mirror::Class* field_type; |
| { |
| StackHandleScope<3> hs(Thread::Current()); |
| HandleWrapper<mirror::Object> h_v(hs.NewHandleWrapper(&v)); |
| HandleWrapper<mirror::ArtField> h_f(hs.NewHandleWrapper(&f)); |
| HandleWrapper<mirror::Object> h_o(hs.NewHandleWrapper(&o)); |
| field_type = h_f->GetType(true); |
| } |
| if (!field_type->IsAssignableFrom(v->GetClass())) { |
| return JDWP::ERR_INVALID_OBJECT; |
| } |
| } |
| // Debugging can't use transactional mode (runtime only). |
| f->SetObject<false>(o, v); |
| } |
| |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::SetFieldValue(JDWP::ObjectId object_id, JDWP::FieldId field_id, uint64_t value, |
| int width) { |
| return SetFieldValueImpl(object_id, field_id, value, width, false); |
| } |
| |
| JDWP::JdwpError Dbg::SetStaticFieldValue(JDWP::FieldId field_id, uint64_t value, int width) { |
| return SetFieldValueImpl(0, field_id, value, width, true); |
| } |
| |
| JDWP::JdwpError Dbg::StringToUtf8(JDWP::ObjectId string_id, std::string* str) { |
| JDWP::JdwpError error; |
| mirror::Object* obj = gRegistry->Get<mirror::Object*>(string_id, &error); |
| if (error != JDWP::ERR_NONE) { |
| return error; |
| } |
| if (obj == nullptr) { |
| return JDWP::ERR_INVALID_OBJECT; |
| } |
| { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| mirror::Class* java_lang_String = soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_String); |
| if (!java_lang_String->IsAssignableFrom(obj->GetClass())) { |
| // This isn't a string. |
| return JDWP::ERR_INVALID_STRING; |
| } |
| } |
| *str = obj->AsString()->ToModifiedUtf8(); |
| return JDWP::ERR_NONE; |
| } |
| |
| void Dbg::OutputJValue(JDWP::JdwpTag tag, const JValue* return_value, JDWP::ExpandBuf* pReply) { |
| if (IsPrimitiveTag(tag)) { |
| expandBufAdd1(pReply, tag); |
| if (tag == JDWP::JT_BOOLEAN || tag == JDWP::JT_BYTE) { |
| expandBufAdd1(pReply, return_value->GetI()); |
| } else if (tag == JDWP::JT_CHAR || tag == JDWP::JT_SHORT) { |
| expandBufAdd2BE(pReply, return_value->GetI()); |
| } else if (tag == JDWP::JT_FLOAT || tag == JDWP::JT_INT) { |
| expandBufAdd4BE(pReply, return_value->GetI()); |
| } else if (tag == JDWP::JT_DOUBLE || tag == JDWP::JT_LONG) { |
| expandBufAdd8BE(pReply, return_value->GetJ()); |
| } else { |
| CHECK_EQ(tag, JDWP::JT_VOID); |
| } |
| } else { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| mirror::Object* value = return_value->GetL(); |
| expandBufAdd1(pReply, TagFromObject(soa, value)); |
| expandBufAddObjectId(pReply, gRegistry->Add(value)); |
| } |
| } |
| |
| JDWP::JdwpError Dbg::GetThreadName(JDWP::ObjectId thread_id, std::string* name) { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| MutexLock mu(soa.Self(), *Locks::thread_list_lock_); |
| JDWP::JdwpError error; |
| Thread* thread = DecodeThread(soa, thread_id, &error); |
| UNUSED(thread); |
| if (error != JDWP::ERR_NONE && error != JDWP::ERR_THREAD_NOT_ALIVE) { |
| return error; |
| } |
| |
| // We still need to report the zombie threads' names, so we can't just call Thread::GetThreadName. |
| mirror::Object* thread_object = gRegistry->Get<mirror::Object*>(thread_id, &error); |
| CHECK(thread_object != nullptr) << error; |
| mirror::ArtField* java_lang_Thread_name_field = |
| soa.DecodeField(WellKnownClasses::java_lang_Thread_name); |
| mirror::String* s = |
| reinterpret_cast<mirror::String*>(java_lang_Thread_name_field->GetObject(thread_object)); |
| if (s != nullptr) { |
| *name = s->ToModifiedUtf8(); |
| } |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetThreadGroup(JDWP::ObjectId thread_id, JDWP::ExpandBuf* pReply) { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| JDWP::JdwpError error; |
| mirror::Object* thread_object = gRegistry->Get<mirror::Object*>(thread_id, &error); |
| if (error != JDWP::ERR_NONE) { |
| return JDWP::ERR_INVALID_OBJECT; |
| } |
| ScopedAssertNoThreadSuspension ants(soa.Self(), "Debugger: GetThreadGroup"); |
| // Okay, so it's an object, but is it actually a thread? |
| { |
| MutexLock mu(soa.Self(), *Locks::thread_list_lock_); |
| Thread* thread = DecodeThread(soa, thread_id, &error); |
| UNUSED(thread); |
| } |
| if (error == JDWP::ERR_THREAD_NOT_ALIVE) { |
| // Zombie threads are in the null group. |
| expandBufAddObjectId(pReply, JDWP::ObjectId(0)); |
| error = JDWP::ERR_NONE; |
| } else if (error == JDWP::ERR_NONE) { |
| mirror::Class* c = soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_Thread); |
| CHECK(c != nullptr); |
| mirror::ArtField* f = soa.DecodeField(WellKnownClasses::java_lang_Thread_group); |
| CHECK(f != nullptr); |
| mirror::Object* group = f->GetObject(thread_object); |
| CHECK(group != nullptr); |
| JDWP::ObjectId thread_group_id = gRegistry->Add(group); |
| expandBufAddObjectId(pReply, thread_group_id); |
| } |
| return error; |
| } |
| |
| static mirror::Object* DecodeThreadGroup(ScopedObjectAccessUnchecked& soa, |
| JDWP::ObjectId thread_group_id, JDWP::JdwpError* error) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| mirror::Object* thread_group = Dbg::GetObjectRegistry()->Get<mirror::Object*>(thread_group_id, |
| error); |
| if (*error != JDWP::ERR_NONE) { |
| return nullptr; |
| } |
| if (thread_group == nullptr) { |
| *error = JDWP::ERR_INVALID_OBJECT; |
| return nullptr; |
| } |
| mirror::Class* c = soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_ThreadGroup); |
| CHECK(c != nullptr); |
| if (!c->IsAssignableFrom(thread_group->GetClass())) { |
| // This is not a java.lang.ThreadGroup. |
| *error = JDWP::ERR_INVALID_THREAD_GROUP; |
| return nullptr; |
| } |
| *error = JDWP::ERR_NONE; |
| return thread_group; |
| } |
| |
| JDWP::JdwpError Dbg::GetThreadGroupName(JDWP::ObjectId thread_group_id, JDWP::ExpandBuf* pReply) { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| JDWP::JdwpError error; |
| mirror::Object* thread_group = DecodeThreadGroup(soa, thread_group_id, &error); |
| if (error != JDWP::ERR_NONE) { |
| return error; |
| } |
| ScopedAssertNoThreadSuspension ants(soa.Self(), "Debugger: GetThreadGroupName"); |
| mirror::ArtField* f = soa.DecodeField(WellKnownClasses::java_lang_ThreadGroup_name); |
| CHECK(f != nullptr); |
| mirror::String* s = reinterpret_cast<mirror::String*>(f->GetObject(thread_group)); |
| |
| std::string thread_group_name(s->ToModifiedUtf8()); |
| expandBufAddUtf8String(pReply, thread_group_name); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetThreadGroupParent(JDWP::ObjectId thread_group_id, JDWP::ExpandBuf* pReply) { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| JDWP::JdwpError error; |
| mirror::Object* thread_group = DecodeThreadGroup(soa, thread_group_id, &error); |
| if (error != JDWP::ERR_NONE) { |
| return error; |
| } |
| mirror::Object* parent; |
| { |
| ScopedAssertNoThreadSuspension ants(soa.Self(), "Debugger: GetThreadGroupParent"); |
| mirror::ArtField* f = soa.DecodeField(WellKnownClasses::java_lang_ThreadGroup_parent); |
| CHECK(f != nullptr); |
| parent = f->GetObject(thread_group); |
| } |
| JDWP::ObjectId parent_group_id = gRegistry->Add(parent); |
| expandBufAddObjectId(pReply, parent_group_id); |
| return JDWP::ERR_NONE; |
| } |
| |
| static void GetChildThreadGroups(ScopedObjectAccessUnchecked& soa, mirror::Object* thread_group, |
| std::vector<JDWP::ObjectId>* child_thread_group_ids) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| CHECK(thread_group != nullptr); |
| |
| // Get the ArrayList<ThreadGroup> "groups" out of this thread group... |
| mirror::ArtField* groups_field = soa.DecodeField(WellKnownClasses::java_lang_ThreadGroup_groups); |
| mirror::Object* groups_array_list = groups_field->GetObject(thread_group); |
| { |
| // The "groups" field is declared as a java.util.List: check it really is |
| // an instance of java.util.ArrayList. |
| CHECK(groups_array_list != nullptr); |
| mirror::Class* java_util_ArrayList_class = |
| soa.Decode<mirror::Class*>(WellKnownClasses::java_util_ArrayList); |
| CHECK(groups_array_list->InstanceOf(java_util_ArrayList_class)); |
| } |
| |
| // Get the array and size out of the ArrayList<ThreadGroup>... |
| mirror::ArtField* array_field = soa.DecodeField(WellKnownClasses::java_util_ArrayList_array); |
| mirror::ArtField* size_field = soa.DecodeField(WellKnownClasses::java_util_ArrayList_size); |
| mirror::ObjectArray<mirror::Object>* groups_array = |
| array_field->GetObject(groups_array_list)->AsObjectArray<mirror::Object>(); |
| const int32_t size = size_field->GetInt(groups_array_list); |
| |
| // Copy the first 'size' elements out of the array into the result. |
| ObjectRegistry* registry = Dbg::GetObjectRegistry(); |
| for (int32_t i = 0; i < size; ++i) { |
| child_thread_group_ids->push_back(registry->Add(groups_array->Get(i))); |
| } |
| } |
| |
| JDWP::JdwpError Dbg::GetThreadGroupChildren(JDWP::ObjectId thread_group_id, |
| JDWP::ExpandBuf* pReply) { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| JDWP::JdwpError error; |
| mirror::Object* thread_group = DecodeThreadGroup(soa, thread_group_id, &error); |
| if (error != JDWP::ERR_NONE) { |
| return error; |
| } |
| |
| // Add child threads. |
| { |
| std::vector<JDWP::ObjectId> child_thread_ids; |
| GetThreads(thread_group, &child_thread_ids); |
| expandBufAdd4BE(pReply, child_thread_ids.size()); |
| for (JDWP::ObjectId child_thread_id : child_thread_ids) { |
| expandBufAddObjectId(pReply, child_thread_id); |
| } |
| } |
| |
| // Add child thread groups. |
| { |
| std::vector<JDWP::ObjectId> child_thread_groups_ids; |
| GetChildThreadGroups(soa, thread_group, &child_thread_groups_ids); |
| expandBufAdd4BE(pReply, child_thread_groups_ids.size()); |
| for (JDWP::ObjectId child_thread_group_id : child_thread_groups_ids) { |
| expandBufAddObjectId(pReply, child_thread_group_id); |
| } |
| } |
| |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::ObjectId Dbg::GetSystemThreadGroupId() { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| mirror::ArtField* f = soa.DecodeField(WellKnownClasses::java_lang_ThreadGroup_systemThreadGroup); |
| mirror::Object* group = f->GetObject(f->GetDeclaringClass()); |
| return gRegistry->Add(group); |
| } |
| |
| JDWP::JdwpThreadStatus Dbg::ToJdwpThreadStatus(ThreadState state) { |
| switch (state) { |
| case kBlocked: |
| return JDWP::TS_MONITOR; |
| case kNative: |
| case kRunnable: |
| case kSuspended: |
| return JDWP::TS_RUNNING; |
| case kSleeping: |
| return JDWP::TS_SLEEPING; |
| case kStarting: |
| case kTerminated: |
| return JDWP::TS_ZOMBIE; |
| case kTimedWaiting: |
| case kWaitingForCheckPointsToRun: |
| case kWaitingForDebuggerSend: |
| case kWaitingForDebuggerSuspension: |
| case kWaitingForDebuggerToAttach: |
| case kWaitingForDeoptimization: |
| case kWaitingForGcToComplete: |
| case kWaitingForJniOnLoad: |
| case kWaitingForMethodTracingStart: |
| case kWaitingForSignalCatcherOutput: |
| case kWaitingForVisitObjects: |
| case kWaitingInMainDebuggerLoop: |
| case kWaitingInMainSignalCatcherLoop: |
| case kWaitingPerformingGc: |
| case kWaiting: |
| return JDWP::TS_WAIT; |
| // Don't add a 'default' here so the compiler can spot incompatible enum changes. |
| } |
| LOG(FATAL) << "Unknown thread state: " << state; |
| return JDWP::TS_ZOMBIE; |
| } |
| |
| JDWP::JdwpError Dbg::GetThreadStatus(JDWP::ObjectId thread_id, JDWP::JdwpThreadStatus* pThreadStatus, |
| JDWP::JdwpSuspendStatus* pSuspendStatus) { |
| ScopedObjectAccess soa(Thread::Current()); |
| |
| *pSuspendStatus = JDWP::SUSPEND_STATUS_NOT_SUSPENDED; |
| |
| MutexLock mu(soa.Self(), *Locks::thread_list_lock_); |
| JDWP::JdwpError error; |
| Thread* thread = DecodeThread(soa, thread_id, &error); |
| if (error != JDWP::ERR_NONE) { |
| if (error == JDWP::ERR_THREAD_NOT_ALIVE) { |
| *pThreadStatus = JDWP::TS_ZOMBIE; |
| return JDWP::ERR_NONE; |
| } |
| return error; |
| } |
| |
| if (IsSuspendedForDebugger(soa, thread)) { |
| *pSuspendStatus = JDWP::SUSPEND_STATUS_SUSPENDED; |
| } |
| |
| *pThreadStatus = ToJdwpThreadStatus(thread->GetState()); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetThreadDebugSuspendCount(JDWP::ObjectId thread_id, JDWP::ExpandBuf* pReply) { |
| ScopedObjectAccess soa(Thread::Current()); |
| MutexLock mu(soa.Self(), *Locks::thread_list_lock_); |
| JDWP::JdwpError error; |
| Thread* thread = DecodeThread(soa, thread_id, &error); |
| if (error != JDWP::ERR_NONE) { |
| return error; |
| } |
| MutexLock mu2(soa.Self(), *Locks::thread_suspend_count_lock_); |
| expandBufAdd4BE(pReply, thread->GetDebugSuspendCount()); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::Interrupt(JDWP::ObjectId thread_id) { |
| ScopedObjectAccess soa(Thread::Current()); |
| MutexLock mu(soa.Self(), *Locks::thread_list_lock_); |
| JDWP::JdwpError error; |
| Thread* thread = DecodeThread(soa, thread_id, &error); |
| if (error != JDWP::ERR_NONE) { |
| return error; |
| } |
| thread->Interrupt(soa.Self()); |
| return JDWP::ERR_NONE; |
| } |
| |
| static bool IsInDesiredThreadGroup(ScopedObjectAccessUnchecked& soa, |
| mirror::Object* desired_thread_group, mirror::Object* peer) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| // Do we want threads from all thread groups? |
| if (desired_thread_group == nullptr) { |
| return true; |
| } |
| mirror::ArtField* thread_group_field = soa.DecodeField(WellKnownClasses::java_lang_Thread_group); |
| DCHECK(thread_group_field != nullptr); |
| mirror::Object* group = thread_group_field->GetObject(peer); |
| return (group == desired_thread_group); |
| } |
| |
| void Dbg::GetThreads(mirror::Object* thread_group, std::vector<JDWP::ObjectId>* thread_ids) { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| std::list<Thread*> all_threads_list; |
| { |
| MutexLock mu(Thread::Current(), *Locks::thread_list_lock_); |
| all_threads_list = Runtime::Current()->GetThreadList()->GetList(); |
| } |
| for (Thread* t : all_threads_list) { |
| if (t == Dbg::GetDebugThread()) { |
| // Skip the JDWP thread. Some debuggers get bent out of shape when they can't suspend and |
| // query all threads, so it's easier if we just don't tell them about this thread. |
| continue; |
| } |
| if (t->IsStillStarting()) { |
| // This thread is being started (and has been registered in the thread list). However, it is |
| // not completely started yet so we must ignore it. |
| continue; |
| } |
| mirror::Object* peer = t->GetPeer(); |
| if (peer == nullptr) { |
| // peer might be NULL if the thread is still starting up. We can't tell the debugger about |
| // this thread yet. |
| // TODO: if we identified threads to the debugger by their Thread* |
| // rather than their peer's mirror::Object*, we could fix this. |
| // Doing so might help us report ZOMBIE threads too. |
| continue; |
| } |
| if (IsInDesiredThreadGroup(soa, thread_group, peer)) { |
| thread_ids->push_back(gRegistry->Add(peer)); |
| } |
| } |
| } |
| |
| static int GetStackDepth(Thread* thread) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { |
| struct CountStackDepthVisitor : public StackVisitor { |
| explicit CountStackDepthVisitor(Thread* thread_in) |
| : StackVisitor(thread_in, nullptr), depth(0) {} |
| |
| // TODO: Enable annotalysis. We know lock is held in constructor, but abstraction confuses |
| // annotalysis. |
| bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS { |
| if (!GetMethod()->IsRuntimeMethod()) { |
| ++depth; |
| } |
| return true; |
| } |
| size_t depth; |
| }; |
| |
| CountStackDepthVisitor visitor(thread); |
| visitor.WalkStack(); |
| return visitor.depth; |
| } |
| |
| JDWP::JdwpError Dbg::GetThreadFrameCount(JDWP::ObjectId thread_id, size_t* result) { |
| ScopedObjectAccess soa(Thread::Current()); |
| MutexLock mu(soa.Self(), *Locks::thread_list_lock_); |
| JDWP::JdwpError error; |
| *result = 0; |
| Thread* thread = DecodeThread(soa, thread_id, &error); |
| if (error != JDWP::ERR_NONE) { |
| return error; |
| } |
| if (!IsSuspendedForDebugger(soa, thread)) { |
| return JDWP::ERR_THREAD_NOT_SUSPENDED; |
| } |
| *result = GetStackDepth(thread); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::GetThreadFrames(JDWP::ObjectId thread_id, size_t start_frame, |
| size_t frame_count, JDWP::ExpandBuf* buf) { |
| class GetFrameVisitor : public StackVisitor { |
| public: |
| GetFrameVisitor(Thread* thread, size_t start_frame_in, size_t frame_count_in, |
| JDWP::ExpandBuf* buf_in) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) |
| : StackVisitor(thread, nullptr), depth_(0), |
| start_frame_(start_frame_in), frame_count_(frame_count_in), buf_(buf_in) { |
| expandBufAdd4BE(buf_, frame_count_); |
| } |
| |
| // TODO: Enable annotalysis. We know lock is held in constructor, but abstraction confuses |
| // annotalysis. |
| virtual bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS { |
| if (GetMethod()->IsRuntimeMethod()) { |
| return true; // The debugger can't do anything useful with a frame that has no Method*. |
| } |
| if (depth_ >= start_frame_ + frame_count_) { |
| return false; |
| } |
| if (depth_ >= start_frame_) { |
| JDWP::FrameId frame_id(GetFrameId()); |
| JDWP::JdwpLocation location; |
| SetJdwpLocation(&location, GetMethod(), GetDexPc()); |
| VLOG(jdwp) << StringPrintf(" Frame %3zd: id=%3" PRIu64 " ", depth_, frame_id) << location; |
| expandBufAdd8BE(buf_, frame_id); |
| expandBufAddLocation(buf_, location); |
| } |
| ++depth_; |
| return true; |
| } |
| |
| private: |
| size_t depth_; |
| const size_t start_frame_; |
| const size_t frame_count_; |
| JDWP::ExpandBuf* buf_; |
| }; |
| |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| MutexLock mu(soa.Self(), *Locks::thread_list_lock_); |
| JDWP::JdwpError error; |
| Thread* thread = DecodeThread(soa, thread_id, &error); |
| if (error != JDWP::ERR_NONE) { |
| return error; |
| } |
| if (!IsSuspendedForDebugger(soa, thread)) { |
| return JDWP::ERR_THREAD_NOT_SUSPENDED; |
| } |
| GetFrameVisitor visitor(thread, start_frame, frame_count, buf); |
| visitor.WalkStack(); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::ObjectId Dbg::GetThreadSelfId() { |
| return GetThreadId(Thread::Current()); |
| } |
| |
| JDWP::ObjectId Dbg::GetThreadId(Thread* thread) { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| return gRegistry->Add(thread->GetPeer()); |
| } |
| |
| void Dbg::SuspendVM() { |
| Runtime::Current()->GetThreadList()->SuspendAllForDebugger(); |
| } |
| |
| void Dbg::ResumeVM() { |
| Runtime::Current()->GetThreadList()->ResumeAllForDebugger(); |
| } |
| |
| JDWP::JdwpError Dbg::SuspendThread(JDWP::ObjectId thread_id, bool request_suspension) { |
| Thread* self = Thread::Current(); |
| ScopedLocalRef<jobject> peer(self->GetJniEnv(), nullptr); |
| { |
| ScopedObjectAccess soa(self); |
| JDWP::JdwpError error; |
| peer.reset(soa.AddLocalReference<jobject>(gRegistry->Get<mirror::Object*>(thread_id, &error))); |
| } |
| if (peer.get() == nullptr) { |
| return JDWP::ERR_THREAD_NOT_ALIVE; |
| } |
| // Suspend thread to build stack trace. |
| bool timed_out; |
| ThreadList* thread_list = Runtime::Current()->GetThreadList(); |
| Thread* thread = thread_list->SuspendThreadByPeer(peer.get(), request_suspension, true, |
| &timed_out); |
| if (thread != nullptr) { |
| return JDWP::ERR_NONE; |
| } else if (timed_out) { |
| return JDWP::ERR_INTERNAL; |
| } else { |
| return JDWP::ERR_THREAD_NOT_ALIVE; |
| } |
| } |
| |
| void Dbg::ResumeThread(JDWP::ObjectId thread_id) { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| JDWP::JdwpError error; |
| mirror::Object* peer = gRegistry->Get<mirror::Object*>(thread_id, &error); |
| CHECK(peer != nullptr) << error; |
| Thread* thread; |
| { |
| MutexLock mu(soa.Self(), *Locks::thread_list_lock_); |
| thread = Thread::FromManagedThread(soa, peer); |
| } |
| if (thread == nullptr) { |
| LOG(WARNING) << "No such thread for resume: " << peer; |
| return; |
| } |
| bool needs_resume; |
| { |
| MutexLock mu2(soa.Self(), *Locks::thread_suspend_count_lock_); |
| needs_resume = thread->GetSuspendCount() > 0; |
| } |
| if (needs_resume) { |
| Runtime::Current()->GetThreadList()->Resume(thread, true); |
| } |
| } |
| |
| void Dbg::SuspendSelf() { |
| Runtime::Current()->GetThreadList()->SuspendSelfForDebugger(); |
| } |
| |
| struct GetThisVisitor : public StackVisitor { |
| GetThisVisitor(Thread* thread, Context* context, JDWP::FrameId frame_id_in) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) |
| : StackVisitor(thread, context), this_object(nullptr), frame_id(frame_id_in) {} |
| |
| // TODO: Enable annotalysis. We know lock is held in constructor, but abstraction confuses |
| // annotalysis. |
| virtual bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS { |
| if (frame_id != GetFrameId()) { |
| return true; // continue |
| } else { |
| this_object = GetThisObject(); |
| return false; |
| } |
| } |
| |
| mirror::Object* this_object; |
| JDWP::FrameId frame_id; |
| }; |
| |
| JDWP::JdwpError Dbg::GetThisObject(JDWP::ObjectId thread_id, JDWP::FrameId frame_id, |
| JDWP::ObjectId* result) { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| Thread* thread; |
| { |
| MutexLock mu(soa.Self(), *Locks::thread_list_lock_); |
| JDWP::JdwpError error; |
| thread = DecodeThread(soa, thread_id, &error); |
| if (error != JDWP::ERR_NONE) { |
| return error; |
| } |
| if (!IsSuspendedForDebugger(soa, thread)) { |
| return JDWP::ERR_THREAD_NOT_SUSPENDED; |
| } |
| } |
| std::unique_ptr<Context> context(Context::Create()); |
| GetThisVisitor visitor(thread, context.get(), frame_id); |
| visitor.WalkStack(); |
| *result = gRegistry->Add(visitor.this_object); |
| return JDWP::ERR_NONE; |
| } |
| |
| // Walks the stack until we find the frame with the given FrameId. |
| class FindFrameVisitor FINAL : public StackVisitor { |
| public: |
| FindFrameVisitor(Thread* thread, Context* context, JDWP::FrameId frame_id) |
| SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) |
| : StackVisitor(thread, context), frame_id_(frame_id), error_(JDWP::ERR_INVALID_FRAMEID) {} |
| |
| // TODO: Enable annotalysis. We know lock is held in constructor, but abstraction confuses |
| // annotalysis. |
| bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS { |
| if (GetFrameId() != frame_id_) { |
| return true; // Not our frame, carry on. |
| } |
| mirror::ArtMethod* m = GetMethod(); |
| if (m->IsNative()) { |
| // We can't read/write local value from/into native method. |
| error_ = JDWP::ERR_OPAQUE_FRAME; |
| } else { |
| // We found our frame. |
| error_ = JDWP::ERR_NONE; |
| } |
| return false; |
| } |
| |
| JDWP::JdwpError GetError() const { |
| return error_; |
| } |
| |
| private: |
| const JDWP::FrameId frame_id_; |
| JDWP::JdwpError error_; |
| }; |
| |
| JDWP::JdwpError Dbg::GetLocalValues(JDWP::Request* request, JDWP::ExpandBuf* pReply) { |
| JDWP
|