diff --git a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_debug.hpp b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_debug.hpp index 58fa54964..9151e9fd4 100644 --- a/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_debug.hpp +++ b/libraries/libmesosphere/include/mesosphere/arch/arm64/kern_k_debug.hpp @@ -35,6 +35,10 @@ namespace ams::kern::arch::arm64 { static void PostDestroy(uintptr_t arg) { /* ... */ } public: + static uintptr_t GetProgramCounter(const KThread &thread); + static void SetPreviousProgramCounter(); + + static Result BreakIfAttached(ams::svc::BreakReason break_reason, uintptr_t address, size_t size); static Result SetHardwareBreakPoint(ams::svc::HardwareBreakPointRegisterName name, u64 flags, u64 value); /* TODO: This is a placeholder definition. */ diff --git a/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp b/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp index 1fc1af444..23abf02d2 100644 --- a/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp +++ b/libraries/libmesosphere/include/mesosphere/kern_k_process.hpp @@ -193,6 +193,8 @@ namespace ams::kern { KProcess::State SetDebugObject(void *debug_object); void ClearDebugObject(KProcess::State state); + bool EnterJitDebug(ams::svc::DebugEvent event, ams::svc::DebugException exception, uintptr_t param1 = 0, uintptr_t param2 = 0, uintptr_t param3 = 0, uintptr_t param4 = 0); + KEventInfo *GetJitDebugInfo(); void ClearJitDebugInfo(); diff --git a/libraries/libmesosphere/source/arch/arm64/kern_k_debug.cpp b/libraries/libmesosphere/source/arch/arm64/kern_k_debug.cpp index 034fdce86..72af04700 100644 --- a/libraries/libmesosphere/source/arch/arm64/kern_k_debug.cpp +++ b/libraries/libmesosphere/source/arch/arm64/kern_k_debug.cpp @@ -38,6 +38,36 @@ namespace ams::kern::arch::arm64 { static_assert(ForbiddenWatchPointFlagsMask == 0xFFFFFFFF00F0E006ul); } + uintptr_t KDebug::GetProgramCounter(const KThread &thread) { + return GetExceptionContext(std::addressof(thread))->pc; + } + + void KDebug::SetPreviousProgramCounter() { + /* Get the current thread. */ + KThread *thread = GetCurrentThreadPointer(); + MESOSPHERE_ASSERT(thread->IsCallingSvc()); + + /* Get the exception context. */ + KExceptionContext *e_ctx = GetExceptionContext(thread); + + /* Set the previous pc. */ + if (e_ctx->write == 0) { + /* Subtract from the program counter. */ + if (thread->GetOwnerProcess()->Is64Bit()) { + e_ctx->pc -= sizeof(u32); + } else { + e_ctx->pc -= (e_ctx->psr & 0x20) ? sizeof(u16) : sizeof(u32); + } + + /* Mark that we've set. */ + e_ctx->write = 1; + } + } + + Result KDebug::BreakIfAttached(ams::svc::BreakReason break_reason, uintptr_t address, size_t size) { + return KDebugBase::OnDebugEvent(ams::svc::DebugEvent_Exception, ams::svc::DebugException_UserBreak, GetProgramCounter(GetCurrentThread()), break_reason, address, size); + } + #define MESOSPHERE_SET_HW_BREAK_POINT(ID, FLAGS, VALUE) \ ({ \ cpu::SetDbgBcr##ID##El1(0); \ diff --git a/libraries/libmesosphere/source/kern_k_process.cpp b/libraries/libmesosphere/source/kern_k_process.cpp index 2c64ac968..f6179ce2a 100644 --- a/libraries/libmesosphere/source/kern_k_process.cpp +++ b/libraries/libmesosphere/source/kern_k_process.cpp @@ -465,7 +465,7 @@ namespace ams::kern { KScopedSchedulerLock sl; - if (this->state == State_Running || this->state == State_RunningAttached|| this->state == State_Crashed || this->state == State_DebugBreak) { + if (this->state == State_Running || this->state == State_RunningAttached || this->state == State_Crashed || this->state == State_DebugBreak) { this->ChangeState(State_Terminating); needs_terminate = true; } @@ -999,6 +999,90 @@ namespace ams::kern { } } + bool KProcess::EnterJitDebug(ams::svc::DebugEvent event, ams::svc::DebugException exception, uintptr_t param1, uintptr_t param2, uintptr_t param3, uintptr_t param4) { + /* Check that we're the current process. */ + MESOSPHERE_ASSERT_THIS(); + MESOSPHERE_ASSERT(this == GetCurrentProcessPointer()); + + /* If we aren't allowed to enter jit debug, don't. */ + if ((this->flags & ams::svc::CreateProcessFlag_EnableDebug) == 0) { + return false; + } + + /* We're the current process, so we should be some kind of running. */ + MESOSPHERE_ASSERT(this->state != State_Created); + MESOSPHERE_ASSERT(this->state != State_CreatedAttached); + MESOSPHERE_ASSERT(this->state != State_Terminated); + + /* Try to enter JIT debug. */ + while (true) { + /* Lock ourselves and the scheduler. */ + KScopedLightLock lk(this->state_lock); + KScopedLightLock list_lk(this->list_lock); + KScopedSchedulerLock sl; + + /* If we're attached to a debugger, we're necessarily in debug. */ + if (this->IsAttachedToDebugger()) { + return true; + } + + /* If the current thread is terminating, we can't enter debug. */ + if (GetCurrentThread().IsTerminationRequested()) { + return false; + } + + /* We're not attached to debugger, so check that. */ + MESOSPHERE_ASSERT(this->state != State_RunningAttached); + MESOSPHERE_ASSERT(this->state != State_DebugBreak); + + /* If we're terminating, we can't enter debug. */ + if (this->state != State_Running && this->state != State_Crashed) { + MESOSPHERE_ASSERT(this->state == State_Terminating); + return false; + } + + /* If the current thread is suspended, retry. */ + if (GetCurrentThread().IsSuspended()) { + continue; + } + + /* Suspend all our threads. */ + { + auto end = this->GetThreadList().end(); + for (auto it = this->GetThreadList().begin(); it != end; ++it) { + it->RequestSuspend(KThread::SuspendType_Debug); + } + } + + /* Change our state to crashed. */ + this->ChangeState(State_Crashed); + + /* Enter jit debug. */ + this->is_jit_debug = true; + this->jit_debug_event_type = event; + this->jit_debug_exception_type = exception; + this->jit_debug_params[0] = param1; + this->jit_debug_params[1] = param2; + this->jit_debug_params[2] = param3; + this->jit_debug_params[3] = param4; + this->jit_debug_thread_id = GetCurrentThread().GetId(); + + /* Exit our retry loop. */ + break; + } + + /* Check if our state indicates we're in jit debug. */ + { + KScopedSchedulerLock sl; + + if (this->state == State_Running || this->state == State_RunningAttached || this->state == State_Crashed || this->state == State_DebugBreak) { + return true; + } + } + + return false; + } + KEventInfo *KProcess::GetJitDebugInfo() { MESOSPHERE_ASSERT_THIS(); MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread()); diff --git a/libraries/libmesosphere/source/svc/kern_svc_exception.cpp b/libraries/libmesosphere/source/svc/kern_svc_exception.cpp index 6932b6b50..f4ad17c0e 100644 --- a/libraries/libmesosphere/source/svc/kern_svc_exception.cpp +++ b/libraries/libmesosphere/source/svc/kern_svc_exception.cpp @@ -21,25 +21,54 @@ namespace ams::kern::svc { namespace { - void Break(ams::svc::BreakReason break_reason, uintptr_t address, size_t size) { - /* Log for debug that Break was called. */ - MESOSPHERE_LOG("%s: Break(%08x, %016lx, %zu)\n", GetCurrentProcess().GetName(), static_cast(break_reason), address, size); + [[maybe_unused]] void PrintBreak(ams::svc::BreakReason break_reason) { + /* Print that break was called. */ + MESOSPHERE_RELEASE_LOG("%s: svc::Break(%d) was called, pid=%ld, tid=%ld\n", GetCurrentProcess().GetName(), static_cast(break_reason), GetCurrentProcess().GetId(), GetCurrentThread().GetId()); - /* If the current process is attached to debugger, notify it. */ + /* Print the current thread's registers. */ + /* TODO: KDebug::PrintRegisters(); */ + + /* Print a backtrace. */ + /* TODO: KDebug::PrintBacktrace(); */ + } + + void Break(ams::svc::BreakReason break_reason, uintptr_t address, size_t size) { + /* Determine whether the break is only a notification. */ + const bool is_notification = (break_reason & ams::svc::BreakReason_NotificationOnlyFlag) != 0; + + /* If the break isn't a notification, print it. */ + if (!is_notification) { + #ifdef MESOSPHERE_BUILD_FOR_DEBUGGING + PrintBreak(break_reason); + #endif + } + + /* If the current process is attached to debugger, try to notify it. */ if (GetCurrentProcess().IsAttachedToDebugger()) { - MESOSPHERE_UNIMPLEMENTED(); + if (R_SUCCEEDED(KDebug::BreakIfAttached(break_reason, address, size))) { + /* If we attached, set the pc to the instruction before the current one and return. */ + KDebug::SetPreviousProgramCounter(); + return; + } } /* If the break is only a notification, we're done. */ - if ((break_reason & ams::svc::BreakReason_NotificationOnlyFlag) != 0) { + if (is_notification) { return; } - /* TODO */ - if (size == sizeof(u32)) { - MESOSPHERE_LOG("DEBUG: %08x\n", *reinterpret_cast(address)); + /* Print that break was called. */ + MESOSPHERE_RELEASE_LOG("Break() called. %016lx\n", GetCurrentProcess().GetProgramId()); + + /* Try to enter JIT debug state. */ + if (GetCurrentProcess().EnterJitDebug(ams::svc::DebugEvent_Exception, ams::svc::DebugException_UserBreak, KDebug::GetProgramCounter(GetCurrentThread()), break_reason, address, size)) { + /* We entered JIT debug, so set the pc to the instruction before the current one and return. */ + KDebug::SetPreviousProgramCounter(); + return; } - MESOSPHERE_PANIC("Break was called\n"); + + /* Exit the current process. */ + GetCurrentProcess().Exit(); } }