Add GDB Stub

Author: merry, svc64
This commit is contained in:
Coxxs 2025-06-20 16:14:12 +08:00
parent 0cc94fdf37
commit 890165707a
53 changed files with 2428 additions and 21 deletions

View file

@ -338,6 +338,9 @@ namespace Ryujinx.Headless
false,
string.Empty,
string.Empty,
options.EnableGdbStub,
options.GdbStubPort,
options.DebuggerSuspendOnStart,
options.CustomVSyncInterval
)
.Configure(

View file

@ -423,6 +423,17 @@ namespace Ryujinx.Headless
[Option("skip-user-profiles-manager", Required = false, Default = false, HelpText = "Enable skips the Profiles Manager popup during gameplay. Select the desired profile before starting the game")]
public bool SkipUserProfilesManager { 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)]

View file

@ -218,6 +218,10 @@ namespace Ryujinx.Ava.Systems
ConfigurationState.Instance.Multiplayer.LdnServer.Event += UpdateLdnServerState;
ConfigurationState.Instance.Multiplayer.DisableP2p.Event += UpdateDisableP2pState;
ConfigurationState.Instance.Debug.EnableGdbStub.Event += UpdateEnableGdbStubState;
ConfigurationState.Instance.Debug.GdbStubPort.Event += UpdateGdbStubPortState;
ConfigurationState.Instance.Debug.DebuggerSuspendOnStart.Event += UpdateDebuggerSuspendOnStartState;
_gpuCancellationTokenSource = new CancellationTokenSource();
_gpuDoneEvent = new ManualResetEvent(false);
}
@ -564,6 +568,21 @@ namespace Ryujinx.Ava.Systems
Device.Configuration.MultiplayerDisableP2p = e.NewValue;
}
private void UpdateEnableGdbStubState(object sender, ReactiveEventArgs<bool> e)
{
Device.Configuration.EnableGdbStub = e.NewValue;
}
private void UpdateGdbStubPortState(object sender, ReactiveEventArgs<ushort> e)
{
Device.Configuration.GdbStubPort = e.NewValue;
}
private void UpdateDebuggerSuspendOnStartState(object sender, ReactiveEventArgs<bool> e)
{
Device.Configuration.DebuggerSuspendOnStart = e.NewValue;
}
public void Stop()
{
_isActive = false;

View file

@ -464,6 +464,21 @@ namespace Ryujinx.Ava.Systems.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>
/// Show toggles for dirty hacks in the UI.
/// </summary>

View file

@ -156,6 +156,10 @@ namespace Ryujinx.Ava.Systems.Configuration
Multiplayer.LdnPassphrase.Value = cff.MultiplayerLdnPassphrase;
Multiplayer.LdnServer.Value = cff.LdnServer;
Debug.EnableGdbStub.Value = cff.EnableGdbStub;
Debug.GdbStubPort.Value = cff.GdbStubPort;
Debug.DebuggerSuspendOnStart.Value = cff.DebuggerSuspendOnStart;
{
Hacks.ShowDirtyHacks.Value = shouldLoadFromFile ? cff.ShowDirtyHacks : Hacks.ShowDirtyHacks.Value; // Get from global config only

View file

@ -703,6 +703,37 @@ namespace Ryujinx.Ava.Systems.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.LogChangesToValue(nameof(EnableGdbStub));
GdbStubPort = new ReactiveObject<ushort>();
GdbStubPort.LogChangesToValue(nameof(GdbStubPort));
DebuggerSuspendOnStart = new ReactiveObject<bool>();
DebuggerSuspendOnStart.LogChangesToValue(nameof(DebuggerSuspendOnStart));
}
}
public class HacksSection
{
/// <summary>
@ -801,6 +832,11 @@ namespace Ryujinx.Ava.Systems.Configuration
/// </summary>
public MultiplayerSection Multiplayer { get; private set; }
/// <summary>
/// The Debug
/// </summary>
public DebugSection Debug { get; private set; }
/// <summary>
/// The Dirty Hacks section
/// </summary>
@ -854,6 +890,7 @@ namespace Ryujinx.Ava.Systems.Configuration
Graphics = new GraphicsSection();
Hid = new HidSection();
Multiplayer = new MultiplayerSection();
Debug = new DebugSection();
Hacks = new HacksSection();
UpdateCheckerType = new ReactiveObject<UpdaterType>();
FocusLostActionType = new ReactiveObject<FocusLostType>();
@ -893,6 +930,9 @@ namespace Ryujinx.Ava.Systems.Configuration
Multiplayer.DisableP2p,
Multiplayer.LdnPassphrase,
Multiplayer.GetLdnServer(),
Debug.EnableGdbStub,
Debug.GdbStubPort,
Debug.DebuggerSuspendOnStart,
Graphics.CustomVSyncInterval,
Hacks.ShowDirtyHacks ? Hacks.EnabledHacks : null);
}

View file

