From a2a47c990ec9da100aa457282defc5db6ba060c4 Mon Sep 17 00:00:00 2001 From: Max Fierke Date: Sat, 7 Sep 2024 20:50:43 -0500 Subject: [PATCH] Implement a basic interactive debugger Supports: * Breakpoints * Disassembling instruction at PC or a specific address (w/ operand values) * Print registers/CPU state * Print values at a given address --- README.md | 1 + debug/debugger.go | 2 + debug/interactive.go | 423 +++++++++++++++++++++++++++++++++++++++++++ go.mod | 20 +- go.sum | 48 ++++- main.go | 2 +- 6 files changed, 481 insertions(+), 15 deletions(-) create mode 100644 debug/interactive.go diff --git a/README.md b/README.md index 42c3d86..d74e7f0 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ a gameboy emulator for funsies - [X] Implement timer - [X] Pass Blargg's `cpu_instrs`/`02-interrupts.gb` ROM (manually verified) - [X] Pass Blargg's `instr_timing.gb` ROM (manually verified) +- [X] Implement a basic interactive debugger - [ ] Pass Blargg's `mem_timing.gb` ROM (manually verified) - [ ] Implement LCD - [ ] Implement PPU, VRAM, OAM, etc. diff --git a/debug/debugger.go b/debug/debugger.go index 04c1bbb..efac7a1 100644 --- a/debug/debugger.go +++ b/debug/debugger.go @@ -20,6 +20,8 @@ func NewDebugger(name string) (Debugger, error) { switch name { case "gameboy-doctor": return NewGBDoctorDebugger(), nil + case "interactive": + return NewInteractiveDebugger() case "none": return NewNullDebugger(), nil default: diff --git a/debug/interactive.go b/debug/interactive.go new file mode 100644 index 0000000..2ad336e --- /dev/null +++ b/debug/interactive.go @@ -0,0 +1,423 @@ +package debug + +import ( + "errors" + "fmt" + "slices" + "strconv" + "strings" + + "github.com/abiosoft/ishell/v2" + "github.com/maxfierke/gogo-gb/cpu" + "github.com/maxfierke/gogo-gb/mem" + "github.com/samber/lo" +) + +var ErrConsoleNotAttached = errors.New("debugger must be attached to a running console") + +var registerNames = []string{ + "A", "F", "B", "C", "D", "E", "H", "L", + "AF", "BC", "DE", "HL", "SP", "PC", +} + +type InteractiveDebugger struct { + breakpoints []uint16 + isStepping bool + shell *ishell.Shell +} + +var _ Debugger = (*InteractiveDebugger)(nil) + +func NewInteractiveDebugger() (*InteractiveDebugger, error) { + debugger := &InteractiveDebugger{ + breakpoints: []uint16{}, + } + + shell := ishell.New() + shell.Println("gogo-gb interactive debugger") + + shell.AddCmd(&ishell.Cmd{ + Name: "break", + Aliases: []string{"br", "b"}, + Help: "Set breakpoint at address", + Func: func(c *ishell.Context) { + if len(c.Args) == 0 { + c.Err(errors.New("must provide an address")) + } + + addr, err := parseAddr(c.Args[0]) + if err != nil { + c.Err(fmt.Errorf("parsing addr %w:", err)) + return + } + + if !slices.Contains(debugger.breakpoints, addr) { + debugger.breakpoints = append(debugger.breakpoints, addr) + c.Printf("added breakpoint @ 0x%02X\n", addr) + } + }, + }) + + shell.AddCmd(&ishell.Cmd{ + Name: "continue", + Aliases: []string{"c"}, + Help: "Continue execution until next breakpoint", + Func: func(c *ishell.Context) { + debugger.isStepping = false + c.Stop() + }, + }) + + shell.AddCmd(&ishell.Cmd{ + Name: "step", + Aliases: []string{"s"}, + Help: "Execute the next instruction", + Func: func(c *ishell.Context) { + debugger.isStepping = true + c.Stop() + }, + }) + + shell.AddCmd(&ishell.Cmd{ + Name: "examine", + Aliases: []string{"x"}, + Help: "Examine value at address", + Func: func(c *ishell.Context) { + if len(c.Args) == 0 { + c.Err(errors.New("must provide an address")) + return + } + + if slices.Contains(registerNames, c.Args[0]) { + regName := c.Args[0] + + cpu, err := getCPU(c) + if err != nil { + c.Err(fmt.Errorf("accessing cpu: %w", err)) + return + } + + switch regName { + case "AF", "BC", "DE", "HL": + c.Printf("%s: %04X\n", regName, getCompoundRegister(cpu, regName).Read()) + case "SP": + c.Printf("%s: %04X\n", regName, cpu.SP.Read()) + case "PC": + c.Printf("%s: %04X\n", regName, cpu.PC.Read()) + default: + c.Printf("%s: %02X\n", regName, getRegister(cpu, regName).Read()) + } + } else { + addr, err := parseAddr(c.Args[0]) + if err != nil { + c.Err(fmt.Errorf("parsing addr %w:", err)) + return + } + + mmu, err := getMMU(c) + if err != nil { + c.Err(fmt.Errorf("accessing mmu: %w", err)) + return + } + + if len(c.Args) > 1 && c.Args[1] == "16" { + c.Printf("0x%04X: %04X\n", addr, mmu.Read16(addr)) + } else { + c.Printf("0x%04X: %02X\n", addr, mmu.Read8(addr)) + } + } + }, + }) + + shell.AddCmd(&ishell.Cmd{ + Name: "disassemble", + Aliases: []string{"d", "di", "dis"}, + Help: "Disassemble at address", + Func: func(c *ishell.Context) { + var addr uint16 + + cpu, err := getCPU(c) + if err != nil { + c.Err(fmt.Errorf("accessing cpu: %w", err)) + return + } + + mmu, err := getMMU(c) + if err != nil { + c.Err(fmt.Errorf("accessing mmu: %w", err)) + return + } + + if len(c.Args) == 0 { + addr = cpu.PC.Read() + } else { + addr, err = parseAddr(c.Args[0]) + if err != nil { + c.Err(fmt.Errorf("parsing addr %w:", err)) + return + } + } + + inst, err := cpu.FetchAndDecode(mmu, addr) + if err != nil { + c.Err(fmt.Errorf("disassembling: %w", err)) + return + } + + operands := make([]string, 0, len(inst.Opcode.Operands)) + + operandOffset := 1 + for _, operand := range inst.Opcode.Operands { + switch { + case operand.Bytes == 1 && operand.Immediate: + operands = append(operands, fmt.Sprintf("$%02X", mmu.Read8(addr+1))) + case operand.Bytes == 1 && !operand.Immediate: + operands = append(operands, fmt.Sprintf("($%02X)", mmu.Read8(addr+1))) + case operand.Bytes == 2 && operand.Immediate: + operands = append(operands, fmt.Sprintf("$%04X", mmu.Read16(addr+1))) + case operand.Bytes == 2 && !operand.Immediate: + operands = append(operands, fmt.Sprintf("($%04X)", mmu.Read16(addr+1))) + default: + operands = append(operands, operand.String()) + } + + operandOffset += operand.Bytes + } + + c.Printf( + "0x%04X 0x%02X %s %s\n", + inst.Addr, + inst.Opcode.Addr, + inst.Opcode.Mnemonic, + strings.Join(operands, ", "), + ) + }, + }) + + shell.AddCmd(&ishell.Cmd{ + Name: "registers", + Aliases: []string{"r", "regs"}, + Help: "Print registers and other CPU state", + Func: func(c *ishell.Context) { + cpu, err := getCPU(c) + if err != nil { + c.Err(fmt.Errorf("accessing cpu: %w", err)) + return + } + + mmu, err := getMMU(c) + if err != nil { + c.Err(fmt.Errorf("accessing mmu: %w", err)) + return + } + + debugger.printState(cpu, mmu) + }, + }) + + shell.AddCmd(&ishell.Cmd{ + Name: "list", + Aliases: []string{"ls"}, + Help: "List set breakpoints", + Func: func(c *ishell.Context) { + c.Println("Active breakpoints:") + for i, breakpoint := range debugger.breakpoints { + c.Printf("* %d: 0x%02X\n", i, breakpoint) + } + }, + }) + + shell.AddCmd(&ishell.Cmd{ + Name: "clear", + Aliases: []string{"cl"}, + Help: "Clear breakpoint at address", + Func: func(c *ishell.Context) { + if len(c.Args) == 0 { + c.Err(errors.New("must provide an address")) + return + } + + addr, err := parseAddr(c.Args[0]) + if err != nil { + c.Err(fmt.Errorf("parsing addr %w:", err)) + return + } + + if !slices.Contains(debugger.breakpoints, addr) { + debugger.breakpoints = lo.Filter(debugger.breakpoints, func(b uint16, _ int) bool { + return b == addr + }) + c.Printf("cleared breakpoint @ 0x%02X\n", addr) + } + }, + }) + + shell.AddCmd(&ishell.Cmd{ + Name: "reset", + Aliases: []string{"res"}, + Help: "Reset the CPU", + Func: func(c *ishell.Context) { + cpu, err := getCPU(c) + if err != nil { + c.Err(fmt.Errorf("accessing cpu: %w", err)) + return + } + + cpu.Reset() + }, + }) + + debugger.shell = shell + + return debugger, nil +} + +func (i *InteractiveDebugger) OnDecode(cpu *cpu.CPU, mmu *mem.MMU) { + if slices.Contains(i.breakpoints, cpu.PC.Read()) { + i.shell.Printf("reached 0x%02X\n", cpu.PC.Read()) + i.attachShell(cpu, mmu) + } else if i.isStepping { + i.attachShell(cpu, mmu) + } +} + +func (i *InteractiveDebugger) OnExecute(cpu *cpu.CPU, mmu *mem.MMU) { +} + +func (i *InteractiveDebugger) OnInterrupt(cpu *cpu.CPU, mmu *mem.MMU) { +} + +func (i *InteractiveDebugger) OnRead(mmu *mem.MMU, addr uint16) mem.MemRead { + return mem.ReadPassthrough() +} + +func (i *InteractiveDebugger) OnWrite(mmu *mem.MMU, addr uint16, value byte) mem.MemWrite { + return mem.WritePassthrough() +} + +func (i *InteractiveDebugger) Setup(cpu *cpu.CPU, mmu *mem.MMU) { + i.attachShell(cpu, mmu) +} + +func (i *InteractiveDebugger) attachShell(cpu *cpu.CPU, mmu *mem.MMU) { + i.shell.Set("cpu", cpu) + i.shell.Set("mmu", mmu) + + // Remove references to CPU & MMU once we're done, since control will pass + // back to the console and we shouldn't hold onto references while emulation + // is running + defer i.shell.Del("cpu") + defer i.shell.Del("mmu") + + i.shell.Run() +} + +func (i *InteractiveDebugger) printState(cpu *cpu.CPU, mmu *mem.MMU) { + i.shell.Printf("Registers:\n") + i.shell.Printf( + " A: %02X F: %02X AF: %04X\n", + cpu.Reg.A.Read(), + cpu.Reg.F.Read(), + cpu.Reg.AF.Read(), + ) + i.shell.Printf( + " B: %02X C: %02X BC: %04X\n", + cpu.Reg.B.Read(), + cpu.Reg.C.Read(), + cpu.Reg.BC.Read(), + ) + i.shell.Printf( + " D: %02X E: %02X DE: %04X\n", + cpu.Reg.D.Read(), + cpu.Reg.E.Read(), + cpu.Reg.DE.Read(), + ) + i.shell.Printf( + " H: %02X L: %02X HL: %04X\n", + cpu.Reg.H.Read(), + cpu.Reg.L.Read(), + cpu.Reg.HL.Read(), + ) + i.shell.Printf("Flags:\n") + i.shell.Printf( + " Z: %t N: %t H: %t C: %t\n", + cpu.Reg.F.Zero, + cpu.Reg.F.Subtract, + cpu.Reg.F.HalfCarry, + cpu.Reg.F.Carry, + ) + i.shell.Printf("Program state:\n") + i.shell.Printf( + "SP: %04X PC: %04X PCMEM: %02X,%02X,%02X,%02X\n", + cpu.SP.Read(), + cpu.PC.Read(), + mmu.Read8(cpu.PC.Read()), + mmu.Read8(cpu.PC.Read()+1), + mmu.Read8(cpu.PC.Read()+2), + mmu.Read8(cpu.PC.Read()+3), + ) +} + +func parseAddr(addrString string) (uint16, error) { + addrString = strings.TrimPrefix(addrString, "$") + addrString = strings.TrimPrefix(addrString, "0x") + + parsedAddr, err := strconv.ParseUint(addrString, 16, 16) + if err != nil { + return 0, err + } + return uint16(parsedAddr), nil +} + +func getCPU(c *ishell.Context) (*cpu.CPU, error) { + cpu, ok := c.Get("cpu").(*cpu.CPU) + if !ok { + return nil, ErrConsoleNotAttached + } + return cpu, nil +} + +func getMMU(c *ishell.Context) (*mem.MMU, error) { + mmu, ok := c.Get("mmu").(*mem.MMU) + if !ok { + return nil, ErrConsoleNotAttached + } + + return mmu, nil +} + +func getRegister(cpu *cpu.CPU, regName string) *cpu.Register[uint8] { + switch regName { + case "A": + return cpu.Reg.A + case "B": + return cpu.Reg.B + case "C": + return cpu.Reg.C + case "D": + return cpu.Reg.D + case "E": + return cpu.Reg.E + case "H": + return cpu.Reg.H + case "L": + return cpu.Reg.L + default: + panic("tried to access non-existent register") + } +} + +func getCompoundRegister(cpu *cpu.CPU, regName string) *cpu.CompoundRegister { + switch regName { + case "AF": + return cpu.Reg.AF + case "BC": + return cpu.Reg.BC + case "DE": + return cpu.Reg.DE + case "HL": + return cpu.Reg.HL + } + + panic("tried to access non-existent register") +} diff --git a/go.mod b/go.mod index 4f4e36b..b7bdf0e 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,30 @@ module github.com/maxfierke/gogo-gb go 1.23.0 require ( - github.com/hajimehoshi/ebiten/v2 v2.7.3 + github.com/abiosoft/ishell/v2 v2.0.2 + github.com/hajimehoshi/ebiten/v2 v2.7.8 + github.com/samber/lo v1.47.0 github.com/stretchr/testify v1.9.0 ) require ( + github.com/abiosoft/ishell v2.0.0+incompatible // indirect + golang.org/x/text v0.16.0 // indirect +) + +require ( + github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8 // indirect + github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 // indirect github.com/ebitengine/hideconsole v1.0.0 // indirect github.com/ebitengine/purego v0.7.0 // indirect + github.com/fatih/color v1.12.0 // indirect + github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect github.com/jezek/xgb v1.1.1 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect + github.com/mattn/go-isatty v0.0.12 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index f4c4d8f..0149d98 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,54 @@ +github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw= +github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg= +github.com/abiosoft/ishell/v2 v2.0.2 h1:5qVfGiQISaYM8TkbBl7RFO6MddABoXpATrsFbVI+SNo= +github.com/abiosoft/ishell/v2 v2.0.2/go.mod h1:E4oTCXfo6QjoCart0QYa5m9w4S+deXs/P/9jA77A9Bs= +github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8= +github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8 h1:5e8X7WEdOWrjrKvgaWF6PRnDvJicfrkEnwAkWtMN74g= -github.com/ebitengine/gomobile v0.0.0-20240329170434-1771503ff0a8/go.mod h1:tWboRRNagZwwwis4QIgEFG1ZNFwBJ3LAhSLAXAAxobQ= +github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU= +github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M= github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE= github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A= github.com/ebitengine/purego v0.7.0 h1:HPZpl61edMGCEW6XK2nsR6+7AnJ3unUxpTZBkkIXnMc= github.com/ebitengine/purego v0.7.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= -github.com/hajimehoshi/ebiten/v2 v2.7.3 h1:lDpj8KbmmjzwD19rsjXNkyelicu0XGvklZW6/tjrgNs= -github.com/hajimehoshi/ebiten/v2 v2.7.3/go.mod h1:1vjyPw+h3n30rfTOpIsbWRXSxZ0Oz1cYc6Tq/2DKoQg= +github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI= +github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M= +github.com/hajimehoshi/ebiten/v2 v2.7.8 h1:QrlvF2byCzMuDsbxFReJkOCbM3O2z1H/NKQaGcA8PKk= +github.com/hajimehoshi/ebiten/v2 v2.7.8/go.mod h1:Ulbq5xDmdx47P24EJ+Mb31Zps7vQq+guieG9mghQUaA= github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4= github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= +github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= -golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= +golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index a0a04e7..42f116c 100644 --- a/main.go +++ b/main.go @@ -59,7 +59,7 @@ func main() { func parseOptions(options *CLIOptions) { flag.StringVar(&options.cartPath, "cart", "", "Path to cartridge file (.gb, .gbc)") flag.StringVar(&options.serialPort, "serial-port", "", "Path to serial port IO (could be a file, UNIX socket, etc.)") - flag.StringVar(&options.debugger, "debugger", "none", "Specify debugger to use (\"none\", \"gameboy-doctor\")") + flag.StringVar(&options.debugger, "debugger", "none", "Specify debugger to use (\"none\", \"gameboy-doctor\", \"interactive\")") flag.StringVar(&options.debugPrint, "debug-print", "", "Print out something for debugging purposes (\"cart-header\", \"opcodes\")") flag.StringVar(&options.logPath, "log", "", "Path to log file. Default/empty implies stdout") flag.BoolVar(&options.ui, "ui", false, "Launch with UI")