Refactor the debugger interface

Also support stepping through blocking syscalls
This commit is contained in:
svc64 2023-11-11 16:21:36 +02:00
parent 0f50273d4f
commit e3b8060417
5 changed files with 109 additions and 98 deletions

View File

@ -47,11 +47,9 @@ namespace Ryujinx.HLE.Debugger
HandlerThread.Start();
}
private void HaltApplication() => Device.System.DebugGetApplicationProcess().DebugStopAllThreads();
private ulong[] GetThreadIds() => Device.System.DebugGetApplicationProcess().DebugGetThreadUids();
private KThread GetThread(ulong threadUid) => Device.System.DebugGetApplicationProcess().DebugGetThread(threadUid);
private KThread[] GetThreads() => GetThreadIds().Select(x => GetThread(x)).ToArray();
private IVirtualMemoryManager GetMemory() => Device.System.DebugGetApplicationProcess().CpuMemory;
private IDebuggableProcess DebugProcess => Device.System.DebugGetApplicationProcess();
private KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray();
private IVirtualMemoryManager GetMemory() => DebugProcess.CpuMemory;
private void InvalidateCacheRegion(ulong address, ulong size) =>
Device.System.DebugGetApplicationProcess().InvalidateCacheRegion(address, size);
private KernelContext KernelContext => Device.System.KernelContext;
@ -171,7 +169,7 @@ namespace Ryujinx.HLE.Debugger
break;
case ThreadBreakMessage msg:
HaltApplication();
DebugProcess.DebugStop();
Reply($"T05thread:{msg.Context.ThreadUid:x};");
break;
}
@ -289,7 +287,7 @@ namespace Ryujinx.HLE.Debugger
if (ss.ConsumeRemaining("fThreadInfo"))
{
Reply($"m{string.Join(",", GetThreadIds().Select(x => $"{x:x}"))}");
Reply($"m{string.Join(",", DebugProcess.GetThreadUids().Select(x => $"{x:x}"))}");
break;
}
@ -308,8 +306,7 @@ namespace Ryujinx.HLE.Debugger
break;
}
KThread thread = GetThread(threadId.Value);
if (thread.GetDebugState() == DebugState.Stopped)
if (DebugProcess.GetDebugState() == DebugState.Stopped)
{
Reply(ToHex("Stopped"));
}
@ -376,8 +373,8 @@ namespace Ryujinx.HLE.Debugger
void CommandQuery()
{
// GDB is performing initial contact. Stop everything.
HaltApplication();
gThread = cThread = GetThreadIds().First();
DebugProcess.DebugStop();
gThread = cThread = DebugProcess.GetThreadUids().First();
Reply($"T05thread:{cThread:x};");
}
@ -391,13 +388,10 @@ namespace Ryujinx.HLE.Debugger
return;
}
GetThread(cThread.Value).Context.DebugPc = newPc.Value;
DebugProcess.GetThread(cThread.Value).Context.DebugPc = newPc.Value;
}
foreach (var thread in GetThreads())
{
thread.DebugContinue();
}
DebugProcess.DebugContinue();
}
void CommandDetach()
@ -414,7 +408,7 @@ namespace Ryujinx.HLE.Debugger
return;
}
var ctx = GetThread(gThread.Value).Context;
var ctx = DebugProcess.GetThread(gThread.Value).Context;
string registers = "";
for (int i = 0; i < GdbRegisterCount; i++)
{
@ -432,7 +426,7 @@ namespace Ryujinx.HLE.Debugger
return;
}
var ctx = GetThread(gThread.Value).Context;
var ctx = DebugProcess.GetThread(gThread.Value).Context;
for (int i = 0; i < GdbRegisterCount; i++)
{
if (!GdbWriteRegister(ctx, i, ss))
@ -517,7 +511,7 @@ namespace Ryujinx.HLE.Debugger
return;
}
var ctx = GetThread(gThread.Value).Context;
var ctx = DebugProcess.GetThread(gThread.Value).Context;
string result = GdbReadRegister(ctx, gdbRegId);
if (result != null)
{
@ -537,7 +531,7 @@ namespace Ryujinx.HLE.Debugger
return;
}
var ctx = GetThread(gThread.Value).Context;
var ctx = DebugProcess.GetThread(gThread.Value).Context;
if (GdbWriteRegister(ctx, gdbRegId, ss) && ss.IsEmpty())
{
ReplyOK();
@ -556,14 +550,14 @@ namespace Ryujinx.HLE.Debugger
return;
}
var thread = GetThread(cThread.Value);
var thread = DebugProcess.GetThread(cThread.Value);
if (newPc.HasValue)
{
thread.Context.DebugPc = newPc.Value;
}
if (!thread.DebugStep())
if (!DebugProcess.DebugStep(thread))
{
ReplyError();
}
@ -745,7 +739,7 @@ namespace Ryujinx.HLE.Debugger
Messages.Add(new ThreadBreakMessage(ctx, address, imm));
KThread currentThread = GetThread(ctx.ThreadUid);
KThread currentThread = DebugProcess.GetThread(ctx.ThreadUid);
if (currentThread.Context.Running &&
currentThread.Owner != null &&

View File

@ -5,9 +5,12 @@ namespace Ryujinx.HLE.Debugger
{
internal interface IDebuggableProcess
{
void DebugStopAllThreads();
ulong[] DebugGetThreadUids();
public KThread DebugGetThread(ulong threadUid);
void DebugStop();
void DebugContinue();
bool DebugStep(KThread thread);
KThread GetThread(ulong threadUid);
DebugState GetDebugState();
ulong[] GetThreadUids();
IVirtualMemoryManager CpuMemory { get; }
void InvalidateCacheRegion(ulong address, ulong size);
}

View File

@ -479,7 +479,7 @@ namespace Ryujinx.HLE.HOS
{
lock (KernelContext.Processes)
{
return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication)?.GdbStubInterface;
return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication)?.DebugInterface;
}
}
}

