From dfd57b09a32eb2dd577cb860cedb99bc522aecbc Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Fri, 17 Sep 2021 18:10:05 -0700 Subject: [PATCH] kern: improve kdebug attach semantics --- libraries/libmesosphere/Makefile | 2 +- .../include/mesosphere/kern_k_debug_base.hpp | 37 +- .../source/arch/arm64/kern_k_debug.cpp | 10 +- .../source/kern_k_debug_base.cpp | 353 ++++++++++++------ .../kern_k_debug_base_process_holder.cpp | 47 +++ .../source/svc/kern_svc_debug.cpp | 12 +- .../source/svc/kern_svc_process.cpp | 34 +- .../source/svc/kern_svc_thread.cpp | 9 +- 8 files changed, 369 insertions(+), 135 deletions(-) create mode 100644 libraries/libmesosphere/source/kern_k_debug_base_process_holder.cpp diff --git a/libraries/libmesosphere/Makefile b/libraries/libmesosphere/Makefile index 3bc92c36c..8c8c0720c 100644 --- a/libraries/libmesosphere/Makefile +++ b/libraries/libmesosphere/Makefile @@ -142,7 +142,7 @@ $(OFILES_SRC) : $(HFILES_BIN) kern_libc_generic.o: CFLAGS += -fno-builtin -kern_k_auto_object.o: CXXFLAGS += -fno-lto +kern_k_auto_object.o kern_k_debug_base_process_holder.o: CXXFLAGS += -fno-lto #--------------------------------------------------------------------------------- %_bin.h %.bin.o : %.bin diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_debug_base.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_debug_base.hpp index 9edfa0c85..a4830edc0 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_debug_base.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_debug_base.hpp @@ -25,14 +25,31 @@ namespace ams::kern { class KDebugBase : public KSynchronizationObject { protected: using DebugEventList = util::IntrusiveListBaseTraits::ListType; + private: + class ProcessHolder { + private: + friend class KDebugBase; + private: + KProcess *m_process; + std::atomic m_ref_count; + private: + explicit ProcessHolder() : m_process(nullptr) { /* ... */ } + + void Attach(KProcess *process); + void Detach(); + + bool Open(); + void Close(); + }; private: DebugEventList m_event_info_list; u32 m_continue_flags; - KProcess *m_process; + ProcessHolder m_process_holder; KLightLock m_lock; KProcess::State m_old_process_state; + bool m_is_attached; public: - explicit KDebugBase() { /* ... */ } + explicit KDebugBase() : m_event_info_list(), m_process_holder(), m_lock() { /* ... */ } protected: bool Is64Bit() const; public: @@ -59,7 +76,21 @@ namespace ams::kern { Result GetDebugEventInfo(ams::svc::lp64::DebugEventInfo *out); Result GetDebugEventInfo(ams::svc::ilp32::DebugEventInfo *out); - KScopedAutoObject GetProcess(); + ALWAYS_INLINE bool IsAttached() const { + return m_is_attached; + } + + ALWAYS_INLINE bool OpenProcess() { + return m_process_holder.Open(); + } + + ALWAYS_INLINE void CloseProcess() { + return m_process_holder.Close(); + } + + ALWAYS_INLINE KProcess *GetProcessUnsafe() const { + return m_process_holder.m_process; + } private: void PushDebugEvent(ams::svc::DebugEvent event, uintptr_t param0 = 0, uintptr_t param1 = 0, uintptr_t param2 = 0, uintptr_t param3 = 0, uintptr_t param4 = 0); void EnqueueDebugEventInfo(KEventInfo *info); diff --git a/libraries/libmesosphere/source/arch/arm64/kern_k_debug.cpp b/libraries/libmesosphere/source/arch/arm64/kern_k_debug.cpp index 40f595854..970d98964 100644 --- a/libraries/libmesosphere/source/arch/arm64/kern_k_debug.cpp +++ b/libraries/libmesosphere/source/arch/arm64/kern_k_debug.cpp @@ -307,8 +307,14 @@ namespace ams::kern::arch::arm64 { R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); /* Get the process from the debug object. */ - process = debug->GetProcess(); - R_UNLESS(process.IsNotNull(), svc::ResultProcessTerminated()); + R_UNLESS(debug->IsAttached(), svc::ResultProcessTerminated()); + R_UNLESS(debug->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close the process when we're done. */ + ON_SCOPE_EXIT { debug->CloseProcess(); }; + + /* Get the proces. */ + KProcess * const process = debug->GetProcessUnsafe(); /* Set the value to be the context id. */ value = process->GetId() & 0xFFFFFFFF; diff --git a/libraries/libmesosphere/source/kern_k_debug_base.cpp b/libraries/libmesosphere/source/kern_k_debug_base.cpp index 544cde9ce..30a92c620 100644 --- a/libraries/libmesosphere/source/kern_k_debug_base.cpp +++ b/libraries/libmesosphere/source/kern_k_debug_base.cpp @@ -25,47 +25,103 @@ namespace ams::kern { } + void KDebugBase::ProcessHolder::Attach(KProcess *process) { + MESOSPHERE_ASSERT(m_process == nullptr); + + /* Set our process. */ + m_process = process; + + /* Open reference to our process. */ + m_process->Open(); + + /* Set our reference count. */ + m_ref_count = 1; + } + + void KDebugBase::ProcessHolder::Detach() { + /* Close our process, if we have one. */ + KProcess * const process = m_process; + if (AMS_LIKELY(process != nullptr)) { + /* Set our process to a debug sentinel value, which will cause crash if accessed. */ + m_process = reinterpret_cast(1); + + /* Close reference to our process. */ + process->Close(); + } + } + void KDebugBase::Initialize() { - /* Clear the process and continue flags. */ - m_process = nullptr; + /* Clear the continue flags. */ m_continue_flags = 0; } bool KDebugBase::Is64Bit() const { MESOSPHERE_ASSERT(m_lock.IsLockedByCurrentThread()); - MESOSPHERE_ASSERT(m_process != nullptr); - return m_process->Is64Bit(); + MESOSPHERE_ASSERT(m_is_attached); + + KProcess * const process = this->GetProcessUnsafe(); + MESOSPHERE_ASSERT(process != nullptr); + return process->Is64Bit(); } Result KDebugBase::QueryMemoryInfo(ams::svc::MemoryInfo *out_memory_info, ams::svc::PageInfo *out_page_info, KProcessAddress address) { + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + /* Lock ourselves. */ KScopedLightLock lk(m_lock); - /* Check that we have a valid process. */ - R_UNLESS(m_process != nullptr, svc::ResultProcessTerminated()); - R_UNLESS(!m_process->IsTerminated(), svc::ResultProcessTerminated()); + /* Check that we're still attached now that we're locked. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Get the process pointer. */ + KProcess * const process = this->GetProcessUnsafe(); + + /* Check that the process isn't terminated. */ + R_UNLESS(!process->IsTerminated(), svc::ResultProcessTerminated()); /* Query the mapping's info. */ KMemoryInfo info; - R_TRY(m_process->GetPageTable().QueryInfo(std::addressof(info), out_page_info, address)); + R_TRY(process->GetPageTable().QueryInfo(std::addressof(info), out_page_info, address)); /* Write output. */ *out_memory_info = info.GetSvcMemoryInfo(); + return ResultSuccess(); } Result KDebugBase::ReadMemory(KProcessAddress buffer, KProcessAddress address, size_t size) { + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + /* Lock ourselves. */ KScopedLightLock lk(m_lock); - /* Check that we have a valid process. */ - R_UNLESS(m_process != nullptr, svc::ResultProcessTerminated()); - R_UNLESS(!m_process->IsTerminated(), svc::ResultProcessTerminated()); + /* Check that we're still attached now that we're locked. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Get the process pointer. */ + KProcess * const process = this->GetProcessUnsafe(); + + /* Check that the process isn't terminated. */ + R_UNLESS(!process->IsTerminated(), svc::ResultProcessTerminated()); /* Get the page tables. */ KProcessPageTable &debugger_pt = GetCurrentProcess().GetPageTable(); - KProcessPageTable &target_pt = m_process->GetPageTable(); + KProcessPageTable &target_pt = process->GetPageTable(); /* Verify that the regions are in range. */ R_UNLESS(target_pt.Contains(address, size), svc::ResultInvalidCurrentMemory()); @@ -105,16 +161,30 @@ namespace ams::kern { } Result KDebugBase::WriteMemory(KProcessAddress buffer, KProcessAddress address, size_t size) { + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + /* Lock ourselves. */ KScopedLightLock lk(m_lock); - /* Check that we have a valid process. */ - R_UNLESS(m_process != nullptr, svc::ResultProcessTerminated()); - R_UNLESS(!m_process->IsTerminated(), svc::ResultProcessTerminated()); + /* Check that we're still attached now that we're locked. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Get the process pointer. */ + KProcess * const process = this->GetProcessUnsafe(); + + /* Check that the process isn't terminated. */ + R_UNLESS(!process->IsTerminated(), svc::ResultProcessTerminated()); /* Get the page tables. */ KProcessPageTable &debugger_pt = GetCurrentProcess().GetPageTable(); - KProcessPageTable &target_pt = m_process->GetPageTable(); + KProcessPageTable &target_pt = process->GetPageTable(); /* Verify that the regions are in range. */ R_UNLESS(target_pt.Contains(address, size), svc::ResultInvalidCurrentMemory()); @@ -154,9 +224,17 @@ namespace ams::kern { } Result KDebugBase::GetRunningThreadInfo(ams::svc::LastThreadContext *out_context, u64 *out_thread_id) { - /* Get the attached process. */ - KScopedAutoObject process = this->GetProcess(); - R_UNLESS(process.IsNotNull(), svc::ResultProcessTerminated()); + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + + /* Get the process pointer. */ + KProcess * const process = this->GetProcessUnsafe(); /* Get the thread info. */ { @@ -187,6 +265,9 @@ namespace ams::kern { /* Check that the process isn't null. */ MESOSPHERE_ASSERT(target != nullptr); + /* Clear ourselves as unattached. */ + m_is_attached = false; + /* Attach to the process. */ { /* Lock both ourselves, the target process, and the scheduler. */ @@ -216,20 +297,20 @@ namespace ams::kern { MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); } - /* Set our process member, and open a reference to the target. */ - m_process = target; - m_process->Open(); + /* Attach to the target. */ + m_process_holder.Attach(target); + m_is_attached = true; /* Set ourselves as the process's attached object. */ - m_old_process_state = m_process->SetDebugObject(this); + m_old_process_state = target->SetDebugObject(this); /* Send an event for our attaching to the process. */ this->PushDebugEvent(ams::svc::DebugEvent_CreateProcess); /* Send events for attaching to each thread in the process. */ { - auto end = m_process->GetThreadList().end(); - for (auto it = m_process->GetThreadList().begin(); it != end; ++it) { + auto end = target->GetThreadList().end(); + for (auto it = target->GetThreadList().begin(); it != end; ++it) { /* Request that we suspend the thread. */ it->RequestSuspend(KThread::SuspendType_Debug); @@ -245,7 +326,7 @@ namespace ams::kern { } /* Send the process's jit debug info, if relevant. */ - if (KEventInfo *jit_info = m_process->GetJitDebugInfo(); jit_info != nullptr) { + if (KEventInfo *jit_info = target->GetJitDebugInfo(); jit_info != nullptr) { this->EnqueueDebugEventInfo(jit_info); } @@ -261,9 +342,17 @@ namespace ams::kern { } Result KDebugBase::BreakProcess() { - /* Get the attached process. */ - KScopedAutoObject target = this->GetProcess(); - R_UNLESS(target.IsNotNull(), svc::ResultProcessTerminated()); + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + + /* Get the process pointer. */ + KProcess * const target = this->GetProcessUnsafe(); /* Lock both ourselves, the target process, and the scheduler. */ KScopedLightLock state_lk(target->GetStateLock()); @@ -271,10 +360,11 @@ namespace ams::kern { KScopedLightLock this_lk(m_lock); KScopedSchedulerLock sl; - /* Check that we're still attached to the process, and that it's not terminated. */ - /* NOTE: Here Nintendo only checks that this->process is not nullptr. */ - R_UNLESS(m_process == target.GetPointerUnsafe(), svc::ResultProcessTerminated()); - R_UNLESS(!target->IsTerminated(), svc::ResultProcessTerminated()); + /* Check that we're still attached now that we're locked. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Check that the process isn't terminated. */ + R_UNLESS(!target->IsTerminated(), svc::ResultProcessTerminated()); /* Get the currently active threads. */ constexpr u64 ThreadIdNoThread = -1ll; @@ -324,9 +414,17 @@ namespace ams::kern { } Result KDebugBase::TerminateProcess() { - /* Get the attached process. If we don't have one, we have nothing to do. */ - KScopedAutoObject target = this->GetProcess(); - R_SUCCEED_IF(target.IsNull()); + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), ResultSuccess()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), ResultSuccess()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + + /* Get the process pointer. */ + KProcess * const target = this->GetProcessUnsafe(); /* Detach from the process. */ { @@ -335,11 +433,8 @@ namespace ams::kern { KScopedLightLock list_lk(target->GetListLock()); KScopedLightLock this_lk(m_lock); - /* Check that we still have our process. */ - if (m_process != nullptr) { - /* Check that our process is the one we got earlier. */ - MESOSPHERE_ASSERT(m_process == target.GetPointerUnsafe()); - + /* Check that we're still attached. */ + if (this->IsAttached()) { /* Lock the scheduler. */ KScopedSchedulerLock sl; @@ -384,16 +479,16 @@ namespace ams::kern { /* Detach from the process. */ target->ClearDebugObject(new_state); - m_process = nullptr; + m_is_attached = false; + + /* Close the initial reference opened to our process. */ + this->CloseProcess(); /* Clear our continue flags. */ m_continue_flags = 0; } } - /* Close the reference we held to the process while we were attached to it. */ - target->Close(); - /* Terminate the process. */ target->Terminate(); @@ -401,16 +496,31 @@ namespace ams::kern { } Result KDebugBase::GetThreadContext(ams::svc::ThreadContext *out, u64 thread_id, u32 context_flags) { + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + /* Lock ourselves. */ KScopedLightLock lk(m_lock); + /* Check that we're still attached now that we're locked. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Get the process pointer. */ + KProcess * const process = this->GetProcessUnsafe(); + /* Get the thread from its id. */ KThread *thread = KThread::GetThreadFromId(thread_id); R_UNLESS(thread != nullptr, svc::ResultInvalidId()); ON_SCOPE_EXIT { thread->Close(); }; /* Verify that the thread is owned by our process. */ - R_UNLESS(m_process == thread->GetOwnerProcess(), svc::ResultInvalidId()); + R_UNLESS(process == thread->GetOwnerProcess(), svc::ResultInvalidId()); /* Verify that the thread isn't terminated. */ R_UNLESS(thread->GetState() != KThread::ThreadState_Terminated, svc::ResultTerminationRequested()); @@ -448,16 +558,31 @@ namespace ams::kern { } Result KDebugBase::SetThreadContext(const ams::svc::ThreadContext &ctx, u64 thread_id, u32 context_flags) { + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + /* Lock ourselves. */ KScopedLightLock lk(m_lock); + /* Check that we're still attached now that we're locked. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Get the process pointer. */ + KProcess * const process = this->GetProcessUnsafe(); + /* Get the thread from its id. */ KThread *thread = KThread::GetThreadFromId(thread_id); R_UNLESS(thread != nullptr, svc::ResultInvalidId()); ON_SCOPE_EXIT { thread->Close(); }; /* Verify that the thread is owned by our process. */ - R_UNLESS(m_process == thread->GetOwnerProcess(), svc::ResultInvalidId()); + R_UNLESS(process == thread->GetOwnerProcess(), svc::ResultInvalidId()); /* Verify that the thread isn't terminated. */ R_UNLESS(thread->GetState() != KThread::ThreadState_Terminated, svc::ResultTerminationRequested()); @@ -525,9 +650,17 @@ namespace ams::kern { Result KDebugBase::ContinueDebug(const u32 flags, const u64 *thread_ids, size_t num_thread_ids) { - /* Get the attached process. */ - KScopedAutoObject target = this->GetProcess(); - R_UNLESS(target.IsNotNull(), svc::ResultProcessTerminated()); + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + + /* Get the process pointer. */ + KProcess * const target = this->GetProcessUnsafe(); /* Lock both ourselves, the target process, and the scheduler. */ KScopedLightLock state_lk(target->GetStateLock()); @@ -535,9 +668,11 @@ namespace ams::kern { KScopedLightLock this_lk(m_lock); KScopedSchedulerLock sl; - /* Check that we're still attached to the process, and that it's not terminated. */ - R_UNLESS(m_process == target.GetPointerUnsafe(), svc::ResultProcessTerminated()); - R_UNLESS(!target->IsTerminated(), svc::ResultProcessTerminated()); + /* Check that we're still attached now that we're locked. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Check that the process isn't terminated. */ + R_UNLESS(!target->IsTerminated(), svc::ResultProcessTerminated()); /* Check that we have no pending events. */ R_UNLESS(m_event_info_list.empty(), svc::ResultBusy()); @@ -747,18 +882,19 @@ namespace ams::kern { } - KScopedAutoObject KDebugBase::GetProcess() { - /* Lock ourselves. */ - KScopedLightLock lk(m_lock); - - return m_process; - } - template requires (std::same_as || std::same_as) Result KDebugBase::GetDebugEventInfoImpl(T *out) { - /* Get the attached process. */ - KScopedAutoObject process = this->GetProcess(); - R_UNLESS(process.IsNotNull(), svc::ResultProcessTerminated()); + /* Check that we're attached. */ + R_UNLESS(this->IsAttached(), svc::ResultProcessTerminated()); + + /* Open a reference to our process. */ + R_UNLESS(this->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close our reference to our process when we're done. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; + + /* Get the process pointer. */ + KProcess * const process = this->GetProcessUnsafe(); /* Pop an event info from our queue. */ KEventInfo *info = nullptr; @@ -875,56 +1011,50 @@ namespace ams::kern { void KDebugBase::OnFinalizeSynchronizationObject() { /* Detach from our process, if we have one. */ - { - /* Get the attached process. */ - KScopedAutoObject process = this->GetProcess(); + if (this->IsAttached() && this->OpenProcess()) { + /* Close the process when we're done with it. */ + ON_SCOPE_EXIT { this->CloseProcess(); }; - /* If the process isn't null, detach. */ - if (process.IsNotNull()) { - /* Detach. */ + /* Get the process pointer. */ + KProcess * const process = this->GetProcessUnsafe(); + + /* Lock both ourselves and the target process. */ + KScopedLightLock state_lk(process->GetStateLock()); + KScopedLightLock list_lk(process->GetListLock()); + KScopedLightLock this_lk(m_lock); + + /* Check that we're still attached. */ + if (m_is_attached) { + KScopedSchedulerLock sl; + + /* Detach ourselves from the process. */ + process->ClearDebugObject(m_old_process_state); + + /* Release all threads. */ + const bool resume = (process->GetState() != KProcess::State_Crashed); { - /* Lock both ourselves and the target process. */ - KScopedLightLock state_lk(process->GetStateLock()); - KScopedLightLock list_lk(process->GetListLock()); - KScopedLightLock this_lk(m_lock); + auto end = process->GetThreadList().end(); + for (auto it = process->GetThreadList().begin(); it != end; ++it) { + #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) + /* Clear the thread's single-step state. */ + it->ClearSingleStep(); + #endif - /* Ensure we finalize exactly once. */ - if (m_process != nullptr) { - MESOSPHERE_ASSERT(m_process == process.GetPointerUnsafe()); - { - KScopedSchedulerLock sl; - - /* Detach ourselves from the process. */ - process->ClearDebugObject(m_old_process_state); - - /* Release all threads. */ - const bool resume = (process->GetState() != KProcess::State_Crashed); - { - auto end = process->GetThreadList().end(); - for (auto it = process->GetThreadList().begin(); it != end; ++it) { - #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) - /* Clear the thread's single-step state. */ - it->ClearSingleStep(); - #endif - - if (resume) { - /* If the process isn't crashed, resume threads. */ - it->Resume(KThread::SuspendType_Debug); - } else { - /* Otherwise, suspend them. */ - it->RequestSuspend(KThread::SuspendType_Debug); - } - } - } - - /* Clear our process. */ - m_process = nullptr; + if (resume) { + /* If the process isn't crashed, resume threads. */ + it->Resume(KThread::SuspendType_Debug); + } else { + /* Otherwise, suspend them. */ + it->RequestSuspend(KThread::SuspendType_Debug); } - - /* We're done detaching, so clear the reference we opened when we attached. */ - process->Close(); } } + + /* Note we're now unattached. */ + m_is_attached = false; + + /* Close the initial reference opened to our process. */ + this->CloseProcess(); } } @@ -941,9 +1071,14 @@ namespace ams::kern { } bool KDebugBase::IsSignaled() const { - KScopedSchedulerLock sl; + bool empty; + { + KScopedSchedulerLock sl; - return (!m_event_info_list.empty()) || m_process == nullptr || m_process->IsTerminated(); + empty = m_event_info_list.empty(); + } + + return !empty || !m_is_attached || this->GetProcessUnsafe()->IsTerminated(); } Result KDebugBase::ProcessDebugEvent(ams::svc::DebugEvent event, uintptr_t param0, uintptr_t param1, uintptr_t param2, uintptr_t param3, uintptr_t param4) { diff --git a/libraries/libmesosphere/source/kern_k_debug_base_process_holder.cpp b/libraries/libmesosphere/source/kern_k_debug_base_process_holder.cpp new file mode 100644 index 000000000..996fb8668 --- /dev/null +++ b/libraries/libmesosphere/source/kern_k_debug_base_process_holder.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include + +namespace ams::kern { + + NOINLINE bool KDebugBase::ProcessHolder::Open() { + /* Atomically increment the reference count, only if it's positive. */ + u32 cur_ref_count = m_ref_count.load(std::memory_order_relaxed); + do { + if (AMS_UNLIKELY(cur_ref_count == 0)) { + MESOSPHERE_AUDIT(cur_ref_count != 0); + return false; + } + MESOSPHERE_ABORT_UNLESS(cur_ref_count < cur_ref_count + 1); + } while (!m_ref_count.compare_exchange_weak(cur_ref_count, cur_ref_count + 1, std::memory_order_relaxed)); + + return true; + } + + NOINLINE void KDebugBase::ProcessHolder::Close() { + /* Atomically decrement the reference count, not allowing it to become negative. */ + u32 cur_ref_count = m_ref_count.load(std::memory_order_relaxed); + do { + MESOSPHERE_ABORT_UNLESS(cur_ref_count > 0); + } while (!m_ref_count.compare_exchange_weak(cur_ref_count, cur_ref_count - 1, std::memory_order_relaxed)); + + /* If ref count hits zero, schedule the object for destruction. */ + if (cur_ref_count - 1 == 0) { + this->Detach(); + } + } + +} diff --git a/libraries/libmesosphere/source/svc/kern_svc_debug.cpp b/libraries/libmesosphere/source/svc/kern_svc_debug.cpp index 96844f7ce..e52dbe306 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_debug.cpp +++ b/libraries/libmesosphere/source/svc/kern_svc_debug.cpp @@ -313,11 +313,17 @@ namespace ams::kern::svc { ON_SCOPE_EXIT { thread->Close(); }; /* Get the process from the debug object. */ - KScopedAutoObject process = debug->GetProcess(); - R_UNLESS(process.IsNotNull(), svc::ResultProcessTerminated()); + R_UNLESS(debug->IsAttached(), svc::ResultProcessTerminated()); + R_UNLESS(debug->OpenProcess(), svc::ResultProcessTerminated()); + + /* Close the process when we're done. */ + ON_SCOPE_EXIT { debug->CloseProcess(); }; + + /* Get the proces. */ + KProcess * const process = debug->GetProcessUnsafe(); /* Verify that the process is the thread's parent. */ - R_UNLESS(process.GetPointerUnsafe() == thread->GetOwnerProcess(), svc::ResultInvalidThreadId()); + R_UNLESS(process == thread->GetOwnerProcess(), svc::ResultInvalidThreadId()); /* Get the parameter. */ switch (param) { diff --git a/libraries/libmesosphere/source/svc/kern_svc_process.cpp b/libraries/libmesosphere/source/svc/kern_svc_process.cpp index 6d12f2bfd..3bc04c2f9 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_process.cpp +++ b/libraries/libmesosphere/source/svc/kern_svc_process.cpp @@ -36,27 +36,35 @@ namespace ams::kern::svc { R_UNLESS(obj.IsNotNull(), svc::ResultInvalidHandle()); /* Get the process from the object. */ - KProcess *process = nullptr; - if (KProcess *p = obj->DynamicCast(); p != nullptr) { + if (KProcess *process = obj->DynamicCast(); process != nullptr) { /* The object is a process, so we can use it directly. */ - process = p; + + /* Make sure the target process exists. */ + R_UNLESS(process != nullptr, svc::ResultInvalidHandle()); + + /* Get the process id. */ + *out_process_id = process->GetId(); } else if (KThread *t = obj->DynamicCast(); t != nullptr) { /* The object is a thread, so we want to use its parent. */ - process = reinterpret_cast(obj.GetPointerUnsafe())->GetOwnerProcess(); + KProcess *process = t->GetOwnerProcess(); + + /* Make sure the target process exists. */ + R_UNLESS(process != nullptr, svc::ResultInvalidHandle()); + + /* Get the process id. */ + *out_process_id = process->GetId(); } else if (KDebug *d = obj->DynamicCast(); d != nullptr) { /* The object is a debug, so we want to use the process it's attached to. */ - obj = d->GetProcess(); - if (obj.IsNotNull()) { - process = static_cast(obj.GetPointerUnsafe()); - } + /* Make sure the target process exists. */ + R_UNLESS(d->IsAttached(), svc::ResultInvalidHandle()); + R_UNLESS(d->OpenProcess(), svc::ResultInvalidHandle()); + ON_SCOPE_EXIT { d->CloseProcess(); }; + + /* Get the process id. */ + *out_process_id = d->GetProcessUnsafe()->GetProcessId(); } - /* Make sure the target process exists. */ - R_UNLESS(process != nullptr, svc::ResultInvalidHandle()); - - /* Get the process id. */ - *out_process_id = process->GetId(); return ResultSuccess(); } diff --git a/libraries/libmesosphere/source/svc/kern_svc_thread.cpp b/libraries/libmesosphere/source/svc/kern_svc_thread.cpp index e8277a22b..edd3e7ae0 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_thread.cpp +++ b/libraries/libmesosphere/source/svc/kern_svc_thread.cpp @@ -235,12 +235,13 @@ namespace ams::kern::svc { /* Try to get as a debug object. */ KScopedAutoObject debug = handle_table.GetObject(debug_handle); if (debug.IsNotNull()) { - /* Get the debug object's process. */ - KScopedAutoObject process = debug->GetProcess(); - R_UNLESS(process.IsNotNull(), svc::ResultProcessTerminated()); + /* Check that the debug object has a process. */ + R_UNLESS(debug->IsAttached(), svc::ResultProcessTerminated()); + R_UNLESS(debug->OpenProcess(), svc::ResultProcessTerminated()); + ON_SCOPE_EXIT { debug->CloseProcess(); }; /* Get the thread list. */ - R_TRY(process->GetThreadList(out_num_threads, out_thread_ids, max_out_count)); + R_TRY(debug->GetProcessUnsafe()->GetThreadList(out_num_threads, out_thread_ids, max_out_count)); } else { /* Try to get as a process. */ KScopedAutoObject process = handle_table.GetObjectWithoutPseudoHandle(debug_handle);