Skip to main content

GDB Integration

Sogen implements the GDB Remote Serial Protocol, enabling you to debug emulated Windows applications using industry-standard debugging tools including GDB, LLDB, IDA Pro, and Visual Studio Code.

Overview

The GDB stub allows debuggers to:
  • Set breakpoints (software and hardware)
  • Single-step through code
  • Read and write memory
  • Inspect and modify registers
  • View loaded libraries
  • Debug multi-threaded applications

Starting the GDB Server

When running Sogen with the -d flag, it starts a GDB server on port 28960:
analyzer.exe -d C:\path\to\program.exe
The emulator will pause and wait for a debugger to connect before starting execution.

GDB Stub Architecture

Debugging Handler Interface

The GDB stub works through the debugging_handler interface:
// From src/gdb-stub/gdb_stub.hpp:30-83
struct debugging_handler
{
    virtual action run() = 0;
    virtual action singlestep() = 0;
    
    virtual size_t get_register_count() = 0;
    virtual size_t get_max_register_size() = 0;
    
    virtual size_t read_register(size_t reg, void* data, size_t max_length) = 0;
    virtual size_t write_register(size_t reg, const void* data, size_t size) = 0;
    
    virtual bool read_memory(uint64_t address, void* data, size_t length) = 0;
    virtual bool write_memory(uint64_t address, const void* data, size_t length) = 0;
    
    virtual bool set_breakpoint(breakpoint_type type, uint64_t address, size_t size) = 0;
    virtual bool delete_breakpoint(breakpoint_type type, uint64_t address, size_t size) = 0;
    
    virtual std::vector<uint32_t> get_thread_ids() = 0;
    virtual uint32_t get_current_thread_id() = 0;
    virtual bool switch_to_thread(uint32_t thread_id) = 0;
    
    virtual std::vector<library_info> get_libraries() = 0;
};

Breakpoint Types

Sogen supports multiple breakpoint types:
// From src/gdb-stub/gdb_stub.hpp:14-22
enum class breakpoint_type : uint8_t
{
    software = 0,              // Software breakpoint (INT3)
    hardware_exec = 1,         // Hardware execution breakpoint
    hardware_write = 2,        // Hardware write watchpoint
    hardware_read = 3,         // Hardware read watchpoint
    hardware_read_write = 4,   // Hardware access watchpoint
};

Connection Handling

The stub accepts connections on a TCP socket:
// From src/gdb-stub/gdb_stub.cpp:699-709
bool run_gdb_stub(const network::address& bind_address, debugging_handler& handler)
{
    auto client = accept_client(bind_address, should_stop);
    if (!client) {
        return false;
    }
    
    // Process GDB protocol packets
    while (!should_stop()) {
        const auto packet = connection.get_packet();
        process_packet(c, *packet);
    }
}

Connecting with GDB

1. Start Sogen in Debug Mode

analyzer.exe -d C:\Windows\System32\notepad.exe
Output:
Listening for debugger on 127.0.0.1:28960...

2. Connect GDB

gdb
(gdb) target remote localhost:28960
(gdb) continue

3. Set Breakpoints

# Break at address
(gdb) break *0x7ff700001000

# Break at symbol (if symbols loaded)
(gdb) break CreateFileW

# Hardware watchpoint
(gdb) watch *0x7ff700002000

4. Inspect State

# View registers
(gdb) info registers

# Read memory
(gdb) x/32x 0x7ff700001000

# View threads
(gdb) info threads

# Switch threads
(gdb) thread 2

Connecting with LLDB

LLDB uses the same GDB protocol:
lldb
(lldb) gdb-remote localhost:28960
(lldb) continue

LLDB Commands

# Set breakpoint
(lldb) breakpoint set --address 0x7ff700001000

# View registers
(lldb) register read

# Read memory
(lldb) memory read --size 4 --format x --count 32 0x7ff700001000

# Single step
(lldb) thread step-inst

Connecting with IDA Pro

IDA Pro has excellent GDB stub support:

1. Start Remote Debugging

  1. Start Sogen in debug mode
  2. In IDA: DebuggerAttachRemote GDB debugger
  3. Set hostname: localhost
  4. Set port: 28960
  5. Click OK