View File

@ -91,7 +91,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
public IVirtualMemoryManager CpuMemory => Context.AddressSpace;
public HleProcessDebugger Debugger { get; private set; }
public IDebuggableProcess GdbStubInterface { get { return new DebuggerInterface(this); } }
public IDebuggableProcess DebugInterface { get; private set; }
public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context)
{
@ -113,6 +113,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
_threads = new LinkedList<KThread>();
Debugger = new HleProcessDebugger(this);
DebugInterface = new DebuggerInterface(this);
}
public Result InitializeKip(
@ -1189,24 +1190,103 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
private class DebuggerInterface : IDebuggableProcess
{
private readonly KProcess _parent;
private readonly KernelContext KernelContext;
private int _debugState = (int)DebugState.Running;
public DebuggerInterface(KProcess p)
{
_parent = p;
}
public void DebugStopAllThreads()
public void DebugStop()
{
if (Interlocked.CompareExchange(ref _debugState, (int)DebugState.Stopping,
(int)DebugState.Running) != (int)DebugState.Running)
{
return;
}
_parent.KernelContext.CriticalSection.Enter();
lock (_parent._threadingLock)
{
foreach (KThread thread in _parent._threads)
{
thread.DebugStop();
thread.Suspend(ThreadSchedState.ThreadPauseFlag);
thread.Context.RequestInterrupt();
thread.DebugHalt.WaitOne();
}
}
_debugState = (int)DebugState.Stopped;
_parent.KernelContext.CriticalSection.Leave();
}
public ulong[] DebugGetThreadUids()
public void DebugContinue()
{
if (Interlocked.CompareExchange(ref _debugState, (int)DebugState.Running,
(int)DebugState.Stopped) != (int)DebugState.Stopped)
{
return;
}
_parent.KernelContext.CriticalSection.Enter();
lock (_parent._threadingLock)
{
foreach (KThread thread in _parent._threads)
{
thread.Resume(ThreadSchedState.ThreadPauseFlag);
}
}
_parent.KernelContext.CriticalSection.Leave();
}
public bool DebugStep(KThread target)
{
if (_debugState != (int)DebugState.Stopped)
{
return false;
}
_parent.KernelContext.CriticalSection.Enter();
bool wasPaused = (target.SchedFlags & ThreadSchedState.LowMask) == ThreadSchedState.Paused;
target.Context.RequestDebugStep();
target.Resume(ThreadSchedState.ThreadPauseFlag);
if (wasPaused)
{
lock (_parent._threadingLock)
{
foreach (KThread thread in _parent._threads)
{
thread.Resume(ThreadSchedState.ThreadPauseFlag);
}
}
}
_parent.KernelContext.CriticalSection.Leave();
target.Context.StepBarrier.SignalAndWait();
target.Context.StepBarrier.SignalAndWait();
_parent.KernelContext.CriticalSection.Enter();
target.Suspend(ThreadSchedState.ThreadPauseFlag);
if (wasPaused)
{
lock (_parent._threadingLock)
{
foreach (KThread thread in _parent._threads)
{
thread.Suspend(ThreadSchedState.ThreadPauseFlag);
}
}
}
_parent.KernelContext.CriticalSection.Leave();
return true;
}
public DebugState GetDebugState()
{
return (DebugState)_debugState;
}
public ulong[] GetThreadUids()
{
lock (_parent._threadingLock)
{
@ -1214,7 +1294,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
}
}
public KThread DebugGetThread(ulong threadUid)
public KThread GetThread(ulong threadUid)
{
lock (_parent._threadingLock)
{

View File

@ -115,7 +115,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private readonly object _activityOperationLock = new();
private int _debugState = (int)DebugState.Running;
internal readonly ManualResetEvent DebugHalt = new(false);
public KThread(KernelContext context) : base(context)
@ -1437,70 +1436,5 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
Owner.CpuMemory.Write<ushort>(_tlsAddress + TlsUserInterruptFlagOffset, 0);
}
public bool DebugStep()
{
lock (_activityOperationLock)
{
KernelContext.CriticalSection.Enter();
bool blocked = MutexOwner != null || WaitingInArbitration || WaitingSync;
if (_debugState != (int)DebugState.Stopped
|| (_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0
|| blocked)
{
KernelContext.CriticalSection.Leave();
return false;
}
KernelContext.CriticalSection.Leave();
Context.RequestDebugStep();
Resume(ThreadSchedState.ThreadPauseFlag);
Context.StepBarrier.SignalAndWait();
Suspend(ThreadSchedState.ThreadPauseFlag);
Context.StepBarrier.SignalAndWait();
return true;
}
}
public void DebugStop()
{
lock (_activityOperationLock)
{
if (Interlocked.CompareExchange(ref _debugState, (int)DebugState.Stopping,
(int)DebugState.Running) != (int)DebugState.Running)
{
return;
}
Suspend(ThreadSchedState.ThreadPauseFlag);
Context.RequestInterrupt();
DebugHalt.WaitOne();
_debugState = (int)DebugState.Stopped;
}
}
public void DebugContinue()
{
lock (_activityOperationLock)
{
if (Interlocked.CompareExchange(ref _debugState, (int)DebugState.Running,
(int)DebugState.Stopped) != (int)DebugState.Stopped)
{
return;
}
if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0)
{
Resume(ThreadSchedState.ThreadPauseFlag);
}
}
}
public DebugState GetDebugState()
{
return (DebugState)_debugState;
}
}
}