Add elf loading support

Former-commit-id: 62edab8ec074c099a23fe9223b35d6043d42fbf6
This commit is contained in:
Michel Heily 2020-04-29 23:18:12 +03:00
parent d8d9fc0651
commit 31161eb5e5
4 changed files with 161 additions and 37 deletions

View file

@ -31,6 +31,8 @@ nom = {version = "5.0.0", optional = true}
gdbstub = { version = "0.1.2", optional = true, features = ["std"] } gdbstub = { version = "0.1.2", optional = true, features = ["std"] }
ringbuf = "0.2.1" ringbuf = "0.2.1"
goblin = { version = "0.2", optional = true }
[target.'cfg(target_arch="wasm32")'.dependencies] [target.'cfg(target_arch="wasm32")'.dependencies]
instant = { version = "0.1.2", features = [ "wasm-bindgen" ] } instant = { version = "0.1.2", features = [ "wasm-bindgen" ] }
@ -44,6 +46,7 @@ criterion = "0.3"
default = ["arm7tdmi_dispatch_table"] default = ["arm7tdmi_dispatch_table"]
debugger = ["nom", "rustyline"] debugger = ["nom", "rustyline"]
gdb = ["gdbstub"] gdb = ["gdbstub"]
elf_support = ["goblin"]
# Uses lookup tables when executing instructions instead of `match` statements. # Uses lookup tables when executing instructions instead of `match` statements.
# Faster, but consumes more memory. # Faster, but consumes more memory.
arm7tdmi_dispatch_table = [] arm7tdmi_dispatch_table = []

View file

