KThread based debug

This commit starts a big refactor to the original debugger PR by merryhime. The debugger now interfaces with KThreads instead of the ExecutionContext.

The corresponding KThread debug functions properly suspend/resume the thread and call the underlying debug related function in the ExecutionContext.

I also added debugging support to the AppleHV ExecutionContext.
This commit is contained in:
svc64 2023-09-25 11:00:07 +03:00
parent 841aa89581
commit ac438d6572
17 changed files with 157 additions and 144 deletions

View file

@ -175,6 +175,11 @@ namespace ARMeilleure.Instructions
ExecutionContext context = GetContext(); ExecutionContext context = GetContext();
if (context.DebugStopped == 1)
{
return false;
}
context.CheckInterrupt(); context.CheckInterrupt();
Statistics.ResumeTimer(); Statistics.ResumeTimer();

View file

@ -101,10 +101,8 @@ namespace ARMeilleure.State
private readonly ExceptionCallback _supervisorCallback; private readonly ExceptionCallback _supervisorCallback;
private readonly ExceptionCallback _undefinedCallback; private readonly ExceptionCallback _undefinedCallback;
internal int _debugState = (int)DebugState.Running; internal int ShouldStep;
internal int _shouldStep = 0; internal int DebugStopped;
internal ManualResetEvent _debugHalt = new ManualResetEvent(true);
internal Barrier _stepBarrier = new Barrier(2);
// This is only valid while debugging is enabled. // This is only valid while debugging is enabled.
public ulong DebugPc; public ulong DebugPc;
@ -160,34 +158,26 @@ namespace ARMeilleure.State
public void DebugStop() public void DebugStop()
{ {
_debugHalt.Reset(); if (Interlocked.CompareExchange(ref DebugStopped, 1, 0) == 0)
Interlocked.CompareExchange(ref _debugState, (int)DebugState.Stopping, (int)DebugState.Running); {
RequestInterrupt();
}
} }
public bool DebugStep() public bool DebugStep()
{ {
if (_debugState != (int)DebugState.Stopped) if (DebugStopped != 1)
{ {
return false; return false;
} }
_shouldStep = 1; ShouldStep = 1;
_debugHalt.Set();
_stepBarrier.SignalAndWait();
_debugHalt.Reset();
_stepBarrier.SignalAndWait();
return true; return true;
} }
public void DebugContinue() public void DebugContinue()
{ {
_debugHalt.Set(); Interlocked.CompareExchange(ref DebugStopped, 0, 1);
}
public DebugState GetDebugState()
{
return (DebugState)_debugState;
} }
internal void OnBreak(ulong address, int imm) internal void OnBreak(ulong address, int imm)

View file

