gdb: Add pause process at start and minor fixes

This commit is contained in:
PabloMK7 2026-05-03 14:52:32 +02:00
parent 795cc06672
commit 418a056147
7 changed files with 123 additions and 12 deletions

View file

@ -4,6 +4,7 @@
#include <QDesktopServices>
#include <QMessageBox>
#include <QTimer>
#include <QUrl>
#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() {

View file

@ -60,6 +60,13 @@
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="debug_next_process">
<property name="text">
<string>Pause next non-sysmodule process at start</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View file

@ -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<u8> info_led_color;
std::function<void()> info_led_color_changed;
bool debug_next_process;
friend class boost::serialization::access;
template <typename Archive>
void serialize(Archive& ar, const unsigned int file_version);

View file

@ -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<bool> 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<char*>(&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<s32>(send(gdbserver_socket, reinterpret_cast<char*>(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<u8> 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<u8> 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;
}

View file

@ -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);

View file

@ -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);

View file

@ -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) {