@ -1,10 +1,7 @@
use std::fs::File;
use std::io::prelude::*;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use memmem::{Searcher, TwoWaySearcher}; use memmem::{Searcher, TwoWaySearcher};
use num::FromPrimitive; use num::FromPrimitive;
use zip::ZipArchive;
use super::super::{GBAError, GBAResult}; use super::super::{GBAError, GBAResult};
use super::backup::eeprom::*; use super::backup::eeprom::*;
@ -14,7 +11,7 @@ use super::header;
use super::BackupMedia; use super::BackupMedia;
use super::Cartridge; use super::Cartridge;
use crate::util::read_bin_file; use super::loader::{load_from_bytes, load_from_file, LoadRom};
#[derive(Debug)] #[derive(Debug)]
pub struct GamepakBuilder { pub struct GamepakBuilder {
@ -87,11 +84,16 @@ impl GamepakBuilder {
} }
pub fn build(mut self) -> GBAResult<Cartridge> { pub fn build(mut self) -> GBAResult<Cartridge> {
let bytes = if let Some(bytes) = self.bytes { let (bytes, symbols) = if let Some(bytes) = self.bytes {
Ok(bytes) match load_from_bytes(bytes.to_vec())? {
LoadRom::Elf { data, symbols } => Ok((data, Some(symbols))),
LoadRom::Raw(data) => Ok((data, None)),
}
} else if let Some(path) = &self.path { } else if let Some(path) = &self.path {
let loaded_rom = load_rom(&path)?; match load_from_file(&path)? {
Ok(loaded_rom.into()) LoadRom::Elf { data, symbols } => Ok((data, Some(symbols))),
LoadRom::Raw(data) => Ok((data, None)),
}
} else { } else {
Err(GBAError::CartridgeLoadError( Err(GBAError::CartridgeLoadError(
"either provide file() or buffer()".to_string(), "either provide file() or buffer()".to_string(),
@ -125,9 +127,10 @@ impl GamepakBuilder {
let size = bytes.len(); let size = bytes.len();
Ok(Cartridge { Ok(Cartridge {
header: header, header: header,
bytes: bytes, bytes: bytes.into_boxed_slice(),
size: size, size: size,
backup: backup, backup: backup,
symbols: symbols,
}) })
} }
} }
@ -163,31 +166,3 @@ fn detect_backup_type(bytes: &[u8]) -> Option<BackupType> {
} }
None None
} }
fn load_rom(path: &Path) -> GBAResult<Vec<u8>> {
match path.extension() {
Some(extension) => match extension.to_str() {
Some("zip") => {
let zipfile = File::open(path)?;
let mut archive = ZipArchive::new(zipfile)?;
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
if file.name().ends_with(".gba") {
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
return Ok(buf);
}
}
panic!("no .gba file contained in the zip file");
}
_ => {
let buf = read_bin_file(path)?;
return Ok(buf);
}
},
_ => {
let buf = read_bin_file(path)?;
return Ok(buf);
}
}
}

View file

@ -0,0 +1,134 @@
use super::super::{GBAError, GBAResult};
use std::collections::HashMap;
use std::io::prelude::*;
use std::io::Cursor;
use std::path::Path;
use crate::util::read_bin_file;
use zip::ZipArchive;
#[cfg(feature = "elf_support")]
use goblin;
pub enum LoadRom {
Elf {
data: Vec<u8>,
symbols: HashMap<String, u32>,
},
Raw(Vec<u8>),
}
type LoadRomResult = GBAResult<LoadRom>;
#[cfg(feature = "elf_support")]
impl From<goblin::error::Error> for GBAError {
fn from(err: goblin::error::Error) -> GBAError {
GBAError::CartridgeLoadError(format!("elf parsing error: {}", err))
}
}
fn try_load_zip(data: &[u8]) -> LoadRomResult {
let reader = Cursor::new(data);
let mut archive = ZipArchive::new(reader)?;
for i in 0..archive.len() {
let mut file = archive.by_index(i)?;
if file.name().ends_with(".gba") {
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
return Ok(LoadRom::Raw(buf));
}
}
Err(GBAError::CartridgeLoadError(
"no .gba files found within the zip archive".to_owned(),
))
}
#[cfg(feature = "elf_support")]
fn try_load_elf(elf_bytes: &[u8]) -> LoadRomResult {
const CART_BASE: usize = 0x0800_0000;
let elf = goblin::elf::Elf::parse(&elf_bytes)?;
let entry = elf.entry;
if entry != (CART_BASE as u64) {
return Err(GBAError::CartridgeLoadError(
"bad elf entry point, maybe multiboot rom ?".to_owned(),
));
}
let mut rom = vec![0; 0x200_0000];
for phdr in &elf.program_headers {
if phdr.p_type == goblin::elf::program_header::PT_LOAD {
let file_range = phdr.file_range();
let phys_range =
(phdr.p_paddr as usize)..(phdr.p_paddr as usize + phdr.p_memsz as usize);
let phys_range_adjusted = (phdr.p_paddr as usize - CART_BASE)
..(phdr.p_paddr as usize + phdr.p_memsz as usize - CART_BASE);
if phys_range_adjusted.start + (phdr.p_filesz as usize) >= rom.len() {
warn!("ELF: skipping program header {:?}", phdr);
continue;
}
info!(
"ELF: loading segment phdr: {:?} range {:#x?} vec range {:#x?}",
phdr, file_range, phys_range,
);
let src = &elf_bytes[file_range];
let dst = &mut rom[phys_range_adjusted];
dst.copy_from_slice(src);
}
}
let mut symbols = HashMap::new();
let strtab = elf.strtab;
for sym in elf.syms.iter() {
if let Some(Ok(name)) = strtab.get(sym.st_name) {
// TODO do I also want to save the symbol size ?
symbols.insert(name.to_owned(), sym.st_value as u32);
} else {
warn!("failed to parse symbol name sym {:?}", sym);
}
}
Ok(LoadRom::Elf {
data: rom,
symbols: symbols,
})
}
pub(super) fn load_from_file(path: &Path) -> LoadRomResult {
let bytes = read_bin_file(path)?;
match path.extension() {
Some(extension) => match extension.to_str() {
Some("zip") => try_load_zip(&bytes),
#[cfg(feature = "elf_support")]
Some("elf") => try_load_elf(&bytes),
_ => {
warn!("unknown file extension, loading as raw binary file");
Ok(LoadRom::Raw(bytes))
}
},
None => Ok(LoadRom::Raw(bytes)),
}
}
pub(super) fn load_from_bytes(bytes: Vec<u8>) -> LoadRomResult {
// first try as zip
if let Ok(result) = try_load_zip(&bytes) {
return Ok(result);
}
// else, try as elf
#[cfg(feature = "elf_support")]
{
if let Ok(result) = try_load_elf(&bytes) {
return Ok(result);
}
}
// if everything else failed, load the rom as raw binary
Ok(LoadRom::Raw(bytes))
}

View file

@ -1,3 +1,5 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::{Addr, Bus}; use super::{Addr, Bus};
@ -12,6 +14,7 @@ pub use backup::BackupType;
use backup::{BackupFile, BackupMemoryInterface}; use backup::{BackupFile, BackupMemoryInterface};
mod builder; mod builder;
mod loader;
pub use builder::GamepakBuilder; pub use builder::GamepakBuilder;
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
@ -22,14 +25,23 @@ pub enum BackupMedia {
Undetected, Undetected,
} }
pub type SymbolTable = HashMap<String, u32>;
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Cartridge { pub struct Cartridge {
pub header: CartridgeHeader, pub header: CartridgeHeader,
bytes: Box<[u8]>, bytes: Box<[u8]>,
size: usize, size: usize,
symbols: Option<SymbolTable>, // TODO move it somewhere else
pub(in crate) backup: BackupMedia, pub(in crate) backup: BackupMedia,
} }
impl Cartridge {
pub fn get_symbols(&self) -> &Option<SymbolTable> {
&self.symbols
}
}
use super::sysbus::consts::*; use super::sysbus::consts::*;
pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00; pub const EEPROM_BASE_ADDR: u32 = 0x0DFF_FF00;