@ -147,21 +147,14 @@ namespace ARMeilleure.Translation
{ {
context.DebugPc = ExecuteSingle(context, context.DebugPc); context.DebugPc = ExecuteSingle(context, context.DebugPc);
while (context._debugState != (int)DebugState.Running) while (context.DebugStopped == 1)
{ {
Interlocked.CompareExchange(ref context._debugState, (int)DebugState.Stopped, (int)DebugState.Stopping); if (Interlocked.CompareExchange(ref context.ShouldStep, 0, 1) == 1)
context._debugHalt.WaitOne();
if (Interlocked.CompareExchange(ref context._shouldStep, 0, 1) == 1)
{ {
context.DebugPc = Step(context, context.DebugPc); context.DebugPc = Step(context, context.DebugPc);
context.RequestInterrupt();
context._stepBarrier.SignalAndWait();
context._stepBarrier.SignalAndWait();
}
else
{
Interlocked.CompareExchange(ref context._debugState, (int)DebugState.Running, (int)DebugState.Stopped);
} }
context.CheckInterrupt();
} }
} }
while (context.Running && context.DebugPc != 0); while (context.Running && context.DebugPc != 0);
@ -222,7 +215,7 @@ namespace ARMeilleure.Translation
return nextAddr; return nextAddr;
} }
public ulong Step(State.ExecutionContext context, ulong address) private ulong Step(State.ExecutionContext context, ulong address)
{ {
TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true); TranslatedFunction func = Translate(address, context.ExecutionMode, highCq: false, singleStep: true);

View file

@ -70,6 +70,8 @@ namespace Ryujinx.Cpu.AppleHv
private readonly ICounter _counter; private readonly ICounter _counter;
private readonly IHvExecutionContext _shadowContext; private readonly IHvExecutionContext _shadowContext;
private IHvExecutionContext _impl; private IHvExecutionContext _impl;
private int _shouldStep;
private int _debugStopped;
private readonly ExceptionCallbacks _exceptionCallbacks; private readonly ExceptionCallbacks _exceptionCallbacks;
@ -131,22 +133,37 @@ namespace Ryujinx.Cpu.AppleHv
} }
/// <inheritdoc/> /// <inheritdoc/>
public void DebugStop() => _impl.DebugStop(); public void DebugStop()
{
if (Interlocked.CompareExchange(ref _debugStopped, 1, 0) == 0)
{
RequestInterrupt();
}
}
/// <inheritdoc/> /// <inheritdoc/>
public bool DebugStep() => _impl.DebugStep(); public bool DebugStep()
{
if (_debugStopped != 1)
{
return false;
}
_shouldStep = 1;
return true;
}
/// <inheritdoc/> /// <inheritdoc/>
public void DebugContinue() => _impl.DebugContinue(); public void DebugContinue()
{
/// <inheritdoc/> Interlocked.CompareExchange(ref _debugStopped, 0, 1);
public DebugState GetDebugState() => _impl.GetDebugState(); }
/// <inheritdoc/> /// <inheritdoc/>
public ulong DebugPc public ulong DebugPc
{ {
get => _impl.DebugPc; get => Pc;
set => _impl.DebugPc = value; set => Pc = value;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -164,6 +181,18 @@ namespace Ryujinx.Cpu.AppleHv
while (Running) while (Running)
{ {
if (Interlocked.CompareExchange(ref _shouldStep, 0, 1) == 1)
{
uint currentEl = Pstate & ~(0xfffffff0);
if (currentEl == 0b0101)
{
HvApi.hv_vcpu_get_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
spsr |= (1 << 21);
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.SPSR_EL1, spsr);
}
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.MDSCR_EL1, 1);
}
HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError(); HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
HvExitReason reason = vcpu.ExitInfo->Reason; HvExitReason reason = vcpu.ExitInfo->Reason;
@ -231,6 +260,21 @@ namespace Ryujinx.Cpu.AppleHv
SupervisorCallHandler(elr - 4UL, id); SupervisorCallHandler(elr - 4UL, id);
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu); vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
break; break;
case ExceptionClass.SoftwareStepLowerEl:
HvApi.hv_vcpu_get_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, out ulong spsr).ThrowOnError();
spsr &= ~((ulong)(1 << 21));
HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.SPSR_EL1, spsr).ThrowOnError();
HvApi.hv_vcpu_set_sys_reg(vcpuHandle, HvSysReg.MDSCR_EL1, 0);
ReturnToPool(vcpu);
InterruptHandler();
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
break;
case ExceptionClass.BrkAarch64:
ReturnToPool(vcpu);
BreakHandler(elr, (ushort)esr);
InterruptHandler();
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
break;
default: default:
throw new Exception($"Unhandled guest exception {ec}."); throw new Exception($"Unhandled guest exception {ec}.");
} }
@ -241,10 +285,7 @@ namespace Ryujinx.Cpu.AppleHv
// TODO: Invalidate only the range that was modified? // TODO: Invalidate only the range that was modified?
return HvAddressSpace.KernelRegionTlbiEretAddress; return HvAddressSpace.KernelRegionTlbiEretAddress;
} }
else return HvAddressSpace.KernelRegionEretAddress;
{
return HvAddressSpace.KernelRegionEretAddress;
}
} }
private static void DataAbort(MemoryTracking tracking, ulong vcpu, uint esr) private static void DataAbort(MemoryTracking tracking, ulong vcpu, uint esr)

View file