@ -147,6 +147,9 @@ namespace Ryujinx.Ava.Systems.Configuration
MultiplayerDisableP2p = Multiplayer.DisableP2p,
MultiplayerLdnPassphrase = Multiplayer.LdnPassphrase,
LdnServer = Multiplayer.LdnServer,
EnableGdbStub = Debug.EnableGdbStub,
GdbStubPort = Debug.GdbStubPort,
DebuggerSuspendOnStart = Debug.DebuggerSuspendOnStart,
ShowDirtyHacks = Hacks.ShowDirtyHacks,
DirtyHacks = Hacks.EnabledHacks.Select(it => it.Pack()).ToArray(),
};
@ -324,6 +327,9 @@ namespace Ryujinx.Ava.Systems.Configuration
},
}
];
Debug.EnableGdbStub.Value = false;
Debug.GdbStubPort.Value = 55555;
Debug.DebuggerSuspendOnStart.Value = false;
}
private static GraphicsBackend DefaultGraphicsBackend()

View file

@ -71,6 +71,10 @@ namespace Ryujinx.Ava.UI.ViewModels
private string _ldnPassphrase;
[ObservableProperty] private string _ldnServer;
private bool _enableGDBStub;
private ushort _gdbStubPort;
private bool _debuggerSuspendOnStart;
public SettingsHacksViewModel DirtyHacks { get; }
private readonly bool _isGameRunning;
@ -387,6 +391,36 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsInvalidLdnPassphraseVisible { get; set; }
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;
@ -680,10 +714,16 @@ namespace Ryujinx.Ava.UI.ViewModels
FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode;
OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value;
// Multiplayer
MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value;
DisableP2P = config.Multiplayer.DisableP2p;
LdnPassphrase = config.Multiplayer.LdnPassphrase;
LdnServer = config.Multiplayer.LdnServer;
// Debug
EnableGdbStub = config.Debug.EnableGdbStub.Value;
GDBStubPort = config.Debug.GdbStubPort.Value;
DebuggerSuspendOnStart = config.Debug.DebuggerSuspendOnStart.Value;
}
public void SaveSettings(bool global = false)
@ -800,12 +840,18 @@ 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;
config.Multiplayer.DisableP2p.Value = DisableP2P;
config.Multiplayer.LdnPassphrase.Value = LdnPassphrase;
config.Multiplayer.LdnServer.Value = LdnServer;
// Debug
config.Debug.EnableGdbStub.Value = EnableGdbStub;
config.Debug.GdbStubPort.Value = GDBStubPort;
config.Debug.DebuggerSuspendOnStart.Value = DebuggerSuspendOnStart;
// Dirty Hacks
config.Hacks.Xc2MenuSoftlockFix.Value = DirtyHacks.Xc2MenuSoftlockFix;
config.Hacks.DisableNifmIsAnyInternetRequestAccepted.Value =

View file

@ -0,0 +1,64 @@
<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:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
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="{ext:Locale SettingsTabDebugTitle}" />
<StackPanel
Margin="10,0,0,0"
HorizontalAlignment="Stretch"
Orientation="Vertical">
<CheckBox IsChecked="{Binding EnableGdbStub}">
<TextBlock Text="{ext:Locale EnableGDBStub}"
ToolTip.Tip="{ext:Locale GDBStubToggleTooltip}" />
</CheckBox>
</StackPanel>
<StackPanel
Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{ext: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="{ext:Locale DebuggerSuspendOnStart}"
ToolTip.Tip="{ext:Locale DebuggerSuspendOnStartTooltip}" />
</CheckBox>
</StackPanel>
</StackPanel>
</Border>
</ScrollViewer>
</UserControl>

View file

@ -0,0 +1,13 @@
using Avalonia.Controls;
namespace Ryujinx.Ava.UI.Views.Settings
{
public partial class SettingsDebugView : UserControl
{
public SettingsDebugView()
{
InitializeComponent();
}
}
}

View file

@ -46,6 +46,7 @@
<settings:SettingsAudioView Name="AudioPage" />
<settings:SettingsNetworkView Name="NetworkPage" />
<settings:SettingsLoggingView Name="LoggingPage" />
<settings:SettingsDebugView Name="DebugPage" />
<settings:SettingsHacksView Name="HacksPage" />
</Grid>
<ui:NavigationView
@ -100,6 +101,10 @@
Content="{ext:Locale SettingsTabLogging}"
Tag="LoggingPage"
IconSource="Document" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabDebug}"
Tag="DebugPage"
IconSource="Star" />
<ui:NavigationViewItem
IsVisible="{Binding ShowDirtyHacks}"
Content="Dirty Hacks"

View file

@ -98,6 +98,9 @@ namespace Ryujinx.Ava.UI.Windows
case "LoggingPage":
NavPanel.Content = LoggingPage;
break;
case "DebugPage":
NavPanel.Content = DebugPage;
break;
case nameof(HacksPage):
HacksPage.DataContext = ViewModel;
NavPanel.Content = HacksPage;