Add GDB stub for debugging (#657)

* Implement GDB stub debugger

Can be enabled by using the "--enable-gdbstub" option (and the debugger GUI, although that's untested) which'll pause any game you launch at start-up. Will start at port 1337 although it'll eventually be user-editable. The code is a bit weirdly sorted and also just needs a general cleanup, so expect that eventually too. And uses egyptian braces but formatting was easier to do at the end, so that's also something to do.

It has been tested to work with IDA Pro, Clion and the standalone interface for now, but I plan on writing some instructions in the PR to follow for people who want to use this. Memory breakpoints aren't possible yet, only execution breakpoints.

This code was aimed to be decoupled from the existing debugger to be able to be ported to the Wii U for an equal debugging experience. That's also why it uses the Cafe OS's thread sleep and resuming functions whenever possible instead of using recompiler/interpreter controls.

* Add memory writing and floating point registers support

* Reformat code a bit

* Format code to adhere to Cemu's coding style

* Rework GDB Stub settings in GUI

* Small styling fixes

* Rework execution breakpoints

Should work better in some edge cases now. But this should also allow for adding access breakpoints since it's now more separated.

* Implement access breakpoints

* Fix some issues with breakpoints

* Fix includes for Linux

* Fix unnecessary include

* Tweaks for Linux compatibility

* Use std::thread instead of std::jthread to fix MacOS support

* Enable GDB read/write breakpoints on x86 only

* Fix compilation for GCC compilers at least

The thread type varies on some platforms, so supporting this is hell... but let's get it to compile on MacOS first.

* Disable them for MacOS due to lack of ptrace

---------

Co-authored-by: Exzap <13877693+Exzap@users.noreply.github.com>
This commit is contained in:
Crementif 2023-02-19 15:41:49 +01:00 committed by GitHub
parent 05d82b09e9
commit 6d75776b28
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1765 additions and 59 deletions

View file

@ -770,27 +770,42 @@ wxPanel* GeneralSettings2::AddDebugPage(wxNotebook* notebook)
auto* panel = new wxPanel(notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
auto* debug_panel_sizer = new wxBoxSizer(wxVERTICAL);
auto* debug_row = new wxFlexGridSizer(0, 2, 0, 0);
debug_row->SetFlexibleDirection(wxBOTH);
debug_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
{
auto* debug_row = new wxFlexGridSizer(0, 2, 0, 0);
debug_row->SetFlexibleDirection(wxBOTH);
debug_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
debug_row->Add(new wxStaticText(panel, wxID_ANY, _("Crash dump"), wxDefaultPosition, wxDefaultSize, 0), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
debug_row->Add(new wxStaticText(panel, wxID_ANY, _("Crash dump"), wxDefaultPosition, wxDefaultSize, 0), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
#if BOOST_OS_WINDOWS
wxString dump_choices[] = { _("Disabled"), _("Lite"), _("Full") };
wxString dump_choices[] = {_("Disabled"), _("Lite"), _("Full")};
#elif BOOST_OS_UNIX
wxString dump_choices[] = { _("Disabled"), _("Enabled") };
wxString dump_choices[] = {_("Disabled"), _("Enabled")};
#endif
m_crash_dump = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(dump_choices), dump_choices);
m_crash_dump->SetSelection(0);
m_crash_dump = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, std::size(dump_choices), dump_choices);
m_crash_dump->SetSelection(0);
#if BOOST_OS_WINDOWS
m_crash_dump->SetToolTip(_("Creates a dump when Cemu crashes\nOnly enable when requested by a developer!\nThe Full option will create a very large dump file (includes a full RAM dump of the Cemu process)"));
m_crash_dump->SetToolTip(_("Creates a dump when Cemu crashes\nOnly enable when requested by a developer!\nThe Full option will create a very large dump file (includes a full RAM dump of the Cemu process)"));
#elif BOOST_OS_UNIX
m_crash_dump->SetToolTip(_("Creates a core dump when Cemu crashes\nOnly enable when requested by a developer!"));
m_crash_dump->SetToolTip(_("Creates a core dump when Cemu crashes\nOnly enable when requested by a developer!"));
#endif
debug_row->Add(m_crash_dump, 0, wxALL | wxEXPAND, 5);
debug_row->Add(m_crash_dump, 0, wxALL | wxEXPAND, 5);
debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5);
}
debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5);
{
auto* debug_row = new wxFlexGridSizer(0, 2, 0, 0);
debug_row->SetFlexibleDirection(wxBOTH);
debug_row->SetNonFlexibleGrowMode(wxFLEX_GROWMODE_SPECIFIED);
debug_row->Add(new wxStaticText(panel, wxID_ANY, _("GDB Stub port"), wxDefaultPosition, wxDefaultSize, 0), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
m_gdb_port = new wxSpinCtrl(panel, wxID_ANY, wxT("1337"), wxDefaultPosition, wxDefaultSize, 0, 1000, 65535);
m_gdb_port->SetToolTip(_("Changes the port that the GDB stub will use, which you can use by either starting Cemu with the --enable-gdbstub option or by enabling it the Debug tab."));
debug_row->Add(m_gdb_port, 0, wxALL | wxEXPAND, 5);
debug_panel_sizer->Add(debug_row, 0, wxALL | wxEXPAND, 5);
}
panel->SetSizerAndFit(debug_panel_sizer);
@ -1002,6 +1017,7 @@ void GeneralSettings2::StoreConfig()
// debug
config.crash_dump = (CrashDump)m_crash_dump->GetSelection();
config.gdb_port = m_gdb_port->GetValue();
g_config.Save();
}
@ -1619,6 +1635,7 @@ void GeneralSettings2::ApplyConfig()
// debug
m_crash_dump->SetSelection((int)config.crash_dump.GetValue());
m_gdb_port->SetValue(config.gdb_port.GetValue());
}
void GeneralSettings2::OnOnlineEnable(wxCommandEvent& event)

