mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2025-02-06 09:44:57 -05:00
Merge 20e7b0ad67
into 7d158acc3b
This commit is contained in:
commit
775e47aa00
54 changed files with 2440 additions and 53 deletions
|
@ -2,6 +2,8 @@ using ARMeilleure.Memory;
|
|||
using ARMeilleure.State;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using ExecutionContext = ARMeilleure.State.ExecutionContext;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
|
@ -175,7 +177,11 @@ namespace ARMeilleure.Instructions
|
|||
|
||||
ExecutionContext context = GetContext();
|
||||
|
||||
context.CheckInterrupt();
|
||||
// If debugging, we'll handle interrupts outside
|
||||
if (!Optimizations.EnableDebugging)
|
||||
{
|
||||
context.CheckInterrupt();
|
||||
}
|
||||
|
||||
Statistics.ResumeTimer();
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ namespace ARMeilleure
|
|||
|
||||
public static bool AllowLcqInFunctionTable { get; set; } = true;
|
||||
public static bool UseUnmanagedDispatchLoop { get; set; } = true;
|
||||
public static bool EnableDebugging { get; set; } = false;
|
||||
|
||||
public static bool UseAdvSimdIfAvailable { get; set; } = true;
|
||||
public static bool UseArm64AesIfAvailable { get; set; } = true;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
using ARMeilleure.Memory;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
|
||||
namespace ARMeilleure.State
|
||||
{
|
||||
|
@ -11,7 +14,7 @@ namespace ARMeilleure.State
|
|||
|
||||
internal IntPtr NativeContextPtr => _nativeContext.BasePtr;
|
||||
|
||||
private bool _interrupted;
|
||||
internal bool Interrupted { get; private set; }
|
||||
|
||||
private readonly ICounter _counter;
|
||||
|
||||
|
@ -68,6 +71,8 @@ namespace ARMeilleure.State
|
|||
|
||||
public bool IsAarch32 { get; set; }
|
||||
|
||||
public ulong ThreadUid { get; set; }
|
||||
|
||||
internal ExecutionMode ExecutionMode
|
||||
{
|
||||
get
|
||||
|
@ -93,14 +98,19 @@ namespace ARMeilleure.State
|
|||
|
||||
private readonly ExceptionCallbackNoArgs _interruptCallback;
|
||||
private readonly ExceptionCallback _breakCallback;
|
||||
private readonly ExceptionCallbackNoArgs _stepCallback;
|
||||
private readonly ExceptionCallback _supervisorCallback;
|
||||
private readonly ExceptionCallback _undefinedCallback;
|
||||
|
||||
internal int ShouldStep;
|
||||
public ulong DebugPc { get; set; }
|
||||
|
||||
public ExecutionContext(
|
||||
IJitMemoryAllocator allocator,
|
||||
ICounter counter,
|
||||
ExceptionCallbackNoArgs interruptCallback = null,
|
||||
ExceptionCallback breakCallback = null,
|
||||
ExceptionCallbackNoArgs stepCallback = null,
|
||||
ExceptionCallback supervisorCallback = null,
|
||||
ExceptionCallback undefinedCallback = null)
|
||||
{
|
||||
|
@ -108,6 +118,7 @@ namespace ARMeilleure.State
|
|||
_counter = counter;
|
||||
_interruptCallback = interruptCallback;
|
||||
_breakCallback = breakCallback;
|
||||
_stepCallback = stepCallback;
|
||||
_supervisorCallback = supervisorCallback;
|
||||
_undefinedCallback = undefinedCallback;
|
||||
|
||||
|
@ -130,9 +141,9 @@ namespace ARMeilleure.State
|
|||
|
||||
internal void CheckInterrupt()
|
||||
{
|
||||
if (_interrupted)
|
||||
if (Interrupted)
|
||||
{
|
||||
_interrupted = false;
|
||||
Interrupted = false;
|
||||
|
||||
_interruptCallback?.Invoke(this);
|
||||
}
|
||||
|
@ -142,7 +153,18 @@ namespace ARMeilleure.State
|
|||
|
||||
public void RequestInterrupt()
|
||||
{
|
||||
_interrupted = true;
|
||||
Interrupted = true;
|
||||
}
|
||||
|
||||
public void StepHandler()
|
||||
{
|
||||
_stepCallback.Invoke(this);
|
||||
}
|
||||
|
||||
public void RequestDebugStep()
|
||||
{
|
||||
Interlocked.Exchange(ref ShouldStep, 1);
|
||||
RequestInterrupt();
|
||||
}
|
||||
|
||||
internal void OnBreak(ulong address, int imm)
|
||||
|
|
|
@ -140,7 +140,25 @@ namespace ARMeilleure.Translation
|
|||
|
||||
NativeInterface.RegisterThread(context, Memory, this);
|
||||
|
||||
if (Optimizations.UseUnmanagedDispatchLoop)
|
||||
if (Optimizations.EnableDebugging)
|
||||
{
|
||||
context.DebugPc = address;
|
||||
do
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref context.ShouldStep, 0, 1) == 1)
|
||||
{
|
||||
context.DebugPc = Step(context, context.DebugPc);
|
||||
context.StepHandler();
|
||||
}
|
||||
else
|
||||
{
|
||||
context.DebugPc = ExecuteSingle(context, context.DebugPc);
|
||||
}
|
||||
context.CheckInterrupt();
|
||||
}
|
||||
while (context.Running && context.DebugPc != 0);
|
||||
}
|
||||
else if (Optimizations.UseUnmanagedDispatchLoop)
|
||||
{
|
||||
Stubs.DispatchLoop(context.NativeContextPtr, address);
|
||||
}
|
||||
|
@ -196,7 +214,7 @@ namespace ARMeilleure.Translation
|
|||
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);
|
||||
|
||||
|
@ -249,7 +267,7 @@ namespace ARMeilleure.Translation
|
|||
Stubs,
|
||||
address,
|
||||
highCq,
|
||||
_ptc.State != PtcState.Disabled,
|
||||
_ptc.State != PtcState.Disabled && !Optimizations.EnableDebugging,
|
||||
mode: Aarch32Mode.User);
|
||||
|
||||
Logger.StartPass(PassName.Decoding);
|
||||
|
@ -382,9 +400,8 @@ namespace ARMeilleure.Translation
|
|||
|
||||
if (block.Exit)
|
||||
{
|
||||
// Left option here as it may be useful if we need to return to managed rather than tail call in
|
||||
// future. (eg. for debug)
|
||||
bool useReturns = false;
|
||||
// Return to managed rather than tail call.
|
||||
bool useReturns = Optimizations.EnableDebugging;
|
||||
|
||||
InstEmitFlowHelper.EmitVirtualJump(context, Const(block.Address), isReturn: useReturns);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace Ryujinx.Common.Logging
|
|||
Cpu,
|
||||
Emulation,
|
||||
FFmpeg,
|
||||
GdbStub,
|
||||
Font,
|
||||
Gpu,
|
||||
Hid,
|
||||
|
|
10
src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs
Normal file
10
src/Ryujinx.Cpu/AppleHv/Arm/ExceptionLevel.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace Ryujinx.Cpu.AppleHv.Arm
|
||||
{
|
||||
enum ExceptionLevel : uint
|
||||
{
|
||||
PstateMask = 0xfffffff0,
|
||||
EL1h = 0b0101,
|
||||
El1t = 0b0100,
|
||||
EL0 = 0b0000,
|
||||
}
|
||||
}
|
|
@ -11,7 +11,18 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
class HvExecutionContext : IExecutionContext
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public ulong Pc => _impl.ElrEl1;
|
||||
public ulong Pc
|
||||
{
|
||||
get
|
||||
{
|
||||
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
|
||||
if (currentEl == (uint)ExceptionLevel.EL1h)
|
||||
{
|
||||
return _impl.ElrEl1;
|
||||
}
|
||||
return _impl.Pc;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public long TpidrEl0
|
||||
|
@ -48,6 +59,9 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
set => _impl.Fpsr = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong ThreadUid { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsAarch32
|
||||
{
|
||||
|
@ -67,6 +81,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
private readonly ICounter _counter;
|
||||
private readonly IHvExecutionContext _shadowContext;
|
||||
private IHvExecutionContext _impl;
|
||||
private int _shouldStep;
|
||||
|
||||
private readonly ExceptionCallbacks _exceptionCallbacks;
|
||||
|
||||
|
@ -103,6 +118,11 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
|
||||
}
|
||||
|
||||
private void StepHandler()
|
||||
{
|
||||
_exceptionCallbacks.StepCallback?.Invoke(this);
|
||||
}
|
||||
|
||||
private void SupervisorCallHandler(ulong address, int imm)
|
||||
{
|
||||
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
|
||||
|
@ -127,6 +147,30 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RequestDebugStep()
|
||||
{
|
||||
Interlocked.Exchange(ref _shouldStep, 1);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong DebugPc
|
||||
{
|
||||
get => Pc;
|
||||
set
|
||||
{
|
||||
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
|
||||
if (currentEl == (uint)ExceptionLevel.EL1h)
|
||||
{
|
||||
_impl.ElrEl1 = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
_impl.Pc = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void StopRunning()
|
||||
{
|
||||
|
@ -142,6 +186,22 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
|
||||
while (Running)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _shouldStep, 0, 1) == 1)
|
||||
{
|
||||
uint currentEl = Pstate & ~((uint)ExceptionLevel.PstateMask);
|
||||
if (currentEl == (uint)ExceptionLevel.EL1h)
|
||||
{
|
||||
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);
|
||||
}
|
||||
else
|
||||
{
|
||||
Pstate |= (1 << 21);
|
||||
}
|
||||
HvApi.hv_vcpu_set_sys_reg(vcpu.Handle, HvSysReg.MDSCR_EL1, 1);
|
||||
}
|
||||
|
||||
HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
|
||||
|
||||
HvExitReason reason = vcpu.ExitInfo->Reason;
|
||||
|
@ -155,7 +215,6 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
{
|
||||
throw new Exception($"Unhandled exception from guest kernel with ESR 0x{hvEsr:X} ({hvEc}).");
|
||||
}
|
||||
|
||||
address = SynchronousException(memoryManager, ref vcpu);
|
||||
HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError();
|
||||
}
|
||||
|
@ -209,6 +268,20 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
SupervisorCallHandler(elr - 4UL, id);
|
||||
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||
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);
|
||||
StepHandler();
|
||||
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||
break;
|
||||
case ExceptionClass.BrkAarch64:
|
||||
ReturnToPool(vcpu);
|
||||
BreakHandler(elr, (ushort)esr);
|
||||
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"Unhandled guest exception {ec}.");
|
||||
}
|
||||
|
@ -219,10 +292,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
// TODO: Invalidate only the range that was modified?
|
||||
return HvAddressSpace.KernelRegionTlbiEretAddress;
|
||||
}
|
||||
else
|
||||
{
|
||||
return HvAddressSpace.KernelRegionEretAddress;
|
||||
}
|
||||
return HvAddressSpace.KernelRegionEretAddress;
|
||||
}
|
||||
|
||||
private static void DataAbort(MemoryTracking tracking, ulong vcpu, uint esr)
|
||||
|
|
|
@ -18,6 +18,8 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
|
||||
public bool IsAarch32 { get; set; }
|
||||
|
||||
public ulong ThreadUid { get; set; }
|
||||
|
||||
private readonly ulong[] _x;
|
||||
private readonly V128[] _v;
|
||||
|
||||
|
@ -46,5 +48,14 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
{
|
||||
_v[index] = value;
|
||||
}
|
||||
|
||||
public void RequestInterrupt()
|
||||
{
|
||||
}
|
||||
|
||||
public bool GetAndClearInterruptRequested()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,10 @@ using ARMeilleure.State;
|
|||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Cpu.AppleHv
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
class HvExecutionContextVcpu : IHvExecutionContext
|
||||
{
|
||||
private static readonly MemoryBlock _setSimdFpRegFuncMem;
|
||||
|
@ -14,6 +13,8 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
private static readonly SetSimdFpReg _setSimdFpReg;
|
||||
private static readonly IntPtr _setSimdFpRegNativePtr;
|
||||
|
||||
public ulong ThreadUid { get; set; }
|
||||
|
||||
static HvExecutionContextVcpu()
|
||||
{
|
||||
// .NET does not support passing vectors by value, so we need to pass a pointer and use a native
|
||||
|
@ -136,6 +137,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
}
|
||||
|
||||
private readonly ulong _vcpu;
|
||||
private int _interruptRequested;
|
||||
|
||||
public HvExecutionContextVcpu(ulong vcpu)
|
||||
{
|
||||
|
@ -181,8 +183,16 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
|
||||
public void RequestInterrupt()
|
||||
{
|
||||
ulong vcpu = _vcpu;
|
||||
HvApi.hv_vcpus_exit(ref vcpu, 1);
|
||||
if (Interlocked.Exchange(ref _interruptRequested, 1) == 0)
|
||||
{
|
||||
ulong vcpu = _vcpu;
|
||||
HvApi.hv_vcpus_exit(ref vcpu, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetAndClearInterruptRequested()
|
||||
{
|
||||
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
|
||||
uint Fpcr { get; set; }
|
||||
uint Fpsr { get; set; }
|
||||
|
||||
ulong ThreadUid { get; set; }
|
||||
ulong GetX(int index);
|
||||
void SetX(int index, ulong value);
|
||||
|
||||
|
@ -39,5 +39,8 @@ namespace Ryujinx.Cpu.AppleHv
|
|||
SetV(i, context.GetV(i));
|
||||
}
|
||||
}
|
||||
|
||||
void RequestInterrupt();
|
||||
bool GetAndClearInterruptRequested();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,11 @@ namespace Ryujinx.Cpu
|
|||
/// </summary>
|
||||
public readonly ExceptionCallback BreakCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for CPU software interrupts caused by single-stepping.
|
||||
/// </summary>
|
||||
public readonly ExceptionCallbackNoArgs StepCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Handler for CPU software interrupts caused by the Arm SVC instruction.
|
||||
/// </summary>
|
||||
|
@ -47,16 +52,19 @@ namespace Ryujinx.Cpu
|
|||
/// </remarks>
|
||||
/// <param name="interruptCallback">Handler for CPU interrupts triggered using <see cref="IExecutionContext.RequestInterrupt"/></param>
|
||||
/// <param name="breakCallback">Handler for CPU software interrupts caused by the Arm BRK instruction</param>
|
||||
/// <param name="stepCallback">Handler for CPU software interrupts caused by single-stepping</param>
|
||||
/// <param name="supervisorCallback">Handler for CPU software interrupts caused by the Arm SVC instruction</param>
|
||||
/// <param name="undefinedCallback">Handler for CPU software interrupts caused by any undefined Arm instruction</param>
|
||||
public ExceptionCallbacks(
|
||||
ExceptionCallbackNoArgs interruptCallback = null,
|
||||
ExceptionCallback breakCallback = null,
|
||||
ExceptionCallbackNoArgs stepCallback = null,
|
||||
ExceptionCallback supervisorCallback = null,
|
||||
ExceptionCallback undefinedCallback = null)
|
||||
{
|
||||
InterruptCallback = interruptCallback;
|
||||
BreakCallback = breakCallback;
|
||||
StepCallback = stepCallback;
|
||||
SupervisorCallback = supervisorCallback;
|
||||
UndefinedCallback = undefinedCallback;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using ARMeilleure.State;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
|
@ -46,6 +47,11 @@ namespace Ryujinx.Cpu
|
|||
/// </summary>
|
||||
bool IsAarch32 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Thread UID.
|
||||
/// </summary>
|
||||
public ulong ThreadUid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whenever the CPU is still running code.
|
||||
/// </summary>
|
||||
|
@ -108,5 +114,23 @@ namespace Ryujinx.Cpu
|
|||
/// If you only need to pause the thread temporarily, use <see cref="RequestInterrupt"/> instead.
|
||||
/// </remarks>
|
||||
void StopRunning();
|
||||
|
||||
/// <summary>
|
||||
/// Requests the thread to stop running temporarily and call <see cref="ExceptionCallbacks.InterruptCallback"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The thread might not pause immediately.
|
||||
/// One must not assume that guest code is no longer being executed by the thread after calling this function.
|
||||
/// After single stepping, the thread should call call <see cref="ExceptionCallbacks.StepCallback"/>.
|
||||
/// </remarks>
|
||||
void RequestDebugStep();
|
||||
|
||||
/// <summary>
|
||||
/// Current Program Counter (for debugging).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// PC register for the debugger. Must not be accessed while the thread isn't stopped for debugging.
|
||||
/// </remarks>
|
||||
ulong DebugPc { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
using ARMeilleure.Memory;
|
||||
using ARMeilleure.State;
|
||||
using System.Threading;
|
||||
using ExecutionContext = ARMeilleure.State.ExecutionContext;
|
||||
|
||||
namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
|
@ -53,6 +55,13 @@ namespace Ryujinx.Cpu.Jit
|
|||
set => _impl.IsAarch32 = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong ThreadUid
|
||||
{
|
||||
get => _impl.ThreadUid;
|
||||
set => _impl.ThreadUid = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Running => _impl.Running;
|
||||
|
||||
|
@ -65,6 +74,7 @@ namespace Ryujinx.Cpu.Jit
|
|||
counter,
|
||||
InterruptHandler,
|
||||
BreakHandler,
|
||||
StepHandler,
|
||||
SupervisorCallHandler,
|
||||
UndefinedHandler);
|
||||
|
||||
|
@ -93,6 +103,11 @@ namespace Ryujinx.Cpu.Jit
|
|||
_exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
|
||||
}
|
||||
|
||||
private void StepHandler(ExecutionContext context)
|
||||
{
|
||||
_exceptionCallbacks.StepCallback?.Invoke(this);
|
||||
}
|
||||
|
||||
private void SupervisorCallHandler(ExecutionContext context, ulong address, int imm)
|
||||
{
|
||||
_exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
|
||||
|
@ -109,6 +124,16 @@ namespace Ryujinx.Cpu.Jit
|
|||
_impl.RequestInterrupt();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void RequestDebugStep() => _impl.RequestDebugStep();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ulong DebugPc
|
||||
{
|
||||
get => _impl.DebugPc;
|
||||
set => _impl.DebugPc = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void StopRunning()
|
||||
{
|
||||
|
|
|
@ -677,7 +677,10 @@ namespace Ryujinx.UI
|
|||
ConfigurationState.Instance.System.AudioVolume,
|
||||
ConfigurationState.Instance.System.UseHypervisor,
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value,
|
||||
ConfigurationState.Instance.Multiplayer.Mode);
|
||||
ConfigurationState.Instance.Multiplayer.Mode,
|
||||
ConfigurationState.Instance.Debug.EnableGdbStub,
|
||||
ConfigurationState.Instance.Debug.GdbStubPort,
|
||||
ConfigurationState.Instance.Debug.DebuggerSuspendOnStart);
|
||||
|
||||
_emulationContext = new HLE.Switch(configuration);
|
||||
}
|
||||
|
@ -790,6 +793,24 @@ namespace Ryujinx.UI
|
|||
|
||||
shadersDumpWarningDialog.Dispose();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Debug.EnableGdbStub.Value)
|
||||
{
|
||||
MessageDialog gdbStubWarningDialog = new MessageDialog(this, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null)
|
||||
{
|
||||
Title = "Ryujinx - Warning",
|
||||
Text = "You have the GDB stub enabled, which is designed to be used by developers only.",
|
||||
SecondaryText = "For optimal performance, it's recommended to disable the GDB stub. Would you like to disable the GDB stub now?"
|
||||
};
|
||||
|
||||
if (gdbStubWarningDialog.Run() == (int)ResponseType.Yes)
|
||||
{
|
||||
ConfigurationState.Instance.Debug.EnableGdbStub.Value = false;
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
gdbStubWarningDialog.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private bool LoadApplication(string path, ulong applicationId, bool isFirmwareTitle)
|
||||
|
@ -1056,9 +1077,11 @@ namespace Ryujinx.UI
|
|||
RendererWidget.WaitEvent.WaitOne();
|
||||
|
||||
RendererWidget.Start();
|
||||
_pauseEmulation.Sensitive = false;
|
||||
_resumeEmulation.Sensitive = false;
|
||||
UpdateMenuItem.Sensitive = true;
|
||||
|
||||
_emulationContext.Dispose();
|
||||
_deviceExitStatus.Set();
|
||||
|
||||
// NOTE: Everything that is here will not be executed when you close the UI.
|
||||
Application.Invoke(delegate
|
||||
|
@ -1153,7 +1176,7 @@ namespace Ryujinx.UI
|
|||
RendererWidget.Exit();
|
||||
|
||||
// Wait for the other thread to dispose the HLE context before exiting.
|
||||
_deviceExitStatus.WaitOne();
|
||||
_emulationContext.ExitStatus.WaitOne();
|
||||
RendererWidget.Dispose();
|
||||
}
|
||||
}
|
||||
|
@ -1491,9 +1514,6 @@ namespace Ryujinx.UI
|
|||
UpdateGameMetadata(_emulationContext.Processes.ActiveApplication.ProgramIdText);
|
||||
}
|
||||
|
||||
_pauseEmulation.Sensitive = false;
|
||||
_resumeEmulation.Sensitive = false;
|
||||
UpdateMenuItem.Sensitive = true;
|
||||
RendererWidget?.Exit();
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,6 @@ namespace Ryujinx.UI
|
|||
|
||||
public static event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
|
||||
|
||||
private bool _isActive;
|
||||
private bool _isStopped;
|
||||
|
||||
private bool _toggleFullscreen;
|
||||
|
@ -464,7 +463,7 @@ namespace Ryujinx.UI
|
|||
|
||||
(Toplevel as MainWindow)?.ActivatePauseMenu();
|
||||
|
||||
while (_isActive)
|
||||
while (Device.IsActive)
|
||||
{
|
||||
if (_isStopped)
|
||||
{
|
||||
|
@ -524,7 +523,7 @@ namespace Ryujinx.UI
|
|||
{
|
||||
_chrono.Restart();
|
||||
|
||||
_isActive = true;
|
||||
Device.IsActive = true;
|
||||
|
||||
Gtk.Window parent = Toplevel as Gtk.Window;
|
||||
|
||||
|
@ -578,9 +577,9 @@ namespace Ryujinx.UI
|
|||
|
||||
_isStopped = true;
|
||||
|
||||
if (_isActive)
|
||||
if (Device.IsActive)
|
||||
{
|
||||
_isActive = false;
|
||||
Device.IsActive = false;
|
||||
|
||||
_exitEvent.WaitOne();
|
||||
_exitEvent.Dispose();
|
||||
|
@ -589,7 +588,7 @@ namespace Ryujinx.UI
|
|||
|
||||
private void NvidiaStutterWorkaround()
|
||||
{
|
||||
while (_isActive)
|
||||
while (Device.IsActive)
|
||||
{
|
||||
// When NVIDIA Threaded Optimization is on, the driver will snapshot all threads in the system whenever the application creates any new ones.
|
||||
// The ThreadPool has something called a "GateThread" which terminates itself after some inactivity.
|
||||
|
@ -608,7 +607,7 @@ namespace Ryujinx.UI
|
|||
|
||||
public void MainLoop()
|
||||
{
|
||||
while (_isActive)
|
||||
while (Device.IsActive)
|
||||
{
|
||||
UpdateFrame();
|
||||
|
||||
|
@ -621,7 +620,7 @@ namespace Ryujinx.UI
|
|||
|
||||
private bool UpdateFrame()
|
||||
{
|
||||
if (!_isActive)
|
||||
if (!Device.IsActive)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -117,7 +117,9 @@ namespace Ryujinx.UI.Windows
|
|||
[GUI] ToggleButton _configureController7;
|
||||
[GUI] ToggleButton _configureController8;
|
||||
[GUI] ToggleButton _configureControllerH;
|
||||
|
||||
[GUI] ToggleButton _gdbStubToggle;
|
||||
[GUI] ToggleButton _suspendOnStartToggle;
|
||||
[GUI] Adjustment _gdbStubPortSpinAdjustment;
|
||||
#pragma warning restore CS0649, IDE0044
|
||||
|
||||
public SettingsWindow(MainWindow parent, VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(parent, new Builder("Ryujinx.Gtk3.UI.Windows.SettingsWindow.glade"), virtualFileSystem, contentManager) { }
|
||||
|
@ -316,6 +318,16 @@ namespace Ryujinx.UI.Windows
|
|||
_custThemeToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Debug.EnableGdbStub)
|
||||
{
|
||||
_gdbStubToggle.Click();
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.Debug.DebuggerSuspendOnStart)
|
||||
{
|
||||
_suspendOnStartToggle.Click();
|
||||
}
|
||||
|
||||
// Custom EntryCompletion Columns. If added to glade, need to override more signals
|
||||
ListStore tzList = new(typeof(string), typeof(string), typeof(string));
|
||||
_systemTimeZoneCompletion.Model = tzList;
|
||||
|
@ -375,6 +387,8 @@ namespace Ryujinx.UI.Windows
|
|||
_fsLogSpinAdjustment.Value = ConfigurationState.Instance.System.FsGlobalAccessLogMode;
|
||||
_systemTimeOffset = ConfigurationState.Instance.System.SystemTimeOffset;
|
||||
|
||||
_gdbStubPortSpinAdjustment.Value = ConfigurationState.Instance.Debug.GdbStubPort;
|
||||
|
||||
_gameDirsBox.AppendColumn("", new CellRendererText(), "text", 0);
|
||||
_gameDirsBoxStore = new ListStore(typeof(string));
|
||||
_gameDirsBox.Model = _gameDirsBoxStore;
|
||||
|
@ -659,6 +673,9 @@ namespace Ryujinx.UI.Windows
|
|||
ConfigurationState.Instance.Graphics.ScalingFilter.Value = Enum.Parse<ScalingFilter>(_scalingFilter.ActiveId);
|
||||
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value = (int)_scalingFilterLevel.Value;
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value = _multiLanSelect.ActiveId;
|
||||
ConfigurationState.Instance.Debug.EnableGdbStub.Value = _gdbStubToggle.Active;
|
||||
ConfigurationState.Instance.Debug.GdbStubPort.Value = (ushort)_gdbStubPortSpinAdjustment.Value;
|
||||
ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Value = _suspendOnStartToggle.Active;
|
||||
|
||||
_previousVolumeLevel = ConfigurationState.Instance.System.AudioVolume.Value;
|
||||
|
||||
|
|
|
@ -46,6 +46,12 @@
|
|||
<property name="inline-completion">True</property>
|
||||
<property name="inline-selection">True</property>
|
||||
</object>
|
||||
<object class="GtkAdjustment" id="_gdbStubPortSpinAdjustment">
|
||||
<property name="lower">1</property>
|
||||
<property name="upper">65535</property>
|
||||
<property name="step-increment">1</property>
|
||||
<property name="page-increment">5</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="_settingsWin">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="title" translatable="yes">Ryujinx - Settings</property>
|
||||
|
@ -3146,6 +3152,155 @@
|
|||
<property name="tab-fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="TabDebug">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-left">5</property>
|
||||
<property name="margin-right">10</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox" id="CatDebug">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-left">5</property>
|
||||
<property name="margin-right">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="label" translatable="yes">Debug (WARNING: For Developer Use Only)</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox" id="DebugOptions">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="valign">start</property>
|
||||
<property name="margin-left">10</property>
|
||||
<property name="margin-right">10</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="_gdbStubToggle">
|
||||
<property name="label" translatable="yes">Enable GDB Stub</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Enables or disables GDB stub (for developer use only)</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="draw-indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Specifies which TCP port for the GDB stub to listen on. Possible values are 1-65535.</property>
|
||||
<property name="label" translatable="yes">GDB Stub Port</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkSpinButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="tooltip-text" translatable="yes">Specifies which TCP port for the GDB stub to listen on. Possible values are 1-65535.</property>
|
||||
<property name="text" translatable="yes">55555</property>
|
||||
<property name="input-purpose">number</property>
|
||||
<property name="adjustment">_gdbStubPortSpinAdjustment</property>
|
||||
<property name="numeric">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">9</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="_suspendOnStartToggle">
|
||||
<property name="label" translatable="yes">Suspend application on start</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">False</property>
|
||||
<property name="tooltip-text" translatable="yes">Suspends the application before executing the first instruction, allowing for debugging from the earliest point.</property>
|
||||
<property name="halign">start</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="draw-indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="padding">5</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Debug</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">6</property>
|
||||
<property name="tab-fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
|
|
9
src/Ryujinx.HLE/Debugger/DebugState.cs
Normal file
9
src/Ryujinx.HLE/Debugger/DebugState.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
public enum DebugState
|
||||
{
|
||||
Running,
|
||||
Stopping,
|
||||
Stopped,
|
||||
}
|
||||
}
|
900
src/Ryujinx.HLE/Debugger/Debugger.cs
Normal file
900
src/Ryujinx.HLE/Debugger/Debugger.cs
Normal file
|
@ -0,0 +1,900 @@
|
|||
using ARMeilleure.State;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.HOS.Kernel;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using IExecutionContext = Ryujinx.Cpu.IExecutionContext;
|
||||
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
public class Debugger : IDisposable
|
||||
{
|
||||
internal Switch Device { get; private set; }
|
||||
|
||||
public ushort GdbStubPort { get; private set; }
|
||||
|
||||
private TcpListener ListenerSocket;
|
||||
private Socket ClientSocket = null;
|
||||
private NetworkStream ReadStream = null;
|
||||
private NetworkStream WriteStream = null;
|
||||
private BlockingCollection<IMessage> Messages = new BlockingCollection<IMessage>(1);
|
||||
private Thread DebuggerThread;
|
||||
private Thread MessageHandlerThread;
|
||||
private bool _shuttingDown = false;
|
||||
|
||||
private ulong? cThread;
|
||||
private ulong? gThread;
|
||||
|
||||
public Debugger(Switch device, ushort port)
|
||||
{
|
||||
Device = device;
|
||||
GdbStubPort = port;
|
||||
|
||||
ARMeilleure.Optimizations.EnableDebugging = true;
|
||||
|
||||
DebuggerThread = new Thread(DebuggerThreadMain);
|
||||
DebuggerThread.Start();
|
||||
MessageHandlerThread = new Thread(MessageHandlerMain);
|
||||
MessageHandlerThread.Start();
|
||||
}
|
||||
|
||||
private IDebuggableProcess DebugProcess => Device.System.DebugGetApplicationProcess();
|
||||
private KThread[] GetThreads() => DebugProcess.GetThreadUids().Select(x => DebugProcess.GetThread(x)).ToArray();
|
||||
private bool IsProcessAarch32 => DebugProcess.GetThread(gThread.Value).Context.IsAarch32;
|
||||
private KernelContext KernelContext => Device.System.KernelContext;
|
||||
|
||||
const int GdbRegisterCount64 = 68;
|
||||
const int GdbRegisterCount32 = 66;
|
||||
/* FPCR = FPSR & ~FpcrMask
|
||||
All of FPCR's bits are reserved in FPCR and vice versa,
|
||||
see ARM's documentation. */
|
||||
private const uint FpcrMask = 0xfc1fffff;
|
||||
|
||||
private string GdbReadRegister64(IExecutionContext state, int gdbRegId)
|
||||
{
|
||||
switch (gdbRegId)
|
||||
{
|
||||
case >= 0 and <= 31:
|
||||
return ToHex(BitConverter.GetBytes(state.GetX(gdbRegId)));
|
||||
case 32:
|
||||
return ToHex(BitConverter.GetBytes(state.DebugPc));
|
||||
case 33:
|
||||
return ToHex(BitConverter.GetBytes(state.Pstate));
|
||||
case >= 34 and <= 65:
|
||||
return ToHex(state.GetV(gdbRegId - 34).ToArray());
|
||||
case 66:
|
||||
return ToHex(BitConverter.GetBytes((uint)state.Fpsr));
|
||||
case 67:
|
||||
return ToHex(BitConverter.GetBytes((uint)state.Fpcr));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool GdbWriteRegister64(IExecutionContext state, int gdbRegId, StringStream ss)
|
||||
{
|
||||
switch (gdbRegId)
|
||||
{
|
||||
case >= 0 and <= 31:
|
||||
{
|
||||
ulong value = ss.ReadLengthAsHex(16);
|
||||
state.SetX(gdbRegId, value);
|
||||
return true;
|
||||
}
|
||||
case 32:
|
||||
{
|
||||
ulong value = ss.ReadLengthAsHex(16);
|
||||
state.DebugPc = value;
|
||||
return true;
|
||||
}
|
||||
case 33:
|
||||
{
|
||||
ulong value = ss.ReadLengthAsHex(8);
|
||||
state.Pstate = (uint)value;
|
||||
return true;
|
||||
}
|
||||
case >= 34 and <= 65:
|
||||
{
|
||||
ulong value0 = ss.ReadLengthAsHex(16);
|
||||
ulong value1 = ss.ReadLengthAsHex(16);
|
||||
state.SetV(gdbRegId - 34, new V128(value0, value1));
|
||||
return true;
|
||||
}
|
||||
case 66:
|
||||
{
|
||||
ulong value = ss.ReadLengthAsHex(8);
|
||||
state.Fpsr = (uint)value;
|
||||
return true;
|
||||
}
|
||||
case 67:
|
||||
{
|
||||
ulong value = ss.ReadLengthAsHex(8);
|
||||
state.Fpcr = (uint)value;
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private string GdbReadRegister32(IExecutionContext state, int gdbRegId)
|
||||
{
|
||||
switch (gdbRegId)
|
||||
{
|
||||
case >= 0 and <= 14:
|
||||
return ToHex(BitConverter.GetBytes((uint)state.GetX(gdbRegId)));
|
||||
case 15:
|
||||
return ToHex(BitConverter.GetBytes((uint)state.DebugPc));
|
||||
case 16:
|
||||
return ToHex(BitConverter.GetBytes((uint)state.Pstate));
|
||||
case >= 17 and <= 32:
|
||||
return ToHex(state.GetV(gdbRegId - 17).ToArray());
|
||||
case >= 33 and <= 64:
|
||||
int reg = (gdbRegId - 33);
|
||||
int n = reg / 2;
|
||||
int shift = reg % 2;
|
||||
ulong value = state.GetV(n).Extract<ulong>(shift);
|
||||
return ToHex(BitConverter.GetBytes(value));
|
||||
case 65:
|
||||
uint fpscr = (uint)state.Fpsr | (uint)state.Fpcr;
|
||||
return ToHex(BitConverter.GetBytes(fpscr));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool GdbWriteRegister32(IExecutionContext state, int gdbRegId, StringStream ss)
|
||||
{
|
||||
switch (gdbRegId)
|
||||
{
|
||||
case >= 0 and <= 14:
|
||||
{
|
||||
ulong value = ss.ReadLengthAsHex(8);
|
||||
state.SetX(gdbRegId, value);
|
||||
return true;
|
||||
}
|
||||
case 15:
|
||||
{
|
||||
ulong value = ss.ReadLengthAsHex(8);
|
||||
state.DebugPc = value;
|
||||
return true;
|
||||
}
|
||||
case 16:
|
||||
{
|
||||
ulong value = ss.ReadLengthAsHex(8);
|
||||
state.Pstate = (uint)value;
|
||||
return true;
|
||||
}
|
||||
case >= 17 and <= 32:
|
||||
{
|
||||
ulong value0 = ss.ReadLengthAsHex(16);
|
||||
ulong value1 = ss.ReadLengthAsHex(16);
|
||||
state.SetV(gdbRegId - 17, new V128(value0, value1));
|
||||
return true;
|
||||
}
|
||||
case >= 33 and <= 64:
|
||||
{
|
||||
ulong value = ss.ReadLengthAsHex(16);
|
||||
int regId = (gdbRegId - 33);
|
||||
int regNum = regId / 2;
|
||||
int shift = regId % 2;
|
||||
V128 reg = state.GetV(regNum);
|
||||
reg.Insert(shift, value);
|
||||
return true;
|
||||
}
|
||||
case 65:
|
||||
{
|
||||
ulong value = ss.ReadLengthAsHex(8);
|
||||
state.Fpsr = (uint)value & FpcrMask;
|
||||
state.Fpcr = (uint)value & ~FpcrMask;
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void MessageHandlerMain()
|
||||
{
|
||||
while (!_shuttingDown)
|
||||
{
|
||||
IMessage msg = Messages.Take();
|
||||
switch (msg)
|
||||
{
|
||||
case BreakInMessage:
|
||||
Logger.Notice.Print(LogClass.GdbStub, "Break-in requested");
|
||||
CommandQuery();
|
||||
break;
|
||||
|
||||
case SendNackMessage:
|
||||
WriteStream.WriteByte((byte)'-');
|
||||
break;
|
||||
|
||||
case CommandMessage { Command: var cmd }:
|
||||
Logger.Debug?.Print(LogClass.GdbStub, $"Received Command: {cmd}");
|
||||
WriteStream.WriteByte((byte)'+');
|
||||
ProcessCommand(cmd);
|
||||
break;
|
||||
|
||||
case ThreadBreakMessage { Context: var ctx }:
|
||||
DebugProcess.DebugStop();
|
||||
Reply($"T05thread:{ctx.ThreadUid:x};");
|
||||
break;
|
||||
|
||||
case KillMessage:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessCommand(string cmd)
|
||||
{
|
||||
StringStream ss = new StringStream(cmd);
|
||||
|
||||
switch (ss.ReadChar())
|
||||
{
|
||||
case '!':
|
||||
if (!ss.IsEmpty())
|
||||
{
|
||||
goto unknownCommand;
|
||||
}
|
||||
|
||||
// Enable extended mode
|
||||
ReplyOK();
|
||||
break;
|
||||
case '?':
|
||||
if (!ss.IsEmpty())
|
||||
{
|
||||
goto unknownCommand;
|
||||
}
|
||||
|
||||
CommandQuery();
|
||||
break;
|
||||
case 'c':
|
||||
CommandContinue(ss.IsEmpty() ? null : ss.ReadRemainingAsHex());
|
||||
break;
|
||||
case 'D':
|
||||
if (!ss.IsEmpty())
|
||||
{
|
||||
goto unknownCommand;
|
||||
}
|
||||
|
||||
CommandDetach();
|
||||
break;
|
||||
case 'g':
|
||||
if (!ss.IsEmpty())
|
||||
{
|
||||
goto unknownCommand;
|
||||
}
|
||||
|
||||
CommandReadRegisters();
|
||||
break;
|
||||
case 'G':
|
||||
CommandWriteRegisters(ss);
|
||||
break;
|
||||
case 'H':
|
||||
{
|
||||
char op = ss.ReadChar();
|
||||
ulong? threadId = ss.ReadRemainingAsThreadUid();
|
||||
CommandSetThread(op, threadId);
|
||||
break;
|
||||
}
|
||||
case 'k':
|
||||
Logger.Notice.Print(LogClass.GdbStub, "Kill request received");
|
||||
Reply("");
|
||||
Device.IsActive = false;
|
||||
Device.ExitStatus.WaitOne();
|
||||
break;
|
||||
case 'm':
|
||||
{
|
||||
ulong addr = ss.ReadUntilAsHex(',');
|
||||
ulong len = ss.ReadRemainingAsHex();
|
||||
CommandReadMemory(addr, len);
|
||||
break;
|
||||
}
|
||||
case 'M':
|
||||
{
|
||||
ulong addr = ss.ReadUntilAsHex(',');
|
||||
ulong len = ss.ReadUntilAsHex(':');
|
||||
CommandWriteMemory(addr, len, ss);
|
||||
break;
|
||||
}
|
||||
case 'p':
|
||||
{
|
||||
ulong gdbRegId = ss.ReadRemainingAsHex();
|
||||
CommandReadRegister((int)gdbRegId);
|
||||
break;
|
||||
}
|
||||
case 'P':
|
||||
{
|
||||
ulong gdbRegId = ss.ReadUntilAsHex('=');
|
||||
CommandWriteRegister((int)gdbRegId, ss);
|
||||
break;
|
||||
}
|
||||
case 'q':
|
||||
if (ss.ConsumeRemaining("GDBServerVersion"))
|
||||
{
|
||||
Reply($"name:Ryujinx;version:{ReleaseInformation.Version};");
|
||||
break;
|
||||
}
|
||||
|
||||
if (ss.ConsumeRemaining("HostInfo"))
|
||||
{
|
||||
if (IsProcessAarch32)
|
||||
{
|
||||
Reply(
|
||||
$"triple:{ToHex("arm-unknown-linux-android")};endian:little;ptrsize:4;hostname:{ToHex("Ryujinx")};");
|
||||
}
|
||||
else
|
||||
{
|
||||
Reply(
|
||||
$"triple:{ToHex("aarch64-unknown-linux-android")};endian:little;ptrsize:8;hostname:{ToHex("Ryujinx")};");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (ss.ConsumeRemaining("ProcessInfo"))
|
||||
{
|
||||
if (IsProcessAarch32)
|
||||
{
|
||||
Reply(
|
||||
$"pid:1;cputype:12;cpusubtype:0;triple:{ToHex("arm-unknown-linux-android")};ostype:unknown;vendor:none;endian:little;ptrsize:4;");
|
||||
}
|
||||
else
|
||||
{
|
||||
Reply(
|
||||
$"pid:1;cputype:100000c;cpusubtype:0;triple:{ToHex("aarch64-unknown-linux-android")};ostype:unknown;vendor:none;endian:little;ptrsize:8;");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (ss.ConsumePrefix("Supported:") || ss.ConsumeRemaining("Supported"))
|
||||
{
|
||||
Reply("PacketSize=10000;qXfer:features:read+");
|
||||
break;
|
||||
}
|
||||
|
||||
if (ss.ConsumeRemaining("fThreadInfo"))
|
||||
{
|
||||
Reply($"m{string.Join(",", DebugProcess.GetThreadUids().Select(x => $"{x:x}"))}");
|
||||
break;
|
||||
}
|
||||
|
||||
if (ss.ConsumeRemaining("sThreadInfo"))
|
||||
{
|
||||
Reply("l");
|
||||
break;
|
||||
}
|
||||
|
||||
if (ss.ConsumePrefix("ThreadExtraInfo,"))
|
||||
{
|
||||
ulong? threadId = ss.ReadRemainingAsThreadUid();
|
||||
if (threadId == null)
|
||||
{
|
||||
ReplyError();
|
||||
break;
|
||||
}
|
||||
|
||||
if (DebugProcess.GetDebugState() == DebugState.Stopped)
|
||||
{
|
||||
Reply(ToHex("Stopped"));
|
||||
}
|
||||
else
|
||||
{
|
||||
Reply(ToHex("Not stopped"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (ss.ConsumePrefix("Xfer:features:read:"))
|
||||
{
|
||||
string feature = ss.ReadUntil(':');
|
||||
ulong addr = ss.ReadUntilAsHex(',');
|
||||
ulong len = ss.ReadRemainingAsHex();
|
||||
|
||||
if (feature == "target.xml")
|
||||
{
|
||||
feature = IsProcessAarch32 ? "target32.xml" : "target64.xml";
|
||||
}
|
||||
|
||||
string data;
|
||||
if (RegisterInformation.Features.TryGetValue(feature, out data))
|
||||
{
|
||||
if (addr >= (ulong)data.Length)
|
||||
{
|
||||
Reply("l");
|
||||
break;
|
||||
}
|
||||
|
||||
if (len >= (ulong)data.Length - addr)
|
||||
{
|
||||
Reply("l" + ToBinaryFormat(data.Substring((int)addr)));
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
Reply("m" + ToBinaryFormat(data.Substring((int)addr, (int)len)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Reply("E00"); // Invalid annex
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
goto unknownCommand;
|
||||
case 'Q':
|
||||
goto unknownCommand;
|
||||
case 's':
|
||||
CommandStep(ss.IsEmpty() ? null : ss.ReadRemainingAsHex());
|
||||
break;
|
||||
case 'T':
|
||||
{
|
||||
ulong? threadId = ss.ReadRemainingAsThreadUid();
|
||||
CommandIsAlive(threadId);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unknownCommand:
|
||||
Logger.Notice.Print(LogClass.GdbStub, $"Unknown command: {cmd}");
|
||||
Reply("");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CommandQuery()
|
||||
{
|
||||
// GDB is performing initial contact. Stop everything.
|
||||
DebugProcess.DebugStop();
|
||||
gThread = cThread = DebugProcess.GetThreadUids().First();
|
||||
Reply($"T05thread:{cThread:x};");
|
||||
}
|
||||
|
||||
void CommandContinue(ulong? newPc)
|
||||
{
|
||||
if (newPc.HasValue)
|
||||
{
|
||||
if (cThread == null)
|
||||
{
|
||||
ReplyError();
|
||||
return;
|
||||
}
|
||||
|
||||
DebugProcess.GetThread(cThread.Value).Context.DebugPc = newPc.Value;
|
||||
}
|
||||
|
||||
DebugProcess.DebugContinue();
|
||||
}
|
||||
|
||||
void CommandDetach()
|
||||
{
|
||||
// TODO: Remove all breakpoints
|
||||
CommandContinue(null);
|
||||
}
|
||||
|
||||
void CommandReadRegisters()
|
||||
{
|
||||
if (gThread == null)
|
||||
{
|
||||
ReplyError();
|
||||
return;
|
||||
}
|
||||
|
||||
var ctx = DebugProcess.GetThread(gThread.Value).Context;
|
||||
string registers = "";
|
||||
if (IsProcessAarch32)
|
||||
{
|
||||
for (int i = 0; i < GdbRegisterCount32; i++)
|
||||
{
|
||||
registers += GdbReadRegister32(ctx, i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < GdbRegisterCount64; i++)
|
||||
{
|
||||
registers += GdbReadRegister64(ctx, i);
|
||||
}
|
||||
}
|
||||
|
||||
Reply(registers);
|
||||
}
|
||||
|
||||
void CommandWriteRegisters(StringStream ss)
|
||||
{
|
||||
if (gThread == null)
|
||||
{
|
||||
ReplyError();
|
||||
return;
|
||||
}
|
||||
|
||||
var ctx = DebugProcess.GetThread(gThread.Value).Context;
|
||||
if (IsProcessAarch32)
|
||||
{
|
||||
for (int i = 0; i < GdbRegisterCount32; i++)
|
||||
{
|
||||
if (!GdbWriteRegister32(ctx, i, ss))
|
||||
{
|
||||
ReplyError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < GdbRegisterCount64; i++)
|
||||
{
|
||||
if (!GdbWriteRegister64(ctx, i, ss))
|
||||
{
|
||||
ReplyError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ss.IsEmpty())
|
||||
{
|
||||
ReplyOK();
|
||||
}
|
||||
else
|
||||
{
|
||||
ReplyError();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandSetThread(char op, ulong? threadId)
|
||||
{
|
||||
if (threadId == 0 || threadId == null)
|
||||
{
|
||||
threadId = GetThreads().First().ThreadUid;
|
||||
}
|
||||
|
||||
if (DebugProcess.GetThread(threadId.Value) == null)
|
||||
{
|
||||
ReplyError();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case 'c':
|
||||
cThread = threadId;
|
||||
ReplyOK();
|
||||
return;
|
||||
case 'g':
|
||||
gThread = threadId;
|
||||
ReplyOK();
|
||||
return;
|
||||
default:
|
||||
ReplyError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void CommandReadMemory(ulong addr, ulong len)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = new byte[len];
|
||||
DebugProcess.CpuMemory.Read(addr, data);
|
||||
Reply(ToHex(data));
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
ReplyError();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandWriteMemory(ulong addr, ulong len, StringStream ss)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = new byte[len];
|
||||
for (ulong i = 0; i < len; i++)
|
||||
{
|
||||
data[i] = (byte)ss.ReadLengthAsHex(2);
|
||||
}
|
||||
|
||||
DebugProcess.CpuMemory.Write(addr, data);
|
||||
DebugProcess.InvalidateCacheRegion(addr, len);
|
||||
ReplyOK();
|
||||
}
|
||||
catch (InvalidMemoryRegionException)
|
||||
{
|
||||
ReplyError();
|
||||
}
|
||||
}
|
||||
|
||||
void CommandReadRegister(int gdbRegId)
|
||||
{
|
||||
if (gThread == null)
|
||||
{
|
||||
ReplyError();
|
||||
return;
|
||||
}
|
||||
|
||||
var ctx = DebugProcess.GetThread(gThread.Value).Context;
|
||||
string result;
|
||||
if (IsProcessAarch32)
|
||||
{
|
||||
result = GdbReadRegister32(ctx, gdbRegId);
|
||||
if (result != null)
|
||||
{
|
||||
Reply(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReplyError();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = GdbReadRegister64(ctx, gdbRegId);
|
||||
if (result != null)
|
||||
{
|
||||
Reply(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReplyError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandWriteRegister(int gdbRegId, StringStream ss)
|
||||
{
|
||||
if (gThread == null)
|
||||
{
|
||||
ReplyError();
|
||||
return;
|
||||
}
|
||||
|
||||
var ctx = DebugProcess.GetThread(gThread.Value).Context;
|
||||
if (IsProcessAarch32)
|
||||
{
|
||||
if (GdbWriteRegister32(ctx, gdbRegId, ss) && ss.IsEmpty())
|
||||
{
|
||||
ReplyOK();
|
||||
}
|
||||
else
|
||||
{
|
||||
ReplyError();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GdbWriteRegister64(ctx, gdbRegId, ss) && ss.IsEmpty())
|
||||
{
|
||||
ReplyOK();
|
||||
}
|
||||
else
|
||||
{
|
||||
ReplyError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CommandStep(ulong? newPc)
|
||||
{
|
||||
if (cThread == null)
|
||||
{
|
||||
ReplyError();
|
||||
return;
|
||||
}
|
||||
|
||||
var thread = DebugProcess.GetThread(cThread.Value);
|
||||
|
||||
if (newPc.HasValue)
|
||||
{
|
||||
thread.Context.DebugPc = newPc.Value;
|
||||
}
|
||||
|
||||
if (!DebugProcess.DebugStep(thread))
|
||||
{
|
||||
ReplyError();
|
||||
}
|
||||
else
|
||||
{
|
||||
Reply($"T05thread:{thread.ThreadUid:x};");
|
||||
}
|
||||
}
|
||||
|
||||
private void CommandIsAlive(ulong? threadId)
|
||||
{
|
||||
if (GetThreads().Any(x => x.ThreadUid == threadId))
|
||||
{
|
||||
ReplyOK();
|
||||
}
|
||||
else
|
||||
{
|
||||
Reply("E00");
|
||||
}
|
||||
}
|
||||
|
||||
private void Reply(string cmd)
|
||||
{
|
||||
Logger.Debug?.Print(LogClass.GdbStub, $"Reply: {cmd}");
|
||||
WriteStream.Write(Encoding.ASCII.GetBytes($"${cmd}#{CalculateChecksum(cmd):x2}"));
|
||||
}
|
||||
|
||||
private void ReplyOK()
|
||||
{
|
||||
Reply("OK");
|
||||
}
|
||||
|
||||
private void ReplyError()
|
||||
{
|
||||
Reply("E01");
|
||||
}
|
||||
|
||||
private void DebuggerThreadMain()
|
||||
{
|
||||
var endpoint = new IPEndPoint(IPAddress.Any, GdbStubPort);
|
||||
ListenerSocket = new TcpListener(endpoint);
|
||||
ListenerSocket.Start();
|
||||
Logger.Notice.Print(LogClass.GdbStub, $"Currently waiting on {endpoint} for GDB client");
|
||||
|
||||
while (!_shuttingDown)
|
||||
{
|
||||
try
|
||||
{
|
||||
ClientSocket = ListenerSocket.AcceptSocket();
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
ClientSocket.NoDelay = true;
|
||||
ReadStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Read);
|
||||
WriteStream = new NetworkStream(ClientSocket, System.IO.FileAccess.Write);
|
||||
Logger.Notice.Print(LogClass.GdbStub, "GDB client connected");
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (ReadStream.ReadByte())
|
||||
{
|
||||
case -1:
|
||||
goto eof;
|
||||
case '+':
|
||||
continue;
|
||||
case '-':
|
||||
Logger.Notice.Print(LogClass.GdbStub, "NACK received!");
|
||||
continue;
|
||||
case '\x03':
|
||||
Messages.Add(new BreakInMessage());
|
||||
break;
|
||||
case '$':
|
||||
string cmd = "";
|
||||
while (true)
|
||||
{
|
||||
int x = ReadStream.ReadByte();
|
||||
if (x == -1)
|
||||
goto eof;
|
||||
if (x == '#')
|
||||
break;
|
||||
cmd += (char)x;
|
||||
}
|
||||
|
||||
string checksum = $"{(char)ReadStream.ReadByte()}{(char)ReadStream.ReadByte()}";
|
||||
if (checksum == $"{CalculateChecksum(cmd):x2}")
|
||||
{
|
||||
Messages.Add(new CommandMessage(cmd));
|
||||
}
|
||||
else
|
||||
{
|
||||
Messages.Add(new SendNackMessage());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
goto eof;
|
||||
}
|
||||
}
|
||||
|
||||
eof:
|
||||
Logger.Notice.Print(LogClass.GdbStub, "GDB client lost connection");
|
||||
ReadStream.Close();
|
||||
ReadStream = null;
|
||||
WriteStream.Close();
|
||||
WriteStream = null;
|
||||
ClientSocket.Close();
|
||||
ClientSocket = null;
|
||||
}
|
||||
}
|
||||
|
||||
private byte CalculateChecksum(string cmd)
|
||||
{
|
||||
byte checksum = 0;
|
||||
foreach (char x in cmd)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
checksum += (byte)x;
|
||||
}
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
private string ToHex(byte[] bytes)
|
||||
{
|
||||
return string.Join("", bytes.Select(x => $"{x:x2}"));
|
||||
}
|
||||
|
||||
private string ToHex(string str)
|
||||
{
|
||||
return ToHex(Encoding.ASCII.GetBytes(str));
|
||||
}
|
||||
|
||||
private string ToBinaryFormat(byte[] bytes)
|
||||
{
|
||||
return string.Join("", bytes.Select(x =>
|
||||
x switch
|
||||
{
|
||||
(byte)'#' => "}\x03",
|
||||
(byte)'$' => "}\x04",
|
||||
(byte)'*' => "}\x0a",
|
||||
(byte)'}' => "}\x5d",
|
||||
_ => Convert.ToChar(x).ToString(),
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
private string ToBinaryFormat(string str)
|
||||
{
|
||||
return ToBinaryFormat(Encoding.ASCII.GetBytes(str));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_shuttingDown = true;
|
||||
|
||||
ListenerSocket.Stop();
|
||||
ClientSocket?.Shutdown(SocketShutdown.Both);
|
||||
ClientSocket?.Close();
|
||||
ReadStream?.Close();
|
||||
WriteStream?.Close();
|
||||
DebuggerThread.Join();
|
||||
Messages.Add(new KillMessage());
|
||||
MessageHandlerThread.Join();
|
||||
Messages.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void BreakHandler(IExecutionContext ctx, ulong address, int imm)
|
||||
{
|
||||
Logger.Notice.Print(LogClass.GdbStub, $"Break hit on thread {ctx.ThreadUid} at pc {address:x016}");
|
||||
|
||||
Messages.Add(new ThreadBreakMessage(ctx, address, imm));
|
||||
DebugProcess.DebugInterruptHandler(ctx);
|
||||
}
|
||||
|
||||
public void StepHandler(IExecutionContext ctx)
|
||||
{
|
||||
DebugProcess.DebugInterruptHandler(ctx);
|
||||
}
|
||||
}
|
||||
}
|
15
src/Ryujinx.HLE/Debugger/GdbSignal.cs
Normal file
15
src/Ryujinx.HLE/Debugger/GdbSignal.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
enum GdbSignal
|
||||
{
|
||||
Zero = 0,
|
||||
Int = 2,
|
||||
Quit = 3,
|
||||
Trap = 5,
|
||||
Abort = 6,
|
||||
Alarm = 14,
|
||||
IO = 23,
|
||||
XCPU = 24,
|
||||
Unknown = 143
|
||||
}
|
||||
}
|
93
src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml
Normal file
93
src/Ryujinx.HLE/Debugger/GdbXml/aarch64-core.xml
Normal file
|
@ -0,0 +1,93 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- Copyright (C) 2009-2022 Free Software Foundation, Inc.
|
||||
Contributed by ARM Ltd.
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. -->
|
||||
|
||||
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
|
||||
<feature name="org.gnu.gdb.aarch64.core">
|
||||
<reg name="x0" bitsize="64"/>
|
||||
<reg name="x1" bitsize="64"/>
|
||||
<reg name="x2" bitsize="64"/>
|
||||
<reg name="x3" bitsize="64"/>
|
||||
<reg name="x4" bitsize="64"/>
|
||||
<reg name="x5" bitsize="64"/>
|
||||
<reg name="x6" bitsize="64"/>
|
||||
<reg name="x7" bitsize="64"/>
|
||||
<reg name="x8" bitsize="64"/>
|
||||
<reg name="x9" bitsize="64"/>
|
||||
<reg name="x10" bitsize="64"/>
|
||||
<reg name="x11" bitsize="64"/>
|
||||
<reg name="x12" bitsize="64"/>
|
||||
<reg name="x13" bitsize="64"/>
|
||||
<reg name="x14" bitsize="64"/>
|
||||
<reg name="x15" bitsize="64"/>
|
||||
<reg name="x16" bitsize="64"/>
|
||||
<reg name="x17" bitsize="64"/>
|
||||
<reg name="x18" bitsize="64"/>
|
||||
<reg name="x19" bitsize="64"/>
|
||||
<reg name="x20" bitsize="64"/>
|
||||
<reg name="x21" bitsize="64"/>
|
||||
<reg name="x22" bitsize="64"/>
|
||||
<reg name="x23" bitsize="64"/>
|
||||
<reg name="x24" bitsize="64"/>
|
||||
<reg name="x25" bitsize="64"/>
|
||||
<reg name="x26" bitsize="64"/>
|
||||
<reg name="x27" bitsize="64"/>
|
||||
<reg name="x28" bitsize="64"/>
|
||||
<reg name="x29" bitsize="64"/>
|
||||
<reg name="x30" bitsize="64"/>
|
||||
<reg name="sp" bitsize="64" type="data_ptr"/>
|
||||
|
||||
<reg name="pc" bitsize="64" type="code_ptr"/>
|
||||
|
||||
<flags id="cpsr_flags" size="4">
|
||||
<!-- Stack Pointer. -->
|
||||
<field name="SP" start="0" end="0"/>
|
||||
|
||||
<!-- Exception Level. -->
|
||||
<field name="EL" start="2" end="3"/>
|
||||
<!-- Execution state. -->
|
||||
<field name="nRW" start="4" end="4"/>
|
||||
|
||||
<!-- FIQ interrupt mask. -->
|
||||
<field name="F" start="6" end="6"/>
|
||||
<!-- IRQ interrupt mask. -->
|
||||
<field name="I" start="7" end="7"/>
|
||||
<!-- SError interrupt mask. -->
|
||||
<field name="A" start="8" end="8"/>
|
||||
<!-- Debug exception mask. -->
|
||||
<field name="D" start="9" end="9"/>
|
||||
|
||||
<!-- ARMv8.5-A: Branch Target Identification BTYPE. -->
|
||||
<field name="BTYPE" start="10" end="11"/>
|
||||
|
||||
<!-- ARMv8.0-A: Speculative Store Bypass. -->
|
||||
<field name="SSBS" start="12" end="12"/>
|
||||
|
||||
<!-- Illegal Execution state. -->
|
||||
<field name="IL" start="20" end="20"/>
|
||||
<!-- Software Step. -->
|
||||
<field name="SS" start="21" end="21"/>
|
||||
<!-- ARMv8.1-A: Privileged Access Never. -->
|
||||
<field name="PAN" start="22" end="22"/>
|
||||
<!-- ARMv8.2-A: User Access Override. -->
|
||||
<field name="UAO" start="23" end="23"/>
|
||||
<!-- ARMv8.4-A: Data Independent Timing. -->
|
||||
<field name="DIT" start="24" end="24"/>
|
||||
<!-- ARMv8.5-A: Tag Check Override. -->
|
||||
<field name="TCO" start="25" end="25"/>
|
||||
|
||||
<!-- Overflow Condition flag. -->
|
||||
<field name="V" start="28" end="28"/>
|
||||
<!-- Carry Condition flag. -->
|
||||
<field name="C" start="29" end="29"/>
|
||||
<!-- Zero Condition flag. -->
|
||||
<field name="Z" start="30" end="30"/>
|
||||
<!-- Negative Condition flag. -->
|
||||
<field name="N" start="31" end="31"/>
|
||||
</flags>
|
||||
<reg name="cpsr" bitsize="32" type="cpsr_flags"/>
|
||||
|
||||
</feature>
|
159
src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml
Normal file
159
src/Ryujinx.HLE/Debugger/GdbXml/aarch64-fpu.xml
Normal file
|
@ -0,0 +1,159 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- Copyright (C) 2009-2022 Free Software Foundation, Inc.
|
||||
Contributed by ARM Ltd.
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. -->
|
||||
|
||||
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
|
||||
<feature name="org.gnu.gdb.aarch64.fpu">
|
||||
<vector id="v2d" type="ieee_double" count="2"/>
|
||||
<vector id="v2u" type="uint64" count="2"/>
|
||||
<vector id="v2i" type="int64" count="2"/>
|
||||
<vector id="v4f" type="ieee_single" count="4"/>
|
||||
<vector id="v4u" type="uint32" count="4"/>
|
||||
<vector id="v4i" type="int32" count="4"/>
|
||||
<vector id="v8f" type="ieee_half" count="8"/>
|
||||
<vector id="v8u" type="uint16" count="8"/>
|
||||
<vector id="v8i" type="int16" count="8"/>
|
||||
<vector id="v8bf16" type="bfloat16" count="8"/>
|
||||
<vector id="v16u" type="uint8" count="16"/>
|
||||
<vector id="v16i" type="int8" count="16"/>
|
||||
<vector id="v1u" type="uint128" count="1"/>
|
||||
<vector id="v1i" type="int128" count="1"/>
|
||||
<union id="vnd">
|
||||
<field name="f" type="v2d"/>
|
||||
<field name="u" type="v2u"/>
|
||||
<field name="s" type="v2i"/>
|
||||
</union>
|
||||
<union id="vns">
|
||||
<field name="f" type="v4f"/>
|
||||
<field name="u" type="v4u"/>
|
||||
<field name="s" type="v4i"/>
|
||||
</union>
|
||||
<union id="vnh">
|
||||
<field name="bf" type="v8bf16"/>
|
||||
<field name="f" type="v8f"/>
|
||||
<field name="u" type="v8u"/>
|
||||
<field name="s" type="v8i"/>
|
||||
</union>
|
||||
<union id="vnb">
|
||||
<field name="u" type="v16u"/>
|
||||
<field name="s" type="v16i"/>
|
||||
</union>
|
||||
<union id="vnq">
|
||||
<field name="u" type="v1u"/>
|
||||
<field name="s" type="v1i"/>
|
||||
</union>
|
||||
<union id="aarch64v">
|
||||
<field name="d" type="vnd"/>
|
||||
<field name="s" type="vns"/>
|
||||
<field name="h" type="vnh"/>
|
||||
<field name="b" type="vnb"/>
|
||||
<field name="q" type="vnq"/>
|
||||
</union>
|
||||
<reg name="v0" bitsize="128" type="aarch64v" regnum="34"/>
|
||||
<reg name="v1" bitsize="128" type="aarch64v" />
|
||||
<reg name="v2" bitsize="128" type="aarch64v" />
|
||||
<reg name="v3" bitsize="128" type="aarch64v" />
|
||||
<reg name="v4" bitsize="128" type="aarch64v" />
|
||||
<reg name="v5" bitsize="128" type="aarch64v" />
|
||||
<reg name="v6" bitsize="128" type="aarch64v" />
|
||||
<reg name="v7" bitsize="128" type="aarch64v" />
|
||||
<reg name="v8" bitsize="128" type="aarch64v" />
|
||||
<reg name="v9" bitsize="128" type="aarch64v" />
|
||||
<reg name="v10" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v11" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v12" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v13" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v14" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v15" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v16" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v17" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v18" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v19" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v20" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v21" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v22" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v23" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v24" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v25" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v26" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v27" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v28" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v29" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v30" bitsize="128" type="aarch64v"/>
|
||||
<reg name="v31" bitsize="128" type="aarch64v"/>
|
||||
|
||||
<flags id="fpsr_flags" size="4">
|
||||
<!-- Invalid Operation cumulative floating-point exception bit. -->
|
||||
<field name="IOC" start="0" end="0"/>
|
||||
<!-- Divide by Zero cumulative floating-point exception bit. -->
|
||||
<field name="DZC" start="1" end="1"/>
|
||||
<!-- Overflow cumulative floating-point exception bit. -->
|
||||
<field name="OFC" start="2" end="2"/>
|
||||
<!-- Underflow cumulative floating-point exception bit. -->
|
||||
<field name="UFC" start="3" end="3"/>
|
||||
<!-- Inexact cumulative floating-point exception bit.. -->
|
||||
<field name="IXC" start="4" end="4"/>
|
||||
<!-- Input Denormal cumulative floating-point exception bit. -->
|
||||
<field name="IDC" start="7" end="7"/>
|
||||
<!-- Cumulative saturation bit, Advanced SIMD only. -->
|
||||
<field name="QC" start="27" end="27"/>
|
||||
<!-- When AArch32 is supported at any Exception level and AArch32
|
||||
floating-point is implemented: Overflow condition flag for AArch32
|
||||
floating-point comparison operations. -->
|
||||
<field name="V" start="28" end="28"/>
|
||||
<!-- When AArch32 is supported at any Exception level and AArch32
|
||||
floating-point is implemented:
|
||||
Carry condition flag for AArch32 floating-point comparison operations.
|
||||
-->
|
||||
<field name="C" start="29" end="29"/>
|
||||
<!-- When AArch32 is supported at any Exception level and AArch32
|
||||
floating-point is implemented:
|
||||
Zero condition flag for AArch32 floating-point comparison operations.
|
||||
-->
|
||||
<field name="Z" start="30" end="30"/>
|
||||
<!-- When AArch32 is supported at any Exception level and AArch32
|
||||
floating-point is implemented:
|
||||
Negative condition flag for AArch32 floating-point comparison
|
||||
operations. -->
|
||||
<field name="N" start="31" end="31"/>
|
||||
</flags>
|
||||
<reg name="fpsr" bitsize="32" type="fpsr_flags"/>
|
||||
|
||||
<flags id="fpcr_flags" size="4">
|
||||
<!-- Flush Inputs to Zero (part of Armv8.7). -->
|
||||
<field name="FIZ" start="0" end="0"/>
|
||||
<!-- Alternate Handling (part of Armv8.7). -->
|
||||
<field name="AH" start="1" end="1"/>
|
||||
<!-- Controls how the output elements other than the lowest element of the
|
||||
vector are determined for Advanced SIMD scalar instructions (part of
|
||||
Armv8.7). -->
|
||||
<field name="NEP" start="2" end="2"/>
|
||||
<!-- Invalid Operation floating-point exception trap enable. -->
|
||||
<field name="IOE" start="8" end="8"/>
|
||||
<!-- Divide by Zero floating-point exception trap enable. -->
|
||||
<field name="DZE" start="9" end="9"/>
|
||||
<!-- Overflow floating-point exception trap enable. -->
|
||||
<field name="OFE" start="10" end="10"/>
|
||||
<!-- Underflow floating-point exception trap enable. -->
|
||||
<field name="UFE" start="11" end="11"/>
|
||||
<!-- Inexact floating-point exception trap enable. -->
|
||||
<field name="IXE" start="12" end="12"/>
|
||||
<!-- Input Denormal floating-point exception trap enable. -->
|
||||
<field name="IDE" start="15" end="15"/>
|
||||
<!-- Flush-to-zero mode control bit on half-precision data-processing
|
||||
instructions. -->
|
||||
<field name="FZ16" start="19" end="19"/>
|
||||
<!-- Rounding Mode control field. -->
|
||||
<field name="RMode" start="22" end="23"/>
|
||||
<!-- Flush-to-zero mode control bit. -->
|
||||
<field name="FZ" start="24" end="24"/>
|
||||
<!-- Default NaN mode control bit. -->
|
||||
<field name="DN" start="25" end="25"/>
|
||||
<!-- Alternative half-precision control bit. -->
|
||||
<field name="AHP" start="26" end="26"/>
|
||||
</flags>
|
||||
<reg name="fpcr" bitsize="32" type="fpcr_flags"/>
|
||||
</feature>
|
27
src/Ryujinx.HLE/Debugger/GdbXml/arm-core.xml
Normal file
27
src/Ryujinx.HLE/Debugger/GdbXml/arm-core.xml
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- Copyright (C) 2008 Free Software Foundation, Inc.
|
||||
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. -->
|
||||
|
||||
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
|
||||
<feature name="org.gnu.gdb.arm.core">
|
||||
<reg name="r0" bitsize="32"/>
|
||||
<reg name="r1" bitsize="32"/>
|
||||
<reg name="r2" bitsize="32"/>
|
||||
<reg name="r3" bitsize="32"/>
|
||||
<reg name="r4" bitsize="32"/>
|
||||
<reg name="r5" bitsize="32"/>
|
||||
<reg name="r6" bitsize="32"/>
|
||||
<reg name="r7" bitsize="32"/>
|
||||
<reg name="r8" bitsize="32"/>
|
||||
<reg name="r9" bitsize="32"/>
|
||||
<reg name="r10" bitsize="32"/>
|
||||
<reg name="r11" bitsize="32"/>
|
||||
<reg name="r12" bitsize="32"/>
|
||||
<reg name="sp" bitsize="32" type="data_ptr"/>
|
||||
<reg name="lr" bitsize="32"/>
|
||||
<reg name="pc" bitsize="32" type="code_ptr"/>
|
||||
<reg name="cpsr" bitsize="32" />
|
||||
</feature>
|
86
src/Ryujinx.HLE/Debugger/GdbXml/arm-neon.xml
Normal file
86
src/Ryujinx.HLE/Debugger/GdbXml/arm-neon.xml
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- Copyright (C) 2008 Free Software Foundation, Inc.
|
||||
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. -->
|
||||
<!DOCTYPE feature SYSTEM "gdb-target.dtd">
|
||||
<feature name="org.gnu.gdb.arm.vfp">
|
||||
<vector id="neon_uint8x8" type="uint8" count="8"/>
|
||||
<vector id="neon_uint16x4" type="uint16" count="4"/>
|
||||
<vector id="neon_uint32x2" type="uint32" count="2"/>
|
||||
<vector id="neon_float32x2" type="ieee_single" count="2"/>
|
||||
<union id="neon_d">
|
||||
<field name="u8" type="neon_uint8x8"/>
|
||||
<field name="u16" type="neon_uint16x4"/>
|
||||
<field name="u32" type="neon_uint32x2"/>
|
||||
<field name="u64" type="uint64"/>
|
||||
<field name="f32" type="neon_float32x2"/>
|
||||
<field name="f64" type="ieee_double"/>
|
||||
</union>
|
||||
<vector id="neon_uint8x16" type="uint8" count="16"/>
|
||||
<vector id="neon_uint16x8" type="uint16" count="8"/>
|
||||
<vector id="neon_uint32x4" type="uint32" count="4"/>
|
||||
<vector id="neon_uint64x2" type="uint64" count="2"/>
|
||||
<vector id="neon_float32x4" type="ieee_single" count="4"/>
|
||||
<vector id="neon_float64x2" type="ieee_double" count="2"/>
|
||||
<union id="neon_q">
|
||||
<field name="u8" type="neon_uint8x16"/>
|
||||
<field name="u16" type="neon_uint16x8"/>
|
||||
<field name="u32" type="neon_uint32x4"/>
|
||||
<field name="u64" type="neon_uint64x2"/>
|
||||
<field name="f32" type="neon_float32x4"/>
|
||||
<field name="f64" type="neon_float64x2"/>
|
||||
</union>
|
||||
<reg name="d0" bitsize="64" type="neon_d"/>
|
||||
<reg name="d1" bitsize="64" type="neon_d"/>
|
||||
<reg name="d2" bitsize="64" type="neon_d"/>
|
||||
<reg name="d3" bitsize="64" type="neon_d"/>
|
||||
<reg name="d4" bitsize="64" type="neon_d"/>
|
||||
<reg name="d5" bitsize="64" type="neon_d"/>
|
||||
<reg name="d6" bitsize="64" type="neon_d"/>
|
||||
<reg name="d7" bitsize="64" type="neon_d"/>
|
||||
<reg name="d8" bitsize="64" type="neon_d"/>
|
||||
<reg name="d9" bitsize="64" type="neon_d"/>
|
||||
<reg name="d10" bitsize="64" type="neon_d"/>
|
||||
<reg name="d11" bitsize="64" type="neon_d"/>
|
||||
<reg name="d12" bitsize="64" type="neon_d"/>
|
||||
<reg name="d13" bitsize="64" type="neon_d"/>
|
||||
<reg name="d14" bitsize="64" type="neon_d"/>
|
||||
<reg name="d15" bitsize="64" type="neon_d"/>
|
||||
<reg name="d16" bitsize="64" type="neon_d"/>
|
||||
<reg name="d17" bitsize="64" type="neon_d"/>
|
||||
<reg name="d18" bitsize="64" type="neon_d"/>
|
||||
<reg name="d19" bitsize="64" type="neon_d"/>
|
||||
<reg name="d20" bitsize="64" type="neon_d"/>
|
||||
<reg name="d21" bitsize="64" type="neon_d"/>
|
||||
<reg name="d22" bitsize="64" type="neon_d"/>
|
||||
<reg name="d23" bitsize="64" type="neon_d"/>
|
||||
<reg name="d24" bitsize="64" type="neon_d"/>
|
||||
<reg name="d25" bitsize="64" type="neon_d"/>
|
||||
<reg name="d26" bitsize="64" type="neon_d"/>
|
||||
<reg name="d27" bitsize="64" type="neon_d"/>
|
||||
<reg name="d28" bitsize="64" type="neon_d"/>
|
||||
<reg name="d29" bitsize="64" type="neon_d"/>
|
||||
<reg name="d30" bitsize="64" type="neon_d"/>
|
||||
<reg name="d31" bitsize="64" type="neon_d"/>
|
||||
|
||||
<reg name="q0" bitsize="128" type="neon_q"/>
|
||||
<reg name="q1" bitsize="128" type="neon_q"/>
|
||||
<reg name="q2" bitsize="128" type="neon_q"/>
|
||||
<reg name="q3" bitsize="128" type="neon_q"/>
|
||||
<reg name="q4" bitsize="128" type="neon_q"/>
|
||||
<reg name="q5" bitsize="128" type="neon_q"/>
|
||||
<reg name="q6" bitsize="128" type="neon_q"/>
|
||||
<reg name="q7" bitsize="128" type="neon_q"/>
|
||||
<reg name="q8" bitsize="128" type="neon_q"/>
|
||||
<reg name="q9" bitsize="128" type="neon_q"/>
|
||||
<reg name="q10" bitsize="128" type="neon_q"/>
|
||||
<reg name="q11" bitsize="128" type="neon_q"/>
|
||||
<reg name="q12" bitsize="128" type="neon_q"/>
|
||||
<reg name="q13" bitsize="128" type="neon_q"/>
|
||||
<reg name="q14" bitsize="128" type="neon_q"/>
|
||||
<reg name="q15" bitsize="128" type="neon_q"/>
|
||||
|
||||
<reg name="fpscr" bitsize="32" type="int" group="float"/>
|
||||
</feature>
|
14
src/Ryujinx.HLE/Debugger/GdbXml/target32.xml
Normal file
14
src/Ryujinx.HLE/Debugger/GdbXml/target32.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- Copyright (C) 2009-2013 Free Software Foundation, Inc.
|
||||
Contributed by ARM Ltd.
|
||||
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. -->
|
||||
|
||||
<!DOCTYPE target SYSTEM "gdb-target.dtd">
|
||||
<target>
|
||||
<architecture>arm</architecture>
|
||||
<xi:include href="arm-core.xml"/>
|
||||
<xi:include href="arm-neon.xml"/>
|
||||
</target>
|
14
src/Ryujinx.HLE/Debugger/GdbXml/target64.xml
Normal file
14
src/Ryujinx.HLE/Debugger/GdbXml/target64.xml
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- Copyright (C) 2009-2013 Free Software Foundation, Inc.
|
||||
Contributed by ARM Ltd.
|
||||
|
||||
Copying and distribution of this file, with or without modification,
|
||||
are permitted in any medium without royalty provided the copyright
|
||||
notice and this notice are preserved. -->
|
||||
|
||||
<!DOCTYPE target SYSTEM "gdb-target.dtd">
|
||||
<target>
|
||||
<architecture>aarch64</architecture>
|
||||
<xi:include href="aarch64-core.xml"/>
|
||||
<xi:include href="aarch64-fpu.xml"/>
|
||||
</target>
|
19
src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs
Normal file
19
src/Ryujinx.HLE/Debugger/IDebuggableProcess.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.Memory;
|
||||
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
internal interface IDebuggableProcess
|
||||
{
|
||||
void DebugStop();
|
||||
void DebugContinue();
|
||||
bool DebugStep(KThread thread);
|
||||
KThread GetThread(ulong threadUid);
|
||||
DebugState GetDebugState();
|
||||
ulong[] GetThreadUids();
|
||||
public void DebugInterruptHandler(IExecutionContext ctx);
|
||||
IVirtualMemoryManager CpuMemory { get; }
|
||||
void InvalidateCacheRegion(ulong address, ulong size);
|
||||
}
|
||||
}
|
6
src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
Normal file
6
src/Ryujinx.HLE/Debugger/Message/BreakInMessage.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
struct BreakInMessage : IMessage
|
||||
{
|
||||
}
|
||||
}
|
12
src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs
Normal file
12
src/Ryujinx.HLE/Debugger/Message/CommandMessage.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
struct CommandMessage : IMessage
|
||||
{
|
||||
public string Command;
|
||||
|
||||
public CommandMessage(string cmd)
|
||||
{
|
||||
Command = cmd;
|
||||
}
|
||||
}
|
||||
}
|
6
src/Ryujinx.HLE/Debugger/Message/IMessage.cs
Normal file
6
src/Ryujinx.HLE/Debugger/Message/IMessage.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
interface IMessage
|
||||
{
|
||||
}
|
||||
}
|
6
src/Ryujinx.HLE/Debugger/Message/KillMessage.cs
Normal file
6
src/Ryujinx.HLE/Debugger/Message/KillMessage.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
struct KillMessage : IMessage
|
||||
{
|
||||
}
|
||||
}
|
6
src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
Normal file
6
src/Ryujinx.HLE/Debugger/Message/SendNackMessage.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
struct SendNackMessage : IMessage
|
||||
{
|
||||
}
|
||||
}
|
18
src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs
Normal file
18
src/Ryujinx.HLE/Debugger/Message/ThreadBreakMessage.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using IExecutionContext = Ryujinx.Cpu.IExecutionContext;
|
||||
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
public class ThreadBreakMessage : IMessage
|
||||
{
|
||||
public IExecutionContext Context { get; }
|
||||
public ulong Address { get; }
|
||||
public int Opcode { get; }
|
||||
|
||||
public ThreadBreakMessage(IExecutionContext context, ulong address, int opcode)
|
||||
{
|
||||
Context = context;
|
||||
Address = address;
|
||||
Opcode = opcode;
|
||||
}
|
||||
}
|
||||
}
|
28
src/Ryujinx.HLE/Debugger/RegisterInformation.cs
Normal file
28
src/Ryujinx.HLE/Debugger/RegisterInformation.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
class RegisterInformation
|
||||
{
|
||||
public static readonly Dictionary<string, string> Features = new()
|
||||
{
|
||||
{ "target64.xml", GetEmbeddedResourceContent("target64.xml") },
|
||||
{ "target32.xml", GetEmbeddedResourceContent("target32.xml") },
|
||||
{ "aarch64-core.xml", GetEmbeddedResourceContent("aarch64-core.xml") },
|
||||
{ "aarch64-fpu.xml", GetEmbeddedResourceContent("aarch64-fpu.xml") },
|
||||
{ "arm-core.xml", GetEmbeddedResourceContent("arm-core.xml") },
|
||||
{ "arm-neon.xml", GetEmbeddedResourceContent("arm-neon.xml") },
|
||||
};
|
||||
|
||||
private static string GetEmbeddedResourceContent(string resourceName)
|
||||
{
|
||||
Stream stream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Ryujinx.HLE.Debugger.GdbXml." + resourceName);
|
||||
StreamReader reader = new StreamReader(stream);
|
||||
string result = reader.ReadToEnd();
|
||||
reader.Dispose();
|
||||
stream.Dispose();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
109
src/Ryujinx.HLE/Debugger/StringStream.cs
Normal file
109
src/Ryujinx.HLE/Debugger/StringStream.cs
Normal file
|
@ -0,0 +1,109 @@
|
|||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Ryujinx.HLE.Debugger
|
||||
{
|
||||
class StringStream
|
||||
{
|
||||
private readonly string Data;
|
||||
private int Position;
|
||||
|
||||
public StringStream(string s)
|
||||
{
|
||||
Data = s;
|
||||
}
|
||||
|
||||
public char ReadChar()
|
||||
{
|
||||
return Data[Position++];
|
||||
}
|
||||
|
||||
public string ReadUntil(char needle)
|
||||
{
|
||||
int needlePos = Data.IndexOf(needle, Position);
|
||||
|
||||
if (needlePos == -1)
|
||||
{
|
||||
needlePos = Data.Length;
|
||||
}
|
||||
|
||||
string result = Data.Substring(Position, needlePos - Position);
|
||||
Position = needlePos + 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
public string ReadLength(int len)
|
||||
{
|
||||
string result = Data.Substring(Position, len);
|
||||
Position += len;
|
||||
return result;
|
||||
}
|
||||
|
||||
public string ReadRemaining()
|
||||
{
|
||||
string result = Data.Substring(Position);
|
||||
Position = Data.Length;
|
||||
return result;
|
||||
}
|
||||
|
||||
public ulong ReadRemainingAsHex()
|
||||
{
|
||||
return ulong.Parse(ReadRemaining(), NumberStyles.HexNumber);
|
||||
}
|
||||
|
||||
public ulong ReadUntilAsHex(char needle)
|
||||
{
|
||||
return ulong.Parse(ReadUntil(needle), NumberStyles.HexNumber);
|
||||
}
|
||||
|
||||
public ulong ReadLengthAsHex(int len)
|
||||
{
|
||||
return ulong.Parse(ReadLength(len), NumberStyles.HexNumber);
|
||||
}
|
||||
|
||||
public ulong ReadLengthAsLEHex(int len)
|
||||
{
|
||||
Debug.Assert(len % 2 == 0);
|
||||
|
||||
ulong result = 0;
|
||||
int pos = 0;
|
||||
while (pos < len)
|
||||
{
|
||||
result += ReadLengthAsHex(2) << (4 * pos);
|
||||
pos += 2;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public ulong? ReadRemainingAsThreadUid()
|
||||
{
|
||||
string s = ReadRemaining();
|
||||
return s == "-1" ? null : ulong.Parse(s, NumberStyles.HexNumber);
|
||||
}
|
||||
|
||||
public bool ConsumePrefix(string prefix)
|
||||
{
|
||||
if (Data.Substring(Position).StartsWith(prefix))
|
||||
{
|
||||
Position += prefix.Length;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ConsumeRemaining(string match)
|
||||
{
|
||||
if (Data.Substring(Position) == match)
|
||||
{
|
||||
Position += match.Length;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsEmpty()
|
||||
{
|
||||
return Position >= Data.Length;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -169,6 +169,21 @@ namespace Ryujinx.HLE
|
|||
/// </summary>
|
||||
public Action RefreshInputConfig { internal get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables gdbstub to allow for debugging of the guest .
|
||||
/// </summary>
|
||||
public bool EnableGdbStub { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// A TCP port to use to expose a gdbstub for a debugger to connect to.
|
||||
/// </summary>
|
||||
public ushort GdbStubPort { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Suspend execution when starting an application
|
||||
/// </summary>
|
||||
public bool DebuggerSuspendOnStart { get; internal set; }
|
||||
|
||||
public HLEConfiguration(VirtualFileSystem virtualFileSystem,
|
||||
LibHacHorizonManager libHacHorizonManager,
|
||||
ContentManager contentManager,
|
||||
|
@ -194,7 +209,10 @@ namespace Ryujinx.HLE
|
|||
float audioVolume,
|
||||
bool useHypervisor,
|
||||
string multiplayerLanInterfaceId,
|
||||
MultiplayerMode multiplayerMode)
|
||||
MultiplayerMode multiplayerMode,
|
||||
bool enableGdbStub,
|
||||
ushort gdbStubPort,
|
||||
bool debuggerSuspendOnStart)
|
||||
{
|
||||
VirtualFileSystem = virtualFileSystem;
|
||||
LibHacHorizonManager = libHacHorizonManager;
|
||||
|
@ -222,6 +240,9 @@ namespace Ryujinx.HLE
|
|||
UseHypervisor = useHypervisor;
|
||||
MultiplayerLanInterfaceId = multiplayerLanInterfaceId;
|
||||
MultiplayerMode = multiplayerMode;
|
||||
EnableGdbStub = enableGdbStub;
|
||||
GdbStubPort = gdbStubPort;
|
||||
DebuggerSuspendOnStart = debuggerSuspendOnStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ using LibHac.Fs.Shim;
|
|||
using LibHac.FsSystem;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.Debugger;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Kernel;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
|
@ -473,5 +474,13 @@ namespace Ryujinx.HLE.HOS
|
|||
}
|
||||
IsPaused = pause;
|
||||
}
|
||||
|
||||
internal IDebuggableProcess DebugGetApplicationProcess()
|
||||
{
|
||||
lock (KernelContext.Processes)
|
||||
{
|
||||
return KernelContext.Processes.Values.FirstOrDefault(x => x.IsApplication)?.DebugInterface;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.Debugger;
|
||||
using Ryujinx.HLE.Exceptions;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
|
@ -11,6 +12,8 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using ExceptionCallback = Ryujinx.Cpu.ExceptionCallback;
|
||||
using ExceptionCallbackNoArgs = Ryujinx.Cpu.ExceptionCallbackNoArgs;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
|
@ -89,6 +92,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||
public IVirtualMemoryManager CpuMemory => Context.AddressSpace;
|
||||
|
||||
public HleProcessDebugger Debugger { get; private set; }
|
||||
public IDebuggableProcess DebugInterface { get; private set; }
|
||||
protected int debugState = (int)DebugState.Running;
|
||||
|
||||
public KProcess(KernelContext context, bool allowCodeMemoryForJit = false) : base(context)
|
||||
{
|
||||
|
@ -110,6 +115,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||
_threads = new LinkedList<KThread>();
|
||||
|
||||
Debugger = new HleProcessDebugger(this);
|
||||
DebugInterface = new DebuggerInterface(this);
|
||||
}
|
||||
|
||||
public Result InitializeKip(
|
||||
|
@ -680,6 +686,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||
|
||||
SetState(newState);
|
||||
|
||||
if (KernelContext.Device.Configuration.DebuggerSuspendOnStart && IsApplication)
|
||||
{
|
||||
mainThread.Suspend(ThreadSchedState.ThreadPauseFlag);
|
||||
debugState = (int)DebugState.Stopped;
|
||||
}
|
||||
|
||||
result = mainThread.Start();
|
||||
|
||||
if (result != Result.Success)
|
||||
|
@ -728,9 +740,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||
|
||||
public IExecutionContext CreateExecutionContext()
|
||||
{
|
||||
ExceptionCallback breakCallback = null;
|
||||
ExceptionCallbackNoArgs stepCallback = null;
|
||||
|
||||
if (KernelContext.Device.Configuration.EnableGdbStub)
|
||||
{
|
||||
breakCallback = KernelContext.Device.Debugger.BreakHandler;
|
||||
stepCallback = KernelContext.Device.Debugger.StepHandler;
|
||||
}
|
||||
|
||||
return Context?.CreateExecutionContext(new ExceptionCallbacks(
|
||||
InterruptHandler,
|
||||
null,
|
||||
breakCallback,
|
||||
stepCallback,
|
||||
KernelContext.SyscallHandler.SvcCall,
|
||||
UndefinedInstructionHandler));
|
||||
}
|
||||
|
@ -1175,5 +1197,154 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||
{
|
||||
return Capabilities.IsSvcPermitted(svcId);
|
||||
}
|
||||
|
||||
private class DebuggerInterface : IDebuggableProcess
|
||||
{
|
||||
private Barrier StepBarrier;
|
||||
private readonly KProcess _parent;
|
||||
private readonly KernelContext _kernelContext;
|
||||
private KThread steppingThread;
|
||||
|
||||
public DebuggerInterface(KProcess p)
|
||||
{
|
||||
_parent = p;
|
||||
_kernelContext = p.KernelContext;
|
||||
StepBarrier = new(2);
|
||||
}
|
||||
|
||||
public void DebugStop()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _parent.debugState, (int)DebugState.Stopping,
|
||||
(int)DebugState.Running) != (int)DebugState.Running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_kernelContext.CriticalSection.Enter();
|
||||
lock (_parent._threadingLock)
|
||||
{
|
||||
foreach (KThread thread in _parent._threads)
|
||||
{
|
||||
thread.Suspend(ThreadSchedState.ThreadPauseFlag);
|
||||
thread.Context.RequestInterrupt();
|
||||
thread.DebugHalt.WaitOne();
|
||||
}
|
||||
}
|
||||
|
||||
_parent.debugState = (int)DebugState.Stopped;
|
||||
_kernelContext.CriticalSection.Leave();
|
||||
}
|
||||
|
||||
public void DebugContinue()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _parent.debugState, (int)DebugState.Running,
|
||||
(int)DebugState.Stopped) != (int)DebugState.Stopped)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_kernelContext.CriticalSection.Enter();
|
||||
lock (_parent._threadingLock)
|
||||
{
|
||||
foreach (KThread thread in _parent._threads)
|
||||
{
|
||||
thread.Resume(ThreadSchedState.ThreadPauseFlag);
|
||||
}
|
||||
}
|
||||
_kernelContext.CriticalSection.Leave();
|
||||
}
|
||||
|
||||
public bool DebugStep(KThread target)
|
||||
{
|
||||
if (_parent.debugState != (int)DebugState.Stopped)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_kernelContext.CriticalSection.Enter();
|
||||
steppingThread = target;
|
||||
bool waiting = target.MutexOwner != null || target.WaitingSync || target.WaitingInArbitration;
|
||||
target.Context.RequestDebugStep();
|
||||
if (waiting)
|
||||
{
|
||||
lock (_parent._threadingLock)
|
||||
{
|
||||
foreach (KThread thread in _parent._threads)
|
||||
{
|
||||
thread.Resume(ThreadSchedState.ThreadPauseFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
target.Resume(ThreadSchedState.ThreadPauseFlag);
|
||||
}
|
||||
_kernelContext.CriticalSection.Leave();
|
||||
|
||||
StepBarrier.SignalAndWait();
|
||||
|
||||
_kernelContext.CriticalSection.Enter();
|
||||
steppingThread = null;
|
||||
if (waiting)
|
||||
{
|
||||
lock (_parent._threadingLock)
|
||||
{
|
||||
foreach (KThread thread in _parent._threads)
|
||||
{
|
||||
thread.Suspend(ThreadSchedState.ThreadPauseFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
target.Suspend(ThreadSchedState.ThreadPauseFlag);
|
||||
}
|
||||
_kernelContext.CriticalSection.Leave();
|
||||
StepBarrier.SignalAndWait();
|
||||
return true;
|
||||
}
|
||||
|
||||
public DebugState GetDebugState()
|
||||
{
|
||||
return (DebugState)_parent.debugState;
|
||||
}
|
||||
|
||||
public ulong[] GetThreadUids()
|
||||
{
|
||||
lock (_parent._threadingLock)
|
||||
{
|
||||
var threads = _parent._threads.Where(x => !x.TerminationRequested).ToArray();
|
||||
return threads.Select(x => x.ThreadUid).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public KThread GetThread(ulong threadUid)
|
||||
{
|
||||
lock (_parent._threadingLock)
|
||||
{
|
||||
var threads = _parent._threads.Where(x => !x.TerminationRequested).ToArray();
|
||||
return threads.FirstOrDefault(x => x.ThreadUid == threadUid);
|
||||
}
|
||||
}
|
||||
|
||||
public void DebugInterruptHandler(IExecutionContext ctx)
|
||||
{
|
||||
_kernelContext.CriticalSection.Enter();
|
||||
bool stepping = steppingThread != null;
|
||||
_kernelContext.CriticalSection.Leave();
|
||||
if (stepping)
|
||||
{
|
||||
StepBarrier.SignalAndWait();
|
||||
StepBarrier.SignalAndWait();
|
||||
}
|
||||
_parent.InterruptHandler(ctx);
|
||||
}
|
||||
|
||||
public IVirtualMemoryManager CpuMemory { get { return _parent.CpuMemory; } }
|
||||
|
||||
public void InvalidateCacheRegion(ulong address, ulong size)
|
||||
{
|
||||
_parent.Context.InvalidateCacheRegion(address, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using ARMeilleure.State;
|
||||
using Ryujinx.Cpu;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
{
|
||||
|
@ -17,10 +18,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||
|
||||
public bool IsAarch32 { get => false; set { } }
|
||||
|
||||
public ulong ThreadUid { get; set; }
|
||||
|
||||
public bool Running { get; private set; } = true;
|
||||
|
||||
private readonly ulong[] _x = new ulong[32];
|
||||
|
||||
public ulong DebugPc { get; set; }
|
||||
|
||||
public ulong GetX(int index) => _x[index];
|
||||
public void SetX(int index, ulong value) => _x[index] = value;
|
||||
|
||||
|
@ -31,6 +36,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||
{
|
||||
}
|
||||
|
||||
public void RequestDebugStep()
|
||||
{
|
||||
}
|
||||
|
||||
public void StopRunning()
|
||||
{
|
||||
Running = false;
|
||||
|
|
|
@ -296,6 +296,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
|
||||
currentThread.SchedulerWaitEvent.Reset();
|
||||
currentThread.ThreadContext.Unlock();
|
||||
currentThread.DebugHalt.Set();
|
||||
|
||||
// Wake all the threads that might be waiting until this thread context is unlocked.
|
||||
for (int core = 0; core < CpuCoresCount; core++)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.Debugger;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||
using Ryujinx.HLE.HOS.Kernel.SupervisorCall;
|
||||
|
@ -114,6 +115,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
|
||||
private readonly object _activityOperationLock = new();
|
||||
|
||||
internal readonly ManualResetEvent DebugHalt = new(false);
|
||||
|
||||
public KThread(KernelContext context) : base(context)
|
||||
{
|
||||
WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects];
|
||||
|
@ -202,8 +205,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
}
|
||||
|
||||
Context.TpidrroEl0 = (long)_tlsAddress;
|
||||
Context.DebugPc = _entrypoint;
|
||||
|
||||
ThreadUid = KernelContext.NewThreadUid();
|
||||
Context.ThreadUid = ThreadUid;
|
||||
|
||||
HostThread.Name = customThreadStart != null ? $"HLE.OsThread.{ThreadUid}" : $"HLE.GuestThread.{ThreadUid}";
|
||||
|
||||
|
@ -307,7 +312,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
{
|
||||
KernelContext.CriticalSection.Enter();
|
||||
|
||||
if (Owner != null && Owner.PinnedThreads[KernelStatic.GetCurrentThread().CurrentCore] == this)
|
||||
KThread currentThread = KernelStatic.GetCurrentThread();
|
||||
|
||||
if (Owner != null && currentThread != null && Owner.PinnedThreads[currentThread.CurrentCore] == this)
|
||||
{
|
||||
Owner.UnpinThread(this);
|
||||
}
|
||||
|
@ -362,7 +369,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
{
|
||||
ThreadSchedState state = PrepareForTermination();
|
||||
|
||||
if (state != ThreadSchedState.TerminationPending)
|
||||
if (KernelStatic.GetCurrentThread() == this && state != ThreadSchedState.TerminationPending)
|
||||
{
|
||||
KernelContext.Synchronization.WaitFor(new KSynchronizationObject[] { this }, -1, out _);
|
||||
}
|
||||
|
@ -1248,6 +1255,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||
private void ThreadStart()
|
||||
{
|
||||
_schedulerWaitEvent.WaitOne();
|
||||
DebugHalt.Reset();
|
||||
KernelStatic.SetKernelContext(KernelContext, this);
|
||||
|
||||
if (_customThreadStart != null)
|
||||
|
|
|
@ -32,6 +32,12 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Debugger\GdbXml\aarch64-core.xml" />
|
||||
<None Remove="Debugger\GdbXml\aarch64-fpu.xml" />
|
||||
<None Remove="Debugger\GdbXml\arm-core.xml" />
|
||||
<None Remove="Debugger\GdbXml\arm-neon.xml" />
|
||||
<None Remove="Debugger\GdbXml\target64.xml" />
|
||||
<None Remove="Debugger\GdbXml\target32.xml" />
|
||||
<None Remove="Homebrew.npdm" />
|
||||
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
|
||||
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
|
||||
|
@ -41,6 +47,12 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Debugger\GdbXml\aarch64-core.xml" />
|
||||
<EmbeddedResource Include="Debugger\GdbXml\aarch64-fpu.xml" />
|
||||
<EmbeddedResource Include="Debugger\GdbXml\arm-core.xml" />
|
||||
<EmbeddedResource Include="Debugger\GdbXml\arm-neon.xml" />
|
||||
<EmbeddedResource Include="Debugger\GdbXml\target64.xml" />
|
||||
<EmbeddedResource Include="Debugger\GdbXml\target32.xml" />
|
||||
<EmbeddedResource Include="Homebrew.npdm" />
|
||||
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
|
||||
<EmbeddedResource Include="HOS\Applets\SoftwareKeyboard\Resources\Icon_BtnA.png" />
|
||||
|
|
|
@ -10,6 +10,7 @@ using Ryujinx.HLE.Loaders.Processes;
|
|||
using Ryujinx.HLE.UI;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE
|
||||
{
|
||||
|
@ -26,11 +27,15 @@ namespace Ryujinx.HLE
|
|||
public Hid Hid { get; }
|
||||
public TamperMachine TamperMachine { get; }
|
||||
public IHostUIHandler UIHandler { get; }
|
||||
public Debugger.Debugger Debugger { get; }
|
||||
public ManualResetEvent ExitStatus { get; }
|
||||
|
||||
public bool EnableDeviceVsync { get; set; } = true;
|
||||
|
||||
public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable;
|
||||
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
public Switch(HLEConfiguration configuration)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(configuration.GpuRenderer);
|
||||
|
@ -49,11 +54,13 @@ namespace Ryujinx.HLE
|
|||
AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(Configuration.AudioDeviceDriver);
|
||||
Memory = new MemoryBlock(Configuration.MemoryConfiguration.ToDramSize(), memoryAllocationFlags);
|
||||
Gpu = new GpuContext(Configuration.GpuRenderer);
|
||||
Debugger = Configuration.EnableGdbStub ? new Debugger.Debugger(this, Configuration.GdbStubPort) : null;
|
||||
System = new HOS.Horizon(this);
|
||||
Statistics = new PerformanceStatistics();
|
||||
Hid = new Hid(this, System.HidStorage);
|
||||
Processes = new ProcessLoader(this);
|
||||
TamperMachine = new TamperMachine();
|
||||
ExitStatus = new ManualResetEvent(false);
|
||||
|
||||
System.InitializeServices();
|
||||
System.State.SetLanguage(Configuration.SystemLanguage);
|
||||
|
@ -154,6 +161,8 @@ namespace Ryujinx.HLE
|
|||
AudioDeviceDriver.Dispose();
|
||||
FileSystem.Dispose();
|
||||
Memory.Dispose();
|
||||
ExitStatus.Set();
|
||||
Debugger.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -225,6 +225,17 @@ namespace Ryujinx.Headless.SDL2
|
|||
[Option("ignore-missing-services", Required = false, Default = false, HelpText = "Enable ignoring missing services.")]
|
||||
public bool IgnoreMissingServices { get; set; }
|
||||
|
||||
// Debug
|
||||
|
||||
[Option("enable-gdb-stub", Required = false, Default = false, HelpText = "Enables the GDB stub so that a developer can attach a debugger to the emulated process.")]
|
||||
public bool EnableGdbStub { get; set; }
|
||||
|
||||
[Option("gdb-stub-port", Required = false, Default = 55555, HelpText = "Specifies which TCP port the GDB stub listens on.")]
|
||||
public ushort GdbStubPort { get; set; }
|
||||
|
||||
[Option("suspend-on-start", Required = false, Default = false, HelpText = "Suspend execution when starting an application.")]
|
||||
public bool DebuggerSuspendOnStart { get; set; }
|
||||
|
||||
// Values
|
||||
|
||||
[Value(0, MetaName = "input", HelpText = "Input to load.", Required = true)]
|
||||
|
|
|
@ -580,7 +580,10 @@ namespace Ryujinx.Headless.SDL2
|
|||
options.AudioVolume,
|
||||
options.UseHypervisor ?? true,
|
||||
options.MultiplayerLanInterfaceId,
|
||||
Common.Configuration.Multiplayer.MultiplayerMode.Disabled);
|
||||
Common.Configuration.Multiplayer.MultiplayerMode.Disabled,
|
||||
options.EnableGdbStub,
|
||||
options.GdbStubPort,
|
||||
options.DebuggerSuspendOnStart);
|
||||
|
||||
return new Switch(configuration);
|
||||
}
|
||||
|
|
|
@ -386,6 +386,21 @@ namespace Ryujinx.UI.Common.Configuration
|
|||
/// </summary>
|
||||
public bool UseHypervisor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables the GDB stub
|
||||
/// </summary>
|
||||
public bool EnableGdbStub { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Which TCP port should the GDB stub listen on
|
||||
/// </summary>
|
||||
public ushort GdbStubPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Suspend execution when starting an application
|
||||
/// </summary>
|
||||
public bool DebuggerSuspendOnStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Loads a configuration file from disk
|
||||
/// </summary>
|
||||
|
|
|
@ -577,6 +577,37 @@ namespace Ryujinx.UI.Common.Configuration
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Debug configuration section
|
||||
/// </summary>
|
||||
public class DebugSection
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables or disables the GDB stub
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> EnableGdbStub { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Which TCP port should the GDB stub listen on
|
||||
/// </summary>
|
||||
public ReactiveObject<ushort> GdbStubPort { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Suspend execution when starting an application
|
||||
/// </summary>
|
||||
public ReactiveObject<bool> DebuggerSuspendOnStart { get; private set; }
|
||||
|
||||
public DebugSection()
|
||||
{
|
||||
EnableGdbStub = new ReactiveObject<bool>();
|
||||
EnableGdbStub.Event += static (sender, e) => LogValueChange(e, nameof(EnableGdbStub));
|
||||
GdbStubPort = new ReactiveObject<ushort>();
|
||||
GdbStubPort.Event += static (sender, e) => LogValueChange(e, nameof(GdbStubPort));
|
||||
DebuggerSuspendOnStart = new ReactiveObject<bool>();
|
||||
DebuggerSuspendOnStart.Event += static (sender, e) => LogValueChange(e, nameof(DebuggerSuspendOnStart));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The default configuration instance
|
||||
/// </summary>
|
||||
|
@ -607,6 +638,11 @@ namespace Ryujinx.UI.Common.Configuration
|
|||
/// </summary>
|
||||
public HidSection Hid { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Debug
|
||||
/// </summary>
|
||||
public DebugSection Debug { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The Multiplayer section
|
||||
/// </summary>
|
||||
|
@ -649,6 +685,7 @@ namespace Ryujinx.UI.Common.Configuration
|
|||
System = new SystemSection();
|
||||
Graphics = new GraphicsSection();
|
||||
Hid = new HidSection();
|
||||
Debug = new DebugSection();
|
||||
Multiplayer = new MultiplayerSection();
|
||||
EnableDiscordIntegration = new ReactiveObject<bool>();
|
||||
CheckUpdatesOnStart = new ReactiveObject<bool>();
|
||||
|
@ -766,6 +803,9 @@ namespace Ryujinx.UI.Common.Configuration
|
|||
PreferredGpu = Graphics.PreferredGpu,
|
||||
MultiplayerLanInterfaceId = Multiplayer.LanInterfaceId,
|
||||
MultiplayerMode = Multiplayer.Mode,
|
||||
EnableGdbStub = Debug.EnableGdbStub,
|
||||
GdbStubPort = Debug.GdbStubPort,
|
||||
DebuggerSuspendOnStart = Debug.DebuggerSuspendOnStart,
|
||||
};
|
||||
|
||||
return configurationFile;
|
||||
|
@ -923,6 +963,9 @@ namespace Ryujinx.UI.Common.Configuration
|
|||
},
|
||||
},
|
||||
};
|
||||
Debug.EnableGdbStub.Value = false;
|
||||
Debug.GdbStubPort.Value = 55555;
|
||||
Debug.DebuggerSuspendOnStart.Value = false;
|
||||
}
|
||||
|
||||
public void Load(ConfigurationFileFormat configurationFileFormat, string configurationFilePath)
|
||||
|
@ -1437,6 +1480,16 @@ namespace Ryujinx.UI.Common.Configuration
|
|||
|
||||
configurationFileUpdated = true;
|
||||
}
|
||||
if (configurationFileFormat.Version < 48)
|
||||
{
|
||||
Ryujinx.Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 38.");
|
||||
|
||||
configurationFileFormat.EnableGdbStub = false;
|
||||
configurationFileFormat.GdbStubPort = 55555;
|
||||
configurationFileFormat.DebuggerSuspendOnStart = false;
|
||||
|
||||
configurationFileUpdated = true;
|
||||
}
|
||||
|
||||
if (configurationFileFormat.Version < 48)
|
||||
{
|
||||
|
@ -1564,6 +1617,9 @@ namespace Ryujinx.UI.Common.Configuration
|
|||
Hid.EnableMouse.Value = configurationFileFormat.EnableMouse;
|
||||
Hid.Hotkeys.Value = configurationFileFormat.Hotkeys;
|
||||
Hid.InputConfig.Value = configurationFileFormat.InputConfig;
|
||||
Debug.EnableGdbStub.Value = configurationFileFormat.EnableGdbStub;
|
||||
Debug.GdbStubPort.Value = configurationFileFormat.GdbStubPort;
|
||||
Debug.DebuggerSuspendOnStart.Value = configurationFileFormat.DebuggerSuspendOnStart;
|
||||
|
||||
if (Hid.InputConfig.Value == null)
|
||||
{
|
||||
|
|
|
@ -104,7 +104,6 @@ namespace Ryujinx.Ava
|
|||
CursorStates.CursorIsVisible : CursorStates.CursorIsHidden;
|
||||
|
||||
private bool _isStopped;
|
||||
private bool _isActive;
|
||||
private bool _renderingStarted;
|
||||
|
||||
private readonly ManualResetEvent _gpuDoneEvent;
|
||||
|
@ -427,8 +426,6 @@ namespace Ryujinx.Ava
|
|||
|
||||
RendererHost.BoundsChanged += Window_BoundsChanged;
|
||||
|
||||
_isActive = true;
|
||||
|
||||
_renderingThread.Start();
|
||||
|
||||
_viewModel.Volume = ConfigurationState.Instance.System.AudioVolume.Value;
|
||||
|
@ -497,7 +494,7 @@ namespace Ryujinx.Ava
|
|||
|
||||
public void Stop()
|
||||
{
|
||||
_isActive = false;
|
||||
Device.IsActive = false;
|
||||
}
|
||||
|
||||
private void Exit()
|
||||
|
@ -510,14 +507,14 @@ namespace Ryujinx.Ava
|
|||
}
|
||||
|
||||
_isStopped = true;
|
||||
_isActive = false;
|
||||
Device.IsActive = false;
|
||||
}
|
||||
|
||||
public void DisposeContext()
|
||||
{
|
||||
Dispose();
|
||||
|
||||
_isActive = false;
|
||||
Device.IsActive = false;
|
||||
|
||||
// NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose.
|
||||
// We only need to wait for all commands submitted during the main gpu loop to be processed.
|
||||
|
@ -872,7 +869,10 @@ namespace Ryujinx.Ava
|
|||
ConfigurationState.Instance.System.AudioVolume,
|
||||
ConfigurationState.Instance.System.UseHypervisor,
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value,
|
||||
ConfigurationState.Instance.Multiplayer.Mode);
|
||||
ConfigurationState.Instance.Multiplayer.Mode,
|
||||
ConfigurationState.Instance.Debug.EnableGdbStub.Value,
|
||||
ConfigurationState.Instance.Debug.GdbStubPort.Value,
|
||||
ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Value);
|
||||
|
||||
Device = new Switch(configuration);
|
||||
}
|
||||
|
@ -948,7 +948,7 @@ namespace Ryujinx.Ava
|
|||
|
||||
private void MainLoop()
|
||||
{
|
||||
while (_isActive)
|
||||
while (Device.IsActive)
|
||||
{
|
||||
UpdateFrame();
|
||||
|
||||
|
@ -999,7 +999,7 @@ namespace Ryujinx.Ava
|
|||
|
||||
_renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync);
|
||||
|
||||
while (_isActive)
|
||||
while (Device.IsActive)
|
||||
{
|
||||
_ticks += _chrono.ElapsedTicks;
|
||||
|
||||
|
@ -1098,7 +1098,7 @@ namespace Ryujinx.Ava
|
|||
|
||||
private bool UpdateFrame()
|
||||
{
|
||||
if (!_isActive)
|
||||
if (!Device.IsActive)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -781,5 +781,12 @@
|
|||
"MultiplayerMode": "Mode:",
|
||||
"MultiplayerModeTooltip": "Change LDN multiplayer mode.\n\nLdnMitm will modify local wireless/local play functionality in games to function as if it were LAN, allowing for local, same-network connections with other Ryujinx instances and hacked Nintendo Switch consoles that have the ldn_mitm module installed.\n\nMultiplayer requires all players to be on the same game version (i.e. Super Smash Bros. Ultimate v13.0.1 can't connect to v13.0.0).\n\nLeave DISABLED if unsure.",
|
||||
"MultiplayerModeDisabled": "Disabled",
|
||||
"MultiplayerModeLdnMitm": "ldn_mitm"
|
||||
"MultiplayerModeLdnMitm": "ldn_mitm",
|
||||
"SettingsTabDebug": "Debug",
|
||||
"SettingsTabDebugTitle": "Debug (WARNING: For developer use only)",
|
||||
"EnableGDBStub": "Enable GDB Stub",
|
||||
"GDBStubToggleTooltip": "Enables the GDB stub which makes it possible to debug the running application. For development use only!",
|
||||
"GDBStubPort": "GDB stub port:",
|
||||
"DebuggerSuspendOnStart": "Suspend application on start",
|
||||
"DebuggerSuspendOnStartTooltip": "Suspends the application before executing the first instruction, allowing for debugging from the earliest point."
|
||||
}
|
||||
|
|
|
@ -54,6 +54,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
public event Action SaveSettingsEvent;
|
||||
private int _networkInterfaceIndex;
|
||||
private int _multiplayerModeIndex;
|
||||
private bool _enableGDBStub;
|
||||
private ushort _gdbStubPort;
|
||||
private bool _debuggerSuspendOnStart;
|
||||
|
||||
public int ResolutionScale
|
||||
{
|
||||
|
@ -259,6 +262,36 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
public bool EnableGdbStub
|
||||
{
|
||||
get => _enableGDBStub;
|
||||
set
|
||||
{
|
||||
_enableGDBStub = value;
|
||||
ConfigurationState.Instance.Debug.EnableGdbStub.Value = _enableGDBStub;
|
||||
}
|
||||
}
|
||||
|
||||
public ushort GDBStubPort
|
||||
{
|
||||
get => _gdbStubPort;
|
||||
set
|
||||
{
|
||||
_gdbStubPort = value;
|
||||
ConfigurationState.Instance.Debug.GdbStubPort.Value = _gdbStubPort;
|
||||
}
|
||||
}
|
||||
|
||||
public bool DebuggerSuspendOnStart
|
||||
{
|
||||
get => _debuggerSuspendOnStart;
|
||||
set
|
||||
{
|
||||
_debuggerSuspendOnStart = value;
|
||||
ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Value = _debuggerSuspendOnStart;
|
||||
}
|
||||
}
|
||||
|
||||
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
|
@ -472,7 +505,13 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
|
||||
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
|
||||
|
||||
// Multiplayer
|
||||
MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value;
|
||||
|
||||
// Debug
|
||||
EnableGdbStub = config.Debug.EnableGdbStub.Value;
|
||||
GDBStubPort = config.Debug.GdbStubPort.Value;
|
||||
DebuggerSuspendOnStart = config.Debug.DebuggerSuspendOnStart.Value;
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
|
@ -578,9 +617,15 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||
config.System.FsGlobalAccessLogMode.Value = FsGlobalAccessLogMode;
|
||||
config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel;
|
||||
|
||||
// Multiplayer
|
||||
config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]];
|
||||
config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex;
|
||||
|
||||
// Debug
|
||||
config.Debug.EnableGdbStub.Value = EnableGdbStub;
|
||||
config.Debug.GdbStubPort.Value = GDBStubPort;
|
||||
config.Debug.DebuggerSuspendOnStart.Value = DebuggerSuspendOnStart;
|
||||
|
||||
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
|
||||
|
||||
MainWindow.UpdateGraphicsConfig();
|
||||
|
|
63
src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
Normal file
63
src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml
Normal file
|
@ -0,0 +1,63 @@
|
|||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsDebugView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
mc:Ignorable="d"
|
||||
x:DataType="viewModels:SettingsViewModel">
|
||||
<Design.DataContext>
|
||||
<viewModels:SettingsViewModel />
|
||||
</Design.DataContext>
|
||||
<ScrollViewer
|
||||
Name="DebugPage"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<Border Classes="settings">
|
||||
<StackPanel
|
||||
Margin="10"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabDebugTitle}" />
|
||||
<StackPanel
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<CheckBox IsChecked="{Binding EnableGdbStub}">
|
||||
<TextBlock Text="{locale:Locale EnableGDBStub}"
|
||||
ToolTip.Tip="{locale:Locale GDBStubToggleTooltip}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale GDBStubPort}"
|
||||
Width="250" />
|
||||
<ui:NumberBox Value="{Binding GDBStubPort}"
|
||||
Width="350"
|
||||
SmallChange="1"
|
||||
LargeChange="10"
|
||||
SimpleNumberFormat="F0"
|
||||
SpinButtonPlacementMode="Inline"
|
||||
Minimum="1024"
|
||||
Maximum="65535" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<CheckBox IsChecked="{Binding DebuggerSuspendOnStart}">
|
||||
<TextBlock Text="{locale:Locale DebuggerSuspendOnStart}"
|
||||
ToolTip.Tip="{locale:Locale DebuggerSuspendOnStartTooltip}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
13
src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs
Normal file
13
src/Ryujinx/UI/Views/Settings/SettingsDebugView.axaml.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using Avalonia.Controls;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Views.Settings
|
||||
{
|
||||
public partial class SettingsDebugView : UserControl
|
||||
{
|
||||
public SettingsDebugView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -42,6 +42,7 @@
|
|||
<settings:SettingsAudioView Name="AudioPage" />
|
||||
<settings:SettingsNetworkView Name="NetworkPage" />
|
||||
<settings:SettingsLoggingView Name="LoggingPage" />
|
||||
<settings:SettingsDebugView Name="DebugPage" />
|
||||
</Grid>
|
||||
<ui:NavigationView
|
||||
Grid.Row="1"
|
||||
|
@ -96,6 +97,10 @@
|
|||
Content="{locale:Locale SettingsTabLogging}"
|
||||
Tag="LoggingPage"
|
||||
IconSource="Document" />
|
||||
<ui:NavigationViewItem
|
||||
Content="{locale:Locale SettingsTabDebug}"
|
||||
Tag="DebugPage"
|
||||
IconSource="Star" />
|
||||
</ui:NavigationView.MenuItems>
|
||||
<ui:NavigationView.Styles>
|
||||
<Style Selector="Grid#PlaceholderGrid">
|
||||
|
|
|
@ -87,6 +87,9 @@ namespace Ryujinx.Ava.UI.Windows
|
|||
case "LoggingPage":
|
||||
NavPanel.Content = LoggingPage;
|
||||
break;
|
||||
case "DebugPage":
|
||||
NavPanel.Content = DebugPage;
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue