From 7cbbfd6fc589ec5a025c2eb3bbc44459c8dfb402 Mon Sep 17 00:00:00 2001 From: zoey Date: Wed, 3 Jun 2026 15:04:55 +0100 Subject: [PATCH] gba: add support for mGBA debugging Implements `putchar` when `mgbadebug` build tag is set which outputs text through the mGBA debugging output interface. mGBA allows the games running in the emulator to output debug text the process to do so is: 1. set 0x4FFF780 to value of 0xC0DE 2. write text in memory 0x4FFF600 to 0x4FFF700 3. set 0x4FFF700 to the desired log level value from 0x100 (fatal) to 0x104 (debug) Once this is done mGBA will output the text in its logs view as well as through stdout. (Setting the log level to fatal also produces a dialog box with the text) The text output in the CLI is prefixed with `[DEBUG] GBA Debug: ` so I modified the regex used for address matching in panic messages to be able to read the address when the output line doesn't start with `panic`. --- monitor.go | 2 +- src/runtime/runtime_arm7tdmi.go | 2 +- src/runtime/runtime_mgba.go | 121 ++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 src/runtime/runtime_mgba.go diff --git a/monitor.go b/monitor.go index 35a256fca..63f731a7d 100644 --- a/monitor.go +++ b/monitor.go @@ -278,7 +278,7 @@ func ListSerialPorts() ([]SerialPortInfo, error) { return serialPortInfo, nil } -var addressMatch = regexp.MustCompile(`^panic: runtime error at 0x([0-9a-f]+): `) +var addressMatch = regexp.MustCompile(`panic: runtime error at 0x([0-9a-f]+): `) // Extract the address from the "panic: runtime error at" message. func extractPanicAddress(line []byte) uint64 { diff --git a/src/runtime/runtime_arm7tdmi.go b/src/runtime/runtime_arm7tdmi.go index fe0b648b5..ebab4a23f 100644 --- a/src/runtime/runtime_arm7tdmi.go +++ b/src/runtime/runtime_arm7tdmi.go @@ -1,4 +1,4 @@ -//go:build arm7tdmi +//go:build arm7tdmi && !mgbadebug package runtime diff --git a/src/runtime/runtime_mgba.go b/src/runtime/runtime_mgba.go new file mode 100644 index 000000000..911a2a8b5 --- /dev/null +++ b/src/runtime/runtime_mgba.go @@ -0,0 +1,121 @@ +//go:build gameboyadvance && mgbadebug + +package runtime + +import ( + _ "runtime/interrupt" // make sure the interrupt handler is defined + "runtime/volatile" + "unsafe" +) + +var ( + // Setting this memory address to 0xC0DE enables mGBA's debug printing + debugEnable = (*volatile.Register16)(unsafe.Pointer(uintptr(0x4FFF780))) + // mGBA supports log levels from 0x100(fatal) to 0x104(debug) + logLevel uint16 = 0x104 // use the debug log level + // Once we are ready to output we set the debug flags register to logLevel + // mGBA will output the text and then clear the text buffer when set + debugFlags = (*volatile.Register16)(unsafe.Pointer(uintptr(0x4FFF700))) + + textBuffer = (*[255]byte)(unsafe.Pointer(uintptr(0x4FFF600))) + index = 0 +) + +func putchar(c byte) { + if c == '\n' || index >= len(textBuffer) { + debugFlags.Set(logLevel) + index = 0 + + // mGBA automatically prints a new line so we can ignore it + if c == '\n' { + return + } + } + + textBuffer[index] = c + index++ +} + +func getchar() byte { + // dummy, TODO + return 0 +} + +func buffered() int { + // dummy, TODO + return 0 +} + +//go:extern _sbss +var _sbss [0]byte + +//go:extern _ebss +var _ebss [0]byte + +//go:extern _sdata +var _sdata [0]byte + +//go:extern _sidata +var _sidata [0]byte + +//go:extern _edata +var _edata [0]byte + +// Entry point for Go. Initialize all packages and call main.main(). +// +//export main +func main() { + // Initialize .data and .bss sections. + preinit() + + // Enable mGBA debugging. + debugEnable.Set(0xC0DE) + + // Run program. + run() +} + +func preinit() { + // Initialize .bss: zero-initialized global variables. + ptr := unsafe.Pointer(&_sbss) + for ptr != unsafe.Pointer(&_ebss) { + *(*uint32)(ptr) = 0 + ptr = unsafe.Add(ptr, 4) + } + + // Initialize .data: global variables initialized from flash. + src := unsafe.Pointer(&_sidata) + dst := unsafe.Pointer(&_sdata) + for dst != unsafe.Pointer(&_edata) { + *(*uint32)(dst) = *(*uint32)(src) + dst = unsafe.Add(dst, 4) + src = unsafe.Add(src, 4) + } +} + +func ticksToNanoseconds(ticks timeUnit) int64 { + return int64(ticks) +} + +func nanosecondsToTicks(ns int64) timeUnit { + return timeUnit(ns) +} + +func ticks() timeUnit { + // TODO + return 0 +} + +func sleepTicks(d timeUnit) { + // TODO +} + +func exit(code int) { + abort() +} + +func abort() { + // TODO + for { + } +}