2. Configure Architecture

IDA should auto-detect x86-64, but verify:
  • DebuggerDebugger options
  • Ensure “x86-64” is selected

3. Debug Session

Once connected:
  • Set breakpoints by pressing F2
  • Step over with F8
  • Step into with F7
  • Run with F9
  • View registers in DebuggerRegisters
  • View memory in DebuggerMemory map

Loading Symbols in IDA

The GDB stub reports loaded libraries:
// From src/gdb-stub/gdb_stub.cpp:159-171
void handle_libraries(const debugging_context& c, const std::string_view payload)
{
    std::string xml = "<library-list version=\"1.0\">\n";
    
    for (const auto& library : c.handler.get_libraries()) {
        xml += "<library name=\"" + escape_xml(library.name) + 
               \"><segment address=\"0x" + 
               utils::string::to_hex_number(library.segment_address) + 
               "\"/></library>\n";
    }
    
    xml += "</library-list>";
}
IDA will automatically load symbols for recognized DLLs.

Connecting with VS Code

Visual Studio Code can debug through the GDB protocol using the C/C++ extension.

1. Install Extension

Install the C/C++ extension by Microsoft.

2. Create Launch Configuration

Create .vscode/launch.json:
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Sogen Debug",
      "type": "cppdbg",
      "request": "launch",
      "program": "C:\\path\\to\\target.exe",
      "miDebuggerServerAddress": "localhost:28960",
      "miDebuggerPath": "C:\\path\\to\\gdb.exe",
      "cwd": "${workspaceFolder}",
      "setupCommands": [
        {
          "description": "Enable pretty-printing",
          "text": "-enable-pretty-printing",
          "ignoreFailures": true
        }
      ]
    }
  ]
}

3. Start Debugging

  1. Start Sogen: analyzer.exe -d target.exe
  2. In VS Code: Press F5 or RunStart Debugging
  3. Use the Debug toolbar to step through code

4. Set Breakpoints

Click in the gutter next to line numbers to set breakpoints. VS Code will translate these to memory addresses.

Protocol Details

Supported Commands

The GDB stub implements these GDB Remote Serial Protocol commands:
CommandDescriptionSource Reference
?Stop reasongdb_stub.cpp:634-636
cContinuegdb_stub.cpp:612-615
sSingle stepgdb_stub.cpp:617-619
gRead registersgdb_stub.cpp:642-644
GWrite registersgdb_stub.cpp:646-648
pRead single registergdb_stub.cpp:650-652
PWrite single registergdb_stub.cpp:654-656
mRead memorygdb_stub.cpp:658-660
MWrite memorygdb_stub.cpp:662-664
XWrite memory (binary)gdb_stub.cpp:666-668
ZSet breakpointgdb_stub.cpp:629-632
zRemove breakpointgdb_stub.cpp:629-632
HSet threadgdb_stub.cpp:670-672
qSupportedFeature negotiationgdb_stub.cpp:218-220
qXfer:featuresTarget descriptiongdb_stub.cpp:196-198
qXfer:librariesLibrary listgdb_stub.cpp:200-202
qCCurrent threadgdb_stub.cpp:234-237
qfThreadInfoThread listgdb_stub.cpp:243-254
vContExtended continuegdb_stub.cpp:372-387

Register Access

Registers are accessed by index:
// From gdb_stub.cpp:390-413
void read_registers(const debugging_context& c)
{
    std::string response{};
    std::vector<std::byte> data{};
    data.resize(c.handler.get_max_register_size());
    
    const auto registers = c.handler.get_register_count();
    
    for (size_t i = 0; i < registers; ++i) {
        const auto size = c.handler.read_register(i, data.data(), data.size());
        const std::span register_data(data.data(), size);
        response.append(utils::string::to_hex_string(register_data));
    }
    
    c.connection.send_reply(response);
}

Memory Operations

