diff --git a/lib/zba-gdbstub b/lib/zba-gdbstub index 08bf0f9..4bca44e 160000 --- a/lib/zba-gdbstub +++ b/lib/zba-gdbstub @@ -1 +1 @@ -Subproject commit 08bf0f9201a0cd6d317e3f73a26d245eedfd1121 +Subproject commit 4bca44e5f2a4aa19361b51b7617a21f46f88eef4 diff --git a/src/core/emu.zig b/src/core/emu.zig index 1d97864..ece7e58 100644 --- a/src/core/emu.zig +++ b/src/core/emu.zig @@ -162,3 +162,59 @@ fn sleep(timer: *Timer, wake_time: u64) ?u64 { fn spinLoop(timer: *Timer, wake_time: u64) void { while (true) if (timer.read() > wake_time) break; } + +pub const EmuThing = struct { + const Self = @This(); + const Interface = @import("gdbstub").Emulator; + + cpu: *Arm7tdmi, + scheduler: *Scheduler, + + pub fn init(cpu: *Arm7tdmi, scheduler: *Scheduler) Self { + return .{ .cpu = cpu, .scheduler = scheduler }; + } + + pub fn interface(self: *Self) Interface { + return Interface.init(self); + } + + pub fn read(self: *const Self, addr: u32) u8 { + return self.cpu.bus.dbgRead(u8, addr); + } + + pub fn write(self: *Self, addr: u32, value: u8) void { + _ = value; + _ = self; + _ = addr; + + std.debug.panic("TODO: Implement Debug Writes?", .{}); + } + + pub fn registers(self: *const Self) *[16]u32 { + return &self.cpu.r; + } + + pub fn cpsr(self: *const Self) u32 { + return self.cpu.cpsr.raw; + } + + pub fn step(self: *Self) void { + const cpu = self.cpu; + const sched = self.scheduler; + + // TODO: How can I make it easier to keep this in lock-step with runFrame? + while (true) { + if (!cpu.stepDmaTransfer()) { + if (cpu.isHalted()) { + // Fast-forward to next Event + sched.tick = sched.queue.peek().?.tick; + } else { + cpu.step(); + break; // this function won't return until we've actually stepped once + } + } + + if (sched.tick >= sched.nextTimestamp()) sched.handleEvent(cpu); + } + } +}; diff --git a/src/main.zig b/src/main.zig index 463f388..cc89a24 100644 --- a/src/main.zig +++ b/src/main.zig @@ -22,6 +22,7 @@ const params = clap.parseParamsComptime( \\-h, --help Display this help and exit. \\-s, --skip Skip BIOS. \\-b, --bios Optional path to a GBA BIOS ROM. + \\ --gdb Run ZBA from the context of a GDB Server \\ Path to the GBA GamePak ROM. \\ ); @@ -87,10 +88,37 @@ pub fn main() void { cpu.fastBoot(); } - var gui = Gui.init(&bus.pak.title, &bus.apu, width, height) catch |e| exitln("failed to init gui: {}", .{e}); - defer gui.deinit(); + if (result.args.gdb) { + const Server = @import("gdbstub").Server; + const EmuThing = @import("core/emu.zig").EmuThing; - gui.run(&cpu, &scheduler) catch |e| exitln("failed to run gui thread: {}", .{e}); + var emu_thing = EmuThing.init(&cpu, &scheduler); + const emulator = emu_thing.interface(); + + { + const frames_per_second: usize = 60; + const emu = @import("core/emu.zig"); + + var i: usize = 0; + while (i < frames_per_second * 120) : (i += 1) { + emu.runFrame(&scheduler, &cpu); + + std.debug.print("Frame {:0>3}/{:0>3}\r", .{ i, frames_per_second * 120 }); + } + } + + log.info("Ready to connect", .{}); + + var server = Server.init(emulator) catch |e| exitln("failed to init gdb server: {}", .{e}); + defer server.deinit(allocator); + + server.run(allocator) catch |e| exitln("gdb server crashed: {}", .{e}); + } else { + var gui = Gui.init(&bus.pak.title, &bus.apu, width, height) catch |e| exitln("failed to init gui: {}", .{e}); + defer gui.deinit(); + + gui.run(&cpu, &scheduler) catch |e| exitln("failed to run gui thread: {}", .{e}); + } } fn handleArguments(allocator: Allocator, data_path: []const u8, result: *const clap.Result(clap.Help, ¶ms, clap.parsers.default)) !FilePaths {