View file

@ -74,6 +74,7 @@ private:
// Debug
wxChoice* m_crash_dump;
wxSpinCtrl* m_gdb_port;
void OnAccountCreate(wxCommandEvent& event);
void OnAccountDelete(wxCommandEvent& event);

View file

@ -124,6 +124,7 @@ enum
// debug
MAINFRAME_MENU_ID_DEBUG_RENDER_UPSIDE_DOWN = 21100,
MAINFRAME_MENU_ID_DEBUG_VIEW_LOGGING_WINDOW,
MAINFRAME_MENU_ID_DEBUG_TOGGLE_GDB_STUB,
MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_THREADS,
MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_DEBUGGER,
MAINFRAME_MENU_ID_DEBUG_VIEW_AUDIO_DEBUGGER,
@ -183,7 +184,7 @@ EVT_MENU(MAINFRAME_MENU_ID_OPTIONS_INPUT, MainWindow::OnOptionsInput)
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_MEMORY_SEARCHER, MainWindow::OnToolsInput)
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_TITLE_MANAGER, MainWindow::OnToolsInput)
EVT_MENU(MAINFRAME_MENU_ID_TOOLS_DOWNLOAD_MANAGER, MainWindow::OnToolsInput)
//// cpu menu
// cpu menu
EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_8X, MainWindow::OnDebugSetting)
EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_4X, MainWindow::OnDebugSetting)
EVT_MENU(MAINFRAME_MENU_ID_TIMER_SPEED_2X, MainWindow::OnDebugSetting)
@ -209,6 +210,7 @@ EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_RAM, MainWindow::OnDebugSetting)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_DUMP_FST, MainWindow::OnDebugSetting)
// debug -> View ...
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_LOGGING_WINDOW, MainWindow::OnLoggingWindow)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_TOGGLE_GDB_STUB, MainWindow::OnGDBStubToggle)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_THREADS, MainWindow::OnDebugViewPPCThreads)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_DEBUGGER, MainWindow::OnDebugViewPPCDebugger)
EVT_MENU(MAINFRAME_MENU_ID_DEBUG_VIEW_AUDIO_DEBUGGER, MainWindow::OnDebugViewAudioDebugger)
@ -344,6 +346,10 @@ MainWindow::MainWindow()
{
MainWindow::RequestLaunchGame(LaunchSettings::GetLoadFile().value(), wxLaunchGameEvent::INITIATED_BY::COMMAND_LINE);
}
if (LaunchSettings::GDBStubEnabled())
{
g_gdbstub = std::make_unique<GDBServer>(config.gdb_port);
}
}
MainWindow::~MainWindow()
@ -1108,6 +1114,18 @@ void MainWindow::OnLoggingWindow(wxCommandEvent& event)
m_logging_window->Show(true);
}
void MainWindow::OnGDBStubToggle(wxCommandEvent& event)
{
if (g_gdbstub)
{
g_gdbstub.release();
return;
}
const auto& config = GetConfig();
g_gdbstub = std::make_unique<GDBServer>(config.gdb_port);
}
void MainWindow::OnDebugViewPPCThreads(wxCommandEvent& event)
{
auto frame = new DebugPPCThreadsWindow(*this);
@ -2168,6 +2186,10 @@ void MainWindow::RecreateMenu()
#ifdef CEMU_DEBUG_ASSERT
debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_LOGGING_WINDOW, _("&Open logging window"));
#endif
m_gdbstub_toggle = debugMenu->AppendCheckItem(MAINFRAME_MENU_ID_DEBUG_TOGGLE_GDB_STUB, _("&Launch with GDB stub"), wxEmptyString);
m_gdbstub_toggle->Check(g_gdbstub != nullptr);
m_gdbstub_toggle->Enable(!m_game_launched);
debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_THREADS, _("&View PPC threads"));
debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_PPC_DEBUGGER, _("&View PPC debugger"));
debugMenu->Append(MAINFRAME_MENU_ID_DEBUG_VIEW_AUDIO_DEBUGGER, _("&View audio debugger"));

View file

@ -14,6 +14,7 @@
#include "gui/components/wxGameList.h"
#include <future>
#include "Cafe/HW/Espresso/Debugger/GDBStub.h"
class DebuggerWindow2;
struct GameEntry;
@ -106,6 +107,7 @@ public:
void OnDebugDumpUsedTextures(wxCommandEvent& event);
void OnDebugDumpUsedShaders(wxCommandEvent& event);
void OnLoggingWindow(wxCommandEvent& event);
void OnGDBStubToggle(wxCommandEvent& event);
void OnDebugViewPPCThreads(wxCommandEvent& event);
void OnDebugViewPPCDebugger(wxCommandEvent& event);
void OnDebugViewAudioDebugger(wxCommandEvent& event);
@ -171,6 +173,7 @@ private:
std::string m_launched_game_name;
wxMenuItem* m_gdbstub_toggle{};
DebuggerWindow2* m_debugger_window = nullptr;
LoggingWindow* m_logging_window = nullptr;

View file

@ -66,8 +66,6 @@ wxBEGIN_EVENT_TABLE(DebuggerWindow2, wxFrame)
EVT_COMMAND(wxID_ANY, wxEVT_NOTIFY_MODULE_UNLOADED, DebuggerWindow2::OnNotifyModuleUnloaded)
// file menu
EVT_MENU(MENU_ID_FILE_EXIT, DebuggerWindow2::OnExit)
// setting
EVT_MENU(MENU_ID_OPTIONS_PIN_TO_MAINWINDOW, DebuggerWindow2::OnOptionsInput)
// window
EVT_MENU_RANGE(MENU_ID_WINDOW_REGISTERS, MENU_ID_WINDOW_MODULE, DebuggerWindow2::OnWindowMenu)
wxEND_EVENT_TABLE()
@ -470,7 +468,7 @@ bool DebuggerWindow2::Show(bool show)
std::wstring DebuggerWindow2::GetModuleStoragePath(std::string module_name, uint32_t crc_hash) const
{
if (module_name.empty() || crc_hash == 0) return std::wstring();
if (module_name.empty() || crc_hash == 0) return {};
return ActiveSettings::GetConfigPath("debugger/{}_{:#10x}.xml", module_name, crc_hash).generic_wstring();
}
@ -529,24 +527,24 @@ void DebuggerWindow2::OnBreakpointChange(wxCommandEvent& event)
void DebuggerWindow2::OnOptionsInput(wxCommandEvent& event)
{
switch(event.GetId())
switch (event.GetId())
{
case MENU_ID_OPTIONS_PIN_TO_MAINWINDOW:
{
const bool value = !m_config.data().pin_to_main;
m_config.data().pin_to_main = value;
if(value)
OnParentMove(m_main_position, m_main_size);
break;
}
{
const bool value = !m_config.data().pin_to_main;
m_config.data().pin_to_main = value;
if (value)
OnParentMove(m_main_position, m_main_size);
break;
}
case MENU_ID_OPTIONS_BREAK_ON_START:
{
{
const bool value = !m_config.data().break_on_start;
m_config.data().break_on_start = value;
debuggerState.breakOnEntry = value;
break;
}
}
default:
return;
}

View file

@ -4,6 +4,7 @@
#include "config/XMLConfig.h"
#include "Cafe/HW/Espresso/Debugger/Debugger.h"
#include "Cafe/OS/RPL/rpl.h"
#include "Cafe/HW/Espresso/Debugger/GDBStub.h"
#include <wx/bitmap.h>
#include <wx/frame.h>
@ -26,10 +27,9 @@ wxDECLARE_EVENT(wxEVT_NOTIFY_MODULE_UNLOADED, wxCommandEvent);
struct DebuggerConfig
{
DebuggerConfig()
: pin_to_main(true), break_on_start(true), show_register(true), show_dump(true), show_stack(true), show_breakpoints(true), show_modules(true) {}
: pin_to_main(true), break_on_start(true), show_register(true), show_dump(true), show_stack(true), show_breakpoints(true), show_modules(true), show_symbols(true) {}
bool pin_to_main;
bool break_on_start;
bool show_register;
@ -95,7 +95,7 @@ private:
wxPoint m_main_position;
wxSize m_main_size;
RegisterWindow* m_register_window;
DumpWindow* m_dump_window;
BreakpointWindow* m_breakpoint_window;

View file

@ -12,6 +12,7 @@
#include <wx/slider.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/spinctrl.h>
#include <wx/listbase.h>
#include <wx/display.h>
#include <wx/aboutdlg.h>