@ -23,8 +23,6 @@ namespace Ryujinx.Cpu.AppleHv
private readonly ulong[] _x; private readonly ulong[] _x;
private readonly V128[] _v; private readonly V128[] _v;
public ulong DebugPc { get; set; }
public HvExecutionContextShadow() public HvExecutionContextShadow()
{ {
_x = new ulong[32]; _x = new ulong[32];
@ -55,24 +53,6 @@ namespace Ryujinx.Cpu.AppleHv
{ {
} }
public void DebugStop()
{
}
public bool DebugStep()
{
return false;
}
public void DebugContinue()
{
}
public DebugState GetDebugState()
{
return DebugState.Stopped;
}
public bool GetAndClearInterruptRequested() public bool GetAndClearInterruptRequested()
{ {
return false; return false;

View file

@ -13,14 +13,6 @@ namespace Ryujinx.Cpu.AppleHv
private static readonly SetSimdFpReg _setSimdFpReg; private static readonly SetSimdFpReg _setSimdFpReg;
private static readonly IntPtr _setSimdFpRegNativePtr; private static readonly IntPtr _setSimdFpRegNativePtr;
internal int _debugState = (int)DebugState.Running;
internal int _shouldStep = 0;
internal ManualResetEvent _debugHalt = new ManualResetEvent(true);
internal Barrier _stepBarrier = new Barrier(2);
// This is only valid while debugging is enabled.
public ulong DebugPc { get; set; }
public ulong ThreadUid { get; set; } public ulong ThreadUid { get; set; }
static HvExecutionContextVcpu() static HvExecutionContextVcpu()
@ -198,41 +190,6 @@ namespace Ryujinx.Cpu.AppleHv
} }
} }
public void DebugStop()
{
_debugHalt.Reset();
Interlocked.CompareExchange(ref _debugState, (int)DebugState.Stopping, (int)DebugState.Running);
ulong vcpu = _vcpu;
HvApi.hv_vcpus_exit(ref vcpu, 1);
}
public bool DebugStep()
{
if (_debugState != (int)DebugState.Stopped)
{
return false;
}
_shouldStep = 1;
_debugHalt.Set();
_stepBarrier.SignalAndWait();
_debugHalt.Reset();
_stepBarrier.SignalAndWait();
return true;
}
public void DebugContinue()
{
_debugHalt.Set();
HvApi.hv_vcpu_run(_vcpu);
}
public DebugState GetDebugState()
{
return (DebugState)_debugState;
}
public bool GetAndClearInterruptRequested() public bool GetAndClearInterruptRequested()
{ {
return Interlocked.Exchange(ref _interruptRequested, 0) != 0; return Interlocked.Exchange(ref _interruptRequested, 0) != 0;

View file

@ -42,13 +42,5 @@ namespace Ryujinx.Cpu.AppleHv
void RequestInterrupt(); void RequestInterrupt();
bool GetAndClearInterruptRequested(); bool GetAndClearInterruptRequested();
// TODO: comments
void DebugStop();
bool DebugStep();
void DebugContinue();
DebugState GetDebugState();
ulong DebugPc { get; set; }
} }
} }

View file

@ -118,7 +118,6 @@ namespace Ryujinx.Cpu
void DebugStop(); void DebugStop();
bool DebugStep(); bool DebugStep();
void DebugContinue(); void DebugContinue();
DebugState GetDebugState();
ulong DebugPc { get; set; } ulong DebugPc { get; set; }
} }

View file

