debugging

This commit is contained in:
Alex Janka 2023-04-20 10:16:05 +10:00
parent e805f32380
commit 8b94964204
8 changed files with 249 additions and 38 deletions

View file

@ -8,7 +8,7 @@ use gb_emu_lib::connect::{AudioOutput, DownsampleType};
const FRAMES_TO_BUFFER: usize = 1;
const DOWNSAMPLE_TYPE: DownsampleType = DownsampleType::ZeroOrderHold;
pub fn create_output() -> (AudioOutput, Stream) {
pub fn create_output(muted: bool) -> (AudioOutput, Stream) {
let host = cpal::default_host();
let device = host
@ -28,24 +28,43 @@ pub fn create_output() -> (AudioOutput, Stream) {
let (output, mut rx) =
AudioOutput::new(sample_rate as f32, true, FRAMES_TO_BUFFER, DOWNSAMPLE_TYPE);
let stream = device
.build_output_stream(
&config.config(),
move |data: &mut [f32], _info: &cpal::OutputCallbackInfo| {
for v in data.chunks_exact_mut(2) {
match executor::block_on(rx.pop()) {
Some(a) => v.copy_from_slice(&a),
None => panic!("Audio queue disconnected!"),
let stream = if muted {
device
.build_output_stream(
&config.config(),
move |data: &mut [f32], _info: &cpal::OutputCallbackInfo| {
for _ in data.chunks_exact_mut(2) {
match executor::block_on(rx.pop()) {
Some(_) => {}
None => panic!("Audio queue disconnected!"),
}
}
}
},
move |err| {
// react to errors here.
println!("audio error: {err}");
},
None,
)
.unwrap();
},
move |err| {
println!("audio error: {err}");
},
None,
)
.unwrap()
} else {
device
.build_output_stream(
&config.config(),
move |data: &mut [f32], _info: &cpal::OutputCallbackInfo| {
for v in data.chunks_exact_mut(2) {
match executor::block_on(rx.pop()) {
Some(a) => v.copy_from_slice(&a),
None => panic!("Audio queue disconnected!"),
}
}
},
move |err| {
println!("audio error: {err}");
},
None,
)
.unwrap()
};
stream.play().unwrap();
(output, stream)

121
gb-emu/src/debug.rs Normal file
View file

@ -0,0 +1,121 @@
use gb_emu_lib::{
connect::{EmulatorMessage, NoCamera},
EmulatorCore,
};
use std::{
collections::HashMap,
io::{self, Write},
str::FromStr,
sync::mpsc::Receiver,
};
use crate::window::WindowRenderer;
pub enum CommandErr {
InvalidCommand,
InvalidArgument,
}
pub enum Commands {
Watch(u16),
Step,
Continue,
}
impl FromStr for Commands {
type Err = CommandErr;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let lower = s.trim().to_lowercase();
match lower.as_str() {
"step" => Ok(Self::Step),
"continue" => Ok(Self::Continue),
_ if let Some(addr) = lower.strip_prefix("watch") => {
if let Ok(addr) = u16::from_str_radix(addr.trim().trim_start_matches("0x"), 16) {
Ok(Self::Watch(addr))
} else {
Err(CommandErr::InvalidArgument)
}
}
_ => Err(CommandErr::InvalidCommand),
}
}
}
pub struct Debugger {
core: EmulatorCore<u32, WindowRenderer, NoCamera>,
debug_receiver: Receiver<EmulatorMessage>,
stepping: bool,
last_command: String,
watches: HashMap<u16, u8>,
}
impl Debugger {
pub fn new(
core: EmulatorCore<u32, WindowRenderer, NoCamera>,
debug_receiver: Receiver<EmulatorMessage>,
) -> Self {
Self {
core,
debug_receiver,
stepping: true,
last_command: String::from(""),
watches: HashMap::new(),
}
}
pub fn step(&mut self) {
if let Ok(EmulatorMessage::Stop) = self.debug_receiver.try_recv() {
self.core.run();
}
if self.should_pause() {
println!("cycles: {}", self.core.cycle_count());
println!();
println!("{}", self.core.print_reg());
println!();
print!(">");
io::stdout().flush().unwrap();
let mut line = String::new();
line = match io::stdin().read_line(&mut line) {
Ok(_) => line,
Err(_) => String::from(""),
};
if line.trim().is_empty() {
line = self.last_command.clone();
} else {
self.last_command = line.clone();
}
if let Ok(command) = Commands::from_str(&line) {
match command {
Commands::Watch(address) => {
println!("watching {address:#X}");
self.watches.insert(address, self.core.get_memory(address));
return;
}
Commands::Step => self.stepping = true,
Commands::Continue => self.stepping = false,
}
} else {
println!("Invalid command");
return;
}
}
self.core.run();
}
fn should_pause(&mut self) -> bool {
let mut should_pause = self.stepping;
for (address, data) in &mut self.watches {
let new_data = self.core.get_memory(*address);
if new_data != *data {
should_pause = true;
println!("Memory at 0x{address:0>4X} changed:");
println!(" from 0b{0:0>8b}/0x{0:0>2X}", *data);
println!(" to 0b{0:0>8b}/0x{0:0>2X}", new_data);
*data = new_data;
}
}
should_pause
}
}

View file

@ -1,21 +1,21 @@
#![feature(let_chains)]
#![feature(let_chains, if_let_guard)]
#[cfg(feature = "camera")]
use camera::Webcam;
use clap::{ArgGroup, Parser};
use debug::Debugger;
use gb_emu_lib::{
connect::{EmulatorMessage, EmulatorOptions, NoCamera, RomFile, SerialTarget},
EmulatorCore,
};
use gilrs::Gilrs;
use std::sync::mpsc::channel;
use window::WindowRenderer;
mod audio;
#[cfg(feature = "camera")]
mod camera;
mod debug;
mod window;
/// Gameboy (DMG-A/B/C) emulator
@ -55,13 +55,17 @@ struct Args {
#[arg(short, long)]
tile_window: bool,
/// Step emulation by...
#[arg(long)]
step_by: Option<usize>,
/// Scale display by...
#[arg(short, long)]
scale_factor: Option<usize>,
/// Mute audio
#[arg(long)]
mute: bool,
/// Run debug console
#[arg(short, long)]
debug: bool,
}
fn main() {
@ -80,10 +84,15 @@ fn main() {
};
let (sender, receiver) = channel::<EmulatorMessage>();
let (debug_sender, debug_receiver) = channel::<EmulatorMessage>();
ctrlc::set_handler(move || sender.send(EmulatorMessage::Stop).unwrap()).unwrap();
ctrlc::set_handler(move || {
sender.send(EmulatorMessage::Stop).unwrap();
debug_sender.send(EmulatorMessage::Stop).unwrap()
})
.unwrap();
let (output, _stream) = audio::create_output();
let (output, _stream) = audio::create_output(args.mute);
#[cfg(feature = "webcam")]
let camera = Webcam::new();
@ -109,12 +118,14 @@ fn main() {
let mut core = EmulatorCore::init(receiver, options);
match args.step_by {
Some(step_size) => loop {
core.run_stepped(step_size);
},
None => loop {
if args.debug {
let mut debugger = Debugger::new(core, debug_receiver);
loop {
debugger.step();
}
} else {
loop {
core.run();
},
}
}
}

View file

@ -1,6 +1,9 @@
#![feature(exclusive_range_pattern, let_chains, bigint_helper_methods, step_trait)]
use crate::{processor::memory::Memory, util::pause};
use crate::{
processor::{memory::Memory, Flags},
util::pause,
};
use connect::{
AudioOutput, CameraWrapper, CameraWrapperRef, EmulatorMessage, EmulatorOptions, NoCamera,
PocketCamera, Renderer, RomFile, SerialTarget,
@ -126,6 +129,54 @@ where
self.cpu.memory.replace_output(new);
}
pub fn cycle_count(&self) -> usize {
self.cpu.cycle_count
}
pub fn print_reg(&self) -> String {
format!(
"A:{:0>2X}, F:{}, BC:{:0>4X}, DE:{:0>4X}, HL:{:0>4X}, SP:{:0>4X}, PC:{:0>4X}\nLast instruction: {:0>2X} from 0x{:0>4X}",
self.cpu.reg.get_8(processor::Reg8::A),
self.print_flags(),
self.cpu.reg.bc,
self.cpu.reg.de,
self.cpu.reg.hl,
self.cpu.reg.sp,
self.cpu.reg.pc,
self.cpu.last_instruction,self.cpu.last_instruction_addr
)
}
fn print_flags(&self) -> String {
format!(
"{}{}{}{}",
if self.cpu.is_flag(Flags::Zero) {
"Z"
} else {
"-"
},
if self.cpu.is_flag(Flags::NSubtract) {
"N"
} else {
"-"
},
if self.cpu.is_flag(Flags::HalfCarry) {
"H"
} else {
"-"
},
if self.cpu.is_flag(Flags::Carry) {
"C"
} else {
"-"
},
)
}
pub fn get_memory(&self, address: u16) -> u8 {
self.cpu.memory.get(address)
}
pub fn run(&mut self) {
self.process_messages();
self.run_cycle();

View file

@ -364,6 +364,7 @@ where
{
pub fn increment_timers(&mut self, machine_cycles: u8) {
let steps = (machine_cycles as usize) * 4;
self.cycle_count += steps;
self.memory.oam_tick(steps);

View file

@ -33,7 +33,7 @@ fn bool_to_shifted(input: bool, shift: u8) -> u8 {
(if input { 1 } else { 0 }) << shift
}
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub enum Interrupt {
Vblank,
LcdStat,

View file

@ -198,7 +198,7 @@ pub(super) struct Object {
pub(super) oam_location: u8,
}
#[derive(Serialize, Deserialize, Clone, Copy)]
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
pub(super) struct Stat {
pub(super) lyc_eq_ly_interrupt_enabled: bool,
pub(super) mode_2_interrupt_enabled: bool,

View file

@ -32,9 +32,10 @@ where
pub memory: Memory<ColourFormat, R, C>,
pub reg: Registers,
pub last_instruction: u8,
last_instruction_addr: u16,
pub last_instruction_addr: u16,
halted: bool,
should_halt_bug: bool,
pub(super) cycle_count: usize,
}
#[derive(Serialize, Deserialize)]
@ -49,6 +50,7 @@ where
last_instruction_addr: u16,
halted: bool,
should_halt_bug: bool,
cycle_count: usize,
}
impl<ColourFormat, R> CpuSaveState<ColourFormat, R>
@ -64,6 +66,7 @@ where
last_instruction_addr: cpu.last_instruction_addr,
halted: cpu.halted,
should_halt_bug: cpu.should_halt_bug,
cycle_count: cpu.cycle_count,
}
}
}
@ -85,6 +88,7 @@ where
last_instruction_addr: 0x0,
halted: false,
should_halt_bug: false,
cycle_count: 0,
}
}
@ -146,6 +150,9 @@ where
fn handle_interrupts(&mut self) -> u8 {
if self.memory.ime {
if let Some(interrupt) = self.memory.interrupts.get_next_interrupt() {
// if interrupt != Interrupt::Vblank {
// println!("interrupt: {interrupt:?}");
// }
let interrupt_addr = match interrupt {
Interrupt::Vblank => 0x40,
Interrupt::LcdStat => 0x48,
@ -195,6 +202,7 @@ where
last_instruction_addr: state.last_instruction_addr,
halted: state.halted,
should_halt_bug: state.should_halt_bug,
cycle_count: state.cycle_count,
}
}
}
@ -245,7 +253,7 @@ impl Registers {
}
impl Registers {
fn get_8(&self, register: Reg8) -> u8 {
pub(crate) fn get_8(&self, register: Reg8) -> u8 {
match register {
Reg8::A => self.af.get_high(),
Reg8::B => self.bc.get_high(),