- Update README.md with accurate lokkju fork info and troubleshooting - Add CLAUDE.md development guide for future sessions - Document GDB protocol details, architecture, and common issues
372 lines
11 KiB
Markdown
372 lines
11 KiB
Markdown
# DOSBox-X MCP Server - Development Guide
|
|
|
|
## Project Overview
|
|
|
|
An MCP server that enables Claude to programmatically debug DOS binaries running in DOSBox-X via the GDB Remote Serial Protocol. The primary use case is reverse engineering the unpublished Bezier algorithm in RIPTERM.EXE.
|
|
|
|
---
|
|
|
|
## Quick Reference
|
|
|
|
### Running the System
|
|
|
|
```bash
|
|
# Start DOSBox-X container (must be running for MCP tools to work)
|
|
xhost +local:docker && docker compose up -d
|
|
|
|
# Verify GDB server is listening
|
|
nc -zv localhost 1234
|
|
|
|
# Check container logs
|
|
docker compose logs -f
|
|
|
|
# Stop container
|
|
docker compose down
|
|
```
|
|
|
|
### Testing the MCP Server
|
|
|
|
```bash
|
|
# Run full test suite
|
|
uv run pytest
|
|
|
|
# Run specific test file
|
|
uv run pytest tests/test_gdb_client.py -v
|
|
|
|
# Test MCP tools via CLI (important: --allowedTools required!)
|
|
claude -p "attach to localhost:1234 and read registers" \
|
|
--allowedTools "mcp__dosbox-mcp__*"
|
|
```
|
|
|
|
### Common Development Commands
|
|
|
|
```bash
|
|
# Run server directly (for debugging)
|
|
uv run dosbox-mcp
|
|
|
|
# Lint and format
|
|
uv run ruff check src/ && uv run ruff format src/
|
|
|
|
# Rebuild container after Dockerfile changes
|
|
docker compose build --no-cache
|
|
```
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
### Component Overview
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Claude Code │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
│ MCP (stdio)
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ FastMCP Server │
|
|
│ server.py - registers tools from tools/ modules │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
│
|
|
┌───────────────┼───────────────┐
|
|
▼ ▼ ▼
|
|
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
|
│ state.py │ │ types.py │ │ utils.py │
|
|
│ Global client │ │ Registers │ │ parse_address │
|
|
│ and manager │ │ Breakpoint │ │ format helpers │
|
|
└─────────────────┘ │ MemoryRegion │ └─────────────────┘
|
|
│ └─────────────────┘
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ gdb_client.py │
|
|
│ GDB Remote Serial Protocol client │
|
|
│ - Packet formatting ($cmd#checksum) │
|
|
│ - Register/memory read/write │
|
|
│ - Breakpoint management │
|
|
│ - Execution control (step, continue) │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
│ TCP :1234
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ DOSBox-X Container (lokkju fork) │
|
|
│ Built with --enable-remotedebug │
|
|
│ Ports: 1234 (GDB), 4444 (QMP) │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Key Files
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `server.py` | FastMCP entry point, registers all tools |
|
|
| `gdb_client.py` | GDB protocol implementation (~400 lines) |
|
|
| `state.py` | Singleton instances (manager, client) to avoid circular imports |
|
|
| `types.py` | Dataclasses: Registers, Breakpoint, MemoryRegion, etc. |
|
|
| `utils.py` | Address parsing, format conversion |
|
|
| `tools/execution.py` | launch, attach, continue, step, quit |
|
|
| `tools/breakpoints.py` | breakpoint_set, list, delete |
|
|
| `tools/inspection.py` | registers, memory_read/write, disassemble, stack |
|
|
| `tools/peripheral.py` | screenshot, serial_send (stubs) |
|
|
|
|
---
|
|
|
|
## GDB Remote Serial Protocol
|
|
|
|
### Packet Format
|
|
|
|
```
|
|
Send: $<command>#<2-digit-hex-checksum>
|
|
Receive: +$<response>#<checksum>
|
|
(+ is ACK, - is NAK)
|
|
```
|
|
|
|
### Key Commands
|
|
|
|
| Command | Description | Example |
|
|
|---------|-------------|---------|
|
|
| `qSupported` | Handshake/capabilities | First packet after connect |
|
|
| `g` | Read all registers | Returns hex blob |
|
|
| `G<hex>` | Write all registers | |
|
|
| `m<addr>,<len>` | Read memory | `m7c00,10` |
|
|
| `M<addr>,<len>:<hex>` | Write memory | |
|
|
| `Z0,<addr>,1` | Set software breakpoint | `Z0,7c00,1` |
|
|
| `z0,<addr>,1` | Clear breakpoint | |
|
|
| `c` | Continue execution | |
|
|
| `s` | Step one instruction | |
|
|
| `?` | Query stop reason | Returns `S05` (SIGTRAP) etc. |
|
|
|
|
### Register Order (x86)
|
|
|
|
GDB returns registers in this order:
|
|
```
|
|
EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI, EIP, EFLAGS,
|
|
CS, SS, DS, ES, FS, GS
|
|
```
|
|
|
|
Each is 32-bit little-endian hex (8 chars), except segments (16-bit, 4 chars).
|
|
|
|
---
|
|
|
|
## DOSBox-X Configuration
|
|
|
|
### Critical: Use `[dosbox]` Section
|
|
|
|
The lokkju fork's GDB/QMP config goes in `[dosbox]`, NOT `[debug]`:
|
|
|
|
```ini
|
|
[dosbox]
|
|
gdbserver=true
|
|
gdbserver port=1234
|
|
qmpserver=true
|
|
qmpserver port=4444
|
|
```
|
|
|
|
### Why lokkju/dosbox-x-remotedebug?
|
|
|
|
Mainline DOSBox-X has no GDB stub. The lokkju fork (branch: `remotedebug`) adds:
|
|
- `--enable-remotedebug` configure flag
|
|
- `gdbserver.cpp` and `qmp.cpp` sources
|
|
- Runtime config options
|
|
|
|
Build command in Dockerfile:
|
|
```bash
|
|
./configure --enable-remotedebug --enable-debug --enable-sdl2
|
|
```
|
|
|
|
---
|
|
|
|
## Real Mode Addressing
|
|
|
|
DOS uses segment:offset addressing (20-bit physical):
|
|
|
|
```python
|
|
physical = (segment << 4) + offset
|
|
|
|
# Example: 1234:5678
|
|
# physical = 0x12340 + 0x5678 = 0x179B8
|
|
```
|
|
|
|
The `utils.py` module handles parsing:
|
|
- `"1234:5678"` → segment:offset
|
|
- `"0x12345"` or `"12345"` → flat hex
|
|
- `"#65536"` → decimal
|
|
- `"CS:IP"` → register-based (requires current register state)
|
|
|
|
---
|
|
|
|
## Adding New Tools
|
|
|
|
### 1. Create the Tool Function
|
|
|
|
In appropriate `tools/*.py` file:
|
|
|
|
```python
|
|
from ..state import client, manager
|
|
from ..types import SomeType
|
|
from ..utils import parse_address
|
|
|
|
def my_new_tool(address: str, count: int = 10) -> dict:
|
|
"""Short description for MCP.
|
|
|
|
Args:
|
|
address: Memory address (segment:offset or flat hex)
|
|
count: Number of items (default: 10)
|
|
|
|
Returns:
|
|
Result dictionary with data
|
|
"""
|
|
if not client.connected:
|
|
return {"error": "Not connected. Use attach() first."}
|
|
|
|
addr = parse_address(address, client.read_registers())
|
|
# ... implementation ...
|
|
return {"address": f"{addr:05x}", "data": result}
|
|
```
|
|
|
|
### 2. Export from `tools/__init__.py`
|
|
|
|
```python
|
|
from .mymodule import my_new_tool
|
|
|
|
__all__ = [
|
|
# ...existing...
|
|
"my_new_tool",
|
|
]
|
|
```
|
|
|
|
### 3. Register in `server.py`
|
|
|
|
```python
|
|
mcp.tool()(tools.my_new_tool)
|
|
```
|
|
|
|
### 4. Add Tests
|
|
|
|
In `tests/test_tools.py` or new file:
|
|
|
|
```python
|
|
def test_my_new_tool():
|
|
# Mock the GDB client
|
|
...
|
|
```
|
|
|
|
---
|
|
|
|
## Unfinished Work
|
|
|
|
### Peripheral Tools (Stubs)
|
|
|
|
`screenshot` and `serial_send` are placeholders. Implementation ideas:
|
|
|
|
**screenshot**: DOSBox-X can capture via:
|
|
- Built-in screenshot command (key binding)
|
|
- QMP protocol on port 4444
|
|
- X11 window capture
|
|
|
|
**serial_send**: Configure serial port in dosbox.conf:
|
|
```ini
|
|
[serial]
|
|
serial1=nullmodem server:1234
|
|
```
|
|
Then send data via socket.
|
|
|
|
### Step Over Implementation
|
|
|
|
`step_over` in `execution.py` is basic — it steps but doesn't properly handle CALL instructions. A full implementation would:
|
|
1. Disassemble current instruction
|
|
2. If CALL, set temp breakpoint after it
|
|
3. Continue instead of step
|
|
4. Remove temp breakpoint
|
|
|
|
### Disassembler
|
|
|
|
Current disassembler in `inspection.py` is minimal (pattern matching common opcodes). For full disassembly, consider:
|
|
- Capstone library (`pip install capstone`)
|
|
- External tool like `ndisasm`
|
|
|
|
---
|
|
|
|
## Testing Against Real DOSBox-X
|
|
|
|
### Manual GDB Connection
|
|
|
|
```bash
|
|
# From host, after container is running
|
|
nc localhost 1234
|
|
|
|
# Type (no newline):
|
|
$qSupported#37
|
|
|
|
# Should receive capabilities response
|
|
```
|
|
|
|
### Python Quick Test
|
|
|
|
```python
|
|
from dosbox_mcp.gdb_client import GDBClient
|
|
|
|
client = GDBClient()
|
|
client.connect("localhost", 1234)
|
|
regs = client.read_registers()
|
|
print(f"CS:IP = {regs.cs:04x}:{regs.ip:04x}")
|
|
client.disconnect()
|
|
```
|
|
|
|
---
|
|
|
|
## Debugging Tips
|
|
|
|
### Enable Verbose Logging
|
|
|
|
```python
|
|
import logging
|
|
logging.getLogger("dosbox_mcp").setLevel(logging.DEBUG)
|
|
```
|
|
|
|
### Check Container Logs
|
|
|
|
```bash
|
|
# GDB connections show up here
|
|
docker logs dosbox-mcp 2>&1 | grep -i gdb
|
|
```
|
|
|
|
### Common Issues
|
|
|
|
| Symptom | Cause | Fix |
|
|
|---------|-------|-----|
|
|
| "Connection refused" | Container not running | `docker compose up -d` |
|
|
| "Connection closed" | Wrong DOSBox build | Verify lokkju fork |
|
|
| Config not applied | Wrong section | Use `[dosbox]` not `[debug]` |
|
|
| Timeout on continue | No breakpoint hit | Set breakpoint first |
|
|
| "unhealthy" status | Missing `nc` in image | Cosmetic, ignore or add netcat |
|
|
|
|
---
|
|
|
|
## Future Enhancements
|
|
|
|
1. **RIPTERM Bezier Tracing**
|
|
- Set breakpoints at drawing routines
|
|
- Capture coordinate pairs from registers/stack
|
|
- Build coordinate trace for algorithm analysis
|
|
|
|
2. **QMP Integration**
|
|
- Port 4444 provides machine control
|
|
- Could enable screenshots, state snapshots
|
|
|
|
3. **Memory Watchpoints**
|
|
- GDB protocol supports `Z2`/`z2` for write watchpoints
|
|
- Useful for tracking variable changes
|
|
|
|
4. **Scripted Automation**
|
|
- Create `examples/ripterm_bezier.py` for the target use case
|
|
- Document coordinate capture methodology
|
|
|
|
---
|
|
|
|
## Related Resources
|
|
|
|
- [GDB Remote Protocol Docs](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Remote-Protocol.html)
|
|
- [lokkju fork PR/issue](https://github.com/joncampbell123/dosbox-x/issues/752) — Original discussion
|
|
- [FastMCP Docs](https://gofastmcp.com/) — MCP server framework
|
|
- [RIPscrip Spec](https://wiki.preterhuman.net/RIPscrip_Graphics_Protocol_Specification)
|