gdb: Add cmake toggle to enable GDB stub

This commit is contained in:
PabloMK7 2026-05-04 14:53:34 +02:00
parent bafd58b0df
commit c6931ac76d
20 changed files with 114 additions and 22 deletions

View file

@ -99,7 +99,7 @@ endif()
# Track which options were explicitly set by the user (for libretro conflict detection)
set(_LIBRETRO_INCOMPATIBLE_OPTIONS
ENABLE_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING
ENABLE_SDL2 ENABLE_QT ENABLE_WEB_SERVICE ENABLE_SCRIPTING ENABLE_GDBSTUB
ENABLE_OPENAL ENABLE_ROOM ENABLE_ROOM_STANDALONE ENABLE_CUBEB ENABLE_LIBUSB)
set(_USER_SET_OPTIONS "")
foreach(_opt IN LISTS _LIBRETRO_INCOMPATIBLE_OPTIONS)
@ -122,6 +122,7 @@ CMAKE_DEPENDENT_OPTION(ENABLE_ROOM_STANDALONE "Enable generating a standalone de
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
option(ENABLE_SCRIPTING "Enable RPC server for scripting" ON)
option(ENABLE_GDBSTUB "Enable GDB stub for emulated applications" ON)
CMAKE_DEPENDENT_OPTION(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF)
option(ENABLE_OPENAL "Enables the OpenAL audio backend" ON)

View file

@ -9,6 +9,7 @@ COPY . /var/azahar-src
RUN mkdir builddir && cd builddir && \
cmake /var/azahar-src -G Ninja \
-DENABLE_QT=OFF \
-DENABLE_GDBSTUB=OFF \
-DENABLE_TESTS=OFF \
-DENABLE_ROOM=ON \
-DENABLE_ROOM_STANDALONE=ON \

View file

@ -79,7 +79,8 @@ android {
"-DENABLE_QT=0", // Don't use QT
"-DENABLE_SDL2=0", // Don't use SDL
"-DANDROID_ARM_NEON=true", // cryptopp requires Neon to work
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" // Support Android 15 16KiB page sizes
"-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON", // Support Android 15 16KiB page sizes
"-DENABLE_GDBSTUB=OFF", // Disable GDB stub
)
}
}

View file

@ -37,6 +37,8 @@ constexpr std::array android_config_omitted_keys = {
Keys::audio_bitrate,
Keys::last_artic_base_addr, // On Android, this value is stored as a "preference"
Keys::break_on_unmapped_memory_access, // Does nothing as the error is ignored
Keys::use_gdbstub, // GDB functionality disabled by deafult on Android
Keys::gdbstub_port,
};
// clang-format off
@ -532,10 +534,6 @@ static const char* android_config_default_file_content = (BOOST_HANA_STRING(R"(
# 0 (default): Off, 1: On
)") DECLARE_KEY(renderer_debug) BOOST_HANA_STRING(R"(
# Port for listening to GDB connections.
)") DECLARE_KEY(use_gdbstub) BOOST_HANA_STRING(R"(
)") DECLARE_KEY(gdbstub_port) BOOST_HANA_STRING(R"(
# Flush log output on every message
# Immediately commits the debug log to file. Use this if Azahar crashes and the log output is being cut.
)") DECLARE_KEY(instant_debug_log) BOOST_HANA_STRING(R"(

View file

@ -100,6 +100,10 @@ ConfigureDebug::ConfigureDebug(bool is_powered_on_, QWidget* parent)
ui->clock_speed_label->setVisible(Settings::IsConfiguringGlobal());
ui->clock_speed_combo->setVisible(!Settings::IsConfiguringGlobal());
#ifndef ENABLE_GDBSTUB
ui->gdb_groupbox->setVisible(false);
#endif
SetupPerGameUI();
}
@ -198,7 +202,7 @@ void ConfigureDebug::SetupPerGameUI() {
ConfigurationShared::SetHighlight(ui->clock_speed_widget, index == 1);
});
ui->groupBox->setVisible(false);
ui->gdb_groupbox->setVisible(false);
ui->groupBox_2->setVisible(false);
ui->enable_rpc_server->setVisible(false);
ui->toggle_unique_data_console_type->setVisible(false);

