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

View file

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

View file

@ -479,7 +479,7 @@ namespace Ryujinx.HLE.HOS
{ {
lock (KernelContext.Processes) 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 IVirtualMemoryManager CpuMemory => Context.AddressSpace;
public HleProcessDebugger Debugger { get; private set; } 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) public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context)
{ {
@ -113,6 +113,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
_threads = new LinkedList<KThread>(); _threads = new LinkedList<KThread>();
Debugger = new HleProcessDebugger(this); Debugger = new HleProcessDebugger(this);
DebugInterface = new DebuggerInterface(this);
} }
public Result InitializeKip( public Result InitializeKip(
@ -1189,24 +1190,103 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
private class DebuggerInterface : IDebuggableProcess private class DebuggerInterface : IDebuggableProcess
{ {
private readonly KProcess _parent; private readonly KProcess _parent;
private readonly KernelContext KernelContext;
private int _debugState = (int)DebugState.Running;
public DebuggerInterface(KProcess p) public DebuggerInterface(KProcess p)
{ {
_parent = 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.Suspend(ThreadSchedState.ThreadPauseFlag);
thread.Context.RequestInterrupt();
thread.DebugHalt.WaitOne();
}
}
_debugState = (int)DebugState.Stopped;
_parent.KernelContext.CriticalSection.Leave();
}
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) lock (_parent._threadingLock)
{ {
foreach (KThread thread in _parent._threads) foreach (KThread thread in _parent._threads)
{ {
thread.DebugStop(); 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 ulong[] DebugGetThreadUids() public DebugState GetDebugState()
{
return (DebugState)_debugState;
}
public ulong[] GetThreadUids()
{ {
lock (_parent._threadingLock) 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) lock (_parent._threadingLock)
{ {

View file

@ -115,7 +115,6 @@ 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); internal readonly ManualResetEvent DebugHalt = new(false);
public KThread(KernelContext context) : base(context) public KThread(KernelContext context) : base(context)
@ -1437,70 +1436,5 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{ {
Owner.CpuMemory.Write<ushort>(_tlsAddress + TlsUserInterruptFlagOffset, 0); 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;
}
} }
} }