@ -125,9 +125,6 @@ namespace Ryujinx.Cpu.Jit
/// <inheritdoc/> /// <inheritdoc/>
public void DebugContinue() => _impl.DebugContinue(); public void DebugContinue() => _impl.DebugContinue();
/// <inheritdoc/>
public DebugState GetDebugState() => _impl.GetDebugState();
/// <inheritdoc/> /// <inheritdoc/>
public ulong DebugPc public ulong DebugPc
{ {

View file

@ -1,4 +1,4 @@
namespace ARMeilleure.State namespace Ryujinx.HLE.Debugger
{ {
public enum DebugState public enum DebugState
{ {

View file

@ -2,6 +2,8 @@ using ARMeilleure.State;
using Ryujinx.Common; using Ryujinx.Common;
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Linq; using System.Linq;
@ -46,8 +48,8 @@ namespace Ryujinx.HLE.Debugger
private void HaltApplication() => Device.System.DebugGetApplicationProcess().DebugStopAllThreads(); private void HaltApplication() => Device.System.DebugGetApplicationProcess().DebugStopAllThreads();
private ulong[] GetThreadIds() => Device.System.DebugGetApplicationProcess().DebugGetThreadUids(); private ulong[] GetThreadIds() => Device.System.DebugGetApplicationProcess().DebugGetThreadUids();
private Ryujinx.Cpu.IExecutionContext GetThread(ulong threadUid) => Device.System.DebugGetApplicationProcess().DebugGetThreadContext(threadUid); private KThread GetThread(ulong threadUid) => Device.System.DebugGetApplicationProcess().DebugGetThread(threadUid);
private Ryujinx.Cpu.IExecutionContext[] GetThreads() => GetThreadIds().Select(x => GetThread(x)).ToArray(); private KThread[] GetThreads() => GetThreadIds().Select(x => GetThread(x)).ToArray();
private IVirtualMemoryManager GetMemory() => Device.System.DebugGetApplicationProcess().CpuMemory; private IVirtualMemoryManager GetMemory() => Device.System.DebugGetApplicationProcess().CpuMemory;
private void InvalidateCacheRegion(ulong address, ulong size) => private void InvalidateCacheRegion(ulong address, ulong size) =>
Device.System.DebugGetApplicationProcess().InvalidateCacheRegion(address, size); Device.System.DebugGetApplicationProcess().InvalidateCacheRegion(address, size);
@ -304,8 +306,8 @@ namespace Ryujinx.HLE.Debugger
break; break;
} }
IExecutionContext ctx = GetThread(threadId.Value); KThread thread = GetThread(threadId.Value);
if (ctx.GetDebugState() == DebugState.Stopped) if (thread.GetDebugState() == DebugState.Stopped)
{ {
Reply(ToHex("Stopped")); Reply(ToHex("Stopped"));
} }
@ -387,7 +389,7 @@ namespace Ryujinx.HLE.Debugger
return; return;
} }
GetThread(cThread.Value).DebugPc = newPc.Value; GetThread(cThread.Value).Context.DebugPc = newPc.Value;
} }
foreach (var thread in GetThreads()) foreach (var thread in GetThreads())
@ -410,7 +412,7 @@ namespace Ryujinx.HLE.Debugger
return; return;
} }
var ctx = GetThread(gThread.Value); var ctx = GetThread(gThread.Value).Context;
string registers = ""; string registers = "";
for (int i = 0; i < GdbRegisterCount; i++) for (int i = 0; i < GdbRegisterCount; i++)
{ {
@ -428,7 +430,7 @@ namespace Ryujinx.HLE.Debugger
return; return;
} }
var ctx = GetThread(gThread.Value); var ctx = GetThread(gThread.Value).Context;
for (int i = 0; i < GdbRegisterCount; i++) for (int i = 0; i < GdbRegisterCount; i++)
{ {
if (!GdbWriteRegister(ctx, i, ss)) if (!GdbWriteRegister(ctx, i, ss))
@ -513,7 +515,7 @@ namespace Ryujinx.HLE.Debugger
return; return;
} }
var ctx = GetThread(gThread.Value); var ctx = GetThread(gThread.Value).Context;
string result = GdbReadRegister(ctx, gdbRegId); string result = GdbReadRegister(ctx, gdbRegId);
if (result != null) if (result != null)
{ {
@ -533,7 +535,7 @@ namespace Ryujinx.HLE.Debugger
return; return;
} }
var ctx = GetThread(gThread.Value); var ctx = GetThread(gThread.Value).Context;
if (GdbWriteRegister(ctx, gdbRegId, ss) && ss.IsEmpty()) if (GdbWriteRegister(ctx, gdbRegId, ss) && ss.IsEmpty())
{ {
ReplyOK(); ReplyOK();
@ -552,15 +554,16 @@ namespace Ryujinx.HLE.Debugger
return; return;
} }
var ctx = GetThread(cThread.Value); var thread = GetThread(cThread.Value);
if (newPc.HasValue) if (newPc.HasValue)
{ {
ctx.DebugPc = newPc.Value; thread.Context.DebugPc = newPc.Value;
} }
ctx.DebugStep(); thread.DebugStep();
Reply($"T05thread:{ctx.ThreadUid:x};");
Reply($"T05thread:{thread.ThreadUid:x};");
} }
private void CommandIsAlive(ulong? threadId) private void CommandIsAlive(ulong? threadId)
@ -721,7 +724,9 @@ namespace Ryujinx.HLE.Debugger
public void ThreadBreak(IExecutionContext ctx, ulong address, int imm) public void ThreadBreak(IExecutionContext ctx, ulong address, int imm)
{ {
ctx.DebugStop(); KThread thread = GetThread(ctx.ThreadUid);
thread.DebugStop();
Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}"); Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}");

View file

