From 2855b8ee355057a2fd42ff3e64c4ea79abf4bdc3 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 --- config_templates/exosphere.ini | 2 +- .../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 + .../source/kern_k_capabilities.cpp | 7 ++ .../source/kern_k_debug_base.cpp | 81 ++++--------------- .../source/kern_k_page_table_base.cpp | 38 ++++++--- .../source/svc/kern_svc_debug.cpp | 23 +++++- .../source/svc/kern_svc_process.cpp | 3 + .../source/svc/kern_svc_thread.cpp | 47 ++++++----- stratosphere/creport/creport.json | 1 + stratosphere/dmnt.gen2/dmnt.gen2.json | 1 + stratosphere/fatal/fatal.json | 1 + .../loader/source/ldr_capabilities.cpp | 19 ++++- stratosphere/pm/pm.json | 1 + 17 files changed, 142 insertions(+), 110 deletions(-) diff --git a/config_templates/exosphere.ini b/config_templates/exosphere.ini index e8dc82377..8df772ccf 100644 --- a/config_templates/exosphere.ini +++ b/config_templates/exosphere.ini @@ -1,6 +1,6 @@ # Key: debugmode, default: 1. # Desc: Controls whether kernel is debug mode. -# Disabling this may break Atmosphere's debugger in a future release. +# Disabling this will break Atmosphere. # Key: debugmode_user, default: 0. # Desc: Controls whether userland is debug mode. diff --git a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp index 3ad3c96a1..e18a6fbc8 100644 --- a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_process_page_table.hpp +++ b/libraries/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/libraries/libmesosphere/include/mesosphere/kern_k_capabilities.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_capabilities.hpp index 7c2f5dbcd..2c74b835c 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_capabilities.hpp +++ b/libraries/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/libraries/libmesosphere/include/mesosphere/kern_k_debug_base.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_debug_base.hpp index 6c8fe54f7..6978b9152 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_debug_base.hpp +++ b/libraries/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/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp index 8da4311df..7b2c722e1 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_page_table_base.hpp +++ b/libraries/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/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp index c4ab7f2a5..849f73dab 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp +++ b/libraries/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/libraries/libmesosphere/source/kern_k_capabilities.cpp b/libraries/libmesosphere/source/kern_k_capabilities.cpp index feb245eda..ba0359262 100644 --- a/libraries/libmesosphere/source/kern_k_capabilities.cpp +++ b/libraries/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/libraries/libmesosphere/source/kern_k_debug_base.cpp b/libraries/libmesosphere/source/kern_k_debug_base.cpp index 418d804f3..635673cd2 100644 --- a/libraries/libmesosphere/source/kern_k_debug_base.cpp +++ b/libraries/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/libraries/libmesosphere/source/kern_k_page_table_base.cpp b/libraries/libmesosphere/source/kern_k_page_table_base.cpp index 95bc983ca..2a9402ecc 100644 --- a/libraries/libmesosphere/source/kern_k_page_table_base.cpp +++ b/libraries/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/libraries/libmesosphere/source/svc/kern_svc_debug.cpp b/libraries/libmesosphere/source/svc/kern_svc_debug.cpp index 369885355..986654ce4 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_debug.cpp +++ b/libraries/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/libraries/libmesosphere/source/svc/kern_svc_process.cpp b/libraries/libmesosphere/source/svc/kern_svc_process.cpp index 5141a2c31..98c8740f2 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_process.cpp +++ b/libraries/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/libraries/libmesosphere/source/svc/kern_svc_thread.cpp b/libraries/libmesosphere/source/svc/kern_svc_thread.cpp index 99c8c37a8..6f74dd145 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_thread.cpp +++ b/libraries/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)); } } diff --git a/stratosphere/creport/creport.json b/stratosphere/creport/creport.json index cdd28371d..99859c3d2 100644 --- a/stratosphere/creport/creport.json +++ b/stratosphere/creport/creport.json @@ -110,6 +110,7 @@ "type": "debug_flags", "value": { "allow_debug": false, + "force_debug_prod": false, "force_debug": true } } diff --git a/stratosphere/dmnt.gen2/dmnt.gen2.json b/stratosphere/dmnt.gen2/dmnt.gen2.json index 918f1262c..e0d970789 100644 --- a/stratosphere/dmnt.gen2/dmnt.gen2.json +++ b/stratosphere/dmnt.gen2/dmnt.gen2.json @@ -105,6 +105,7 @@ "type": "debug_flags", "value": { "allow_debug": false, + "force_debug_prod": false, "force_debug": true } }] diff --git a/stratosphere/fatal/fatal.json b/stratosphere/fatal/fatal.json index 45b718089..aef307a68 100644 --- a/stratosphere/fatal/fatal.json +++ b/stratosphere/fatal/fatal.json @@ -104,6 +104,7 @@ "type": "debug_flags", "value": { "allow_debug": false, + "force_debug_prod": false, "force_debug": true } }] diff --git a/stratosphere/loader/source/ldr_capabilities.cpp b/stratosphere/loader/source/ldr_capabilities.cpp index 6835267b3..cd93d855f 100644 --- a/stratosphere/loader/source/ldr_capabilities.cpp +++ b/stratosphere/loader/source/ldr_capabilities.cpp @@ -308,10 +308,19 @@ namespace ams::ldr { ); DEFINE_CAPABILITY_CLASS(DebugFlags, - DEFINE_CAPABILITY_FIELD(AllowDebug, IdBits, 1, bool); - DEFINE_CAPABILITY_FIELD(ForceDebug, AllowDebug, 1, bool); + DEFINE_CAPABILITY_FIELD(AllowDebug, IdBits, 1, bool); + DEFINE_CAPABILITY_FIELD(ForceDebugProd, AllowDebug, 1, bool); + DEFINE_CAPABILITY_FIELD(ForceDebug, ForceDebugProd, 1, bool); bool IsValid(const util::BitPack32 *kac, size_t kac_count) const { + u32 total = 0; + if (this->GetAllowDebug()) { ++total; } + if (this->GetForceDebugProd()) { ++total; } + if (this->GetForceDebug()) { ++total; } + if (total > 1) { + return false; + } + for (size_t i = 0; i < kac_count; i++) { if (GetCapabilityId(kac[i]) == Id) { const auto restriction = Decode(kac[i]); @@ -319,12 +328,14 @@ namespace ams::ldr { return (restriction.GetValue() & this->GetValue()) == this->GetValue(); } } + return false; } - static constexpr util::BitPack32 Encode(bool allow_debug, bool force_debug) { + static constexpr util::BitPack32 Encode(bool allow_debug, bool force_debug_prod, bool force_debug) { util::BitPack32 encoded{IdBitsValue}; encoded.Set(allow_debug); + encoded.Set(force_debug_prod); encoded.Set(force_debug); return encoded; } @@ -406,7 +417,7 @@ namespace ams::ldr { kac[i] = CapabilityApplicationType::Encode(flags & ProgramInfoFlag_ApplicationTypeMask); break; case CapabilityId::DebugFlags: - kac[i] = CapabilityDebugFlags::Encode((flags & ProgramInfoFlag_AllowDebug) != 0, CapabilityDebugFlags::Decode(cap).GetForceDebug()); + kac[i] = CapabilityDebugFlags::Encode((flags & ProgramInfoFlag_AllowDebug) != 0, CapabilityDebugFlags::Decode(cap).GetForceDebugProd(), CapabilityDebugFlags::Decode(cap).GetForceDebug()); break; default: break; diff --git a/stratosphere/pm/pm.json b/stratosphere/pm/pm.json index 005504b72..4ffe5e94e 100644 --- a/stratosphere/pm/pm.json +++ b/stratosphere/pm/pm.json @@ -85,6 +85,7 @@ "type": "debug_flags", "value": { "allow_debug": false, + "force_debug_prod": false, "force_debug": true } }