View file

@ -17,7 +17,7 @@
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox">
<widget class="QGroupBox" name="gdb_groupbox">
<property name="title">
<string>GDB</string>
</property>

View file

@ -126,10 +126,6 @@ add_library(citra_core STATIC
frontend/image_interface.cpp
frontend/image_interface.h
frontend/input.h
gdbstub/gdbstub.cpp
gdbstub/gdbstub.h
gdbstub/hio.cpp
gdbstub/hio.h
hle/applets/applet.cpp
hle/applets/applet.h
hle/applets/erreula.cpp
@ -529,6 +525,16 @@ if (ENABLE_SCRIPTING)
)
endif()
if (ENABLE_GDBSTUB)
target_compile_definitions(citra_core PUBLIC -DENABLE_GDBSTUB)
target_sources(citra_core PRIVATE
gdbstub/gdbstub.cpp
gdbstub/gdbstub.h
gdbstub/hio.cpp
gdbstub/hio.h
)
endif()
if ("x86_64" IN_LIST ARCHITECTURE OR "arm64" IN_LIST ARCHITECTURE)
target_sources(citra_core PRIVATE
arm/dynarmic/arm_dynarmic.cpp

View file

@ -14,7 +14,9 @@
#include "core/arm/dynarmic/arm_tick_counts.h"
#include "core/core.h"
#include "core/core_timing.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/gdbstub.h"
#endif
#include "core/hle/kernel/svc.h"
#include "core/memory.h"
@ -106,11 +108,13 @@ public:
case Dynarmic::A32::Exception::NoExecuteFault:
break;
case Dynarmic::A32::Exception::Breakpoint:
#ifdef ENABLE_GDBSTUB
if (GDBStub::IsConnected()) {
parent.SetPC(pc);
parent.ServeBreak(SIGTRAP);
return;
}
#endif
break;
case Dynarmic::A32::Exception::SendEvent:
case Dynarmic::A32::Exception::SendEventLocal:
@ -124,9 +128,12 @@ public:
}
parent.SetPC(pc);
#ifdef ENABLE_GDBSTUB
if (GDBStub::IsConnected()) {
parent.ServeBreak(SIGILL);
} else {
} else
#endif
{
std::string error;
for (int i = 0; i < 16; i++) {
error += fmt::format("r{:02d} = {:08X}\n", i, parent.GetReg(i));
@ -328,8 +335,10 @@ void ARM_Dynarmic::SetPageTable(const std::shared_ptr<Memory::PageTable>& page_t
jits.emplace(current_page_table, std::move(new_jit));
}
void ARM_Dynarmic::ServeBreak(int signal) {
void ARM_Dynarmic::ServeBreak([[maybe_unused]] int signal) {
#ifdef ENABLE_GDBSTUB
GDBStub::Break(signal);
#endif
}
std::unique_ptr<Dynarmic::A32::Jit> ARM_Dynarmic::MakeJit() {

View file

@ -23,7 +23,9 @@
#include "core/arm/skyeye_common/vfp/vfp.h"
#include "core/core.h"
#include "core/core_timing.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/gdbstub.h"
#endif
#include "core/hle/kernel/svc.h"
#include "core/memory.h"
@ -922,9 +924,11 @@ MICROPROFILE_DEFINE(DynCom_Execute, "DynCom", "Execute", MP_RGB(255, 0, 0));
unsigned InterpreterMainLoop(ARMul_State* cpu) {
MICROPROFILE_SCOPE(DynCom_Execute);
#ifdef ENABLE_GDBSTUB
/// Nearest upcoming GDB code execution breakpoint, relative to the last dispatch's address.
GDBStub::BreakpointAddress breakpoint_data;
breakpoint_data.type = GDBStub::BreakpointType::None;
#endif
#undef RM
#undef RS
@ -952,7 +956,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) {
#define INC_PC(l) ptr += sizeof(arm_inst) + l
#define INC_PC_STUB ptr += sizeof(arm_inst)
#ifdef ANDROID
#ifndef ENABLE_GDBSTUB
#define GDB_BP_CHECK
#else
#define GDB_BP_CHECK \
@ -1653,7 +1657,7 @@ DISPATCH: {
goto END;
}
#ifndef ANDROID
#ifdef ENABLE_GDBSTUB
// Find breakpoint if one exists within the block
if (GDBStub::IsConnected()) {
breakpoint_data =

View file

@ -9,6 +9,9 @@
#include "core/arm/skyeye_common/armstate.h"
#include "core/arm/skyeye_common/vfp/vfp.h"
#include "core/core.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/gdbstub.h"
#endif
#include "core/memory.h"
#ifndef SIGTRAP
@ -580,6 +583,7 @@ void ARMul_State::WriteCP15Register(u32 value, u32 crn, u32 opcode_1, u32 crm, u
}
void ARMul_State::ServeBreak() {
#ifdef ENABLE_GDBSTUB
if (!GDBStub::IsServerEnabled()) {
return;
}
@ -592,4 +596,5 @@ void ARMul_State::ServeBreak() {
last_bkpt_hit = false;
GDBStub::Break(SIGTRAP);
}
#endif
}

View file

@ -1,3 +1,7 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
/* armdefs.h -- ARMulator common definitions: ARM6 Instruction Emulator.
Copyright (C) 1994 Advanced RISC Machines Ltd.
@ -21,7 +25,9 @@
#include <unordered_map>
#include "common/common_types.h"
#include "core/arm/skyeye_common/arm_regformat.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/gdbstub.h"
#endif
namespace Core {
class System;
@ -199,10 +205,12 @@ public:
return TFlag ? 2 : 4;
}
#ifdef ENABLE_GDBSTUB
void RecordBreak(GDBStub::BreakpointAddress bkpt) {
last_bkpt = bkpt;
last_bkpt_hit = true;
}
#endif
void ServeBreak();
@ -267,6 +275,8 @@ private:
u32 exclusive_tag; // The address for which the local monitor is in exclusive access mode
bool exclusive_state;
#ifdef ENABLE_GDBSTUB
GDBStub::BreakpointAddress last_bkpt{};
bool last_bkpt_hit = false;
#endif
};

View file

@ -26,7 +26,9 @@
#include "core/dumping/backend.h"
#include "core/file_sys/ncch_container.h"
#include "core/frontend/image_interface.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/gdbstub.h"
#endif
#include "core/global.h"
#include "core/hle/kernel/ipc_debugger/recorder.h"
#include "core/hle/kernel/kernel.h"
@ -83,6 +85,7 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
return ResultStatus::ErrorNotInitialized;
}
#ifdef ENABLE_GDBSTUB
if (GDBStub::IsServerEnabled()) {
// The break flag is only set if GDB is connected,
// we can do clearing here safely. If it is ever
@ -92,6 +95,7 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
}
GDBStub::HandlePacket(*this);
}
#endif
Signal signal{Signal::None};
u32 param{};
@ -561,7 +565,9 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window,
app_loader->ReadProgramId(loading_title_id);
HW::AES::InitKeys();
Service::Init(*this, loading_title_id, lle_modules, !app_loader->DoingInitialSetup());
#ifdef ENABLE_GDBSTUB
GDBStub::DeferStart();
#endif
if (!registered_image_interface) {
registered_image_interface = std::make_shared<Frontend::ImageInterface>();
@ -686,7 +692,9 @@ void System::Shutdown(bool is_deserializing) {
gpu.reset();
if (!is_deserializing) {
lle_modules.clear();
#ifdef ENABLE_GDBSTUB
GDBStub::Shutdown();
#endif
perf_stats.reset();
app_loader.reset();
}
@ -749,8 +757,10 @@ void System::Reset() {
}
void System::ApplySettings() {
#ifdef ENABLE_GDBSTUB
GDBStub::SetServerPort(Settings::values.gdbstub_port.GetValue());
GDBStub::ToggleServer(Settings::values.use_gdbstub.GetValue());
#endif
if (gpu) {
#ifndef ANDROID

View file

@ -43,10 +43,12 @@
#include "core/loader/loader.h"
#include "core/memory.h"
#pragma optimize("", off)
#ifndef ENABLE_GDBSTUB
#error "File was compiled with GDB stub support disabled"
#endif
// Uncomment to log all GDB traffic
#define PRINT_GDB_TRAFFIC
// #define PRINT_GDB_TRAFFIC
namespace GDBStub {
namespace {
@ -1700,9 +1702,6 @@ static void Init(u16 port) {
}
SetNonBlock(accept_socket, true);
// Wait for gdb to connect
LOG_INFO(Debug_GDBStub, "Waiting for gdb to connect...\n");
}
void Init() {

View file

@ -14,6 +14,10 @@
#include "common/common_types.h"
#include "core/hle/kernel/thread.h"
#ifndef ENABLE_GDBSTUB
#error "File was included with GDB stub support disabled"
#endif
namespace Core {
class System;
}

View file

@ -9,6 +9,10 @@
#include "core/gdbstub/gdbstub.h"
#include "core/gdbstub/hio.h"
#ifndef ENABLE_GDBSTUB
#error "File was compiled with GDB stub support disabled"
#endif
#ifndef SIGTRAP
constexpr u32 SIGTRAP = 5;
#endif

View file

@ -6,6 +6,10 @@
#include "common/common_types.h"
#ifndef ENABLE_GDBSTUB
#error "File was included with GDB stub support disabled"
#endif
namespace Core {
class System;
}

View file

@ -16,7 +16,9 @@
#include "common/logging/log.h"
#include "common/serialization/boost_vector.hpp"
#include "core/core.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/gdbstub.h"
#endif
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/process.h"
@ -265,16 +267,20 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) {
// Pause process at start if flag enabled and we are not a sysmodule
if (Core::System::GetInstance().GetDebugNextProcessFlag() &&
resource_limit->GetCategory() != Kernel::ResourceLimitCategory::Other) {
#ifdef ENABLE_GDBSTUB
if (GDBStub::IsServerEnabled()) {
LOG_INFO(Loader, "Pausing process {} at start", process_id);
SetDebugBreak(true);
}
#endif
Core::System::GetInstance().ClearDebugNextProcessFlag();
}
}
void Process::Exit() {
#ifdef ENABLE_GDBSTUB
GDBStub::OnProcessExit(process_id);
#endif
auto plgldr = Service::PLGLDR::GetService(Core::System::GetInstance());
if (plgldr) {

View file

@ -14,7 +14,9 @@
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/core_timing.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/hio.h"
#endif
#include "core/hle/kernel/address_arbiter.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
@ -1171,7 +1173,9 @@ void SVC::OutputDebugString(VAddr address, s32 len) {
}
if (len == 0) {
#ifdef ENABLE_GDBSTUB
GDBStub::SetHioRequest(system, kernel.GetCurrentProcess().get(), address);
#endif
return;
}

View file

@ -17,6 +17,9 @@
#include "core/arm/arm_interface.h"
#include "core/arm/skyeye_common/armstate.h"
#include "core/core.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/gdbstub.h"
#endif
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/mutex.h"
@ -135,7 +138,9 @@ void Thread::Stop() {
process->resource_limit->Release(ResourceLimitType::Thread, 1);
}
#ifdef ENABLE_GDBSTUB
GDBStub::OnThreadExit(thread_id);
#endif
}
void ThreadManager::SwitchContext(Thread* new_thread) {

View file

@ -17,7 +17,9 @@
#include "common/swap.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#ifdef ENABLE_GDBSTUB
#include "core/gdbstub/gdbstub.h"
#endif
#include "core/global.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/plgldr/plgldr.h"
@ -569,9 +571,12 @@ T MemorySystem::UnmappedAccess(const VAddr vaddr, const T value, bool read) {
const std::string value_str = read ? std::string("") : fmt::format(" 0x{:08X}", value);
const std::string message = fmt::format("unmapped {}{}{} @ 0x{:08X} at PC 0x{:08X}", mode,
sizeof(T) * 8, value_str, vaddr, impl->GetPC());
#ifdef ENABLE_GDBSTUB
if (GDBStub::IsConnected()) {
GDBStub::Break(SIGSEGV);
} else if (Settings::values.break_on_unmapped_memory_access) {
} else
#endif
if (Settings::values.break_on_unmapped_memory_access) {
impl->system.SetStatus(Core::System::ResultStatus::ErrorMemoryExceptionRaised,
message.c_str());
}
@ -621,9 +626,11 @@ T MemorySystem::Read(const std::shared_ptr<PageTable>& page_table, const VAddr v
T value;
std::memcpy(&value, it->second.memory.GetPtr() + (vaddr & CITRA_PAGE_MASK), sizeof(T));
#ifdef ENABLE_GDBSTUB
if (GDBStub::CheckBreakpoint(vaddr, sizeof(T), GDBStub::BreakpointType::Read)) {
GDBStub::Break(SIGTRAP);
}
#endif
return value;
}
@ -640,9 +647,11 @@ T MemorySystem::Read(const std::shared_ptr<PageTable>& page_table, const VAddr v
T value;
std::memcpy(&value, GetPointerForRasterizerCache(vaddr), sizeof(T));
#ifdef ENABLE_GDBSTUB
if (GDBStub::CheckBreakpoint(vaddr, sizeof(T), GDBStub::BreakpointType::Read)) {
GDBStub::Break(SIGTRAP);
}
#endif
return value;
}
@ -695,9 +704,11 @@ void MemorySystem::Write(const std::shared_ptr<PageTable>& page_table, const VAd
std::memcpy(it->second.memory.GetPtr() + (vaddr & CITRA_PAGE_MASK), &data, sizeof(T));
#ifdef ENABLE_GDBSTUB
if (GDBStub::CheckBreakpoint(vaddr, sizeof(T), GDBStub::BreakpointType::Write)) {
GDBStub::Break(SIGTRAP);
}
#endif
break;
}
@ -710,9 +721,11 @@ void MemorySystem::Write(const std::shared_ptr<PageTable>& page_table, const VAd
RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate);
std::memcpy(GetPointerForRasterizerCache(vaddr), &data, sizeof(T));
#ifdef ENABLE_GDBSTUB
if (GDBStub::CheckBreakpoint(vaddr, sizeof(T), GDBStub::BreakpointType::Write)) {
GDBStub::Break(SIGTRAP);
}
#endif
break;
}
@ -749,9 +762,11 @@ bool MemorySystem::WriteExclusive(const VAddr vaddr, const T data, const T expec
bool ret = Common::AtomicCompareAndSwap(volatile_pointer, data, expected);
#ifdef ENABLE_GDBSTUB
if (GDBStub::CheckBreakpoint(vaddr, sizeof(T), GDBStub::BreakpointType::Write)) {
GDBStub::Break(SIGTRAP);
}
#endif
return ret;
}
@ -766,9 +781,11 @@ bool MemorySystem::WriteExclusive(const VAddr vaddr, const T data, const T expec
const auto volatile_pointer =
reinterpret_cast<volatile T*>(GetPointerForRasterizerCache(vaddr).GetPtr());
#ifdef ENABLE_GDBSTUB
if (GDBStub::CheckBreakpoint(vaddr, sizeof(T), GDBStub::BreakpointType::Write)) {
GDBStub::Break(SIGTRAP);
}
#endif
return Common::AtomicCompareAndSwap(volatile_pointer, data, expected);
}