diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_debug_base.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_debug_base.hpp index 2e1708c25..383a94895 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_debug_base.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_debug_base.hpp @@ -36,9 +36,10 @@ namespace ams::kern { virtual ~KDebugBase() { /* ... */ } public: void Initialize(); + Result Attach(KProcess *process); - KScopedAutoObject GetProcess(); + Result ContinueDebug(const u32 flags, const u64 *thread_ids, size_t num_thread_ids); Result QueryMemoryInfo(ams::svc::MemoryInfo *out_memory_info, ams::svc::PageInfo *out_page_info, KProcessAddress address); Result ReadMemory(KProcessAddress buffer, KProcessAddress address, size_t size); @@ -49,6 +50,8 @@ namespace ams::kern { Result GetDebugEventInfo(ams::svc::lp64::DebugEventInfo *out); Result GetDebugEventInfo(ams::svc::ilp32::DebugEventInfo *out); + KScopedAutoObject GetProcess(); + /* TODO: This is a placeholder definition. */ 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); diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp index 846530829..1fc1af444 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp @@ -194,6 +194,7 @@ namespace ams::kern { void ClearDebugObject(KProcess::State state); KEventInfo *GetJitDebugInfo(); + void ClearJitDebugInfo(); bool EnterUserException(); bool LeaveUserException(); @@ -296,6 +297,18 @@ namespace ams::kern { Result Reset(); + void SetDebugBreak() { + if (this->state == State_RunningAttached) { + this->ChangeState(State_DebugBreak); + } + } + + void SetAttached() { + if (this->state == State_DebugBreak) { + this->ChangeState(State_RunningAttached); + } + } + Result SetActivity(ams::svc::ProcessActivity activity); void PinCurrentThread(); diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_thread.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_thread.hpp index c1578c967..1a2ec7491 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_thread.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_thread.hpp @@ -402,6 +402,16 @@ namespace ams::kern { return this->wait_result; } + constexpr void SetDebugExceptionResult(Result result) { + MESOSPHERE_ASSERT_THIS(); + this->debug_exception_result = result; + } + + constexpr Result GetDebugExceptionResult() const { + MESOSPHERE_ASSERT_THIS(); + return this->debug_exception_result; + } + void WaitCancel(); bool IsWaitCancelled() const { return this->wait_cancelled; } diff --git a/libraries/libmesosphere/include/mesosphere/svc/kern_svc_results.hpp b/libraries/libmesosphere/include/mesosphere/svc/kern_svc_results.hpp index 06fc6b49f..4b84670ae 100644 --- a/libraries/libmesosphere/include/mesosphere/svc/kern_svc_results.hpp +++ b/libraries/libmesosphere/include/mesosphere/svc/kern_svc_results.hpp @@ -24,6 +24,8 @@ namespace ams::kern::svc { /* 33 */ using ::ams::svc::ResultNotImplemented; + /* 54 */ using ::ams::svc::ResultStopProcessingException; + /* 57 */ using ::ams::svc::ResultNoSynchronizationObject; /* 59 */ using ::ams::svc::ResultTerminationRequested; diff --git a/libraries/libmesosphere/source/kern_k_debug_base.cpp b/libraries/libmesosphere/source/kern_k_debug_base.cpp index e75f25ca1..749093eb2 100644 --- a/libraries/libmesosphere/source/kern_k_debug_base.cpp +++ b/libraries/libmesosphere/source/kern_k_debug_base.cpp @@ -341,6 +341,95 @@ namespace ams::kern { return ResultSuccess(); } + 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()); + + /* Lock both ourselves, the target process, and the scheduler. */ + KScopedLightLock state_lk(target->GetStateLock()); + KScopedLightLock list_lk(target->GetListLock()); + KScopedLightLock this_lk(this->lock); + KScopedSchedulerLock sl; + + /* Check that we're still attached to the process, and that it's not terminated. */ + R_UNLESS(this->process == target.GetPointerUnsafe(), svc::ResultProcessTerminated()); + R_UNLESS(!target->IsTerminated(), svc::ResultProcessTerminated()); + + /* Check that we have no pending events. */ + R_UNLESS(this->event_info_list.empty(), svc::ResultBusy()); + + /* Clear the target's JIT debug info. */ + target->ClearJitDebugInfo(); + + /* Set our continue flags. */ + this->continue_flags = flags; + + /* Iterate over threads, continuing them as we should. */ + bool has_debug_break_thread = false; + { + /* Parse our flags. */ + const bool exception_handled = (this->continue_flags & ams::svc::ContinueFlag_ExceptionHandled) != 0; + const bool continue_all = (this->continue_flags & ams::svc::ContinueFlag_ContinueAll) != 0; + const bool continue_others = (this->continue_flags & ams::svc::ContinueFlag_ContinueOthers) != 0; + + /* Update each thread. */ + auto end = target->GetThreadList().end(); + for (auto it = target->GetThreadList().begin(); it != end; ++it) { + /* Determine if we should continue the thread. */ + bool should_continue; + { + if (continue_all) { + /* Continue all threads. */ + should_continue = true; + } else if (continue_others) { + /* Continue the thread if it doesn't match one of our target ids. */ + const u64 thread_id = it->GetId(); + should_continue = true; + for (size_t i = 0; i < num_thread_ids; ++i) { + if (thread_ids[i] == thread_id) { + should_continue = false; + break; + } + } + } else { + /* Continue the thread if it matches one of our target ids. */ + const u64 thread_id = it->GetId(); + should_continue = false; + for (size_t i = 0; i < num_thread_ids; ++i) { + if (thread_ids[i] == thread_id) { + should_continue = true; + break; + } + } + } + } + + /* Continue the thread if we should. */ + if (should_continue) { + if (exception_handled) { + it->SetDebugExceptionResult(svc::ResultStopProcessingException()); + } + it->Resume(KThread::SuspendType_Debug); + } + + /* If the thread has debug suspend requested, note so. */ + if (it->IsSuspendRequested(KThread::SuspendType_Debug)) { + has_debug_break_thread = true; + } + } + } + + /* Set the process's state. */ + if (has_debug_break_thread) { + target->SetDebugBreak(); + } else { + target->SetAttached(); + } + + return ResultSuccess(); + } + KEventInfo *KDebugBase::CreateDebugEvent(ams::svc::DebugEvent event, uintptr_t param0, uintptr_t param1, uintptr_t param2, uintptr_t param3, uintptr_t param4, u64 cur_thread_id) { /* Allocate a new event. */ KEventInfo *info = KEventInfo::Allocate(); @@ -350,7 +439,7 @@ namespace ams::kern { /* Set common fields. */ info->event = event; info->thread_id = 0; - info->flags = 1; /* TODO: enum this in ams::svc */ + info->flags = ams::svc::DebugEventFlag_Stopped; /* Set event specific fields. */ switch (event) { diff --git a/libraries/libmesosphere/source/kern_k_process.cpp b/libraries/libmesosphere/source/kern_k_process.cpp index 7a836f1e4..2c64ac968 100644 --- a/libraries/libmesosphere/source/kern_k_process.cpp +++ b/libraries/libmesosphere/source/kern_k_process.cpp @@ -1000,6 +1000,9 @@ namespace ams::kern { } KEventInfo *KProcess::GetJitDebugInfo() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + if (this->is_jit_debug) { return KDebugBase::CreateDebugEvent(this->jit_debug_event_type, this->jit_debug_exception_type, this->jit_debug_params[0], this->jit_debug_params[1], this->jit_debug_params[2], this->jit_debug_params[3], this->jit_debug_thread_id); } else { @@ -1007,6 +1010,13 @@ namespace ams::kern { } } + void KProcess::ClearJitDebugInfo() { + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); + + this->is_jit_debug = false; + } + KProcess *KProcess::GetProcessFromId(u64 process_id) { /* Lock the list. */ KProcess::ListAccessor accessor; diff --git a/libraries/libmesosphere/source/svc/kern_svc_debug.cpp b/libraries/libmesosphere/source/svc/kern_svc_debug.cpp index 3e56dd44b..d0235830a 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_debug.cpp +++ b/libraries/libmesosphere/source/svc/kern_svc_debug.cpp @@ -21,6 +21,8 @@ namespace ams::kern::svc { namespace { + constexpr inline int32_t MaximumDebuggableThreadCount = 0x60; + Result DebugActiveProcess(ams::svc::Handle *out_handle, uint64_t process_id) { /* Get the process from its id. */ KProcess *process = KProcess::GetProcessFromId(process_id); @@ -79,6 +81,60 @@ namespace ams::kern::svc { return ResultSuccess(); } + Result ContinueDebugEventImpl(ams::svc::Handle debug_handle, uint32_t flags, const uint64_t *thread_ids, int32_t num_thread_ids) { + /* Get the debug object. */ + KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject(debug_handle); + R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle()); + + /* Continue the event. */ + R_TRY(debug->ContinueDebug(flags, thread_ids, num_thread_ids)); + + return ResultSuccess(); + } + + Result ContinueDebugEvent(ams::svc::Handle debug_handle, uint32_t flags, KUserPointer user_thread_ids, int32_t num_thread_ids) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented()); + + /* Verify that the flags are valid. */ + R_UNLESS((flags | ams::svc::ContinueFlag_AllMask) == ams::svc::ContinueFlag_AllMask, svc::ResultInvalidEnumValue()); + + /* Verify that continue all and continue others flags are exclusive. */ + constexpr u32 AllAndOthersMask = ams::svc::ContinueFlag_ContinueAll | ams::svc::ContinueFlag_ContinueOthers; + R_UNLESS((flags & AllAndOthersMask) != AllAndOthersMask, svc::ResultInvalidEnumValue()); + + /* Verify that the number of thread ids is valid. */ + R_UNLESS((0 <= num_thread_ids && num_thread_ids <= MaximumDebuggableThreadCount), svc::ResultOutOfRange()); + + /* Copy the threads from userspace. */ + uint64_t thread_ids[MaximumDebuggableThreadCount]; + if (num_thread_ids > 0) { + R_TRY(user_thread_ids.CopyArrayTo(thread_ids, num_thread_ids)); + } + + /* Continue the event. */ + R_TRY(ContinueDebugEventImpl(debug_handle, flags, thread_ids, num_thread_ids)); + + return ResultSuccess(); + } + + Result LegacyContinueDebugEvent(ams::svc::Handle debug_handle, uint32_t flags, uint64_t thread_id) { + /* Only allow invoking the svc on development hardware. */ + R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented()); + + /* Verify that the flags are valid. */ + R_UNLESS((flags | ams::svc::ContinueFlag_AllMask) == ams::svc::ContinueFlag_AllMask, svc::ResultInvalidEnumValue()); + + /* Verify that continue all and continue others flags are exclusive. */ + constexpr u32 AllAndOthersMask = ams::svc::ContinueFlag_ContinueAll | ams::svc::ContinueFlag_ContinueOthers; + R_UNLESS((flags & AllAndOthersMask) != AllAndOthersMask, svc::ResultInvalidEnumValue()); + + /* Continue the event. */ + R_TRY(ContinueDebugEventImpl(debug_handle, flags, std::addressof(thread_id), 1)); + + return ResultSuccess(); + } + Result QueryDebugProcessMemory(ams::svc::MemoryInfo *out_memory_info, ams::svc::PageInfo *out_page_info, ams::svc::Handle debug_handle, uintptr_t address) { /* Get the debug object. */ KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject(debug_handle); @@ -173,11 +229,11 @@ namespace ams::kern::svc { } Result ContinueDebugEvent64(ams::svc::Handle debug_handle, uint32_t flags, KUserPointer thread_ids, int32_t num_thread_ids) { - MESOSPHERE_PANIC("Stubbed SvcContinueDebugEvent64 was called."); + return ContinueDebugEvent(debug_handle, flags, thread_ids, num_thread_ids); } Result LegacyContinueDebugEvent64(ams::svc::Handle debug_handle, uint32_t flags, uint64_t thread_id) { - MESOSPHERE_PANIC("Stubbed SvcLegacyContinueDebugEvent64 was called."); + return LegacyContinueDebugEvent(debug_handle, flags, thread_id); } Result GetDebugThreadContext64(KUserPointer out_context, ams::svc::Handle debug_handle, uint64_t thread_id, uint32_t context_flags) { @@ -227,11 +283,11 @@ namespace ams::kern::svc { } Result ContinueDebugEvent64From32(ams::svc::Handle debug_handle, uint32_t flags, KUserPointer thread_ids, int32_t num_thread_ids) { - MESOSPHERE_PANIC("Stubbed SvcContinueDebugEvent64From32 was called."); + return ContinueDebugEvent(debug_handle, flags, thread_ids, num_thread_ids); } Result LegacyContinueDebugEvent64From32(ams::svc::Handle debug_handle, uint32_t flags, uint64_t thread_id) { - MESOSPHERE_PANIC("Stubbed SvcLegacyContinueDebugEvent64From32 was called."); + return LegacyContinueDebugEvent(debug_handle, flags, thread_id); } Result GetDebugThreadContext64From32(KUserPointer out_context, ams::svc::Handle debug_handle, uint64_t thread_id, uint32_t context_flags) { diff --git a/libraries/libvapours/include/vapours/results/svc_results.hpp b/libraries/libvapours/include/vapours/results/svc_results.hpp index 156e5d03e..1ac86e893 100644 --- a/libraries/libvapours/include/vapours/results/svc_results.hpp +++ b/libraries/libvapours/include/vapours/results/svc_results.hpp @@ -27,6 +27,8 @@ namespace ams::svc { R_DEFINE_ERROR_RESULT(NotImplemented, 33); + R_DEFINE_ERROR_RESULT(StopProcessingException, 54); + R_DEFINE_ERROR_RESULT(NoSynchronizationObject, 57); R_DEFINE_ERROR_RESULT(TerminationRequested, 59); diff --git a/libraries/libvapours/include/vapours/svc/svc_types_common.hpp b/libraries/libvapours/include/vapours/svc/svc_types_common.hpp index 89e0ab330..45e62daf4 100644 --- a/libraries/libvapours/include/vapours/svc/svc_types_common.hpp +++ b/libraries/libvapours/include/vapours/svc/svc_types_common.hpp @@ -292,6 +292,15 @@ namespace ams::svc { ThreadContextFlag_All = (ThreadContextFlag_General | ThreadContextFlag_Control | ThreadContextFlag_Fpu | ThreadContextFlag_FpuControl), }; + enum ContinueFlag : u32 { + ContinueFlag_ExceptionHandled = (1u << 0), + ContinueFlag_EnableExceptionEvent = (1u << 1), + ContinueFlag_ContinueAll = (1u << 2), + ContinueFlag_ContinueOthers = (1u << 3), + + ContinueFlag_AllMask = (1u << 4) - 1, + }; + enum ThreadExitReason : u32 { ThreadExitReason_ExitThread = 0, ThreadExitReason_TerminateThread = 1,