From 49f2449073c67d6a2f37d5b67c34b149dea2b6c2 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Wed, 9 Oct 2024 16:50:20 -0700 Subject: [PATCH] kern/strat: update for new DebugFlags capability semantics --- .../arch/arm64/kern_k_process_page_table.hpp | 4 +- .../mesosphere/kern_k_capabilities.hpp | 11 ++- .../include/mesosphere/kern_k_debug_base.hpp | 5 ++ .../mesosphere/kern_k_page_table_base.hpp | 4 +- .../include/mesosphere/kern_k_process.hpp | 4 + libmesosphere/source/kern_k_capabilities.cpp | 7 ++ libmesosphere/source/kern_k_debug_base.cpp | 81 ++++--------------- .../source/kern_k_page_table_base.cpp | 38 ++++++--- libmesosphere/source/svc/kern_svc_debug.cpp | 23 +++++- libmesosphere/source/svc/kern_svc_process.cpp | 3 + libmesosphere/source/svc/kern_svc_thread.cpp | 47 ++++++----- 11 files changed, 122 insertions(+), 105 deletions(-) diff --git a/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp b/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp index 3ad3c96a..e18a6fbc 100644 --- a/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp +++ b/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp @@ -154,8 +154,8 @@ namespace ams::kern::arch::arm64 { R_RETURN(m_page_table.InvalidateCurrentProcessDataCache(address, size)); } - Result ReadDebugMemory(void *buffer, KProcessAddress address, size_t size) { - R_RETURN(m_page_table.ReadDebugMemory(buffer, address, size)); + Result ReadDebugMemory(void *buffer, KProcessAddress address, size_t size, bool force_debug_prod) { + R_RETURN(m_page_table.ReadDebugMemory(buffer, address, size, force_debug_prod)); } Result ReadDebugIoMemory(void *buffer, KProcessAddress address, size_t size, KMemoryState state) { diff --git a/libmesosphere/include/mesosphere/kern_k_capabilities.hpp b/libmesosphere/include/mesosphere/kern_k_capabilities.hpp index 7c2f5dbc..2c74b835 100644 --- a/libmesosphere/include/mesosphere/kern_k_capabilities.hpp +++ b/libmesosphere/include/mesosphere/kern_k_capabilities.hpp @@ -168,9 +168,10 @@ namespace ams::kern { struct DebugFlags { using IdBits = Field<0, CapabilityId + 1>; - DEFINE_FIELD(AllowDebug, IdBits, 1, bool); - DEFINE_FIELD(ForceDebug, AllowDebug, 1, bool); - DEFINE_FIELD(Reserved, ForceDebug, 13); + DEFINE_FIELD(AllowDebug, IdBits, 1, bool); + DEFINE_FIELD(ForceDebugProd, AllowDebug, 1, bool); + DEFINE_FIELD(ForceDebug, ForceDebugProd, 1, bool); + DEFINE_FIELD(Reserved, ForceDebug, 12); }; #undef DEFINE_FIELD @@ -255,6 +256,10 @@ namespace ams::kern { return m_debug_capabilities.Get(); } + constexpr bool CanForceDebugProd() const { + return m_debug_capabilities.Get(); + } + constexpr bool CanForceDebug() const { return m_debug_capabilities.Get(); } diff --git a/libmesosphere/include/mesosphere/kern_k_debug_base.hpp b/libmesosphere/include/mesosphere/kern_k_debug_base.hpp index 6c8fe54f..6978b915 100644 --- a/libmesosphere/include/mesosphere/kern_k_debug_base.hpp +++ b/libmesosphere/include/mesosphere/kern_k_debug_base.hpp @@ -32,6 +32,7 @@ namespace ams::kern { KLightLock m_lock; KProcess::State m_old_process_state; bool m_is_attached; + bool m_is_force_debug_prod; public: explicit KDebugBase() { /* ... */ } protected: @@ -62,6 +63,10 @@ namespace ams::kern { return m_is_attached; } + ALWAYS_INLINE bool IsForceDebugProd() const { + return m_is_force_debug_prod; + } + ALWAYS_INLINE bool OpenProcess() { return m_process_holder.Open(); } diff --git a/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp b/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp index 8da4311d..7b2c722e 100644 --- a/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp +++ b/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp @@ -328,6 +328,8 @@ namespace ams::kern { R_RETURN(this->CheckMemoryState(nullptr, addr, size, state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr)); } + bool CanReadWriteDebugMemory(KProcessAddress addr, size_t size, bool force_debug_prod); + Result LockMemoryAndOpen(KPageGroup *out_pg, KPhysicalAddress *out_paddr, KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, KMemoryPermission new_perm, u32 lock_attr); Result UnlockMemory(KProcessAddress addr, size_t size, u32 state_mask, u32 state, u32 perm_mask, u32 perm, u32 attr_mask, u32 attr, KMemoryPermission new_perm, u32 lock_attr, const KPageGroup *pg); @@ -421,7 +423,7 @@ namespace ams::kern { Result InvalidateProcessDataCache(KProcessAddress address, size_t size); Result InvalidateCurrentProcessDataCache(KProcessAddress address, size_t size); - Result ReadDebugMemory(void *buffer, KProcessAddress address, size_t size); + Result ReadDebugMemory(void *buffer, KProcessAddress address, size_t size, bool force_debug_prod); Result ReadDebugIoMemory(void *buffer, KProcessAddress address, size_t size, KMemoryState state); Result WriteDebugMemory(KProcessAddress address, const void *buffer, size_t size); diff --git a/libmesosphere/include/mesosphere/kern_k_process.hpp b/libmesosphere/include/mesosphere/kern_k_process.hpp index c4ab7f2a..849f73da 100644 --- a/libmesosphere/include/mesosphere/kern_k_process.hpp +++ b/libmesosphere/include/mesosphere/kern_k_process.hpp @@ -206,6 +206,10 @@ namespace ams::kern { return m_capabilities.IsPermittedDebug(); } + constexpr bool CanForceDebugProd() const { + return m_capabilities.CanForceDebugProd(); + } + constexpr bool CanForceDebug() const { return m_capabilities.CanForceDebug(); } diff --git a/libmesosphere/source/kern_k_capabilities.cpp b/libmesosphere/source/kern_k_capabilities.cpp index feb245ed..ba035926 100644 --- a/libmesosphere/source/kern_k_capabilities.cpp +++ b/libmesosphere/source/kern_k_capabilities.cpp @@ -262,7 +262,14 @@ namespace ams::kern { /* Validate. */ R_UNLESS(cap.Get() == 0, svc::ResultReservedUsed()); + u32 total = 0; + if (cap.Get()) { ++total; } + if (cap.Get()) { ++total; } + if (cap.Get()) { ++total; } + R_UNLESS(total <= 1, svc::ResultInvalidCombination()); + m_debug_capabilities.Set(cap.Get()); + m_debug_capabilities.Set(cap.Get()); m_debug_capabilities.Set(cap.Get()); R_SUCCEED(); } diff --git a/libmesosphere/source/kern_k_debug_base.cpp b/libmesosphere/source/kern_k_debug_base.cpp index 418d804f..635673cd 100644 --- a/libmesosphere/source/kern_k_debug_base.cpp +++ b/libmesosphere/source/kern_k_debug_base.cpp @@ -27,7 +27,8 @@ namespace ams::kern { void KDebugBase::Initialize() { /* Clear the continue flags. */ - m_continue_flags = 0; + m_continue_flags = 0; + m_is_force_debug_prod = GetCurrentProcess().CanForceDebugProd(); } bool KDebugBase::Is64Bit() const { @@ -120,8 +121,11 @@ namespace ams::kern { /* Read the memory. */ if (info.GetSvcState() != ams::svc::MemoryState_Io) { /* The memory is normal memory. */ - R_TRY(target_pt.ReadDebugMemory(GetVoidPointer(buffer), cur_address, cur_size)); + R_TRY(target_pt.ReadDebugMemory(GetVoidPointer(buffer), cur_address, cur_size, this->IsForceDebugProd())); } else { + /* Only allow IO memory to be read if not force debug prod. */ + R_UNLESS(!this->IsForceDebugProd(), svc::ResultInvalidCurrentMemory()); + /* The memory is IO memory. */ R_TRY(target_pt.ReadDebugIoMemory(GetVoidPointer(buffer), cur_address, cur_size, info.GetState())); } @@ -269,6 +273,9 @@ namespace ams::kern { switch (state) { case KProcess::State_Created: case KProcess::State_Running: + /* Created and running processes can only be debugged if the debugger is not ForceDebugProd. */ + R_UNLESS(!this->IsForceDebugProd(), svc::ResultInvalidState()); + break; case KProcess::State_Crashed: break; case KProcess::State_CreatedAttached: @@ -408,69 +415,6 @@ namespace ams::kern { /* Get the process pointer. */ KProcess * const target = this->GetProcessUnsafe(); - /* Detach from the process. */ - { - /* Lock both ourselves and the target process. */ - KScopedLightLock state_lk(target->GetStateLock()); - KScopedLightLock list_lk(target->GetListLock()); - KScopedLightLock this_lk(m_lock); - - /* Check that we're still attached. */ - if (this->IsAttached()) { - /* Lock the scheduler. */ - KScopedSchedulerLock sl; - - /* Get the process's state. */ - const KProcess::State state = target->GetState(); - - /* Check that the process is in a state where we can terminate it. */ - R_UNLESS(state != KProcess::State_Created, svc::ResultInvalidState()); - R_UNLESS(state != KProcess::State_CreatedAttached, svc::ResultInvalidState()); - - /* Decide on a new state for the process. */ - KProcess::State new_state; - if (state == KProcess::State_RunningAttached) { - /* If the process is running, transition it accordingly. */ - new_state = KProcess::State_Running; - } else if (state == KProcess::State_DebugBreak) { - /* If the process is debug breaked, transition it accordingly. */ - new_state = KProcess::State_Crashed; - - /* Suspend all the threads in the process. */ - { - 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); - } - } - } else { - /* Otherwise, don't transition. */ - new_state = state; - } - - #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) - /* Clear single step on all threads. */ - { - auto end = target->GetThreadList().end(); - for (auto it = target->GetThreadList().begin(); it != end; ++it) { - it->ClearHardwareSingleStep(); - } - } - #endif - - /* Detach from the process. */ - target->ClearDebugObject(new_state); - m_is_attached = false; - - /* Close the initial reference opened to our process. */ - this->CloseProcess(); - - /* Clear our continue flags. */ - m_continue_flags = 0; - } - } - /* Terminate the process. */ target->Terminate(); @@ -962,7 +906,12 @@ namespace ams::kern { case ams::svc::DebugException_UndefinedInstruction: { MESOSPHERE_ASSERT(info->info.exception.exception_data_count == 1); - out->info.exception.specific.undefined_instruction.insn = info->info.exception.exception_data[0]; + /* Only save the instruction if the caller is not force debug prod. */ + if (this->IsForceDebugProd()) { + out->info.exception.specific.undefined_instruction.insn = 0; + } else { + out->info.exception.specific.undefined_instruction.insn = info->info.exception.exception_data[0]; + } } break; case ams::svc::DebugException_BreakPoint: diff --git a/libmesosphere/source/kern_k_page_table_base.cpp b/libmesosphere/source/kern_k_page_table_base.cpp index 95bc983c..2a9402ec 100644 --- a/libmesosphere/source/kern_k_page_table_base.cpp +++ b/libmesosphere/source/kern_k_page_table_base.cpp @@ -2695,18 +2695,37 @@ namespace ams::kern { R_RETURN(cpu::InvalidateDataCache(GetVoidPointer(address), size)); } - Result KPageTableBase::ReadDebugMemory(void *buffer, KProcessAddress address, size_t size) { + bool KPageTableBase::CanReadWriteDebugMemory(KProcessAddress address, size_t size, bool force_debug_prod) { + /* Check pre-conditions. */ + MESOSPHERE_ASSERT(this->IsLockedByCurrentThread()); + + /* If the memory is debuggable and user-readable, we can perform the access. */ + if (R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_FlagCanDebug, KMemoryState_FlagCanDebug, KMemoryPermission_NotMapped | KMemoryPermission_UserRead, KMemoryPermission_UserRead, KMemoryAttribute_None, KMemoryAttribute_None))) { + return true; + } + + /* If we're in debug mode, and the process isn't force debug prod, check if the memory is debuggable and kernel-readable and user-executable. */ + if (KTargetSystem::IsDebugMode() && !force_debug_prod) { + if (R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_FlagCanDebug, KMemoryState_FlagCanDebug, KMemoryPermission_KernelRead | KMemoryPermission_UserExecute, KMemoryPermission_KernelRead | KMemoryPermission_UserExecute, KMemoryAttribute_None, KMemoryAttribute_None))) { + return true; + } + } + + /* If neither of the above checks passed, we can't access the memory. */ + return false; + } + + Result KPageTableBase::ReadDebugMemory(void *buffer, KProcessAddress address, size_t size, bool force_debug_prod) { /* Lightly validate the region is in range. */ R_UNLESS(this->Contains(address, size), svc::ResultInvalidCurrentMemory()); /* Lock the table. */ KScopedLightLock lk(m_general_lock); - /* Require that the memory either be user readable or debuggable. */ - const bool can_read = R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_None, KMemoryState_None, KMemoryPermission_UserRead, KMemoryPermission_UserRead, KMemoryAttribute_None, KMemoryAttribute_None)); + /* Require that the memory either be user-readable-and-mapped or debug-accessible. */ + const bool can_read = R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_None, KMemoryState_None, KMemoryPermission_NotMapped | KMemoryPermission_UserRead, KMemoryPermission_UserRead, KMemoryAttribute_None, KMemoryAttribute_None)); if (!can_read) { - const bool can_debug = R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_FlagCanDebug, KMemoryState_FlagCanDebug, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None)); - R_UNLESS(can_debug, svc::ResultInvalidCurrentMemory()); + R_UNLESS(this->CanReadWriteDebugMemory(address, size, force_debug_prod), svc::ResultInvalidCurrentMemory()); } /* Get the impl. */ @@ -2788,11 +2807,10 @@ namespace ams::kern { /* Lock the table. */ KScopedLightLock lk(m_general_lock); - /* Require that the memory either be user writable or debuggable. */ - const bool can_read = R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_None, KMemoryState_None, KMemoryPermission_UserReadWrite, KMemoryPermission_UserReadWrite, KMemoryAttribute_None, KMemoryAttribute_None)); - if (!can_read) { - const bool can_debug = R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_FlagCanDebug, KMemoryState_FlagCanDebug, KMemoryPermission_None, KMemoryPermission_None, KMemoryAttribute_None, KMemoryAttribute_None)); - R_UNLESS(can_debug, svc::ResultInvalidCurrentMemory()); + /* Require that the memory either be user-writable-and-mapped or debug-accessible. */ + const bool can_write = R_SUCCEEDED(this->CheckMemoryStateContiguous(address, size, KMemoryState_None, KMemoryState_None, KMemoryPermission_NotMapped | KMemoryPermission_UserReadWrite, KMemoryPermission_UserReadWrite, KMemoryAttribute_None, KMemoryAttribute_None)); + if (!can_write) { + R_UNLESS(this->CanReadWriteDebugMemory(address, size, false), svc::ResultInvalidCurrentMemory()); } /* Get the impl. */ diff --git a/libmesosphere/source/svc/kern_svc_debug.cpp b/libmesosphere/source/svc/kern_svc_debug.cpp index 36988535..986654ce 100644 --- a/libmesosphere/source/svc/kern_svc_debug.cpp +++ b/libmesosphere/source/svc/kern_svc_debug.cpp @@ -24,6 +24,9 @@ namespace ams::kern::svc { constexpr inline int32_t MaximumDebuggableThreadCount = 0x60; Result DebugActiveProcess(ams::svc::Handle *out_handle, uint64_t process_id) { + /* Check that the SVC can be used. */ + R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented()); + /* Get the process from its id. */ KProcess *process = KProcess::GetProcessFromId(process_id); R_UNLESS(process != nullptr, svc::ResultInvalidProcessId()); @@ -32,9 +35,8 @@ namespace ams::kern::svc { ON_SCOPE_EXIT { process->Close(); }; /* Check that the debugging is allowed. */ - if (!process->IsPermittedDebug()) { - R_UNLESS(GetCurrentProcess().CanForceDebug(), svc::ResultInvalidState()); - } + const bool allowable = process->IsPermittedDebug() || GetCurrentProcess().CanForceDebug() || GetCurrentProcess().CanForceDebugProd(); + R_UNLESS(allowable, svc::ResultInvalidState()); /* Disallow debugging one's own processs, to prevent softlocks. */ R_UNLESS(process != GetCurrentProcessPointer(), svc::ResultInvalidState()); @@ -92,6 +94,9 @@ namespace ams::kern::svc { template Result GetDebugEvent(KUserPointer out_info, ams::svc::Handle debug_handle) { + /* Only allow invoking the svc on development hardware or if force debug prod. */ + R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented()); + /* Get the debug object. */ KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject(debug_handle); R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); @@ -164,6 +169,9 @@ namespace ams::kern::svc { } Result GetDebugThreadContext(KUserPointer out_context, ams::svc::Handle debug_handle, uint64_t thread_id, uint32_t context_flags) { + /* Only allow invoking the svc on development hardware or if force debug prod. */ + R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented()); + /* Validate the context flags. */ R_UNLESS((context_flags | ams::svc::ThreadContextFlag_All) == ams::svc::ThreadContextFlag_All, svc::ResultInvalidEnumValue()); @@ -220,6 +228,9 @@ namespace ams::kern::svc { } Result QueryDebugProcessMemory(ams::svc::MemoryInfo *out_memory_info, ams::svc::PageInfo *out_page_info, ams::svc::Handle debug_handle, uintptr_t address) { + /* Only allow invoking the svc on development hardware or if force debug prod. */ + R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented()); + /* Get the debug object. */ KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject(debug_handle); R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); @@ -261,6 +272,9 @@ namespace ams::kern::svc { } Result ReadDebugProcessMemory(uintptr_t buffer, ams::svc::Handle debug_handle, uintptr_t address, size_t size) { + /* Only allow invoking the svc on development hardware or if force debug prod. */ + R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented()); + /* Validate address / size. */ R_UNLESS(size > 0, svc::ResultInvalidSize()); R_UNLESS((address < address + size), svc::ResultInvalidCurrentMemory()); @@ -306,6 +320,9 @@ namespace ams::kern::svc { } Result GetDebugThreadParam(uint64_t *out_64, uint32_t *out_32, ams::svc::Handle debug_handle, uint64_t thread_id, ams::svc::DebugThreadParam param) { + /* Only allow invoking the svc on development hardware or if force debug prod. */ + R_UNLESS(KTargetSystem::IsDebugMode() || GetCurrentProcess().CanForceDebugProd(), svc::ResultNotImplemented()); + /* Get the debug object. */ KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject(debug_handle); R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); diff --git a/libmesosphere/source/svc/kern_svc_process.cpp b/libmesosphere/source/svc/kern_svc_process.cpp index 5141a2c3..98c8740f 100644 --- a/libmesosphere/source/svc/kern_svc_process.cpp +++ b/libmesosphere/source/svc/kern_svc_process.cpp @@ -71,6 +71,9 @@ namespace ams::kern::svc { } Result GetProcessList(int32_t *out_num_processes, KUserPointer out_process_ids, int32_t max_out_count) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented()); + /* Validate that the out count is valid. */ R_UNLESS((0 <= max_out_count && max_out_count <= static_cast(std::numeric_limits::max() / sizeof(u64))), svc::ResultOutOfRange()); diff --git a/libmesosphere/source/svc/kern_svc_thread.cpp b/libmesosphere/source/svc/kern_svc_thread.cpp index 99c8c37a..6f74dd14 100644 --- a/libmesosphere/source/svc/kern_svc_thread.cpp +++ b/libmesosphere/source/svc/kern_svc_thread.cpp @@ -217,6 +217,9 @@ namespace ams::kern::svc { } Result GetThreadList(int32_t *out_num_threads, KUserPointer out_thread_ids, int32_t max_out_count, ams::svc::Handle debug_handle) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented()); + /* Validate that the out count is valid. */ R_UNLESS((0 <= max_out_count && max_out_count <= static_cast(std::numeric_limits::max() / sizeof(u64))), svc::ResultOutOfRange()); @@ -225,30 +228,34 @@ namespace ams::kern::svc { R_UNLESS(GetCurrentProcess().GetPageTable().Contains(KProcessAddress(out_thread_ids.GetUnsafePointer()), max_out_count * sizeof(u64)), svc::ResultInvalidCurrentMemory()); } - if (debug_handle == ams::svc::InvalidHandle) { - /* If passed invalid handle, we should return the global thread list. */ - R_TRY(KThread::GetThreadList(out_num_threads, out_thread_ids, max_out_count)); + /* Get the handle table. */ + auto &handle_table = GetCurrentProcess().GetHandleTable(); + + /* Try to get as a debug object. */ + KScopedAutoObject debug = handle_table.GetObject(debug_handle); + if (debug.IsNotNull()) { + /* 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(debug->GetProcessUnsafe()->GetThreadList(out_num_threads, out_thread_ids, max_out_count)); } else { - /* Get the handle table. */ - auto &handle_table = GetCurrentProcess().GetHandleTable(); - - /* Try to get as a debug object. */ - KScopedAutoObject debug = handle_table.GetObject(debug_handle); - if (debug.IsNotNull()) { - /* 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(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); - R_UNLESS(process.IsNotNull(), svc::ResultInvalidHandle()); + /* Only allow getting as a process (or global) if the caller does not have ForceDebugProd. */ + R_UNLESS(!GetCurrentProcess().CanForceDebugProd(), svc::ResultInvalidHandle()); + /* Try to get as a process. */ + KScopedAutoObject process = handle_table.GetObjectWithoutPseudoHandle(debug_handle); + if (process.IsNotNull()) { /* Get the thread list. */ R_TRY(process->GetThreadList(out_num_threads, out_thread_ids, max_out_count)); + } else { + /* If the object is not a process, the caller may want the global thread list. */ + R_UNLESS(debug_handle == ams::svc::InvalidHandle, svc::ResultInvalidHandle()); + + /* If passed invalid handle, we should return the global thread list. */ + R_TRY(KThread::GetThreadList(out_num_threads, out_thread_ids, max_out_count)); } }