275 lines
7.3 KiB
Markdown
275 lines
7.3 KiB
Markdown
# AI-Assisted 65816 Assembly Debugging Guide
|
|
|
|
This guide documents how AI agents (Claude, Gemini, etc.) can use the yaze EmulatorService gRPC API to debug 65816 assembly code in SNES ROM hacks like Oracle of Secrets.
|
|
|
|
## Overview
|
|
|
|
The EmulatorService provides comprehensive debugging capabilities:
|
|
- **Disassembly**: Convert raw bytes to human-readable 65816 assembly
|
|
- **Symbol Resolution**: Map addresses to labels from Asar ASM files
|
|
- **Breakpoints/Watchpoints**: Pause execution on conditions
|
|
- **Stepping**: StepInto, StepOver, StepOut with call stack tracking
|
|
- **Memory Inspection**: Read/write SNES memory regions
|
|
|
|
## Getting Started
|
|
|
|
### 1. Start the Emulator Server
|
|
|
|
```bash
|
|
# Launch z3ed with ROM and start gRPC server
|
|
z3ed emu start --rom oracle_of_secrets.sfc --grpc-port 50051
|
|
```
|
|
|
|
### 2. Load Symbols (Optional but Recommended)
|
|
|
|
Load symbols from your ASM source directory for meaningful labels:
|
|
|
|
If you need the vanilla usdasm disassembly, fetch it on demand:
|
|
|
|
```bash
|
|
scripts/fetch_usdasm.sh
|
|
# or: USDASM_DIR=/path/to/usdasm scripts/fetch_usdasm.sh
|
|
```
|
|
|
|
```protobuf
|
|
rpc LoadSymbols(SymbolFileRequest) returns (CommandResponse)
|
|
|
|
// Request:
|
|
// - path: Directory containing .asm files (e.g., "assets/asm/usdasm/bank00/")
|
|
// - format: ASAR_ASM | WLA_DX | MESEN | BSNES
|
|
```
|
|
|
|
### 3. Set Breakpoints
|
|
|
|
```protobuf
|
|
rpc AddBreakpoint(BreakpointRequest) returns (BreakpointResponse)
|
|
|
|
// Request:
|
|
// - address: 24-bit address (e.g., 0x008000 for bank 00, offset $8000)
|
|
// - type: EXECUTE | READ | WRITE
|
|
// - enabled: true/false
|
|
// - condition: Optional expression (e.g., "A == 0x10")
|
|
```
|
|
|
|
### 4. Run Until Breakpoint
|
|
|
|
```protobuf
|
|
rpc RunToBreakpoint(Empty) returns (BreakpointHitResponse)
|
|
|
|
// Response includes:
|
|
// - address: Where execution stopped
|
|
// - breakpoint_id: Which breakpoint triggered
|
|
// - registers: Current CPU state (A, X, Y, PC, SP, P, DBR, PBR, DP)
|
|
```
|
|
|
|
## Debugging Workflow
|
|
|
|
### Disassembling Code
|
|
|
|
```protobuf
|
|
rpc GetDisassembly(DisassemblyRequest) returns (DisassemblyResponse)
|
|
|
|
// Request:
|
|
// - address: Starting 24-bit address
|
|
// - count: Number of instructions to disassemble
|
|
// - m_flag: Accumulator size (true = 8-bit, false = 16-bit)
|
|
// - x_flag: Index register size (true = 8-bit, false = 16-bit)
|
|
```
|
|
|
|
Example response with symbols loaded:
|
|
```
|
|
$008000: SEI ; Disable interrupts
|
|
$008001: CLC ; Clear carry for native mode
|
|
$008002: XCE ; Switch to native mode
|
|
$008003: REP #$30 ; 16-bit A, X, Y
|
|
$008005: LDA #$8000 ; Load screen buffer address
|
|
$008008: STA $2100 ; PPU_BRIGHTNESS
|
|
$00800B: JSR Reset ; Call Reset subroutine
|
|
```
|
|
|
|
### Stepping Through Code
|
|
|
|
**StepInto** - Execute one instruction:
|
|
```protobuf
|
|
rpc StepInstruction(Empty) returns (StepResponse)
|
|
```
|
|
|
|
**StepOver** - Execute subroutine as single step:
|
|
```protobuf
|
|
rpc StepOver(Empty) returns (StepResponse)
|
|
// If current instruction is JSR/JSL, runs until it returns
|
|
// Otherwise equivalent to StepInto
|
|
```
|
|
|
|
**StepOut** - Run until current subroutine returns:
|
|
```protobuf
|
|
rpc StepOut(Empty) returns (StepResponse)
|
|
// Continues execution until RTS/RTL decreases call depth
|
|
```
|
|
|
|
### Reading Memory
|
|
|
|
```protobuf
|
|
rpc ReadMemory(MemoryRequest) returns (MemoryResponse)
|
|
|
|
// Request:
|
|
// - address: Starting address
|
|
// - length: Number of bytes to read
|
|
|
|
// Response:
|
|
// - data: Bytes as hex string or raw bytes
|
|
```
|
|
|
|
Common SNES memory regions:
|
|
- `$7E0000-$7FFFFF`: WRAM (128KB)
|
|
- `$000000-$FFFFFF`: ROM (varies by mapper)
|
|
- `$2100-$213F`: PPU registers
|
|
- `$4200-$421F`: CPU registers
|
|
- `$4300-$437F`: DMA registers
|
|
|
|
### Symbol Lookup
|
|
|
|
```protobuf
|
|
rpc ResolveSymbol(SymbolLookupRequest) returns (SymbolLookupResponse)
|
|
// name: "Player_X" -> address: 0x7E0010
|
|
|
|
rpc GetSymbolAt(AddressRequest) returns (SymbolLookupResponse)
|
|
// address: 0x7E0010 -> name: "Player_X", type: RAM
|
|
```
|
|
|
|
## 65816 Debugging Tips for AI Agents
|
|
|
|
### Understanding M/X Flags
|
|
|
|
The 65816 has variable-width registers controlled by status flags:
|
|
- **M flag** (bit 5 of P): Controls accumulator/memory width
|
|
- M=1: 8-bit accumulator, 8-bit memory operations
|
|
- M=0: 16-bit accumulator, 16-bit memory operations
|
|
- **X flag** (bit 4 of P): Controls index register width
|
|
- X=1: 8-bit X and Y registers
|
|
- X=0: 16-bit X and Y registers
|
|
|
|
Track flag changes from `REP` and `SEP` instructions:
|
|
```asm
|
|
REP #$30 ; M=0, X=0 (16-bit A, X, Y)
|
|
SEP #$20 ; M=1 (8-bit A, X and Y unchanged)
|
|
```
|
|
|
|
### Call Stack Tracking
|
|
|
|
The StepController automatically tracks:
|
|
- `JSR $addr` - 16-bit call within current bank
|
|
- `JSL $addr` - 24-bit long call across banks
|
|
- `RTS` - Return from JSR
|
|
- `RTL` - Return from JSL
|
|
- `RTI` - Return from interrupt
|
|
|
|
Use `GetDebugStatus` to view the current call stack.
|
|
|
|
### Common Debugging Scenarios
|
|
|
|
**1. Finding where a value is modified:**
|
|
```
|
|
1. Add a WRITE watchpoint on the memory address
|
|
2. Run emulation
|
|
3. When watchpoint triggers, examine call stack and code
|
|
```
|
|
|
|
**2. Tracing execution flow:**
|
|
```
|
|
1. Add EXECUTE breakpoint at entry point
|
|
2. Use StepOver to execute subroutines as single steps
|
|
3. Use StepInto when you want to enter a subroutine
|
|
4. Use StepOut to return from deep call stacks
|
|
```
|
|
|
|
**3. Understanding unknown code:**
|
|
```
|
|
1. Load symbols from source ASM files
|
|
2. Disassemble the region of interest
|
|
3. Cross-reference labels with source code
|
|
```
|
|
|
|
## Example: Debugging Player Movement
|
|
|
|
```python
|
|
# Pseudo-code for AI agent debugging workflow
|
|
|
|
# 1. Load symbols from Oracle of Secrets source
|
|
client.LoadSymbols(path="oracle_of_secrets/src/", format=ASAR_ASM)
|
|
|
|
# 2. Find the player update routine
|
|
result = client.ResolveSymbol(name="Player_Update")
|
|
player_update_addr = result.address
|
|
|
|
# 3. Set breakpoint at player update
|
|
bp = client.AddBreakpoint(address=player_update_addr, type=EXECUTE)
|
|
|
|
# 4. Run until we hit the player update
|
|
hit = client.RunToBreakpoint()
|
|
|
|
# 5. Step through and inspect state
|
|
while True:
|
|
step = client.StepOver()
|
|
print(f"PC: ${step.new_pc:06X} - {step.message}")
|
|
|
|
# Read player position after each step
|
|
player_x = client.ReadMemory(address=0x7E0010, length=2)
|
|
player_y = client.ReadMemory(address=0x7E0012, length=2)
|
|
print(f"Player: ({player_x}, {player_y})")
|
|
|
|
if input("Continue? (y/n): ") != "y":
|
|
break
|
|
```
|
|
|
|
## Proto Definitions Reference
|
|
|
|
Key message types from `protos/emulator_service.proto`:
|
|
|
|
```protobuf
|
|
message DisassemblyRequest {
|
|
uint32 address = 1;
|
|
uint32 count = 2;
|
|
bool m_flag = 3;
|
|
bool x_flag = 4;
|
|
}
|
|
|
|
message BreakpointRequest {
|
|
uint32 address = 1;
|
|
BreakpointType type = 2;
|
|
bool enabled = 3;
|
|
string condition = 4;
|
|
}
|
|
|
|
message StepResponse {
|
|
bool success = 1;
|
|
uint32 new_pc = 2;
|
|
uint32 instructions_executed = 3;
|
|
string message = 4;
|
|
}
|
|
|
|
message SymbolLookupRequest {
|
|
string name = 1;
|
|
}
|
|
|
|
message SymbolLookupResponse {
|
|
string name = 1;
|
|
uint32 address = 2;
|
|
string type = 3; // RAM, ROM, CONST
|
|
}
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
**Q: Disassembly shows wrong operand sizes**
|
|
A: The M/X flags might not match. Use `GetGameState` to check current P register, then pass correct `m_flag` and `x_flag` values.
|
|
|
|
**Q: Symbols not resolving**
|
|
A: Ensure you loaded symbols with `LoadSymbols` before calling `ResolveSymbol`. Check that the path points to valid ASM files.
|
|
|
|
**Q: StepOut not working**
|
|
A: The call stack might be empty (program is at top level). Check `GetDebugStatus` for current call depth.
|
|
|
|
**Q: Breakpoint not triggering**
|
|
A: Verify the address is correct (24-bit, bank:offset format). Check that the code actually executes that path.
|