Memory reads are limited to 4KB per request:
// From gdb_stub.cpp:479-502
void read_memory(const debugging_context& c, const std::string& payload)
{
    uint64_t address{};
    size_t size{};
    sscanf_s(payload.c_str(), "%" PRIx64 ",%zx", &address, &size);
    
    if (size > 0x1000) {  // 4KB limit
        c.connection.send_reply("E01");
        return;
    }
    
    std::vector<std::byte> data{};
    data.resize(size);
    
    const auto res = c.handler.read_memory(address, data.data(), data.size());
    if (!res) {
        c.connection.send_reply("E01");
        return;
    }
    
    c.connection.send_reply(utils::string::to_hex_string(data));
}

Thread Support

Multiple threads are supported:
// From gdb_stub.cpp:578-604
void switch_to_thread(const debugging_context& c, const std::string_view payload)
{
    uint32_t id{};
    sscanf_s(std::string(payload.substr(1)).c_str(), "%x", &id);
    
    const auto operation = payload[0];
    if (operation == 'c') {  // Continuation thread
        c.state.continuation_thread = id;
        c.connection.send_reply("OK");
    }
    else if (operation == 'g') {  // Query thread
        const auto res = id == 0 || c.handler.switch_to_thread(id);
        c.connection.send_reply(res ? "OK" : "E01");
    }
}

Advanced Features

Asynchronous Interrupts

The debugger can interrupt execution at any time:
// From gdb_stub.cpp:711-723
async_handler async{[&](std::atomic_bool& can_run) {
    while (can_run) {
        const auto data = client.receive(1);
        
        if (is_interrupt_packet(data)) {
            handler.on_interrupt();
            can_run = false;
        }
    }
}};
Press Ctrl+C in GDB to trigger an interrupt.

Target Description

The stub provides x86-64 register descriptions:
// From gdb_stub.cpp:101-114
void handle_features(const debugging_context& c, const std::string_view payload)
{
    const auto [command, args] = split_string(payload, ':');
    
    if (command != "read") {
        c.connection.send_reply({});
        return;
    }
    
    const auto [file, data] = split_string(args, ':');
    const auto target_description = c.handler.get_target_description(file);
    send_xfer_data(c.connection, std::string(data), target_description);
}

Library Notification

Debuggers are notified when libraries are loaded:
// From gdb_stub.cpp:302-324
void signal_stop(const debugging_context& c)
{
    std::string reply = "T05";  // SIGTRAP
    
    if (c.handler.should_signal_library()) {
        reply += "library:;";
    }
    
    const auto id = c.handler.get_current_thread_id();
    reply += "thread:" + utils::string::to_hex_number(id) + ";";
    
    c.connection.send_reply(reply);
}

Debugging Tips

Finding Entry Points

# Break at program entry
(gdb) break *$rip
(gdb) continue

# Break at main
(gdb) break main

Debugging Syscalls

Set breakpoints on syscall instructions:
# Find syscalls
(gdb) x/100i $rip

# Break before syscall
(gdb) break *0x<address_before_syscall>

Watching Memory

Hardware watchpoints track memory changes:
# Watch 8 bytes
(gdb) watch *0x7ff700002000

# Watch with condition
(gdb) watch *0x7ff700002000 if *0x7ff700002000 == 0x41414141

Examining Strings

Windows uses UTF-16 strings:
# View wide string
(gdb) x/32h 0x<string_address>

# Or in LLDB
(lldb) memory read --size 2 --format x 0x<string_address>

Troubleshooting

Connection Refused

Ensure:
  • Sogen is running with -d flag
  • Firewall allows connections to port 28960
  • You’re connecting to localhost:28960

Breakpoints Not Working

Check:
  • Address is valid and executable
  • Code hasn’t been relocated
  • Using correct breakpoint type for the operation

Register Values Incorrect

The GDB stub reports registers in little-endian byte order. Most debuggers handle this automatically.

Cannot Step Over Instructions

Some complex instructions may require single-stepping (stepi in GDB) instead of stepping over.

Source Code Reference

Key files:
  • src/gdb-stub/gdb_stub.hpp - Main interface
  • src/gdb-stub/gdb_stub.cpp - Protocol implementation
  • src/gdb-stub/connection_handler.hpp - Network handling
  • src/gdb-stub/stream_processor.hpp - Packet parsing
  • src/gdb-stub/async_handler.hpp - Interrupt handling

Next Steps

Build docs developers (and LLMs) love