| /* |
| * 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 "art_field-inl.h" |
| #include "art_method-inl.h" |
| #include "base/time_utils.h" |
| #include "class_linker.h" |
| #include "class_linker-inl.h" |
| #include "dex_file-inl.h" |
| #include "dex_instruction.h" |
| #include "entrypoints/runtime_asm_entrypoints.h" |
| #include "gc/accounting/card_table-inl.h" |
| #include "gc/allocation_record.h" |
| #include "gc/scoped_gc_critical_section.h" |
| #include "gc/space/large_object_space.h" |
| #include "gc/space/space-inl.h" |
| #include "handle_scope.h" |
| #include "jdwp/jdwp_priv.h" |
| #include "jdwp/object_registry.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 "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 "well_known_classes.h" |
| |
| namespace art { |
| |
| // The key identifying the debugger to update instrumentation. |
| static constexpr const char* kDbgInstrumentationKey = "Debugger"; |
| |
| // Limit alloc_record_count to the 2BE value (64k-1) that is the limit of the current protocol. |
| static uint16_t CappedAllocRecordCount(size_t alloc_record_count) { |
| const size_t cap = 0xffff; |
| if (alloc_record_count > cap) { |
| return cap; |
| } |
| return alloc_record_count; |
| } |
| |
| // Takes a method and returns a 'canonical' one if the method is default (and therefore potentially |
| // copied from some other class). This ensures that the debugger does not get confused as to which |
| // method we are in. |
| static ArtMethod* GetCanonicalMethod(ArtMethod* m) |
| SHARED_REQUIRES(Locks::mutator_lock_) { |
| if (LIKELY(!m->IsDefault())) { |
| return m; |
| } else { |
| mirror::Class* declaring_class = m->GetDeclaringClass(); |
| return declaring_class->FindDeclaredVirtualMethod(declaring_class->GetDexCache(), |
| m->GetDexMethodIndex(), |
| sizeof(void*)); |
| } |
| } |
| |
| class Breakpoint : public ValueObject { |
| public: |
| Breakpoint(ArtMethod* method, uint32_t dex_pc, DeoptimizationRequest::Kind deoptimization_kind) |
| : method_(GetCanonicalMethod(method)), |
| dex_pc_(dex_pc), |
| deoptimization_kind_(deoptimization_kind) { |
| CHECK(deoptimization_kind_ == DeoptimizationRequest::kNothing || |
| deoptimization_kind_ == DeoptimizationRequest::kSelectiveDeoptimization || |
| deoptimization_kind_ == DeoptimizationRequest::kFullDeoptimization); |
| } |
| |
| Breakpoint(const Breakpoint& other) SHARED_REQUIRES(Locks::mutator_lock_) |
| : method_(other.method_), |
| dex_pc_(other.dex_pc_), |
| deoptimization_kind_(other.deoptimization_kind_) {} |
| |
| // Method() is called from root visiting, do not use ScopedObjectAccess here or it can cause |
| // GC to deadlock if another thread tries to call SuspendAll while the GC is in a runnable state. |
| ArtMethod* Method() const { |
| return method_; |
| } |
| |
| uint32_t DexPc() const { |
| return dex_pc_; |
| } |
| |
| DeoptimizationRequest::Kind GetDeoptimizationKind() const { |
| return deoptimization_kind_; |
| } |
| |
| // Returns true if the method of this breakpoint and the passed in method should be considered the |
| // same. That is, they are either the same method or they are copied from the same method. |
| bool IsInMethod(ArtMethod* m) const SHARED_REQUIRES(Locks::mutator_lock_) { |
| return method_ == GetCanonicalMethod(m); |
| } |
| |
| private: |
| // The location of this breakpoint. |
| ArtMethod* 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_REQUIRES(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, ArtMethod* method, |
| uint32_t dex_pc) |
| OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { |
| if (method->IsNative()) { |
| // TODO: post location events is a suspension point and native method entry stubs aren't. |
| return; |
| } |
| if (IsListeningToDexPcMoved()) { |
| // We also listen to kDexPcMoved instrumentation event so we know the DexPcMoved method is |
| // going to be called right after us. To avoid sending JDWP events twice for this location, |
| // we report the event in DexPcMoved. However, we must remind this is method entry so we |
| // send the METHOD_ENTRY event. And we can also group it with other events for this location |
| // like BREAKPOINT or SINGLE_STEP (or even METHOD_EXIT if this is a RETURN instruction). |
| thread->SetDebugMethodEntry(); |
| } else if (IsListeningToMethodExit() && IsReturn(method, dex_pc)) { |
| // We also listen to kMethodExited instrumentation event and the current instruction is a |
| // RETURN so we know the MethodExited method is going to be called right after us. To avoid |
| // sending JDWP events twice for this location, we report the event(s) in MethodExited. |
| // However, we must remind this is method entry so we send the METHOD_ENTRY event. And we can |
| // also group it with other events for this location like BREAKPOINT or SINGLE_STEP. |
| thread->SetDebugMethodEntry(); |
| } else { |
| Dbg::UpdateDebugger(thread, this_object, method, 0, Dbg::kMethodEntry, nullptr); |
| } |
| } |
| |
| void MethodExited(Thread* thread, mirror::Object* this_object, ArtMethod* method, |
| uint32_t dex_pc, const JValue& return_value) |
| OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { |
| if (method->IsNative()) { |
| // TODO: post location events is a suspension point and native method entry stubs aren't. |
| return; |
| } |
| uint32_t events = Dbg::kMethodExit; |
| if (thread->IsDebugMethodEntry()) { |
| // It is also the method entry. |
| DCHECK(IsReturn(method, dex_pc)); |
| events |= Dbg::kMethodEntry; |
| thread->ClearDebugMethodEntry(); |
| } |
| Dbg::UpdateDebugger(thread, this_object, method, dex_pc, events, &return_value); |
| } |
| |
| void MethodUnwind(Thread* thread ATTRIBUTE_UNUSED, mirror::Object* this_object ATTRIBUTE_UNUSED, |
| ArtMethod* method, uint32_t dex_pc) |
| OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { |
| // We're not recorded to listen to this kind of event, so complain. |
| LOG(ERROR) << "Unexpected method unwind event in debugger " << PrettyMethod(method) |
| << " " << dex_pc; |
| } |
| |
| void DexPcMoved(Thread* thread, mirror::Object* this_object, ArtMethod* method, |
| uint32_t new_dex_pc) |
| OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { |
| if (IsListeningToMethodExit() && IsReturn(method, new_dex_pc)) { |
| // We also listen to kMethodExited instrumentation event and the current instruction is a |
| // RETURN so we know the MethodExited method is going to be called right after us. Like in |
| // MethodEntered, we delegate event reporting to MethodExited. |
| // Besides, if this RETURN instruction is the only one in the method, we can send multiple |
| // JDWP events in the same packet: METHOD_ENTRY, METHOD_EXIT, BREAKPOINT and/or SINGLE_STEP. |
| // Therefore, we must not clear the debug method entry flag here. |
| } else { |
| uint32_t events = 0; |
| if (thread->IsDebugMethodEntry()) { |
| // It is also the method entry. |
| events = Dbg::kMethodEntry; |
| thread->ClearDebugMethodEntry(); |
| } |
| Dbg::UpdateDebugger(thread, this_object, method, new_dex_pc, events, nullptr); |
| } |
| } |
| |
| void FieldRead(Thread* thread ATTRIBUTE_UNUSED, mirror::Object* this_object, |
| ArtMethod* method, uint32_t dex_pc, ArtField* field) |
| OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { |
| Dbg::PostFieldAccessEvent(method, dex_pc, this_object, field); |
| } |
| |
| void FieldWritten(Thread* thread ATTRIBUTE_UNUSED, mirror::Object* this_object, |
| ArtMethod* method, uint32_t dex_pc, ArtField* field, |
| const JValue& field_value) |
| OVERRIDE SHARED_REQUIRES(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_REQUIRES(Locks::mutator_lock_) { |
| Dbg::PostException(exception_object); |
| } |
| |
| // We only care about branches in the Jit. |
| void Branch(Thread* /*thread*/, ArtMethod* method, uint32_t dex_pc, int32_t dex_pc_offset) |
| OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { |
| LOG(ERROR) << "Unexpected branch event in debugger " << PrettyMethod(method) |
| << " " << dex_pc << ", " << dex_pc_offset; |
| } |
| |
| // We only care about invokes in the Jit. |
| void InvokeVirtualOrInterface(Thread* thread ATTRIBUTE_UNUSED, |
| mirror::Object*, |
| ArtMethod* method, |
| uint32_t dex_pc, |
| ArtMethod*) |
| OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { |
| LOG(ERROR) << "Unexpected invoke event in debugger " << PrettyMethod(method) |
| << " " << dex_pc; |
| } |
| |
| private: |
| static bool IsReturn(ArtMethod* method, uint32_t dex_pc) |
| SHARED_REQUIRES(Locks::mutator_lock_) { |
| const DexFile::CodeItem* code_item = method->GetCodeItem(); |
| const Instruction* instruction = Instruction::At(&code_item->insns_[dex_pc]); |
| return instruction->IsReturn(); |
| } |
| |
| static bool IsListeningToDexPcMoved() SHARED_REQUIRES(Locks::mutator_lock_) { |
| return IsListeningTo(instrumentation::Instrumentation::kDexPcMoved); |
| } |
| |
| static bool IsListeningToMethodExit() SHARED_REQUIRES(Locks::mutator_lock_) { |
| return IsListeningTo(instrumentation::Instrumentation::kMethodExited); |
| } |
| |
| static bool IsListeningTo(instrumentation::Instrumentation::InstrumentationEvent event) |
| SHARED_REQUIRES(Locks::mutator_lock_) { |
| return (Dbg::GetInstrumentationEvents() & event) != 0; |
| } |
| |
| 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 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; |
| |
| bool Dbg::gDebuggerActive = false; |
| bool Dbg::gDisposed = false; |
| ObjectRegistry* Dbg::gRegistry = nullptr; |
| |
| // Deoptimization support. |
| std::vector<DeoptimizationRequest> Dbg::deoptimization_requests_; |
| size_t Dbg::full_deoptimization_event_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(RootVisitor* visitor, const RootInfo& root_info) { |
| receiver.VisitRootIfNonNull(visitor, root_info); // null for static method call. |
| klass.VisitRoot(visitor, 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(ArtMethod* m, uint32_t dex_pc) |
| REQUIRES(!Locks::breakpoint_lock_) |
| SHARED_REQUIRES(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].IsInMethod(m)) { |
| VLOG(jdwp) << "Hit breakpoint #" << i << ": " << gBreakpoints[i]; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static bool IsSuspendedForDebugger(ScopedObjectAccessUnchecked& soa, Thread* thread) |
| REQUIRES(!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_REQUIRES(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_REQUIRES(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) |
| SHARED_REQUIRES(Locks::mutator_lock_) |
| REQUIRES(!Locks::thread_list_lock_, !Locks::thread_suspend_count_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; |
| } |
| |
| MutexLock mu(soa.Self(), *Locks::thread_list_lock_); |
| 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_REQUIRES(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_REQUIRES(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. |
| Dispose(); |
| 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; |
| } |
| |
| bool Dbg::RequiresDeoptimization() { |
| // We don't need deoptimization if everything runs with interpreter after |
| // enabling -Xint mode. |
| return !Runtime::Current()->GetInstrumentation()->IsForcedInterpretOnly(); |
| } |
| |
| // Used to patch boot image method entry point to interpreter bridge. |
| class UpdateEntryPointsClassVisitor : public ClassVisitor { |
| public: |
| explicit UpdateEntryPointsClassVisitor(instrumentation::Instrumentation* instrumentation) |
| : instrumentation_(instrumentation) {} |
| |
| bool operator()(mirror::Class* klass) OVERRIDE REQUIRES(Locks::mutator_lock_) { |
| auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize(); |
| for (auto& m : klass->GetMethods(pointer_size)) { |
| const void* code = m.GetEntryPointFromQuickCompiledCode(); |
| if (Runtime::Current()->GetHeap()->IsInBootImageOatFile(code) && |
| !m.IsNative() && |
| !m.IsProxyMethod()) { |
| instrumentation_->UpdateMethodsCode(&m, GetQuickToInterpreterBridge()); |
| } |
| } |
| return true; |
| } |
| |
| private: |
| instrumentation::Instrumentation* const instrumentation_; |
| }; |
| |
| 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 (IsDebuggerActive()) { |
| return; |
| } |
| |
| Thread* const self = Thread::Current(); |
| { |
| // TODO: dalvik only warned if there were breakpoints left over. clear in Dbg::Disconnected? |
| ReaderMutexLock mu(self, *Locks::breakpoint_lock_); |
| CHECK_EQ(gBreakpoints.size(), 0U); |
| } |
| |
| { |
| MutexLock mu(self, *Locks::deoptimization_lock_); |
| CHECK_EQ(deoptimization_requests_.size(), 0U); |
| CHECK_EQ(full_deoptimization_event_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(); |
| // Since boot image code may be AOT compiled as not debuggable, we need to patch |
| // entry points of methods in boot image to interpreter bridge. |
| // However, the performance cost of this is non-negligible during native-debugging due to the |
| // forced JIT, so we keep the AOT code in that case in exchange for limited native debugging. |
| if (!runtime->GetInstrumentation()->IsForcedInterpretOnly() && !runtime->IsNativeDebuggable()) { |
| ScopedObjectAccess soa(self); |
| UpdateEntryPointsClassVisitor visitor(runtime->GetInstrumentation()); |
| runtime->GetClassLinker()->VisitClasses(&visitor); |
| } |
| |
| ScopedSuspendAll ssa(__FUNCTION__); |
| if (RequiresDeoptimization()) { |
| runtime->GetInstrumentation()->EnableDeoptimization(); |
| } |
| instrumentation_events_ = 0; |
| gDebuggerActive = true; |
| 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(); |
| Thread* self = Thread::Current(); |
| { |
| // Required for DisableDeoptimization. |
| gc::ScopedGCCriticalSection gcs(self, |
| gc::kGcCauseInstrumentation, |
| gc::kCollectorTypeInstrumentation); |
| ScopedSuspendAll ssa(__FUNCTION__); |
| ThreadState old_state = self->SetStateUnsafe(kRunnable); |
| // Debugger may not be active at this point. |
| if (IsDebuggerActive()) { |
| { |
| // 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(self, *Locks::deoptimization_lock_); |
| deoptimization_requests_.clear(); |
| full_deoptimization_event_count_ = 0U; |
| } |
| if (instrumentation_events_ != 0) { |
| runtime->GetInstrumentation()->RemoveListener(&gDebugInstrumentationListener, |
| instrumentation_events_); |
| instrumentation_events_ = 0; |
| } |
| if (RequiresDeoptimization()) { |
| runtime->GetInstrumentation()->DisableDeoptimization(kDbgInstrumentationKey); |
| } |
| gDebuggerActive = false; |
| } |
| CHECK_EQ(self->SetStateUnsafe(old_state), kRunnable); |
| } |
| |
| { |
| ScopedObjectAccess soa(self); |
| gRegistry->Clear(); |
| } |
| |
| gDebuggerConnected = false; |
| } |
| |
| 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::Class* c = DecodeClass(id, &error); |
| if (c == nullptr) { |
| return error; |
| } |
| expandBufAddObjectId(pReply, gRegistry->Add(c->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); |
| |
| MonitorInfo monitor_info; |
| { |
| ScopedThreadSuspension sts(self, kSuspended); |
| ScopedSuspendAll ssa(__FUNCTION__); |
| monitor_info = MonitorInfo(o); |
| } |
| 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_REQUIRES(Locks::mutator_lock_) |
| : StackVisitor(thread, context, StackVisitor::StackWalkKind::kIncludeInlinedFrames), |
| 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_REQUIRES(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()); |
| 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; |
| } |
| 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) { |
| ScopedObjectAccessUnchecked soa(Thread::Current()); |
| *contended_monitor = 0; |
| 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; |
| } |
| mirror::Object* 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; |
| } |
| |
| // Get the complete list of reference classes (i.e. all classes except |
| // the primitive types). |
| // Returns a newly-allocated buffer full of RefTypeId values. |
| class ClassListCreator : public ClassVisitor { |
| public: |
| explicit ClassListCreator(std::vector<JDWP::RefTypeId>* classes) : classes_(classes) {} |
| |
| bool operator()(mirror::Class* c) OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { |
| if (!c->IsPrimitive()) { |
| classes_->push_back(Dbg::GetObjectRegistry()->AddRefType(c)); |
| } |
| return true; |
| } |
| |
| private: |
| std::vector<JDWP::RefTypeId>* const classes_; |
| }; |
| |
| void Dbg::GetClassList(std::vector<JDWP::RefTypeId>* classes) { |
| ClassListCreator clc(classes); |
| Runtime::Current()->GetClassLinker()->VisitClassesWithoutClassesLock(&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; |
| } |
| // Check if the object's type is compatible with the array's type. |
| if (o != nullptr && !o->InstanceOf(oa->GetClass()->GetComponentType())) { |
| return JDWP::ERR_TYPE_MISMATCH; |
| } |
| oa->Set<false>(offset + i, o); |
| } |
| } |
| |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::CreateString(const std::string& str, JDWP::ObjectId* new_string_id) { |
| Thread* self = Thread::Current(); |
| mirror::String* new_string = mirror::String::AllocFromModifiedUtf8(self, str.c_str()); |
| if (new_string == nullptr) { |
| DCHECK(self->IsExceptionPending()); |
| self->ClearException(); |
| LOG(ERROR) << "Could not allocate string"; |
| *new_string_id = 0; |
| return JDWP::ERR_OUT_OF_MEMORY; |
| } |
| *new_string_id = gRegistry->Add(new_string); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::JdwpError Dbg::CreateObject(JDWP::RefTypeId class_id, JDWP::ObjectId* new_object_id) { |
| JDWP::JdwpError error; |
| mirror::Class* c = DecodeClass(class_id, &error); |
| if (c == nullptr) { |
| *new_object_id = 0; |
| return error; |
| } |
| Thread* self = Thread::Current(); |
| mirror::Object* new_object; |
| if (c->IsStringClass()) { |
| // Special case for java.lang.String. |
| gc::AllocatorType allocator_type = Runtime::Current()->GetHeap()->GetCurrentAllocator(); |
| mirror::SetStringCountVisitor visitor(0); |
| new_object = mirror::String::Alloc<true>(self, 0, allocator_type, visitor); |
| } else { |
| new_object = c->AllocObject(self); |
| } |
| if (new_object == nullptr) { |
| DCHECK(self->IsExceptionPending()); |
| self->ClearException(); |
| LOG(ERROR) << "Could not allocate object of type " << PrettyDescriptor(c); |
| *new_object_id = 0; |
| return JDWP::ERR_OUT_OF_MEMORY; |
| } |
| *new_object_id = gRegistry->Add(new_object); |
| 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_id) { |
| JDWP::JdwpError error; |
| mirror::Class* c = DecodeClass(array_class_id, &error); |
| if (c == nullptr) { |
| *new_array_id = 0; |
| return error; |
| } |
| Thread* self = Thread::Current(); |
| gc::Heap* heap = Runtime::Current()->GetHeap(); |
| mirror::Array* new_array = mirror::Array::Alloc<true>(self, c, length, |
| c->GetComponentSizeShift(), |
| heap->GetCurrentAllocator()); |
| if (new_array == nullptr) { |
| DCHECK(self->IsExceptionPending()); |
| self->ClearException(); |
| LOG(ERROR) << "Could not allocate array of type " << PrettyDescriptor(c); |
| *new_array_id = 0; |
| return JDWP::ERR_OUT_OF_MEMORY; |
| } |
| *new_array_id = gRegistry->Add(new_array); |
| return JDWP::ERR_NONE; |
| } |
| |
| JDWP::FieldId Dbg::ToFieldId(const ArtField* f) { |
| return static_cast<JDWP::FieldId>(reinterpret_cast<uintptr_t>(f)); |
| } |
| |
| static JDWP::MethodId ToMethodId(ArtMethod* m) |
| SHARED_REQUIRES(Locks::mutator_lock_) { |
| return static_cast<JDWP::MethodId>(reinterpret_cast<uintptr_t>(GetCanonicalMethod(m))); |
| } |
| |
| static ArtField* FromFieldId(JDWP::FieldId fid) |
| SHARED_REQUIRES(Locks::mutator_lock_) { |
| return reinterpret_cast<ArtField*>(static_cast<uintptr_t>(fid)); |
| } |
| |
| static ArtMethod* FromMethodId(JDWP::MethodId mid) |
| SHARED_REQUIRES(Locks::mutator_lock_) { |
| return reinterpret_cast<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; |
| } |
| 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, |
| ArtField* event_field) { |
| 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, ArtMethod* m, uint32_t dex_pc) { |
| 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) { |
| ArtMethod* m = FromMethodId(method_id); |
| if (m == nullptr) { |
| return "null"; |
| } |
| return m->GetInterfaceMethodIfProxy(sizeof(void*))->GetName(); |
| } |
| |
| std::string Dbg::GetFieldName(JDWP::FieldId field_id) { |
| 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, ArtMethod* m) |
| SHARED_REQUIRES(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, ArtMethod* m, JDWP::JdwpError* error) |
| SHARED_REQUIRES(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); |
| uint16_t vreg_count = ArtMethod::NumArgRegisters(m->GetShorty()); |
| if (slot < vreg_count) { |
| *error = JDWP::ERR_NONE; |
| return slot; |
| } |
| } else { |
| if (slot < code_item->registers_size_) { |
| uint16_t ins_size = code_item->ins_size_; |
| uint16_t locals_size = code_item->registers_size_ - ins_size; |
| *error = JDWP::ERR_NONE; |
| return (slot < ins_size) ? slot + locals_size : slot - ins_size; |
| } |
| } |
| |
| // Slot is invalid in the method. |
| LOG(ERROR) << "Invalid local slot " << slot << " for method " << PrettyMethod(m); |
| *error = JDWP::ERR_INVALID_SLOT; |
| return DexFile::kDexNoIndex16; |
| } |
| |
| 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) { |
| 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; |
| } |
| |
| expandBufAdd4BE(pReply, c->NumMethods()); |
| |
| auto* cl = Runtime::Current()->GetClassLinker(); |
| auto ptr_size = cl->GetImagePointerSize(); |
| for (ArtMethod& m : c->GetMethods(ptr_size)) { |
| expandBufAddMethodId(pReply, ToMethodId(&m)); |
| expandBufAddUtf8String(pReply, m.GetInterfaceMethodIfProxy(sizeof(void*))->GetName()); |
| expandBufAddUtf8String(pReply, |
| m.GetInterfaceMethodIfProxy(sizeof(void*))->GetSignature().ToString()); |
| if (with_generic) { |
| const char* generic_signature = ""; |
| expandBufAddUtf8String(pReply, generic_signature); |
| } |
| 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, const DexFile::PositionInfo& entry) { |
| DebugCallbackContext* pContext = reinterpret_cast<DebugCallbackContext*>(context); |
| expandBufAdd8BE(pContext->pReply, entry.address_); |
| expandBufAdd4BE(pContext->pReply, entry.line_); |
| pContext->numItems++; |
| return false; |
| } |
| }; |
| 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()->DecodeDebugPositionInfo(code_item, DebugCallbackContext::Callback, &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 { |
| ArtMethod* method; |
| JDWP::ExpandBuf* pReply; |
| size_t variable_count; |
| bool with_generic; |
| |
| static void Callback(void* context, const DexFile::LocalInfo& entry) |
| SHARED_REQUIRES(Locks::mutator_lock_) { |
| DebugCallbackContext* pContext = reinterpret_cast<DebugCallbackContext*>(context); |
| |
| uint16_t slot = entry.reg_; |
| VLOG(jdwp) << StringPrintf(" %2zd: %d(%d) '%s' '%s' '%s' actual slot=%d mangled slot=%d", |
| pContext->variable_count, entry.start_address_, |
| entry.end_address_ - entry.start_address_, |
| entry.name_, entry.descriptor_, entry.signature_, slot, |
| MangleSlot(slot, pContext->method)); |
| |
| slot = MangleSlot(slot, pContext->method); |
| |
| expandBufAdd8BE(pContext->pReply, entry.start_address_); |
| expandBufAddUtf8String(pContext->pReply, entry.name_); |
| expandBufAddUtf8String(pContext->pReply, entry.descriptor_); |
| if (pContext->with_generic) { |
| expandBufAddUtf8String(pContext->pReply, entry.signature_); |
| } |
| expandBufAdd4BE(pContext->pReply, entry.end_address_- entry.start_address_); |
| expandBufAdd4BE(pContext->pReply, slot); |
| |
| ++pContext->variable_count; |
| } |
| }; |
| 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, 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()->DecodeDebugLocalInfo( |
| code_item, m->IsStatic(), m->GetDexMethodIndex(), 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) { |
| 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) { |
| 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) { |
| 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 JValue GetArtFieldValue(ArtField* f, mirror::Object* o) |
| SHARED_REQUIRES(Locks::mutator_lock_) { |
| Primitive::Type fieldType = f->GetTypeAsPrimitiveType(); |
| JValue field_value; |
| switch (fieldType) { |
| case Primitive::kPrimBoolean: |
| field_value.SetZ(f->GetBoolean(o)); |
| return field_value; |
| |
| case Primitive::kPrimByte: |
| field_value.SetB(f->GetByte(o)); |
| return field_value; |
| |
| case Primitive::kPrimChar: |
| field_value.SetC(f->GetChar(o)); |
| return field_value; |
| |
| case Primitive::kPrimShort: |
| field_value.SetS(f->GetShort(o)); |
| return field_value; |
| |
| case Primitive::kPrimInt: |
| case Primitive::kPrimFloat: |
| // Int and Float must be treated as 32-bit values in JDWP. |
| field_value.SetI(f->GetInt(o)); |
| return field_value; |
| |
| case Primitive::kPrimLong: |
| case Primitive::kPrimDouble: |
| // Long and Double must be treated as 64-bit values in JDWP. |
| field_value.SetJ(f->GetLong(o)); |
| return field_value; |
| |
| case Primitive::kPrimNot: |
| field_value.SetL(f->GetObject(o)); |
| return field_value; |
| |
| case Primitive::kPrimVoid: |
| LOG(FATAL) << "Attempt to read from field of type 'void'"; |
| UNREACHABLE(); |
| } |
| LOG(FATAL) << "Attempt to read from field of unknown type"; |
| UNREACHABLE(); |
| } |
| |
| static JDWP::JdwpError GetFieldValueImpl(JDWP::RefTypeId ref_type_id, JDWP::ObjectId object_id, |
| JDWP::FieldId field_id, JDWP::ExpandBuf* pReply, |
| bool is_static) |
| SHARED_REQUIRES(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; |
| } |
| 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 null? |
| 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.GetValues" |
| << " on static field " << PrettyField(f); |
| } |
| } |
| if (f->IsStatic()) { |
| o = f->GetDeclaringClass(); |
| } |
| |
| JValue field_value(GetArtFieldValue(f, o)); |
| JDWP::JdwpTag tag = BasicTagFromDescriptor(f->GetTypeDescriptor()); |
| 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 SetArtFieldValue(ArtField* f, mirror::Object* o, uint64_t value, int width) |
| SHARED_REQUIRES(Locks::mutator_lock_) { |
| Primitive::Type fieldType = f->GetTypeAsPrimitiveType(); |
| // Debugging only happens at runtime so we know we are not running in a transaction. |
| static constexpr bool kNoTransactionMode = false; |
| switch (fieldType) { |
| case Primitive::kPrimBoolean: |
| CHECK_EQ(width, 1); |
| f->SetBoolean<kNoTransactionMode>(o, static_cast<uint8_t>(value)); |
| return JDWP::ERR_NONE; |
| |
| case Primitive::kPrimByte: |
| CHECK_EQ(width, 1); |
| f->SetByte<kNoTransactionMode>(o, static_cast<uint8_t>(value)); |
| return JDWP::ERR_NONE; |
| |
| case Primitive::kPrimChar: |
| CHECK_EQ(width, 2); |
| f->SetChar<kNoTransactionMode>(o, static_cast<uint16_t>(value)); |
| return JDWP::ERR_NONE; |
| |
| case Primitive::kPrimShort: |
| CHECK_EQ(width, 2); |
| f->SetShort<kNoTransactionMode>(o, static_cast<int16_t>(value)); |
| return JDWP::ERR_NONE; |
| |
| case Primitive::kPrimInt: |
| case Primitive::kPrimFloat: |
| CHECK_EQ(width, 4); |
| // Int and Float must be treated as 32-bit values in JDWP. |
| f->SetInt<kNoTransactionMode>(o, static_cast<int32_t>(value)); |
| return JDWP::ERR_NONE; |
| |
| case Primitive::kPrimLong: |
| case Primitive::kPrimDouble: |
| CHECK_EQ(width, 8); |
| // Long and Double must be treated as 64-bit values in JDWP. |
| f->SetLong<kNoTransactionMode>(o, value); |
| return JDWP::ERR_NONE; |
| |
| case Primitive::kPrimNot: { |
| JDWP::JdwpError error; |
| 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<2> hs(Thread::Current()); |
| HandleWrapper<mirror::Object> h_v(hs.NewHandleWrapper(&v)); |
| HandleWrapper<mirror::Object> h_o(hs.NewHandleWrapper(&o)); |
| field_type = f->GetType<true>(); |
| } |
| if (!field_type->IsAssignableFrom(v->GetClass())) { |
| return JDWP::ERR_INVALID_OBJECT; |
| } |
| } |
| f->SetObject<kNoTransactionMode>(o, v); |
| return JDWP::ERR_NONE; |
| } |
| |
| case Primitive::kPrimVoid: |
| LOG(FATAL) << "Attempt to write to field of type 'void'"; |
| UNREACHABLE(); |
| } |
| LOG(FATAL) << "Attempt to write to field of unknown type"; |
| UNREACHABLE(); |
| } |
| |
| static JDWP::JdwpError SetFieldValueImpl(JDWP::ObjectId object_id, JDWP::FieldId field_id, |
| uint64_t value, int width, bool is_static) |
| SHARED_REQUIRES(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; |
| } |
| 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(); |
| } |
| return SetArtFieldValue(f, o, value, width); |
| } |
| |
| 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()); |
| JDWP::JdwpError error; |
| DecodeThread(soa, thread_id, &error); |
| 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; |
| 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? |
| DecodeThread(soa, thread_id, &error); |
| 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); |
| 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_REQUIRES(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"); |
| 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"); |
| 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_REQUIRES(Locks::mutator_lock_) { |
| CHECK(thread_group != nullptr); |
| |
| // Get the int "ngroups" count of this thread group... |
| ArtField* ngroups_field = soa.DecodeField(WellKnownClasses::java_lang_ThreadGroup_ngroups); |
| CHECK(ngroups_field != nullptr); |
| const int32_t size = ngroups_field->GetInt(thread_group); |
| if (size == 0) { |
| return; |
| } |
| |
| // Get the ThreadGroup[] "groups" out of this thread group... |
| ArtField* groups_field = soa.DecodeField(WellKnownClasses::java_lang_ThreadGroup_groups); |
| mirror::Object* groups_array = groups_field->GetObject(thread_group); |
| |
| CHECK(groups_array != nullptr); |
| CHECK(groups_array->IsObjectArray()); |
| |
| mirror::ObjectArray<mirror::Object>* groups_array_as_array = |
| groups_array->AsObjectArray<mirror::Object>(); |
| |
| // 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_as_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()); |
| 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 kWaitingForGetObjectsAllocated: |
| case kWaitingForJniOnLoad: |
| case kWaitingForMethodTracingStart: |
| case kWaitingForSignalCatcherOutput: |
| case kWaitingForVisitObjects: |
| case kWaitingInMainDebuggerLoop: |
| case kWaitingInMainSignalCatcherLoop: |
| case kWaitingPerformingGc: |
| case kWaitingWeakGcRootRead: |
| case kWaitingForGcThreadFlip: |
| 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; |
| |
| 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()); |
| 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()); |
| 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_REQUIRES(Locks::mutator_lock_) { |
| // Do we want threads from all thread groups? |
| if (desired_thread_group == nullptr) { |
| return true; |
| } |
| 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_REQUIRES(Locks::mutator_lock_) { |
| struct CountStackDepthVisitor : public StackVisitor { |
| explicit CountStackDepthVisitor(Thread* thread_in) |
| : StackVisitor(thread_in, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), |
| 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()); |
| 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_REQUIRES(Locks::mutator_lock_) |
| : StackVisitor(thread, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames), |
| depth_(0), |
| start_frame_(start_frame_in), |
| frame_count_(frame_count_in), |
| buf_(buf_in) { |
| expandBufAdd4BE(buf_, frame_count_); |
| } |
| |
| bool VisitFrame() OVERRIDE SHARED_REQUIRES(Locks::mutator_lock_) { |
| 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()); |
| 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_REQUIRES(Locks::mutator_lock_) |
| : StackVisitor(thread, context, StackVisitor::StackWalkKind::kIncludeInlinedFrames), |
| |