@ -1,12 +1,13 @@
using Ryujinx.Memory; using Ryujinx.Memory;
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.Debugger namespace Ryujinx.HLE.Debugger
{ {
public interface IDebuggableProcess internal interface IDebuggableProcess
{ {
void DebugStopAllThreads(); void DebugStopAllThreads();
ulong[] DebugGetThreadUids(); ulong[] DebugGetThreadUids();
Ryujinx.Cpu.IExecutionContext DebugGetThreadContext(ulong threadUid); public KThread DebugGetThread(ulong threadUid);
IVirtualMemoryManager CpuMemory { get; } IVirtualMemoryManager CpuMemory { get; }
void InvalidateCacheRegion(ulong address, ulong size); void InvalidateCacheRegion(ulong address, ulong size);
} }

View file

@ -475,7 +475,7 @@ namespace Ryujinx.HLE.HOS
IsPaused = pause; IsPaused = pause;
} }
public IDebuggableProcess DebugGetApplicationProcess() internal IDebuggableProcess DebugGetApplicationProcess()
{ {
lock (KernelContext.Processes) lock (KernelContext.Processes)
{ {

View file

@ -12,6 +12,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using ExceptionCallback = Ryujinx.Cpu.ExceptionCallback;
namespace Ryujinx.HLE.HOS.Kernel.Process namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
@ -748,6 +749,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
KThread currentThread = KernelStatic.GetCurrentThread(); KThread currentThread = KernelStatic.GetCurrentThread();
if (currentThread.GetDebugState() != DebugState.Running)
{
KernelContext.CriticalSection.Enter();
currentThread.Suspend(ThreadSchedState.ThreadPauseFlag);
currentThread.DebugHalt.Set();
KernelContext.CriticalSection.Leave();
}
if (currentThread.Context.Running && if (currentThread.Context.Running &&
currentThread.Owner != null && currentThread.Owner != null &&
currentThread.GetUserDisableCount() != 0 && currentThread.GetUserDisableCount() != 0 &&
@ -1200,7 +1209,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
foreach (KThread thread in _parent._threads) foreach (KThread thread in _parent._threads)
{ {
thread.Context.DebugStop(); thread.DebugStop();
} }
} }
} }
@ -1213,11 +1222,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
} }
} }
public Ryujinx.Cpu.IExecutionContext DebugGetThreadContext(ulong threadUid) public KThread DebugGetThread(ulong threadUid)
{ {
lock (_parent._threadingLock) lock (_parent._threadingLock)
{ {
return _parent._threads.FirstOrDefault(x => x.ThreadUid == threadUid)?.Context; return _parent._threads.FirstOrDefault(x => x.ThreadUid == threadUid);
} }
} }

View file

@ -48,11 +48,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{ {
} }
public DebugState GetDebugState()
{
return DebugState.Stopped;
}
public void StopRunning() public void StopRunning()
{ {
Running = false; Running = false;

View file

@ -296,6 +296,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
currentThread.SchedulerWaitEvent.Reset(); currentThread.SchedulerWaitEvent.Reset();
currentThread.ThreadContext.Unlock(); currentThread.ThreadContext.Unlock();
currentThread.DebugHalt.Set();
// Wake all the threads that might be waiting until this thread context is unlocked. // Wake all the threads that might be waiting until this thread context is unlocked.
for (int core = 0; core < CpuCoresCount; core++) for (int core = 0; core < CpuCoresCount; core++)

View file

@ -1,5 +1,6 @@
using Ryujinx.Common.Logging; using Ryujinx.Common.Logging;
using Ryujinx.Cpu; using Ryujinx.Cpu;
using Ryujinx.HLE.Debugger;
using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.SupervisorCall; using Ryujinx.HLE.HOS.Kernel.SupervisorCall;
@ -114,6 +115,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private readonly object _activityOperationLock = new(); private readonly object _activityOperationLock = new();
private int _debugState = (int)DebugState.Running;
internal readonly ManualResetEvent DebugHalt = new(false);
public KThread(KernelContext context) : base(context) public KThread(KernelContext context) : base(context)
{ {
WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects]; WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects];
@ -1432,5 +1436,49 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{ {
Owner.CpuMemory.Write<ushort>(_tlsAddress + TlsUserInterruptFlagOffset, 0); Owner.CpuMemory.Write<ushort>(_tlsAddress + TlsUserInterruptFlagOffset, 0);
} }
public bool DebugStep()
{
if (_debugState != (int)DebugState.Stopped || !Context.DebugStep())
{
return false;
}
DebugHalt.Reset();
SetActivity(false);
DebugHalt.WaitOne();
return true;
}
public void DebugStop()
{
if (Interlocked.CompareExchange(ref _debugState, (int)DebugState.Stopping,
(int)DebugState.Running) != (int)DebugState.Running)
{
return;
}
Context.DebugStop();
DebugHalt.WaitOne();
DebugHalt.Reset();
_debugState = (int)DebugState.Stopped;
}
public void DebugContinue()
{
if (Interlocked.CompareExchange(ref _debugState, (int)DebugState.Running,
(int)DebugState.Stopped) != (int)DebugState.Stopped)
{
return;
}
Context.DebugContinue();
SetActivity(false);
}
public DebugState GetDebugState()
{
return (DebugState)_debugState;
}
} }
} }