Refactor the debugger interface
Also support stepping through blocking syscalls
This commit is contained in:
parent
0f50273d4f
commit
e3b8060417
5 changed files with 109 additions and 98 deletions
|
@ -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 &&
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
lock (_parent._threadingLock)
|
||||||
{
|
{
|
||||||
foreach (KThread thread in _parent._threads)
|
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)
|
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)
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue