From 418a0561478de2e8082bd6a5dc318aa05dc8adc8 Mon Sep 17 00:00:00 2001 From: PabloMK7 Date: Sun, 3 May 2026 14:52:32 +0200 Subject: [PATCH] gdb: Add pause process at start and minor fixes --- .../configuration/configure_debug.cpp | 20 ++++++ src/citra_qt/configuration/configure_debug.ui | 7 ++ src/core/core.h | 14 ++++ src/core/gdbstub/gdbstub.cpp | 69 +++++++++++++++---- src/core/gdbstub/gdbstub.h | 10 +++ src/core/hle/kernel/process.cpp | 13 ++++ src/core/hle/kernel/thread.cpp | 2 + 7 files changed, 123 insertions(+), 12 deletions(-) diff --git a/src/citra_qt/configuration/configure_debug.cpp b/src/citra_qt/configuration/configure_debug.cpp index db09c7b93..313df5d36 100644 --- a/src/citra_qt/configuration/configure_debug.cpp +++ b/src/citra_qt/configuration/configure_debug.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include "citra_qt/configuration/configuration_shared.h" #include "citra_qt/configuration/configure_debug.h" @@ -12,6 +13,7 @@ #include "common/file_util.h" #include "common/logging/backend.h" #include "common/settings.h" +#include "core/core.h" #include "ui_configure_debug.h" #ifdef ENABLE_VULKAN #include "video_core/renderer_vulkan/vk_instance.h" @@ -33,6 +35,17 @@ ConfigureDebug::ConfigureDebug(bool is_powered_on_, QWidget* parent) ui->setupUi(this); SetConfiguration(); + connect(ui->toggle_gdbstub, &QCheckBox::clicked, + [this](bool checked) { ui->debug_next_process->setEnabled(checked); }); + + connect(ui->debug_next_process, &QCheckBox::clicked, [](bool checked) { + if (checked) { + Core::System::GetInstance().SetDebugNextProcessFlag(); + } else { + Core::System::GetInstance().ClearDebugNextProcessFlag(); + } + }); + connect(ui->open_log_button, &QPushButton::clicked, []() { QString path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::LogDir)); QDesktopServices::openUrl(QUrl::fromLocalFile(path)); @@ -94,6 +107,9 @@ ConfigureDebug::~ConfigureDebug() = default; void ConfigureDebug::SetConfiguration() { ui->toggle_gdbstub->setChecked(Settings::values.use_gdbstub.GetValue()); + if (!ui->toggle_gdbstub->isChecked()) { + ui->debug_next_process->setEnabled(false); + } ui->gdbport_spinbox->setEnabled(Settings::values.use_gdbstub.GetValue()); ui->gdbport_spinbox->setValue(Settings::values.gdbstub_port.GetValue()); ui->toggle_console->setEnabled(!is_powered_on); @@ -135,6 +151,10 @@ void ConfigureDebug::SetConfiguration() { ui->clock_display_label->setText( QStringLiteral("%1%").arg(Settings::values.cpu_clock_percentage.GetValue())); ui->instant_debug_log->setChecked(Settings::values.instant_debug_log.GetValue()); + + if (Core::System::GetInstance().GetDebugNextProcessFlag()) { + ui->debug_next_process->setChecked(true); + } } void ConfigureDebug::ApplyConfiguration() { diff --git a/src/citra_qt/configuration/configure_debug.ui b/src/citra_qt/configuration/configure_debug.ui index d9f66de38..1a470fb6f 100644 --- a/src/citra_qt/configuration/configure_debug.ui +++ b/src/citra_qt/configuration/configure_debug.ui @@ -60,6 +60,13 @@ + + + + Pause next non-sysmodule process at start + + + diff --git a/src/core/core.h b/src/core/core.h index 89df9b00e..971bd7b32 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -405,6 +405,18 @@ public: info_led_color_changed = func; } + void SetDebugNextProcessFlag() { + debug_next_process = true; + } + + bool GetDebugNextProcessFlag() { + return debug_next_process; + } + + void ClearDebugNextProcessFlag() { + debug_next_process = false; + } + private: /** * Initialize the emulated system. @@ -514,6 +526,8 @@ private: Common::Vec3 info_led_color; std::function info_led_color_changed; + bool debug_next_process; + friend class boost::serialization::access; template void serialize(Archive& ar, const unsigned int file_version); diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index 3272e3ba7..9189c3a69 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp @@ -77,6 +77,7 @@ constexpr u32 SIGSEGV = 11; using SOCKET = UINT_PTR; #else using SOCKET = int; +#define closesocket(x) close(x) #endif // _WIN32 #define INVALID_SOCKET ((SOCKET)(~0)) @@ -161,6 +162,9 @@ u16 gdbstub_port = 24689; constexpr bool supports_extended_mode = true; bool is_extended_mode = false; +bool is_running = false; +bool current_process_finished = false; + // If set to false, the server will never be started and no // gdbstub-related functions will be executed. std::atomic server_enabled(false); @@ -202,6 +206,9 @@ static void ResetState() { is_extended_mode = false; + is_running = false; + current_process_finished = false; + accept_socket = INVALID_SOCKET; continue_thread = -1; @@ -452,11 +459,12 @@ static bool SetNonBlock(SOCKET socket, bool nonblock) { /// Read a byte from the gdb client. static u8 ReadByte() { - u8 c; + u8 c{}; std::size_t received_size = recv(gdbserver_socket, reinterpret_cast(&c), 1, MSG_WAITALL); if (received_size != 1) { LOG_ERROR(Debug_GDBStub, "recv failed : {}", GetErrno()); - Shutdown(); + ToggleServer(false); + ToggleServer(true); } return c; @@ -656,7 +664,9 @@ void SendReply(const char* reply) { static_cast(send(gdbserver_socket, reinterpret_cast(ptr), left, 0)); if (sent_size < 0) { LOG_ERROR(Debug_GDBStub, "gdb: send failed"); - return Shutdown(); + ToggleServer(false); + ToggleServer(true); + return; } left -= sent_size; @@ -806,11 +816,16 @@ static void HandleExtendedMode() { * * @param signal Signal to be sent to client. */ -static void SendTStopReply(Kernel::Thread* thread, u32 signal, bool full = true) { +static void SendStopReply(Kernel::Thread* thread, u32 signal, bool full = true) { if (gdbserver_socket == INVALID_SOCKET) { return; } + if (current_process_finished) { + SendReply("W00"); + return; + } + latest_signal = signal; if (!thread) { @@ -849,7 +864,7 @@ static void HandleGetStopReason() { // The process has not been selected yet. SendReply("W00"); } else { - SendTStopReply(current_thread, latest_signal); + SendStopReply(current_thread, latest_signal); } } else { // In non extended mode, select the process corresponding to the "main" @@ -861,8 +876,9 @@ static void HandleGetStopReason() { if (process->codeset->program_id == program_id) { current_process = process.get(); current_process->SetDebugBreak(true); + is_running = false; if (SetThread(0)) { - SendTStopReply(current_thread, 0); + SendStopReply(current_thread, 0); } else { // Should never happen SendReply("W00"); @@ -883,10 +899,11 @@ static void BreakImpl(int signal) { } current_process->SetDebugBreak(true); + is_running = false; latest_signal = signal; - SendTStopReply(current_thread, signal); + SendStopReply(current_thread, signal); } /// Read command from gdb client. @@ -1116,7 +1133,7 @@ static void WriteRegisters() { /// Read location in memory specified by gdb client. static void ReadMemory() { if (!current_process) { - SendReply("E01"); + SendReply(""); return; } @@ -1133,12 +1150,12 @@ static void ReadMemory() { LOG_DEBUG(Debug_GDBStub, "ReadMemory addr: {:08x} len: {:08x}", addr, len); if (len * 2 > sizeof(reply)) { - SendReply("E01"); + SendReply(""); } auto& memory = Core::System::GetInstance().Memory(); if (!memory.IsValidVirtualAddress(*current_process, addr)) { - return SendReply("E14"); + return SendReply(""); } std::vector data(len); @@ -1170,7 +1187,7 @@ static void WriteMemory() { auto& memory = Core::System::GetInstance().Memory(); if (!memory.IsValidVirtualAddress(*current_process, addr)) { - return SendReply("E00"); + return SendReply("E0E"); } std::vector data(len); @@ -1230,6 +1247,8 @@ static void Continue() { } current_process->SetDebugBreak(false, continue_list); + is_running = true; + ClearAllInstructionCache(); } @@ -1394,8 +1413,9 @@ void HandleVCommand() { } else { current_process = process.get(); current_process->SetDebugBreak(true); + is_running = false; if (SetThread(0)) { - SendTStopReply(current_thread, 0); + SendStopReply(current_thread, 0); } else { // Should never happen SendReply("W00"); @@ -1435,12 +1455,34 @@ void HandleVCommand() { } current_process->SetDebugBreak(false, thread_ids); + is_running = true; } } else { SendReply(""); } } +void OnProcessExit(u32 process_id) { + if (!GDBStub::IsConnected || !current_process || current_process->process_id != process_id) { + return; + } + + current_process_finished = true; + if (is_running) { + SendStopReply(nullptr, 0); + } + current_process = nullptr; + current_thread = nullptr; +} + +void OnThreadExit(u32 thread_id) { + if (!GDBStub::IsConnected || !current_thread || current_thread->thread_id != thread_id) { + return; + } + + current_thread = nullptr; +} + void HandlePacket(Core::System& system) { if (!IsConnected()) { @@ -1475,6 +1517,7 @@ void HandlePacket(Core::System& system) { } shutdown(accept_socket, SHUT_RDWR); + closesocket(accept_socket); accept_socket = INVALID_SOCKET; } return; @@ -1676,11 +1719,13 @@ void Shutdown() { LOG_INFO(Debug_GDBStub, "Stopping GDB ..."); if (gdbserver_socket != INVALID_SOCKET) { shutdown(gdbserver_socket, SHUT_RDWR); + closesocket(gdbserver_socket); gdbserver_socket = INVALID_SOCKET; } if (accept_socket != INVALID_SOCKET) { shutdown(accept_socket, SHUT_RDWR); + closesocket(accept_socket); accept_socket = INVALID_SOCKET; } diff --git a/src/core/gdbstub/gdbstub.h b/src/core/gdbstub/gdbstub.h index 14345aa77..fec95d5aa 100644 --- a/src/core/gdbstub/gdbstub.h +++ b/src/core/gdbstub/gdbstub.h @@ -77,6 +77,16 @@ bool IsConnected(); */ void Break(int signal); +/** + * Signal to the GDB stub that the specified process ID is exiting + */ +void OnProcessExit(u32 process_id); + +/** + * Signal to the GDB stub that the specified thread ID is exiting + */ +void OnThreadExit(u32 thread_id); + /// Read and handle packet from gdb client. void HandlePacket(Core::System& system); diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index a808bb2ab..32d9a46cd 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -16,6 +16,7 @@ #include "common/logging/log.h" #include "common/serialization/boost_vector.hpp" #include "core/core.h" +#include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/memory.h" #include "core/hle/kernel/process.h" @@ -260,9 +261,21 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) { vm_manager.LogLayout(Common::Log::Level::Debug); Kernel::SetupMainThread(kernel, codeset->entrypoint, main_thread_priority, SharedFrom(this)); + + // Pause process at start if flag enabled and we are not a sysmodule + if (Core::System::GetInstance().GetDebugNextProcessFlag() && + resource_limit->GetCategory() != Kernel::ResourceLimitCategory::Other) { + if (GDBStub::IsServerEnabled()) { + LOG_INFO(Loader, "Pausing process {} at start", process_id); + SetDebugBreak(true); + } + Core::System::GetInstance().ClearDebugNextProcessFlag(); + } } void Process::Exit() { + GDBStub::OnProcessExit(process_id); + auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance()); if (plgldr) { plgldr->OnProcessExit(*this, kernel); diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index a3cf78ec6..73fced736 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -134,6 +134,8 @@ void Thread::Stop() { process->tls_slots[tls_page].reset(tls_slot); process->resource_limit->Release(ResourceLimitType::Thread, 1); } + + GDBStub::OnThreadExit(thread_id); } void ThreadManager::SwitchContext(Thread* new_thread) {