/* * Copyright (c) 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 { namespace { ALWAYS_INLINE KDebugBase *GetDebugObject(KProcess *process) { return static_cast(process->GetDebugObject()); } } void KDebugBase::Initialize() { /* Clear the continue flags. */ m_continue_flags = 0; m_is_force_debug_prod = GetCurrentProcess().CanForceDebugProd(); } bool KDebugBase::Is64Bit() const { MESOSPHERE_ASSERT(m_lock.IsLockedByCurrentThread()); 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'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(process->GetPageTable().QueryInfo(std::addressof(info), out_page_info, address)); /* Write output. */ *out_memory_info = info.GetSvcMemoryInfo(); R_SUCCEED(); } 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'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 = process->GetPageTable(); /* Verify that the regions are in range. */ R_UNLESS(target_pt.Contains(address, size), svc::ResultInvalidCurrentMemory()); R_UNLESS(debugger_pt.Contains(buffer, size), svc::ResultInvalidCurrentMemory()); /* Iterate over the target process's memory blocks. */ KProcessAddress cur_address = address; size_t remaining = size; while (remaining > 0) { /* Get the current memory info. */ KMemoryInfo info; ams::svc::PageInfo pi; R_TRY(target_pt.QueryInfo(std::addressof(info), std::addressof(pi), cur_address)); /* Check that the memory is accessible. */ R_UNLESS(info.GetState() != static_cast(ams::svc::MemoryState_Inaccessible), svc::ResultInvalidAddress()); /* Get the current size. */ const size_t cur_size = std::min(remaining, info.GetEndAddress() - GetInteger(cur_address)); /* 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, 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())); } /* Advance. */ buffer += cur_size; cur_address += cur_size; remaining -= cur_size; } R_SUCCEED(); } 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'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 = process->GetPageTable(); /* Verify that the regions are in range. */ R_UNLESS(target_pt.Contains(address, size), svc::ResultInvalidCurrentMemory()); R_UNLESS(debugger_pt.Contains(buffer, size), svc::ResultInvalidCurrentMemory()); /* Iterate over the target process's memory blocks. */ KProcessAddress cur_address = address; size_t remaining = size; while (remaining > 0) { /* Get the current memory info. */ KMemoryInfo info; ams::svc::PageInfo pi; R_TRY(target_pt.QueryInfo(std::addressof(info), std::addressof(pi), cur_address)); /* Check that the memory is accessible. */ R_UNLESS(info.GetState() != static_cast(ams::svc::MemoryState_Inaccessible), svc::ResultInvalidAddress()); /* Get the current size. */ const size_t cur_size = std::min(remaining, info.GetEndAddress() - GetInteger(cur_address)); /* Read the memory. */ if (info.GetSvcState() != ams::svc::MemoryState_Io) { /* The memory is normal memory. */ R_TRY(target_pt.WriteDebugMemory(cur_address, GetVoidPointer(buffer), cur_size)); } else { /* The memory is IO memory. */ R_TRY(target_pt.WriteDebugIoMemory(cur_address, GetVoidPointer(buffer), cur_size, info.GetState())); } /* Advance. */ buffer += cur_size; cur_address += cur_size; remaining -= cur_size; } R_SUCCEED(); } Result KDebugBase::GetRunningThreadInfo(ams::svc::LastThreadContext *out_context, u64 *out_thread_id) { /* 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. */ { KScopedSchedulerLock sl; /* Get the running thread. */ const s32 core_id = GetCurrentCoreId(); KThread *thread = process->GetRunningThread(core_id); /* We want to check that the thread is actually running. */ /* If it is, then the scheduler will have just switched from the thread to the current thread. */ /* This implies exactly one switch will have taken place, and the current thread will be on the current core. */ const auto &scheduler = Kernel::GetScheduler(core_id); if (!(thread != nullptr && thread->GetActiveCore() == core_id && process->GetRunningThreadSwitchCount(core_id) + 1 == scheduler.GetSwitchCount())) { /* The most recent thread switch was from a thread other than the expected one to the current one. */ /* We want to use the appropriate result to inform userland about what thread we switched from. */ if (scheduler.GetIdleCount() + 1 == scheduler.GetSwitchCount()) { /* We switched from the idle thread. */ R_THROW(svc::ResultNoThread()); } else { /* We switched from some other unknown thread. */ R_THROW(svc::ResultUnknownThread()); } } /* Get the thread's exception context. */ GetExceptionContext(thread)->GetSvcThreadContext(out_context); /* Get the thread's id. */ *out_thread_id = thread->GetId(); } R_SUCCEED(); } Result KDebugBase::Attach(KProcess *target) { /* 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. */ KScopedLightLock state_lk(target->GetStateLock()); KScopedLightLock list_lk(target->GetListLock()); KScopedLightLock this_lk(m_lock); KScopedSchedulerLock sl; /* Check that the process isn't already being debugged. */ R_UNLESS(!target->IsAttachedToDebugger(), svc::ResultBusy()); { /* Ensure the process is in a state that allows for debugging. */ const KProcess::State state = target->GetState(); 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: case KProcess::State_RunningAttached: case KProcess::State_DebugBreak: R_THROW(svc::ResultBusy()); case KProcess::State_Terminating: case KProcess::State_Terminated: R_THROW(svc::ResultProcessTerminated()); MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); } /* 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 = target->SetDebugObject(this); /* Send an event for our attaching to the process. */ this->PushDebugEvent(ams::svc::DebugEvent_CreateProcess, nullptr, 0); /* Send events for attaching to each thread 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); /* If the thread is in a state for us to do so, generate the event. */ if (const auto thread_state = it->GetState(); thread_state == KThread::ThreadState_Runnable || thread_state == KThread::ThreadState_Waiting) { /* Mark the thread as attached to. */ it->SetDebugAttached(); /* Send the event. */ const uintptr_t params[2] = { it->GetId(), GetInteger(it->GetThreadLocalRegionAddress()) }; this->PushDebugEvent(ams::svc::DebugEvent_CreateThread, params, util::size(params)); } } } /* Send the process's jit debug info, if relevant. */ if (KEventInfo *jit_info = target->GetJitDebugInfo(); jit_info != nullptr) { this->EnqueueDebugEventInfo(jit_info); } /* Send an exception event to represent our attaching. */ const uintptr_t params[1] = { static_cast(ams::svc::DebugException_DebuggerAttached) }; this->PushDebugEvent(ams::svc::DebugEvent_Exception, params, util::size(params)); /* Signal. */ this->NotifyAvailable(); } } R_SUCCEED(); } Result KDebugBase::BreakProcess() { /* 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()); KScopedLightLock list_lk(target->GetListLock()); KScopedLightLock this_lk(m_lock); KScopedSchedulerLock sl; /* 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; constexpr u64 ThreadIdUnknownThread = -2ll; uintptr_t debug_info_params[1 + cpu::NumCores] = { static_cast(ams::svc::DebugException_DebuggerBreak), }; for (size_t i = 0; i < cpu::NumCores; ++i) { /* Get the currently running thread. */ KThread *thread = target->GetRunningThread(i); /* Check that the thread's idle count is correct. */ if (target->GetRunningThreadIdleCount(i) == Kernel::GetScheduler(i).GetIdleCount()) { if (thread != nullptr && static_cast(thread->GetActiveCore()) == i) { debug_info_params[1 + i] = thread->GetId(); } else { /* We found an unknown thread. */ debug_info_params[1 + i] = ThreadIdUnknownThread; } } else { /* We didn't find a thread. */ debug_info_params[1 + i] = ThreadIdNoThread; } } /* 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); } } /* Send an exception event to represent our breaking the process. */ this->PushDebugEvent(ams::svc::DebugEvent_Exception, debug_info_params, util::size(debug_info_params)); /* Signal. */ this->NotifyAvailable(); /* Set the process as breaked. */ target->SetDebugBreak(); R_SUCCEED(); } Result KDebugBase::TerminateProcess() { /* 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(); /* Terminate the process. */ target->Terminate(); R_SUCCEED(); } 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(process == thread->GetOwnerProcess(), svc::ResultInvalidId()); /* Verify that the thread isn't terminated. */ R_UNLESS(thread->GetState() != KThread::ThreadState_Terminated, svc::ResultTerminationRequested()); /* Check that the thread is not the current one. */ /* NOTE: Nintendo does not check this, and thus the following loop will deadlock. */ R_UNLESS(thread != GetCurrentThreadPointer(), svc::ResultInvalidId()); /* Try to get the thread context until the thread isn't current on any core. */ while (true) { KScopedSchedulerLock sl; /* The thread needs to be requested for debug suspension. */ R_UNLESS(thread->IsSuspendRequested(KThread::SuspendType_Debug), svc::ResultInvalidState()); /* If the thread's raw state isn't runnable, check if it's current on some core. */ if (thread->GetRawState() != KThread::ThreadState_Runnable) { bool current = false; for (auto i = 0; i < static_cast(cpu::NumCores); ++i) { if (thread == Kernel::GetScheduler(i).GetSchedulerCurrentThread()) { current = true; break; } } /* If the thread is current, retry until it isn't. */ if (current) { continue; } } /* Get the thread context. */ static_assert(std::derived_from); R_RETURN(static_cast(this)->GetThreadContextImpl(out, thread, context_flags)); } } 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(process == thread->GetOwnerProcess(), svc::ResultInvalidId()); /* Verify that the thread isn't terminated. */ R_UNLESS(thread->GetState() != KThread::ThreadState_Terminated, svc::ResultTerminationRequested()); /* Check that the thread is not the current one. */ /* NOTE: Nintendo does not check this, and thus the following loop will deadlock. */ R_UNLESS(thread != GetCurrentThreadPointer(), svc::ResultInvalidId()); /* Try to get the thread context until the thread isn't current on any core. */ while (true) { KScopedSchedulerLock sl; /* The thread needs to be requested for debug suspension. */ R_UNLESS(thread->IsSuspendRequested(KThread::SuspendType_Debug), svc::ResultInvalidState()); /* If the thread's raw state isn't runnable, check if it's current on some core. */ if (thread->GetRawState() != KThread::ThreadState_Runnable) { bool current = false; for (auto i = 0; i < static_cast(cpu::NumCores); ++i) { if (thread == Kernel::GetScheduler(i).GetSchedulerCurrentThread()) { current = true; break; } } /* If the thread is current, retry until it isn't. */ if (current) { continue; } } /* Update thread single-step state. */ #if defined(MESOSPHERE_ENABLE_HARDWARE_SINGLE_STEP) { if ((context_flags & ams::svc::ThreadContextFlag_SetSingleStep) != 0) { /* Set single step. */ thread->SetHardwareSingleStep(); /* If no other thread flags are present, we're done. */ R_SUCCEED_IF((context_flags & ~ams::svc::ThreadContextFlag_SetSingleStep) == 0); } else if ((context_flags & ams::svc::ThreadContextFlag_ClearSingleStep) != 0) { /* Clear single step. */ thread->ClearHardwareSingleStep(); /* If no other thread flags are present, we're done. */ R_SUCCEED_IF((context_flags & ~ams::svc::ThreadContextFlag_ClearSingleStep) == 0); } } #endif /* Verify that the thread's svc state is valid. */ if (thread->IsCallingSvc()) { const u8 svc_id = thread->GetSvcId(); const bool is_valid_svc = svc_id == svc::SvcId_Break || svc_id == svc::SvcId_ReturnFromException; R_UNLESS(is_valid_svc, svc::ResultInvalidState()); } /* Set the thread context. */ static_assert(std::derived_from); R_RETURN(static_cast(this)->SetThreadContextImpl(ctx, thread, context_flags)); } } Result KDebugBase::ContinueDebug(const u32 flags, const u64 *thread_ids, size_t num_thread_ids) { /* 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()); KScopedLightLock list_lk(target->GetListLock()); KScopedLightLock this_lk(m_lock); KScopedSchedulerLock sl; /* 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()); /* Clear the target's JIT debug info. */ target->ClearJitDebugInfo(); /* Set our continue flags. */ m_continue_flags = flags; /* Iterate over threads, continuing them as we should. */ bool has_debug_break_thread = false; { /* Parse our flags. */ const bool exception_handled = (m_continue_flags & ams::svc::ContinueFlag_ExceptionHandled) != 0; const bool continue_all = (m_continue_flags & ams::svc::ContinueFlag_ContinueAll) != 0; const bool continue_others = (m_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(); } R_SUCCEED(); } KEventInfo *KDebugBase::CreateDebugEvent(ams::svc::DebugEvent event, u64 cur_thread_id, const uintptr_t *params, size_t num_params) { /* Allocate a new event. */ KEventInfo *info = KEventInfo::Allocate(); /* Populate the event info. */ if (info != nullptr) { /* Set common fields. */ info->event = event; info->thread_id = 0; info->flags = ams::svc::DebugEventFlag_Stopped; /* Set event specific fields. */ switch (event) { case ams::svc::DebugEvent_CreateProcess: { /* Check parameters. */ MESOSPHERE_ASSERT(params == nullptr); MESOSPHERE_ASSERT(num_params == 0); } break; case ams::svc::DebugEvent_CreateThread: { /* Check parameters. */ MESOSPHERE_ASSERT(params != nullptr); MESOSPHERE_ASSERT(num_params == 2); /* Set the thread id. */ info->thread_id = params[0]; /* Set the thread creation info. */ info->info.create_thread.thread_id = params[0]; info->info.create_thread.tls_address = params[1]; } break; case ams::svc::DebugEvent_ExitProcess: { /* Check parameters. */ MESOSPHERE_ASSERT(params != nullptr); MESOSPHERE_ASSERT(num_params == 1); /* Set the exit reason. */ info->info.exit_process.reason = static_cast(params[0]); /* Clear the thread id and flags. */ info->thread_id = 0; info->flags = 0; } break; case ams::svc::DebugEvent_ExitThread: { /* Check parameters. */ MESOSPHERE_ASSERT(params != nullptr); MESOSPHERE_ASSERT(num_params == 2); /* Set the thread id. */ info->thread_id = params[0]; /* Set the exit reason. */ info->info.exit_thread.reason = static_cast(params[1]); } break; case ams::svc::DebugEvent_Exception: { /* Check parameters. */ MESOSPHERE_ASSERT(params != nullptr); MESOSPHERE_ASSERT(num_params >= 1); /* Set the thread id. */ info->thread_id = cur_thread_id; /* Set the exception type, and clear the count. */ info->info.exception.exception_type = static_cast(params[0]); info->info.exception.exception_data_count = 0; switch (static_cast(params[0])) { case ams::svc::DebugException_UndefinedInstruction: case ams::svc::DebugException_BreakPoint: case ams::svc::DebugException_UndefinedSystemCall: { MESOSPHERE_ASSERT(num_params >= 3); info->info.exception.exception_address = params[1]; info->info.exception.exception_data_count = 1; info->info.exception.exception_data[0] = params[2]; } break; case ams::svc::DebugException_DebuggerAttached: { info->thread_id = 0; info->info.exception.exception_address = 0; } break; case ams::svc::DebugException_UserBreak: { MESOSPHERE_ASSERT(num_params >= 2); info->info.exception.exception_address = params[1]; info->info.exception.exception_data_count = 0; for (size_t i = 2; i < num_params; ++i) { info->info.exception.exception_data[info->info.exception.exception_data_count++] = params[i]; } } break; case ams::svc::DebugException_DebuggerBreak: { info->thread_id = 0; info->info.exception.exception_address = 0; info->info.exception.exception_data_count = 0; for (size_t i = 1; i < num_params; ++i) { info->info.exception.exception_data[info->info.exception.exception_data_count++] = params[i]; } } break; case ams::svc::DebugException_MemorySystemError: { info->info.exception.exception_address = 0; } break; case ams::svc::DebugException_InstructionAbort: case ams::svc::DebugException_DataAbort: case ams::svc::DebugException_AlignmentFault: default: { MESOSPHERE_ASSERT(num_params >= 2); info->info.exception.exception_address = params[1]; } break; } } break; } } return info; } void KDebugBase::PushDebugEvent(ams::svc::DebugEvent event, const uintptr_t *params, size_t num_params) { /* Create and enqueue and event. */ if (KEventInfo *new_info = CreateDebugEvent(event, GetCurrentThread().GetId(), params, num_params); new_info != nullptr) { this->EnqueueDebugEventInfo(new_info); } } void KDebugBase::EnqueueDebugEventInfo(KEventInfo *info) { /* Lock the scheduler. */ KScopedSchedulerLock sl; /* Push the event to the back of the list. */ m_event_info_list.push_back(*info); } template requires (std::same_as || std::same_as) Result KDebugBase::GetDebugEventInfoImpl(T *out) { /* 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; { KScopedSchedulerLock sl; /* Check that we have an event to dequeue. */ R_UNLESS(!m_event_info_list.empty(), svc::ResultNoEvent()); /* Pop the event from the front of the queue. */ info = std::addressof(m_event_info_list.front()); m_event_info_list.pop_front(); } MESOSPHERE_ASSERT(info != nullptr); /* Free the event info once we're done with it. */ ON_SCOPE_EXIT { KEventInfo::Free(info); }; /* Set common fields. */ out->type = info->event; out->thread_id = info->thread_id; out->flags = info->flags; /* Set event specific fields. */ switch (info->event) { case ams::svc::DebugEvent_CreateProcess: { out->info.create_process.program_id = process->GetProgramId(); out->info.create_process.process_id = process->GetId(); out->info.create_process.flags = process->GetCreateProcessFlags(); out->info.create_process.user_exception_context_address = GetInteger(process->GetProcessLocalRegionAddress()); std::memcpy(out->info.create_process.name, process->GetName(), sizeof(out->info.create_process.name)); } break; case ams::svc::DebugEvent_CreateThread: { out->info.create_thread.thread_id = info->info.create_thread.thread_id; out->info.create_thread.tls_address = info->info.create_thread.tls_address; } break; case ams::svc::DebugEvent_ExitProcess: { out->info.exit_process.reason = info->info.exit_process.reason; } break; case ams::svc::DebugEvent_ExitThread: { out->info.exit_thread.reason = info->info.exit_thread.reason; } break; case ams::svc::DebugEvent_Exception: { out->info.exception.type = info->info.exception.exception_type; out->info.exception.address = info->info.exception.exception_address; switch (info->info.exception.exception_type) { case ams::svc::DebugException_UndefinedInstruction: { MESOSPHERE_ASSERT(info->info.exception.exception_data_count == 1); /* 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: { MESOSPHERE_ASSERT(info->info.exception.exception_data_count == 1); out->info.exception.specific.break_point.type = static_cast(info->info.exception.exception_data[0]); out->info.exception.specific.break_point.address = 0; } break; case ams::svc::DebugException_UserBreak: { MESOSPHERE_ASSERT(info->info.exception.exception_data_count == 3); out->info.exception.specific.user_break.break_reason = static_cast(info->info.exception.exception_data[0]); out->info.exception.specific.user_break.address = info->info.exception.exception_data[1]; out->info.exception.specific.user_break.size = info->info.exception.exception_data[2]; } break; case ams::svc::DebugException_DebuggerBreak: { /* TODO: How does this work with non-4 cpu count? */ static_assert(cpu::NumCores <= 4); MESOSPHERE_ASSERT(info->info.exception.exception_data_count == cpu::NumCores); out->info.exception.specific.debugger_break.active_thread_ids[0] = info->info.exception.exception_data[0]; out->info.exception.specific.debugger_break.active_thread_ids[1] = info->info.exception.exception_data[1]; out->info.exception.specific.debugger_break.active_thread_ids[2] = info->info.exception.exception_data[2]; out->info.exception.specific.debugger_break.active_thread_ids[3] = info->info.exception.exception_data[3]; } break; case ams::svc::DebugException_UndefinedSystemCall: { MESOSPHERE_ASSERT(info->info.exception.exception_data_count == 1); out->info.exception.specific.undefined_system_call.id = info->info.exception.exception_data[0]; } break; default: { /* ... */ } break; } } break; } R_SUCCEED(); } Result KDebugBase::GetDebugEventInfo(ams::svc::lp64::DebugEventInfo *out) { R_RETURN(this->GetDebugEventInfoImpl(out)); } Result KDebugBase::GetDebugEventInfo(ams::svc::ilp32::DebugEventInfo *out) { R_RETURN(this->GetDebugEventInfoImpl(out)); } void KDebugBase::Finalize() { /* Perform base finalization. */ KSynchronizationObject::Finalize(); /* Perform post-synchronization finalization. */ this->OnFinalizeSynchronizationObject(); } void KDebugBase::OnFinalizeSynchronizationObject() { /* Detach from our process, if we have one. */ if (this->IsAttached() && this->OpenProcess()) { /* Close the process when we're done with it. */ ON_SCOPE_EXIT { this->CloseProcess(); }; /* 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); { 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->ClearHardwareSingleStep(); #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); } } } /* Note we're now unattached. */ m_is_attached = false; /* Close the initial reference opened to our process. */ this->CloseProcess(); } } /* Free any pending events. */ { KScopedSchedulerLock sl; while (!m_event_info_list.empty()) { KEventInfo *info = std::addressof(m_event_info_list.front()); m_event_info_list.pop_front(); KEventInfo::Free(info); } } } bool KDebugBase::IsSignaled() const { bool empty; { KScopedSchedulerLock sl; empty = m_event_info_list.empty(); } return !empty || !m_is_attached || this->GetProcessUnsafe()->IsTerminated(); } Result KDebugBase::ProcessDebugEvent(ams::svc::DebugEvent event, const uintptr_t *params, size_t num_params) { /* Get the current process. */ KProcess *process = GetCurrentProcessPointer(); /* If the event is CreateThread and we've already attached, there's nothing to do. */ if (event == ams::svc::DebugEvent_CreateThread) { R_SUCCEED_IF(GetCurrentThread().IsAttachedToDebugger()); } while (true) { /* Lock the process and the scheduler. */ KScopedLightLock state_lk(process->GetStateLock()); KScopedLightLock list_lk(process->GetListLock()); KScopedSchedulerLock sl; /* If the current thread is terminating, we can't process an event. */ R_SUCCEED_IF(GetCurrentThread().IsTerminationRequested()); /* Get the debug object. If we have none, there's nothing to process. */ KDebugBase *debug = GetDebugObject(process); R_SUCCEED_IF(debug == nullptr); /* If the event is an exception and we don't have exception events enabled, we can't handle the event. */ if (event == ams::svc::DebugEvent_Exception && (debug->m_continue_flags & ams::svc::ContinueFlag_EnableExceptionEvent) == 0) { GetCurrentThread().SetDebugExceptionResult(ResultSuccess()); R_THROW(svc::ResultNotHandled()); } /* If the current thread is suspended, retry. */ if (GetCurrentThread().IsSuspended()) { continue; } /* Suspend all the process's threads. */ { auto end = process->GetThreadList().end(); for (auto it = process->GetThreadList().begin(); it != end; ++it) { it->RequestSuspend(KThread::SuspendType_Debug); } } /* Push the event. */ debug->PushDebugEvent(event, params, num_params); debug->NotifyAvailable(); /* Set the process as breaked. */ process->SetDebugBreak(); /* If the event is an exception, set the result and clear single step. */ if (event == ams::svc::DebugEvent_Exception) { GetCurrentThread().SetDebugExceptionResult(ResultSuccess()); } /* Exit our retry loop. */ break; } /* If the event is an exception, get the exception result. */ if (event == ams::svc::DebugEvent_Exception) { /* Lock the scheduler. */ KScopedSchedulerLock sl; /* If the thread is terminating, we can't process the exception. */ R_UNLESS(!GetCurrentThread().IsTerminationRequested(), svc::ResultStopProcessingException()); /* Get the debug object. */ if (KDebugBase *debug = GetDebugObject(process); debug != nullptr) { /* If we have one, check the debug exception. */ R_RETURN(GetCurrentThread().GetDebugExceptionResult()); } else { /* We don't have a debug object, so stop processing the exception. */ R_THROW(svc::ResultStopProcessingException()); } } R_SUCCEED(); } Result KDebugBase::OnDebugEvent(ams::svc::DebugEvent event, const uintptr_t *params, size_t num_params) { if (KProcess *process = GetCurrentProcessPointer(); process != nullptr && process->IsAttachedToDebugger()) { R_RETURN(ProcessDebugEvent(event, params, num_params)); } R_SUCCEED(); } Result KDebugBase::OnExitProcess(KProcess *process) { MESOSPHERE_ASSERT(process != nullptr); /* Check if we're attached to a debugger. */ if (process->IsAttachedToDebugger()) { /* If we are, lock the scheduler. */ KScopedSchedulerLock sl; /* Push the event. */ if (KDebugBase *debug = GetDebugObject(process); debug != nullptr) { const uintptr_t params[1] = { static_cast(ams::svc::ProcessExitReason_ExitProcess) }; debug->PushDebugEvent(ams::svc::DebugEvent_ExitProcess, params, util::size(params)); debug->NotifyAvailable(); } } R_SUCCEED(); } Result KDebugBase::OnTerminateProcess(KProcess *process) { MESOSPHERE_ASSERT(process != nullptr); /* Check if we're attached to a debugger. */ if (process->IsAttachedToDebugger()) { /* If we are, lock the scheduler. */ KScopedSchedulerLock sl; /* Push the event. */ if (KDebugBase *debug = GetDebugObject(process); debug != nullptr) { const uintptr_t params[1] = { static_cast(ams::svc::ProcessExitReason_TerminateProcess) }; debug->PushDebugEvent(ams::svc::DebugEvent_ExitProcess, params, util::size(params)); debug->NotifyAvailable(); } } R_SUCCEED(); } Result KDebugBase::OnExitThread(KThread *thread) { MESOSPHERE_ASSERT(thread != nullptr); /* Check if we're attached to a debugger. */ if (KProcess *process = thread->GetOwnerProcess(); process != nullptr && process->IsAttachedToDebugger()) { /* If we are, submit the event. */ const uintptr_t params[2] = { thread->GetId(), static_cast(thread->IsTerminationRequested() ? ams::svc::ThreadExitReason_TerminateThread : ams::svc::ThreadExitReason_ExitThread) }; R_TRY(OnDebugEvent(ams::svc::DebugEvent_ExitThread, params, util::size(params))); } R_SUCCEED(); } }