kern SvcGetDebugThreadContext, SvcSetDebugThreadContext

This commit is contained in:
Michael Scire 2020-07-31 02:49:43 -07:00 committed by SciresM
parent 3afd723b92
commit 5c4fbf5c67
10 changed files with 363 additions and 4 deletions

View file

@ -34,6 +34,12 @@ namespace ams::kern::arch::arm64 {
virtual ~KDebug() { /* ... */ }
static void PostDestroy(uintptr_t arg) { /* ... */ }
public:
virtual Result GetThreadContextImpl(ams::svc::ThreadContext *out, KThread *thread, u32 context_flags) override;
virtual Result SetThreadContextImpl(const ams::svc::ThreadContext &ctx, KThread *thread, u32 context_flags) override;
private:
Result GetFpuContext(ams::svc::ThreadContext *out, KThread *thread, u32 context_flags);
Result SetFpuContext(const ams::svc::ThreadContext &ctx, KThread *thread, u32 context_flags);
public:
static uintptr_t GetProgramCounter(const KThread &thread);
static void SetPreviousProgramCounter();

View file

@ -74,6 +74,8 @@ namespace ams::kern::arch::arm64 {
void CloneFpuStatus();
void SetFpuRegisters(const u128 *v, bool is_64_bit);
const u128 *GetFpuRegisters() const { return this->fpu_registers; }
public:
static void OnThreadTerminating(const KThread *thread);

View file

@ -34,6 +34,8 @@ namespace ams::kern {
public:
explicit KDebugBase() { /* ... */ }
virtual ~KDebugBase() { /* ... */ }
protected:
bool Is64Bit() const;
public:
void Initialize();
@ -47,6 +49,12 @@ namespace ams::kern {
Result ReadMemory(KProcessAddress buffer, KProcessAddress address, size_t size);
Result WriteMemory(KProcessAddress buffer, KProcessAddress address, size_t size);
Result GetThreadContext(ams::svc::ThreadContext *out, u64 thread_id, u32 context_flags);
Result SetThreadContext(const ams::svc::ThreadContext &ctx, u64 thread_id, u32 context_flags);
virtual Result GetThreadContextImpl(ams::svc::ThreadContext *out, KThread *thread, u32 context_flags) = 0;
virtual Result SetThreadContextImpl(const ams::svc::ThreadContext &ctx, KThread *thread, u32 context_flags) = 0;
Result GetRunningThreadInfo(ams::svc::LastThreadContext *out_context, u64 *out_thread_id);
Result GetDebugEventInfo(ams::svc::lp64::DebugEventInfo *out);

View file

@ -274,6 +274,11 @@ namespace ams::kern {
return this->GetStackParameters().is_calling_svc;
}
ALWAYS_INLINE u8 GetSvcId() const {
MESOSPHERE_ASSERT_THIS();
return this->GetStackParameters().current_svc_id;
}
ALWAYS_INLINE void RegisterDpc(DpcFlag flag) {
this->GetStackParameters().dpc_flags |= flag;
}

View file

@ -72,6 +72,7 @@ namespace ams::kern::svc {
/* 517 */ using ::ams::svc::ResultInvalidProcessId;
/* 518 */ using ::ams::svc::ResultInvalidThreadId;
/* 519 */ using ::ams::svc::ResultInvalidId;
/* 520 */ using ::ams::svc::ResultProcessTerminated;
}

View file

@ -36,6 +36,9 @@ namespace ams::kern::arch::arm64 {
(((1ul << 2) - 1) << 1); /* Privileged Access Control. */
static_assert(ForbiddenWatchPointFlagsMask == 0xFFFFFFFF00F0E006ul);
constexpr inline u32 El0PsrMask = 0xFF0FFE20;
}
uintptr_t KDebug::GetProgramCounter(const KThread &thread) {
@ -64,6 +67,179 @@ namespace ams::kern::arch::arm64 {
}
}
Result KDebug::GetThreadContextImpl(ams::svc::ThreadContext *out, KThread *thread, u32 context_flags) {
MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread());
MESOSPHERE_ASSERT(thread != GetCurrentThreadPointer());
/* Get the exception context. */
const KExceptionContext *e_ctx = GetExceptionContext(thread);
/* If general registers are requested, get them. */
if ((context_flags & ams::svc::ThreadContextFlag_General) != 0) {
if (!thread->IsCallingSvc() || thread->GetSvcId() == svc::SvcId_ReturnFromException) {
if (this->Is64Bit()) {
/* Get X0-X28. */
for (auto i = 0; i <= 28; ++i) {
out->r[i] = e_ctx->x[i];
}
} else {
/* Get R0-R12. */
for (auto i = 0; i <= 12; ++i) {
out->r[i] = static_cast<u32>(e_ctx->x[i]);
}
}
}
}
/* If control flags are requested, get them. */
if ((context_flags & ams::svc::ThreadContextFlag_Control) != 0) {
if (this->Is64Bit()) {
out->fp = e_ctx->x[29];
out->lr = e_ctx->x[30];
out->sp = e_ctx->sp;
out->pc = e_ctx->pc;
out->pstate = (e_ctx->psr & El0PsrMask);
/* Adjust PC if we should. */
if (e_ctx->write == 0 && thread->IsCallingSvc()) {
out->pc -= sizeof(u32);
}
out->tpidr = e_ctx->tpidr;
} else {
out->r[11] = static_cast<u32>(e_ctx->x[11]);
out->r[13] = static_cast<u32>(e_ctx->x[13]);
out->r[14] = static_cast<u32>(e_ctx->x[14]);
out->lr = 0;
out->sp = 0;
out->pc = e_ctx->pc;
out->pstate = (e_ctx->psr & El0PsrMask);
/* Adjust PC if we should. */
if (e_ctx->write == 0 && thread->IsCallingSvc()) {
out->pc -= (e_ctx->psr & 0x20) ? sizeof(u16) : sizeof(u32);
}
out->tpidr = static_cast<u32>(e_ctx->tpidr);
}
}
/* Get the FPU context. */
return this->GetFpuContext(out, thread, context_flags);
}
Result KDebug::SetThreadContextImpl(const ams::svc::ThreadContext &ctx, KThread *thread, u32 context_flags) {
MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread());
MESOSPHERE_ASSERT(thread != GetCurrentThreadPointer());
/* Get the exception context. */
KExceptionContext *e_ctx = GetExceptionContext(thread);
/* If general registers are requested, set them. */
if ((context_flags & ams::svc::ThreadContextFlag_General) != 0) {
if (this->Is64Bit()) {
/* Set X0-X28. */
for (auto i = 0; i <= 28; ++i) {
e_ctx->x[i] = ctx.r[i];
}
} else {
/* Set R0-R12. */
for (auto i = 0; i <= 12; ++i) {
e_ctx->x[i] = static_cast<u32>(ctx.r[i]);
}
}
}
/* If control flags are requested, set them. */
if ((context_flags & ams::svc::ThreadContextFlag_Control) != 0) {
/* Mark ourselve as having adjusted pc. */
e_ctx->write = 1;
if (this->Is64Bit()) {
e_ctx->x[29] = ctx.fp;
e_ctx->x[30] = ctx.lr;
e_ctx->sp = ctx.sp;
e_ctx->pc = ctx.pc;
e_ctx->psr = ((ctx.pstate & El0PsrMask) | (e_ctx->psr & ~El0PsrMask));
e_ctx->tpidr = ctx.tpidr;
} else {
e_ctx->x[13] = static_cast<u32>(ctx.r[13]);
e_ctx->x[14] = static_cast<u32>(ctx.r[14]);
e_ctx->x[30] = 0;
e_ctx->sp = 0;
e_ctx->pc = static_cast<u32>(ctx.pc);
e_ctx->psr = ((ctx.pstate & El0PsrMask) | (e_ctx->psr & ~El0PsrMask));
e_ctx->tpidr = ctx.tpidr;
}
}
/* Set the FPU context. */
return this->SetFpuContext(ctx, thread, context_flags);
}
Result KDebug::GetFpuContext(ams::svc::ThreadContext *out, KThread *thread, u32 context_flags) {
MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread());
MESOSPHERE_ASSERT(thread != GetCurrentThreadPointer());
/* Succeed if there's nothing to do. */
R_SUCCEED_IF((context_flags & (ams::svc::ThreadContextFlag_Fpu | ams::svc::ThreadContextFlag_FpuControl)) == 0);
/* Get the thread context. */
KThreadContext *t_ctx = std::addressof(thread->GetContext());
/* Get the FPU control registers, if required. */
if ((context_flags & ams::svc::ThreadContextFlag_FpuControl) != 0) {
out->fpsr = t_ctx->GetFpsr();
out->fpcr = t_ctx->GetFpcr();
}
/* Get the FPU registers, if required. */
if ((context_flags & ams::svc::ThreadContextFlag_Fpu) != 0) {
static_assert(util::size(ams::svc::ThreadContext{}.v) == KThreadContext::NumFpuRegisters);
const u128 *f = t_ctx->GetFpuRegisters();
if (this->Is64Bit()) {
for (size_t i = 0; i < KThreadContext::NumFpuRegisters; ++i) {
out->v[i] = f[i];
}
} else {
for (size_t i = 0; i < KThreadContext::NumFpuRegisters / 2; ++i) {
out->v[i] = f[i];
}
for (size_t i = KThreadContext::NumFpuRegisters / 2; i < KThreadContext::NumFpuRegisters; ++i) {
out->v[i] = 0;
}
}
}
return ResultSuccess();
}
Result KDebug::SetFpuContext(const ams::svc::ThreadContext &ctx, KThread *thread, u32 context_flags) {
MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread());
MESOSPHERE_ASSERT(thread != GetCurrentThreadPointer());
/* Succeed if there's nothing to do. */
R_SUCCEED_IF((context_flags & (ams::svc::ThreadContextFlag_Fpu | ams::svc::ThreadContextFlag_FpuControl)) == 0);
/* Get the thread context. */
KThreadContext *t_ctx = std::addressof(thread->GetContext());
/* Set the FPU control registers, if required. */
if ((context_flags & ams::svc::ThreadContextFlag_FpuControl) != 0) {
t_ctx->SetFpsr(ctx.fpsr);
t_ctx->SetFpcr(ctx.fpcr);
}
/* Set the FPU registers, if required. */
if ((context_flags & ams::svc::ThreadContextFlag_Fpu) != 0) {
static_assert(util::size(ams::svc::ThreadContext{}.v) == KThreadContext::NumFpuRegisters);
t_ctx->SetFpuRegisters(ctx.v, this->Is64Bit());
}
return ResultSuccess();
}
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);
}

