/* * Copyright (c) 2018-2020 Atmosphère-NX * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include namespace ams::kern { namespace { ALWAYS_INLINE KDebugBase *GetDebugObject(KProcess *process) { return static_cast(process->GetDebugObject()); } } void KDebugBase::Initialize() { /* Clear the process and continue flags. */ m_process = nullptr; m_continue_flags = 0; } bool KDebugBase::Is64Bit() const { MESOSPHERE_ASSERT(m_lock.IsLockedByCurrentThread()); MESOSPHERE_ASSERT(m_process != nullptr); return m_process->Is64Bit(); } Result KDebugBase::QueryMemoryInfo(ams::svc::MemoryInfo *out_memory_info, ams::svc::PageInfo *out_page_info, KProcessAddress address) { /* Lock ourselves. */ KScopedLightLock lk(m_lock); /* Check that we have a valid process. */ R_UNLESS(m_process != nullptr, svc::ResultProcessTerminated()); R_UNLESS(!m_process->IsTerminated(), svc::ResultProcessTerminated()); /* Query the mapping's info. */ KMemoryInfo info; R_TRY(m_process->GetPageTable().QueryInfo(std::addressof(info), out_page_info, address)); /* Write output. */ *out_memory_info = info.GetSvcMemoryInfo(); return ResultSuccess(); } Result KDebugBase::ReadMemory(KProcessAddress buffer, KProcessAddress address, size_t size) { /* Lock ourselves. */ KScopedLightLock lk(m_lock); /* Check that we have a valid process. */ R_UNLESS(m_process != nullptr, svc::ResultProcessTerminated()); R_UNLESS(!m_process->IsTerminated(), svc::ResultProcessTerminated()); /* Get the page tables. */ KProcessPageTable &debugger_pt = GetCurrentProcess().GetPageTable(); KProcessPageTable &target_pt = m_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.GetState() != KMemoryState_Io) { /* The memory is normal memory. */ R_TRY(target_pt.ReadDebugMemory(GetVoidPointer(buffer), cur_address, cur_size)); } else { /* The memory is IO memory. */ /* Verify that the memory is readable. */ R_UNLESS((info.GetPermission() & KMemoryPermission_UserRead) == KMemoryPermission_UserRead, svc::ResultInvalidAddress()); /* Get the physical address of the memory. */ /* NOTE: Nintendo does not verify the result of this call. */ KPhysicalAddress phys_addr; target_pt.GetPhysicalAddress(std::addressof(phys_addr), cur_address); /* Map the address as IO in the current process. */ R_TRY(debugger_pt.MapIo(util::AlignDown(GetInteger(phys_addr), PageSize), PageSize, KMemoryPermission_UserRead)); /* Get the address of the newly mapped IO. */ KProcessAddress io_address; Result query_result = debugger_pt.QueryIoMapping(std::addressof(io_address), util::AlignDown(GetInteger(phys_addr), PageSize), PageSize); MESOSPHERE_R_ASSERT(query_result); R_TRY(query_result); /* Ensure we clean up the new mapping on scope exit. */ ON_SCOPE_EXIT { MESOSPHERE_R_ABORT_UNLESS(debugger_pt.UnmapPages(util::AlignDown(GetInteger(io_address), PageSize), 1, KMemoryState_Io)); }; /* Adjust the io address for alignment. */ io_address += (GetInteger(cur_address) & (PageSize - 1)); /* Get the readable size. */ const size_t readable_size = std::min(cur_size, util::AlignDown(GetInteger(cur_address) + PageSize, PageSize) - GetInteger(cur_address)); /* Read the memory. */ switch ((GetInteger(cur_address) | readable_size) & 3) { case 0: { R_UNLESS(UserspaceAccess::ReadIoMemory32Bit(GetVoidPointer(buffer), GetVoidPointer(io_address), readable_size), svc::ResultInvalidPointer()); } break; case 2: { R_UNLESS(UserspaceAccess::ReadIoMemory16Bit(GetVoidPointer(buffer), GetVoidPointer(io_address), readable_size), svc::ResultInvalidPointer()); } break; default: { R_UNLESS(UserspaceAccess::ReadIoMemory8Bit(GetVoidPointer(buffer), GetVoidPointer(io_address), readable_size), svc::ResultInvalidPointer()); } break; } } /* Advance. */ buffer += cur_size; cur_address += cur_size; remaining -= cur_size; } return ResultSuccess(); } Result KDebugBase::WriteMemory(KProcessAddress buffer, KProcessAddress address, size_t size) { /* Lock ourselves. */ KScopedLightLock lk(m_lock); /* Check that we have a valid process. */ R_UNLESS(m_process != nullptr, svc::ResultProcessTerminated()); R_UNLESS(!m_process->IsTerminated(), svc::ResultProcessTerminated()); /* Get the page tables. */ KProcessPageTable &debugger_pt = GetCurrentProcess().GetPageTable(); KProcessPageTable &target_pt = m_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.GetState() != KMemoryState_Io) { /* The memory is normal memory. */ R_TRY(target_pt.WriteDebugMemory(cur_address, GetVoidPointer(buffer), cur_size)); } else { /* The memory is IO memory. */ /* Verify that the memory is writable. */ R_UNLESS((info.GetPermission() & KMemoryPermission_UserReadWrite) == KMemoryPermission_UserReadWrite, svc::ResultInvalidAddress()); /* Get the physical address of the memory. */ /* NOTE: Nintendo does not verify the result of this call. */ KPhysicalAddress phys_addr; target_pt.GetPhysicalAddress(std::addressof(phys_addr), cur_address); /* Map the address as IO in the current process. */ R_TRY(debugger_pt.MapIo(util::AlignDown(GetInteger(phys_addr), PageSize), PageSize, KMemoryPermission_UserReadWrite)); /* Get the address of the newly mapped IO. */ KProcessAddress io_address; Result query_result = debugger_pt.QueryIoMapping(std::addressof(io_address), util::AlignDown(GetInteger(phys_addr), PageSize), PageSize); MESOSPHERE_R_ASSERT(query_result); R_TRY(query_result); /* Ensure we clean up the new mapping on scope exit. */ ON_SCOPE_EXIT { MESOSPHERE_R_ABORT_UNLESS(debugger_pt.UnmapPages(util::AlignDown(GetInteger(io_address), PageSize), 1, KMemoryState_Io)); }; /* Adjust the io address for alignment. */ io_address += (GetInteger(cur_address) & (PageSize - 1)); /* Get the readable size. */ const size_t readable_size = std::min(cur_size, util::AlignDown(GetInteger(cur_address) + PageSize, PageSize) - GetInteger(cur_address)); /* Read the memory. */ switch ((GetInteger(cur_address) | readable_size) & 3) { case 0: { R_UNLESS(UserspaceAccess::WriteIoMemory32Bit(GetVoidPointer(io_address), GetVoidPointer(buffer), readable_size), svc::ResultInvalidPointer()); } break; case 2: { R_UNLESS(UserspaceAccess::WriteIoMemory16Bit(GetVoidPointer(io_address), GetVoidPointer(buffer), readable_size), svc::ResultInvalidPointer()); } break; default: { R_UNLESS(UserspaceAccess::WriteIoMemory8Bit(GetVoidPointer(io_address), GetVoidPointer(buffer), readable_size), svc::ResultInvalidPointer()); } break; } } /* Advance. */ buffer += cur_size; cur_address += cur_size; remaining -= cur_size; } return ResultSuccess(); } Result KDebugBase::GetRunningThreadInfo(ams::svc::LastThreadContext *out_context, u64 *out_thread_id) { /* Get the attached process. */ KScopedAutoObject process = this->GetProcess(); R_UNLESS(process.IsNotNull(), svc::ResultProcessTerminated()); /* Get the thread info. */ { KScopedSchedulerLock sl; /* Get the running thread. */ const s32 core_id = GetCurrentCoreId(); KThread *thread = process->GetRunningThread(core_id); /* Check that the thread's idle count is correct. */ R_UNLESS(process->GetRunningThreadIdleCount(core_id) == Kernel::GetScheduler(core_id).GetIdleCount(), svc::ResultNoThread()); /* Check that the thread is running on the current core. */ R_UNLESS(thread != nullptr, svc::ResultUnknownThread()); R_UNLESS(thread->GetActiveCore() == core_id, svc::ResultUnknownThread()); /* Get the thread's exception context. */ GetExceptionContext(thread)->GetSvcThreadContext(out_context); /* Get the thread's id. */ *out_thread_id = thread->GetId(); } return ResultSuccess(); } Result KDebugBase::Attach(KProcess *target) { /* Check that the process isn't null. */ MESOSPHERE_ASSERT(target != nullptr); /* 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: case KProcess::State_Crashed: break; case KProcess::State_CreatedAttached: case KProcess::State_RunningAttached: case KProcess::State_DebugBreak: return svc::ResultBusy(); case KProcess::State_Terminating: case KProcess::State_Terminated: return svc::ResultProcessTerminated(); MESOSPHERE_UNREACHABLE_DEFAULT_CASE(); } /* Set our process member, and open a reference to the target. */ m_process = target; m_process->Open(); /* Set ourselves as the process's attached object. */ m_old_process_state = m_process->SetDebugObject(this); /* Send an event for our attaching to the process. */ this->PushDebugEvent(ams::svc::DebugEvent_CreateProcess); /* Send events for attaching to each thread in the process. */ { auto end = m_process->GetThreadList().end(); for (auto it = m_process->GetThreadList().begin(); it != end; ++it) { /* 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. */ this->PushDebugEvent(ams::svc::DebugEvent_CreateThread, it->GetId(), GetInteger(it->GetThreadLocalRegionAddress())); } } } /* Send the process's jit debug info, if relevant. */ if (KEventInfo *jit_info = m_process->GetJitDebugInfo(); jit_info != nullptr) { this->EnqueueDebugEventInfo(jit_info); } /* Send an exception event to represent our attaching. */ this->PushDebugEvent(ams::svc::DebugEvent_Exception, ams::svc::DebugException_DebuggerAttached); /* Signal. */ this->NotifyAvailable(); } } return ResultSuccess(); } Result KDebugBase::BreakProcess() { /* 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(m_lock); KScopedSchedulerLock sl; /* Check that we're still attached to the process, and that it's not terminated. */ /* NOTE: Here Nintendo only checks that this->process is not nullptr. */ R_UNLESS(m_process == target.GetPointerUnsafe(), svc::ResultProcessTerminated()); R_UNLESS(!target->IsTerminated(), svc::ResultProcessTerminated()); /* Get the currently active threads. */ constexpr u64 ThreadIdNoThread = -1ll; constexpr u64 ThreadIdUnknownThread = -2ll; u64 thread_ids[cpu::NumCores]; for (size_t i = 0; i < util::size(thread_ids); ++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) { thread_ids[i] = thread->GetId(); } else { /* We found an unknown thread. */ thread_ids[i] = ThreadIdUnknownThread; } } else { /* We didn't find a thread. */ thread_ids[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. */ static_assert(util::size(thread_ids) >= 4); this->PushDebugEvent(ams::svc::DebugEvent_Exception, ams::svc::DebugException_DebuggerBreak, thread_ids[0], thread_ids[1], thread_ids[2], thread_ids[3]); /* Signal. */ this->NotifyAvailable(); /* Set the process as breaked. */ target->SetDebugBreak(); return ResultSuccess(); } Result KDebugBase::TerminateProcess() { /* Get the attached process. If we don't have one, we have nothing to do. */ KScopedAutoObject target = this->GetProcess(); R_SUCCEED_IF(target.IsNull()); /* 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 still have our process. */ if (m_process != nullptr) { /* Check that our process is the one we got earlier. */ MESOSPHERE_ASSERT(m_process == target.GetPointerUnsafe()); /* 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; } else { /* Otherwise, don't transition. */ new_state = state; } /* Detach from the process. */ target->ClearDebugObject(new_state); m_process = nullptr; /* Clear our continue flags. */ m_continue_flags = 0; } } /* Close the reference we held to the process while we were attached to it. */ target->Close(); /* Terminate the process. */ target->Terminate(); return ResultSuccess(); } Result KDebugBase::GetThreadContext(ams::svc::ThreadContext *out, u64 thread_id, u32 context_flags) { /* Lock ourselves. */ KScopedLightLock lk(m_lock); /* Get the thread from its id. */ KThread *thread = KThread::GetThreadFromId(thread_id); R_UNLESS(thread != nullptr, svc::ResultInvalidId()); ON_SCOPE_EXIT { thread->Close(); }; /* Verify that the thread is owned by our process. */ R_UNLESS(m_process == thread->GetOwnerProcess(), svc::ResultInvalidId()); /* 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. */ return this->GetThreadContextImpl(out, thread, context_flags); } } Result KDebugBase::SetThreadContext(const ams::svc::ThreadContext &ctx, u64 thread_id, u32 context_flags) { /* Lock ourselves. */ KScopedLightLock lk(m_lock); /* Get the thread from its id. */ KThread *thread = KThread::GetThreadFromId(thread_id); R_UNLESS(thread != nullptr, svc::ResultInvalidId()); ON_SCOPE_EXIT { thread->Close(); }; /* Verify that the thread is owned by our process. */ R_UNLESS(m_process == thread->GetOwnerProcess(), svc::ResultInvalidId()); /* 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; } } /* 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. */ return this->SetThreadContextImpl(ctx, thread, context_flags); } } 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(m_lock); KScopedSchedulerLock sl; /* Check that we're still attached to the process, and that it's not terminated. */ R_UNLESS(m_process == target.GetPointerUnsafe(), svc::ResultProcessTerminated()); R_UNLESS(!target->IsTerminated(), svc::ResultProcessTerminated()); /* Check that we 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(); } 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(); /* 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: { /* ... */ } break; case ams::svc::DebugEvent_CreateThread: { /* Set the thread id. */ info->thread_id = param0; /* Set the thread creation info. */ info->info.create_thread.thread_id = param0; info->info.create_thread.tls_address = param1; } break; case ams::svc::DebugEvent_ExitProcess: { /* Set the exit reason. */ info->info.exit_process.reason = static_cast(param0); /* Clear the thread id and flags. */ info->thread_id = 0; info->flags = 0; } break; case ams::svc::DebugEvent_ExitThread: { /* Set the thread id. */ info->thread_id = param0; /* Set the exit reason. */ info->info.exit_thread.reason = static_cast(param1); } break; case ams::svc::DebugEvent_Exception: { /* 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(param0); info->info.exception.exception_data_count = 0; switch (static_cast(param0)) { case ams::svc::DebugException_UndefinedInstruction: case ams::svc::DebugException_BreakPoint: case ams::svc::DebugException_UndefinedSystemCall: { info->info.exception.exception_address = param1; info->info.exception.exception_data_count = 1; info->info.exception.exception_data[0] = param2; } break; case ams::svc::DebugException_DebuggerAttached: { info->thread_id = 0; info->info.exception.exception_address = 0; } break; case ams::svc::DebugException_UserBreak: { info->info.exception.exception_address = param1; info->info.exception.exception_data_count = 3; info->info.exception.exception_data[0] = param2; info->info.exception.exception_data[1] = param3; info->info.exception.exception_data[2] = param4; } break; case ams::svc::DebugException_DebuggerBreak: { info->thread_id = 0; info->info.exception.exception_address = 0; info->info.exception.exception_data_count = 4; info->info.exception.exception_data[0] = param1; info->info.exception.exception_data[1] = param2; info->info.exception.exception_data[2] = param3; info->info.exception.exception_data[3] = param4; } 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: { info->info.exception.exception_address = param1; } break; } } break; } } return info; } void KDebugBase::PushDebugEvent(ams::svc::DebugEvent event, uintptr_t param0, uintptr_t param1, uintptr_t param2, uintptr_t param3, uintptr_t param4) { /* Create and enqueue and event. */ if (KEventInfo *new_info = CreateDebugEvent(event, param0, param1, param2, param3, param4, GetCurrentThread().GetId()); 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); } KScopedAutoObject KDebugBase::GetProcess() { /* Lock ourselves. */ KScopedLightLock lk(m_lock); return m_process; } template requires (std::same_as || std::same_as) Result KDebugBase::GetDebugEventInfoImpl(T *out) { /* Get the attached process. */ KScopedAutoObject process = this->GetProcess(); R_UNLESS(process.IsNotNull(), svc::ResultProcessTerminated()); /* 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); 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: { MESOSPHERE_ASSERT(info->info.exception.exception_data_count == 4); 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; } return ResultSuccess(); } Result KDebugBase::GetDebugEventInfo(ams::svc::lp64::DebugEventInfo *out) { return this->GetDebugEventInfoImpl(out); } Result KDebugBase::GetDebugEventInfo(ams::svc::ilp32::DebugEventInfo *out) { return this->GetDebugEventInfoImpl(out); } void KDebugBase::OnFinalizeSynchronizationObject() { /* Detach from our process, if we have one. */ { /* Get the attached process. */ KScopedAutoObject process = this->GetProcess(); /* If the process isn't null, detach. */ if (process.IsNotNull()) { /* When we're done detaching, clear the reference we opened when we attached. */ ON_SCOPE_EXIT { process->Close(); }; /* Detach. */ { /* Lock both ourselves and the target process. */ KScopedLightLock state_lk(process->GetStateLock()); KScopedLightLock list_lk(process->GetListLock()); KScopedLightLock this_lk(m_lock); /* Ensure we finalize exactly once. */ if (m_process != nullptr) { MESOSPHERE_ASSERT(m_process == process.GetPointerUnsafe()); { KScopedSchedulerLock sl; /* Detach ourselves from the process. */ process->ClearDebugObject(m_old_process_state); /* Release all threads. */ const bool resume = (process->GetState() != KProcess::State_Crashed); { auto end = process->GetThreadList().end(); for (auto it = process->GetThreadList().begin(); it != end; ++it) { if (resume) { /* If the process isn't crashed, resume threads. */ it->Resume(KThread::SuspendType_Debug); } else { /* Otherwise, suspend them. */ it->RequestSuspend(KThread::SuspendType_Debug); } } } /* Clear our process. */ m_process = nullptr; } } } } } /* 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 { KScopedSchedulerLock sl; return (!m_event_info_list.empty()) || m_process == nullptr || m_process->IsTerminated(); } Result KDebugBase::ProcessDebugEvent(ams::svc::DebugEvent event, uintptr_t param0, uintptr_t param1, uintptr_t param2, uintptr_t param3, uintptr_t param4) { /* 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()); return 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, param0, param1, param2, param3, param4); debug->NotifyAvailable(); /* Set the process as breaked. */ process->SetDebugBreak(); /* If the event is an exception, set the result. */ 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. */ return GetCurrentThread().GetDebugExceptionResult(); } else { /* We don't have a debug object, so stop processing the exception. */ return svc::ResultStopProcessingException(); } } return ResultSuccess(); } Result KDebugBase::OnDebugEvent(ams::svc::DebugEvent event, uintptr_t param0, uintptr_t param1, uintptr_t param2, uintptr_t param3, uintptr_t param4) { if (KProcess *process = GetCurrentProcessPointer(); process != nullptr && process->IsAttachedToDebugger()) { return ProcessDebugEvent(event, param0, param1, param2, param3, param4); } return ResultSuccess(); } 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) { debug->PushDebugEvent(ams::svc::DebugEvent_ExitProcess, ams::svc::ProcessExitReason_ExitProcess); debug->NotifyAvailable(); } } return ResultSuccess(); } 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) { debug->PushDebugEvent(ams::svc::DebugEvent_ExitProcess, ams::svc::ProcessExitReason_TerminateProcess); debug->NotifyAvailable(); } } return ResultSuccess(); } 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. */ R_TRY(OnDebugEvent(ams::svc::DebugEvent_ExitThread, thread->GetId(), thread->IsTerminationRequested() ? ams::svc::ThreadExitReason_TerminateThread : ams::svc::ThreadExitReason_ExitThread)); } return ResultSuccess(); } }