From c8c1cdd57b9635276618efefbe1794271dda4c4f Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Mon, 19 Sep 2022 00:10:55 +0300 Subject: [PATCH] Gdb fixes Former-commit-id: 2940580fc6b3760d77b5598b0faf72a773183304 Former-commit-id: cf3f361178c75d4e39832774bdd052ef8aab6be8 --- arm7tdmi/src/cpu.rs | 40 +++- arm7tdmi/src/gdb/target.rs | 4 +- core/src/gba.rs | 19 +- core/src/gdb_support.rs | 204 ++++++++++++-------- core/src/gdb_support/event_loop.rs | 4 +- core/src/gdb_support/gdb_thread.rs | 18 +- core/src/gdb_support/target.rs | 18 +- platform/rustboyadvance-sdl2/src/main.rs | 21 +- platform/rustboyadvance-sdl2/src/options.rs | 7 +- 9 files changed, 205 insertions(+), 130 deletions(-) diff --git a/arm7tdmi/src/cpu.rs b/arm7tdmi/src/cpu.rs index a395099..913424b 100644 --- a/arm7tdmi/src/cpu.rs +++ b/arm7tdmi/src/cpu.rs @@ -1,11 +1,18 @@ +use std::fmt; + use log::debug; use serde::{Deserialize, Serialize}; +use bit::BitIndex; +use num::FromPrimitive; +use ansi_term::{Colour, Style}; + +use rustboyadvance_utils::{Shared, WeakPointer}; pub use super::exception::Exception; +use super::reg_string; use super::{arm::ArmCond, psr::RegPSR, Addr, CpuMode, CpuState}; -use rustboyadvance_utils::{Shared, WeakPointer}; use super::memory::{MemoryAccess, MemoryInterface}; use MemoryAccess::*; @@ -37,18 +44,12 @@ cfg_if! { use super::DecodedInstruction; use super::arm::ArmInstruction; use super::thumb::ThumbInstruction; - use super::reg_string; - use std::fmt; - use ansi_term::{Colour, Style}; } else { } } -use bit::BitIndex; -use num::FromPrimitive; - pub enum CpuAction { AdvancePC(MemoryAccess), PipelineFlushed, @@ -104,7 +105,7 @@ impl Default for DebuggerState { } } -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct Arm7tdmiCore { pub pc: u32, pub bus: Shared, @@ -506,6 +507,29 @@ impl Arm7tdmiCore { } } +impl fmt::Debug for Arm7tdmiCore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "ARM7TDMI Core Status:")?; + writeln!(f, "\tCPSR: {}", self.cpsr)?; + writeln!(f, "\tGeneral Purpose Registers:")?; + let reg_normal_style = Style::new().bold(); + let gpr = self.copy_registers(); + for i in 0..15 { + let mut reg_name = reg_string(i).to_string(); + reg_name.make_ascii_uppercase(); + let entry = format!("\t{:-3} = 0x{:08x}", reg_name, gpr[i]); + write!( + f, + "{}{}", + reg_normal_style.paint(entry), + if (i + 1) % 4 == 0 { "\n" } else { "" } + )?; + } + let pc = format!("\tPC = 0x{:08x}", self.get_next_pc()); + writeln!(f, "{}", reg_normal_style.paint(pc)) + } +} + #[cfg(feature = "debugger")] impl fmt::Display for Core { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/arm7tdmi/src/gdb/target.rs b/arm7tdmi/src/gdb/target.rs index d8305a4..b36e294 100644 --- a/arm7tdmi/src/gdb/target.rs +++ b/arm7tdmi/src/gdb/target.rs @@ -41,7 +41,7 @@ impl SingleThreadBase for Arm7tdmiCore { &mut self, regs: &mut gdbstub_arch::arm::reg::ArmCoreRegs, ) -> TargetResult<(), Self> { - regs.pc = self.get_next_pc(); + regs.pc = self.pc; regs.lr = self.get_reg(REG_LR); regs.sp = self.get_reg(REG_SP); regs.r[..].copy_from_slice(&self.gpr[..13]); @@ -56,7 +56,7 @@ impl SingleThreadBase for Arm7tdmiCore { self.set_reg(REG_PC, regs.pc); self.set_reg(REG_LR, regs.lr); self.set_reg(REG_SP, regs.sp); - self.gpr.copy_from_slice(®s.r); + self.gpr[..13].copy_from_slice(®s.r); self.cpsr.set(regs.cpsr); Ok(()) } diff --git a/core/src/gba.rs b/core/src/gba.rs index aee2379..af6569c 100644 --- a/core/src/gba.rs +++ b/core/src/gba.rs @@ -6,7 +6,7 @@ use arm7tdmi::gdbstub::stub::SingleThreadStopReason; use bincode; use serde::{Deserialize, Serialize}; -use crate::gdb_support::{gdb_thread::start_gdb_server_thread, DebuggerState}; +use crate::gdb_support::{gdb_thread::start_gdb_server_thread, DebuggerRequestHandler}; use super::cartridge::Cartridge; use super::dma::DmaController; @@ -24,13 +24,13 @@ use arm7tdmi::{self, Arm7tdmiCore}; use rustboyadvance_utils::Shared; pub struct GameBoyAdvance { - pub(crate) cpu: Box>, + pub cpu: Box>, pub(crate) sysbus: Shared, pub(crate) io_devs: Shared, pub(crate) scheduler: SharedScheduler, interrupt_flags: SharedInterruptFlags, audio_interface: DynAudioInterface, - pub(crate) debugger: Option, + pub(crate) debugger: Option, } #[derive(Serialize, Deserialize)] @@ -251,17 +251,12 @@ impl GameBoyAdvance { /// Recv & handle messages from the debugger, and return if we are stopped or not pub fn debugger_run(&mut self) { - let mut should_stop = false; + let mut should_interrupt_frame = false; let debugger = self.debugger.take().expect("debugger should be None here"); - let debugger = debugger - .handle_message(self, &mut should_stop) - .map_err(|_| "Failed to handle message") - .unwrap(); - - self.debugger = debugger; + self.debugger = debugger.handle_incoming_requests(self, &mut should_interrupt_frame); if let Some(debugger) = &mut self.debugger { - if should_stop { + if should_interrupt_frame { debugger.notify_stop_reason(SingleThreadStopReason::DoneStep); } else { self.frame_interruptible(); @@ -277,7 +272,7 @@ impl GameBoyAdvance { } #[inline] - pub fn cpu_step(&mut self) { + pub(crate) fn cpu_step(&mut self) { if self.io_devs.intc.irq_pending() { self.cpu.irq(); self.io_devs.haltcnt = HaltState::Running; diff --git a/core/src/gdb_support.rs b/core/src/gdb_support.rs index c94095f..a39d986 100644 --- a/core/src/gdb_support.rs +++ b/core/src/gdb_support.rs @@ -1,9 +1,9 @@ -use std::result; use std::sync::{Arc, Condvar, Mutex}; use std::thread::JoinHandle; type SendSync = Arc>; +use arm7tdmi::gdbstub::common::Signal; use arm7tdmi::gdbstub::stub::{DisconnectReason, SingleThreadStopReason}; use arm7tdmi::gdbstub::target::TargetError; use arm7tdmi::gdbstub::target::{ext::base::singlethread::SingleThreadBase, Target}; @@ -20,7 +20,7 @@ mod target; use crate::GameBoyAdvance; #[derive(Debug)] -pub(crate) enum DebuggerMessage { +pub(crate) enum DebuggerRequest { ReadRegs(SendSync), WriteRegs(ArmCoreRegs), ReadAddrs(Addr, SendSync>), @@ -28,15 +28,15 @@ pub(crate) enum DebuggerMessage { WriteAddrs(Addr, Box<[u8]>), AddSwBreakpoint(Addr), DelSwBreakpoint(Addr), - Stop, + Interrupt, Resume, SingleStep, Disconnected(DisconnectReason), } pub struct DebuggerTarget { - tx: Sender, - operation_signal: Arc<(Mutex, Condvar)>, + tx: Sender, + request_complete_signal: Arc<(Mutex, Condvar)>, stop_signal: Arc<(Mutex>, Condvar)>, memory_map: String, } @@ -44,7 +44,7 @@ pub struct DebuggerTarget { impl DebuggerTarget { #[inline] pub fn wait_for_operation(&mut self) { - let (lock, cvar) = &*self.operation_signal; + let (lock, cvar) = &*self.request_complete_signal; let mut finished = lock.lock().unwrap(); while !*finished { finished = cvar.wait(finished).unwrap(); @@ -53,105 +53,139 @@ impl DebuggerTarget { } } -pub(crate) struct DebuggerState { - rx: Receiver, - operation_signal: Arc<(Mutex, Condvar)>, +pub(crate) struct DebuggerRequestHandler { + rx: Receiver, + request_complete_signal: Arc<(Mutex, Condvar)>, stop_signal: Arc<(Mutex>, Condvar)>, thread: JoinHandle<()>, stopped: bool, } -impl DebuggerState { - pub fn handle_message( +enum DebuggerStatus { + RequestComplete, + ResumeRequested, + StopRequested, + Disconnected(DisconnectReason), +} + +impl DebuggerRequestHandler { + fn handle_request( + &mut self, + gba: &mut GameBoyAdvance, + req: &mut DebuggerRequest, + ) -> Result::Error>> { + use DebuggerRequest::*; + match req { + ReadRegs(regs) => { + let mut regs = regs.lock().unwrap(); + gba.cpu.read_registers(&mut regs)?; + debug!("Debugger requested to read regs: {:?}", regs); + Ok(DebuggerStatus::RequestComplete) + } + WriteRegs(regs) => { + debug!("Debugger requested to write regs: {:?}", regs); + gba.cpu.write_registers(regs)?; + Ok(DebuggerStatus::RequestComplete) + } + ReadAddrs(addr, data) => { + let mut data = data.lock().unwrap(); + debug!( + "Debugger requested to read {} bytes from 0x{:08x}", + data.len(), + addr + ); + gba.cpu.read_addrs(*addr, &mut data)?; + Ok(DebuggerStatus::RequestComplete) + } + WriteAddrs(addr, data) => { + debug!( + "Debugger requested to write {} bytes at 0x{:08x}", + data.len(), + addr + ); + gba.cpu.write_addrs(*addr, &data)?; + Ok(DebuggerStatus::RequestComplete) + } + Interrupt => { + debug!("Debugger requested stopped"); + self.notify_stop_reason(SingleThreadStopReason::Signal(Signal::SIGINT)); + Ok(DebuggerStatus::StopRequested) + } + Resume => { + debug!("Debugger requested resume"); + self.stopped = false; + Ok(DebuggerStatus::ResumeRequested) + } + SingleStep => { + debug!("Debugger requested single step"); + gba.cpu_step(); + let stop_reason = SingleThreadStopReason::DoneStep; + self.notify_stop_reason(stop_reason); + Ok(DebuggerStatus::StopRequested) + } + AddSwBreakpoint(addr) => { + gba.cpu.add_breakpoint(*addr); + Ok(DebuggerStatus::RequestComplete) + } + DelSwBreakpoint(addr) => { + gba.cpu.del_breakpoint(*addr); + Ok(DebuggerStatus::RequestComplete) + } + Disconnected(reason) => Ok(DebuggerStatus::Disconnected(*reason)), + } + } + + fn terminate(mut self, should_interrupt_frame: &mut bool) -> Option { + self.notify_stop_reason(SingleThreadStopReason::Exited(1)); + self.thread.join().unwrap(); + self.stopped = true; + *should_interrupt_frame = true; + None + } + + pub fn handle_incoming_requests( mut self, gba: &mut GameBoyAdvance, - should_stop: &mut bool, - ) -> Result, TargetError<::Error>> { + should_interrupt_frame: &mut bool, + ) -> Option { if self.thread.is_finished() { warn!("gdb server thread unexpectdly died"); - *should_stop = true; - self.thread.join().unwrap(); - return Ok(None); + return self.terminate(should_interrupt_frame); } - if let Ok(msg) = self.rx.try_recv() { - use DebuggerMessage::*; - let mut result = match msg { - ReadRegs(regs) => { - let mut regs = regs.lock().unwrap(); - gba.cpu.read_registers(&mut regs)?; - debug!("Debugger requested to read regs: {:?}", regs); - Ok(Some(self)) + while let Ok(mut req) = self.rx.try_recv() { + match self.handle_request(gba, &mut req) { + Ok(DebuggerStatus::RequestComplete) => { + self.notify_request_complete(); } - WriteRegs(regs) => { - debug!("Debugger requested to write regs: {:?}", regs); - gba.cpu.write_registers(®s)?; - Ok(Some(self)) - } - ReadAddrs(addr, data) => { - let mut data = data.lock().unwrap(); - debug!( - "Debugger requested to read {} bytes from 0x{:08x}", - data.len(), - addr - ); - gba.cpu.read_addrs(addr, &mut data)?; - Ok(Some(self)) - } - WriteAddrs(addr, data) => { - debug!( - "Debugger requested to write {} bytes at 0x{:08x}", - data.len(), - addr - ); - gba.cpu.write_addrs(addr, &data)?; - Ok(Some(self)) - } - Stop => { - debug!("Debugger requested stopped"); + Ok(DebuggerStatus::StopRequested) => { self.stopped = true; - Ok(Some(self)) + self.notify_request_complete(); } - Resume => { - debug!("Debugger requested resume"); + Ok(DebuggerStatus::ResumeRequested) => { self.stopped = false; - Ok(Some(self)) + self.notify_request_complete(); } - SingleStep => { - debug!("Debugger requested single step"); - gba.run::(1); - self.notify_stop_reason(SingleThreadStopReason::DoneStep); - self.stopped = true; - Ok(Some(self)) - } - AddSwBreakpoint(addr) => { - gba.cpu.add_breakpoint(addr); - Ok(Some(self)) - } - DelSwBreakpoint(addr) => { - gba.cpu.del_breakpoint(addr); - Ok(Some(self)) - } - Disconnected(reason) => { + Ok(DebuggerStatus::Disconnected(reason)) => { debug!("Debugger disconnected due to {:?}", reason); debug!("closing gdbserver thread"); - self.thread.join().unwrap(); - Ok(None) + return self.terminate(should_interrupt_frame); + } + + Err(_) => { + error!("An error occured while handling debug request {:?}", req); + return self.terminate(should_interrupt_frame); } - }; - if let Ok(Some(result)) = &mut result { - let (lock, cvar) = &*result.operation_signal; - let mut finished = lock.lock().unwrap(); - *finished = true; - cvar.notify_one(); - *should_stop = result.stopped; - } else { - *should_stop = true; } - result - } else { - *should_stop = self.stopped; - Ok(Some(self)) } + *should_interrupt_frame = self.stopped; + Some(self) + } + + fn notify_request_complete(&mut self) { + let (lock, cvar) = &*self.request_complete_signal; + let mut finished = lock.lock().unwrap(); + *finished = true; + cvar.notify_one(); } pub fn notify_stop_reason(&mut self, reason: SingleThreadStopReason) { diff --git a/core/src/gdb_support/event_loop.rs b/core/src/gdb_support/event_loop.rs index e7593ad..c9bb2f1 100644 --- a/core/src/gdb_support/event_loop.rs +++ b/core/src/gdb_support/event_loop.rs @@ -6,7 +6,7 @@ use arm7tdmi::gdb::gdbstub::{ target::Target, }; -use super::{DebuggerMessage, DebuggerTarget}; +use super::{DebuggerRequest, DebuggerTarget}; pub struct DebuggerEventLoop {} @@ -54,7 +54,7 @@ impl run_blocking::BlockingEventLoop for DebuggerEventLoop { target: &mut DebuggerTarget, ) -> Result>, ::Error> { info!("on_interrupt: sending stop message"); - target.tx.send(DebuggerMessage::Stop).unwrap(); + target.tx.send(DebuggerRequest::Interrupt).unwrap(); target.wait_for_operation(); info!("Waiting for target to stop "); let (lock, cvar) = &*target.stop_signal; diff --git a/core/src/gdb_support/gdb_thread.rs b/core/src/gdb_support/gdb_thread.rs index 389fd77..59e9359 100644 --- a/core/src/gdb_support/gdb_thread.rs +++ b/core/src/gdb_support/gdb_thread.rs @@ -11,21 +11,23 @@ use arm7tdmi::{ use crate::{GBAError, GameBoyAdvance}; -use super::{event_loop::DebuggerEventLoop, DebuggerMessage, DebuggerState, DebuggerTarget}; +use super::{ + event_loop::DebuggerEventLoop, DebuggerRequest, DebuggerRequestHandler, DebuggerTarget, +}; /// Starts a gdbserver thread pub(crate) fn start_gdb_server_thread( gba: &mut GameBoyAdvance, port: u16, -) -> Result { +) -> Result { let (tx, rx) = crossbeam::channel::unbounded(); - let operation_signal = Arc::new((Mutex::new(false), Condvar::new())); + let request_complete_signal = Arc::new((Mutex::new(false), Condvar::new())); let stop_signal = Arc::new(( Mutex::new(SingleThreadStopReason::Signal(Signal::SIGINT)), Condvar::new(), )); let stop_signal_2 = stop_signal.clone(); - let operation_signal_2 = operation_signal.clone(); + let request_complete_signal_2 = request_complete_signal.clone(); let memory_map = gba.sysbus.generate_memory_map_xml().unwrap(); let conn = wait_for_connection(port)?; @@ -35,7 +37,7 @@ pub(crate) fn start_gdb_server_thread( let mut target = DebuggerTarget { tx, - operation_signal: operation_signal_2, + request_complete_signal: request_complete_signal_2, stop_signal: stop_signal_2, memory_map, }; @@ -46,13 +48,13 @@ pub(crate) fn start_gdb_server_thread( .unwrap(); target .tx - .send(DebuggerMessage::Disconnected(disconnect_reason)) + .send(DebuggerRequest::Disconnected(disconnect_reason)) .unwrap(); }); - let mut debugger = DebuggerState { + let mut debugger = DebuggerRequestHandler { rx, - operation_signal, + request_complete_signal, stop_signal, thread, stopped: true, diff --git a/core/src/gdb_support/target.rs b/core/src/gdb_support/target.rs index be8acb0..3bdc5fd 100644 --- a/core/src/gdb_support/target.rs +++ b/core/src/gdb_support/target.rs @@ -12,7 +12,7 @@ use gdbstub::target::ext::breakpoints::BreakpointsOps; use gdbstub::target::{self, Target, TargetError, TargetResult}; use gdbstub_arch::arm::reg::ArmCoreRegs; -use super::{DebuggerMessage, DebuggerTarget}; +use super::{DebuggerRequest, DebuggerTarget}; impl Target for DebuggerTarget { type Error = (); @@ -38,7 +38,7 @@ impl SingleThreadBase for DebuggerTarget { fn read_registers(&mut self, regs: &mut ArmCoreRegs) -> TargetResult<(), Self> { let regs_copy = Arc::new(Mutex::new(ArmCoreRegs::default())); self.tx - .send(DebuggerMessage::ReadRegs(regs_copy.clone())) + .send(DebuggerRequest::ReadRegs(regs_copy.clone())) .unwrap(); self.wait_for_operation(); regs_copy.lock().unwrap().clone_into(regs); @@ -47,7 +47,7 @@ impl SingleThreadBase for DebuggerTarget { fn write_registers(&mut self, regs: &ArmCoreRegs) -> TargetResult<(), Self> { self.tx - .send(DebuggerMessage::WriteRegs(regs.clone())) + .send(DebuggerRequest::WriteRegs(regs.clone())) .unwrap(); self.wait_for_operation(); Ok(()) @@ -56,7 +56,7 @@ impl SingleThreadBase for DebuggerTarget { fn read_addrs(&mut self, start_addr: u32, data: &mut [u8]) -> TargetResult<(), Self> { let buffer = Arc::new(Mutex::new(vec![0; data.len()].into_boxed_slice())); self.tx - .send(DebuggerMessage::ReadAddrs(start_addr, buffer.clone())) + .send(DebuggerRequest::ReadAddrs(start_addr, buffer.clone())) .unwrap(); self.wait_for_operation(); data.copy_from_slice(&buffer.lock().unwrap()); @@ -78,7 +78,7 @@ impl SingleThreadBase for DebuggerTarget { impl SingleThreadResume for DebuggerTarget { fn resume(&mut self, _signal: Option) -> Result<(), Self::Error> { - self.tx.send(DebuggerMessage::Resume).unwrap(); + self.tx.send(DebuggerRequest::Resume).unwrap(); self.wait_for_operation(); Ok(()) } @@ -94,9 +94,7 @@ impl SingleThreadResume for DebuggerTarget { impl SingleThreadSingleStep for DebuggerTarget { fn step(&mut self, _signal: Option) -> Result<(), Self::Error> { - self.tx.send(DebuggerMessage::SingleStep).unwrap(); - self.wait_for_operation(); - self.tx.send(DebuggerMessage::Stop).unwrap(); + self.tx.send(DebuggerRequest::SingleStep).unwrap(); self.wait_for_operation(); Ok(()) } @@ -135,7 +133,7 @@ impl target::ext::breakpoints::SwBreakpoint for DebuggerTarget { _kind: gdbstub_arch::arm::ArmBreakpointKind, ) -> TargetResult { self.tx - .send(DebuggerMessage::AddSwBreakpoint(addr)) + .send(DebuggerRequest::AddSwBreakpoint(addr)) .unwrap(); self.wait_for_operation(); Ok(true) @@ -147,7 +145,7 @@ impl target::ext::breakpoints::SwBreakpoint for DebuggerTarget { _kind: gdbstub_arch::arm::ArmBreakpointKind, ) -> TargetResult { self.tx - .send(DebuggerMessage::DelSwBreakpoint(addr)) + .send(DebuggerRequest::DelSwBreakpoint(addr)) .unwrap(); self.wait_for_operation(); Ok(true) diff --git a/platform/rustboyadvance-sdl2/src/main.rs b/platform/rustboyadvance-sdl2/src/main.rs index 3c7af93..13311cd 100644 --- a/platform/rustboyadvance-sdl2/src/main.rs +++ b/platform/rustboyadvance-sdl2/src/main.rs @@ -95,12 +95,26 @@ fn main() -> Result<(), Box> { audio_interface, )); + // let gba_raw_ptr = Box::into_raw(gba) as usize; + // static mut gba_raw: usize = 0; + // unsafe { gba_raw = gba_raw_ptr }; + // let mut gba = unsafe {Box::from_raw(gba_raw_ptr as *mut GameBoyAdvance) }; + + // std::panic::set_hook(Box::new(|panic_info| { + // let gba = unsafe {Box::from_raw(gba_raw as *mut GameBoyAdvance) }; + // println!("System crashed Oh No!!! {:?}", gba.cpu); + // let normal_panic = std::panic::take_hook(); + // normal_panic(panic_info); + // })); + + if opts.skip_bios { + println!("Skipping bios animation.."); gba.skip_bios(); } if opts.gdbserver { - todo!("gdb") + gba.start_gdbserver(DEFAULT_GDB_SERVER_PORT); } let mut vsync = true; @@ -146,7 +160,10 @@ fn main() -> Result<(), Box> { if opts.savestate_path().is_file() { let save = read_bin_file(&opts.savestate_path())?; info!("Restoring state from {:?}...", opts.savestate_path()); - gba.restore_state(&save)?; + let (audio_interface, _sdl_audio_device_new) = audio::create_audio_player(&sdl_context)?; + _sdl_audio_device = _sdl_audio_device_new; + let rom = opts.read_rom()?.into_boxed_slice(); + gba = Box::new(GameBoyAdvance::from_saved_state(&save, bios_bin.clone(), rom, audio_interface)?); info!("Restored!"); } else { info!("Savestate not created, please create one by pressing F5"); diff --git a/platform/rustboyadvance-sdl2/src/options.rs b/platform/rustboyadvance-sdl2/src/options.rs index 19057a2..9873e08 100644 --- a/platform/rustboyadvance-sdl2/src/options.rs +++ b/platform/rustboyadvance-sdl2/src/options.rs @@ -1,9 +1,10 @@ -use std::path::PathBuf; +use std::{path::PathBuf, io}; use rustboyadvance_core::{ cartridge::{BackupType, GamepakBuilder}, prelude::Cartridge, }; +use rustboyadvance_utils::read_bin_file; use structopt::StructOpt; const SAVE_TYPE_POSSIBLE_VALUES: &[&str] = @@ -61,4 +62,8 @@ impl Options { pub fn rom_name(&self) -> &str { self.rom.file_name().unwrap().to_str().unwrap() } + + pub fn read_rom(&self) -> Result, std::io::Error> { + read_bin_file(&self.rom) + } }