View file

@ -189,6 +189,18 @@ namespace ams::kern::arch::arm64 {
this->SetFpsr(psr);
}
void KThreadContext::SetFpuRegisters(const u128 *v, bool is_64_bit) {
if (is_64_bit) {
for (size_t i = 0; i < KThreadContext::NumFpuRegisters; ++i) {
this->fpu_registers[i] = v[i];
}
} else {
for (size_t i = 0; i < KThreadContext::NumFpuRegisters / 2; ++i) {
this->fpu_registers[i] = v[i];
}
}
}
void GetUserContext(ams::svc::ThreadContext *out, const KThread *thread) {
MESOSPHERE_ASSERT(KScheduler::IsSchedulerLockedByCurrentThread());
MESOSPHERE_ASSERT(thread->IsSuspended());
@ -256,6 +268,9 @@ namespace ams::kern::arch::arm64 {
for (size_t i = 0; i < KThreadContext::NumFpuRegisters / 2; ++i) {
out->v[i] = f[i];
}
for (size_t i = KThreadContext::NumFpuRegisters / 2; i < KThreadContext::NumFpuRegisters; ++i) {
out->v[i] = 0;
}
}
/* Copy fpcr/fpsr. */

View file

@ -31,6 +31,13 @@ namespace ams::kern {
this->continue_flags = 0;
}
bool KDebugBase::Is64Bit() const {
MESOSPHERE_ASSERT(this->lock.IsLockedByCurrentThread());
MESOSPHERE_ASSERT(this->process != nullptr);
return this->process->Is64Bit();
}
Result KDebugBase::QueryMemoryInfo(ams::svc::MemoryInfo *out_memory_info, ams::svc::PageInfo *out_page_info, KProcessAddress address) {
/* Lock ourselves. */
KScopedLightLock lk(this->lock);
@ -459,6 +466,105 @@ namespace ams::kern {
return ResultSuccess();
}
Result KDebugBase::GetThreadContext(ams::svc::ThreadContext *out, u64 thread_id, u32 context_flags) {
/* Lock ourselves. */
KScopedLightLock lk(this->lock);
/* Get the thread from its id. */
KScopedAutoObject thread = KThread::GetThreadFromId(thread_id);
R_UNLESS(thread.IsNotNull(), svc::ResultInvalidId());
/* Verify that the thread is owned by our process. */
R_UNLESS(this->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.GetPointerUnsafe() != 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<s32>(cpu::NumCores); ++i) {
if (thread.GetPointerUnsafe() == Kernel::GetCurrentContext(i).current_thread) {
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.GetPointerUnsafe(), context_flags);
}
}
Result KDebugBase::SetThreadContext(const ams::svc::ThreadContext &ctx, u64 thread_id, u32 context_flags) {
/* Lock ourselves. */
KScopedLightLock lk(this->lock);
/* Get the thread from its id. */
KScopedAutoObject thread = KThread::GetThreadFromId(thread_id);
R_UNLESS(thread.IsNotNull(), svc::ResultInvalidId());
/* Verify that the thread is owned by our process. */
R_UNLESS(this->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.GetPointerUnsafe() != 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<s32>(cpu::NumCores); ++i) {
if (thread.GetPointerUnsafe() == Kernel::GetCurrentContext(i).current_thread) {
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()) {
R_UNLESS(thread->GetSvcId() != svc::SvcId_Break, svc::ResultInvalidState());
R_UNLESS(thread->GetSvcId() != svc::SvcId_ReturnFromException, svc::ResultInvalidState());
}
/* Set the thread context. */
return this->SetThreadContextImpl(ctx, thread.GetPointerUnsafe(), 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();

View file

@ -163,6 +163,45 @@ namespace ams::kern::svc {
return ResultSuccess();
}
Result GetDebugThreadContext(KUserPointer<ams::svc::ThreadContext *> out_context, ams::svc::Handle debug_handle, uint64_t thread_id, uint32_t context_flags) {
/* Validate the context flags. */
R_UNLESS((context_flags | ams::svc::ThreadContextFlag_All) == ams::svc::ThreadContextFlag_All, svc::ResultInvalidEnumValue());
/* Get the debug object. */
KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle);
R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle());
/* Get the thread context. */
ams::svc::ThreadContext context = {};
R_TRY(debug->GetThreadContext(std::addressof(context), thread_id, context_flags));
/* Copy the context to userspace. */
R_TRY(out_context.CopyFrom(std::addressof(context)));
return ResultSuccess();
}
Result SetDebugThreadContext(ams::svc::Handle debug_handle, uint64_t thread_id, KUserPointer<const ams::svc::ThreadContext *> user_context, uint32_t context_flags) {
/* Only allow invoking the svc on development hardware. */
R_UNLESS(KTargetSystem::IsDebugMode(), svc::ResultNotImplemented());
/* Validate the context flags. */
R_UNLESS((context_flags | ams::svc::ThreadContextFlag_All) == ams::svc::ThreadContextFlag_All, svc::ResultInvalidEnumValue());
/* Copy the thread context from userspace. */
ams::svc::ThreadContext context;
R_TRY(user_context.CopyTo(std::addressof(context)));
/* Get the debug object. */
KScopedAutoObject debug = GetCurrentProcess().GetHandleTable().GetObject<KDebug>(debug_handle);
R_UNLESS(debug.IsNotNull(), svc::ResultInvalidHandle());
/* Set the thread context. */
R_TRY(debug->SetThreadContext(context, thread_id, context_flags));
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<KDebug>(debug_handle);
@ -374,11 +413,11 @@ namespace ams::kern::svc {
}
Result GetDebugThreadContext64(KUserPointer<ams::svc::ThreadContext *> out_context, ams::svc::Handle debug_handle, uint64_t thread_id, uint32_t context_flags) {
MESOSPHERE_PANIC("Stubbed SvcGetDebugThreadContext64 was called.");
return GetDebugThreadContext(out_context, debug_handle, thread_id, context_flags);
}
Result SetDebugThreadContext64(ams::svc::Handle debug_handle, uint64_t thread_id, KUserPointer<const ams::svc::ThreadContext *> context, uint32_t context_flags) {
MESOSPHERE_PANIC("Stubbed SvcSetDebugThreadContext64 was called.");
return SetDebugThreadContext(debug_handle, thread_id, context, context_flags);
}
Result QueryDebugProcessMemory64(KUserPointer<ams::svc::lp64::MemoryInfo *> out_memory_info, ams::svc::PageInfo *out_page_info, ams::svc::Handle debug_handle, ams::svc::Address address) {
@ -428,11 +467,11 @@ namespace ams::kern::svc {
}
Result GetDebugThreadContext64From32(KUserPointer<ams::svc::ThreadContext *> out_context, ams::svc::Handle debug_handle, uint64_t thread_id, uint32_t context_flags) {
MESOSPHERE_PANIC("Stubbed SvcGetDebugThreadContext64From32 was called.");
return GetDebugThreadContext(out_context, debug_handle, thread_id, context_flags);
}
Result SetDebugThreadContext64From32(ams::svc::Handle debug_handle, uint64_t thread_id, KUserPointer<const ams::svc::ThreadContext *> context, uint32_t context_flags) {
MESOSPHERE_PANIC("Stubbed SvcSetDebugThreadContext64From32 was called.");
return SetDebugThreadContext(debug_handle, thread_id, context, context_flags);
}
Result QueryDebugProcessMemory64From32(KUserPointer<ams::svc::ilp32::MemoryInfo *> out_memory_info, ams::svc::PageInfo *out_page_info, ams::svc::Handle debug_handle, ams::svc::Address address) {

View file

@ -75,6 +75,7 @@ namespace ams::svc {
R_DEFINE_ERROR_RESULT(InvalidProcessId, 517);
R_DEFINE_ERROR_RESULT(InvalidThreadId, 518);
R_DEFINE_ERROR_RESULT(InvalidId, 519);
R_DEFINE_ERROR_RESULT(ProcessTerminated, 520);
}