From 04c92c1454b38f2a62747e66b1e8ad2f80deb47d Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Sat, 30 Aug 2025 13:02:38 -0400 Subject: [PATCH] Parse ELF exclusively on emulation thread --- Cargo.lock | 42 ++++++++++++++------ Cargo.toml | 2 +- src/emulator.rs | 37 +++++++++-------- src/emulator/cart.rs | 43 +++++++++++++++----- src/emulator/game_info.rs | 83 +++++++++++++++++++++++++++++++++++++++ src/profiler.rs | 10 ++--- src/profiler/recording.rs | 6 +-- src/profiler/state.rs | 29 +++++--------- src/profiler/symbols.rs | 67 ------------------------------- 9 files changed, 182 insertions(+), 137 deletions(-) create mode 100644 src/emulator/game_info.rs delete mode 100644 src/profiler/symbols.rs diff --git a/Cargo.lock b/Cargo.lock index daed724..fb7bbe3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,7 +481,7 @@ dependencies = [ "cfg-if", "libc", "miniz_oxide", - "object", + "object 0.36.7", "rustc-demangle", "windows-targets 0.52.6", ] @@ -1201,12 +1201,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[package]] -name = "elf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55dd888a213fc57e957abf2aa305ee3e8a28dbe05687a251f33b637cd46b0070" - [[package]] name = "elsa" version = "1.11.2" @@ -2176,7 +2170,6 @@ dependencies = [ "egui-wgpu", "egui-winit", "egui_extras", - "elf", "fixed", "fxprof-processed-profile", "gilrs", @@ -2186,6 +2179,7 @@ dependencies = [ "normpath", "num-derive", "num-traits", + "object 0.37.3", "oneshot", "pollster", "rand 0.9.2", @@ -2983,7 +2977,18 @@ checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "flate2", "memchr", - "ruzstd", + "ruzstd 0.7.3", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "flate2", + "memchr", + "ruzstd 0.8.1", ] [[package]] @@ -3823,7 +3828,16 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fad02996bfc73da3e301efe90b1837be9ed8f4a462b6ed410aa35d00381de89f" dependencies = [ - "twox-hash", + "twox-hash 1.6.3", +] + +[[package]] +name = "ruzstd" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640bec8aad418d7d03c72ea2de10d5c646a598f9883c7babc160d91e3c1b26c" +dependencies = [ + "twox-hash 2.1.1", ] [[package]] @@ -3861,7 +3875,7 @@ dependencies = [ "memchr", "msvc-demangler", "nom", - "object", + "object 0.36.7", "pdb-addr2line", "rangemap", "rustc-demangle", @@ -4610,6 +4624,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "twox-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b907da542cbced5261bd3256de1b3a1bf340a3d37f93425a07362a1d687de56" + [[package]] name = "type-map" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 0fb4fa6..449e0ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,6 @@ egui_extras = { version = "0.32", features = ["image"] } egui-notify = "0.20" egui-winit = "0.32" egui-wgpu = { version = "0.32", features = ["winit"] } -elf = "0.8" fxprof-processed-profile = "0.8" fixed = { version = "1.28", features = ["num-traits"] } gilrs = { version = "0.11", features = ["serde-serialize"] } @@ -31,6 +30,7 @@ itertools = "0.14" normpath = "1" num-derive = "0.4" num-traits = "0.2" +object = "0.37" oneshot = "0.1" pollster = "0.4" rand = "0.9" diff --git a/src/emulator.rs b/src/emulator.rs index 0ba327f..2c31bde 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -22,11 +22,13 @@ use crate::{ graphics::TextureSink, memory::{MemoryRange, MemoryRegion}, }; +pub use game_info::GameInfo; use shrooms_vb_core::{EXPECTED_FRAME_SIZE, Sim, StopReason}; pub use shrooms_vb_core::{SimEvent, VBKey, VBRegister, VBWatchpointType}; mod address_set; mod cart; +mod game_info; mod shrooms_vb_core; #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] @@ -221,18 +223,16 @@ impl Emulator { self.sim_state[index].store(SimState::Ready, Ordering::Release); } let mut profiling = false; - if let Some(profiler) = self.profilers[sim_id.to_index()].as_ref() { - if let Some(cart) = self.carts[index].as_ref() { - if profiler - .send(ProfileEvent::Start { - file_path: cart.file_path.clone(), - }) - .is_ok() - { - sim.monitor_events(true); - profiling = true; - } - } + if let Some(profiler) = self.profilers[sim_id.to_index()].as_ref() + && let Some(cart) = self.carts[index].as_ref() + && profiler + .send(ProfileEvent::Start { + info: cart.info.clone(), + }) + .is_ok() + { + sim.monitor_events(true); + profiling = true; } if !profiling { sim.monitor_events(false); @@ -476,16 +476,15 @@ impl Emulator { if !running { continue; } - if let Some(p) = profiler { - if p.send(ProfileEvent::Update { + if let Some(p) = profiler + && p.send(ProfileEvent::Update { cycles, event: sim.take_sim_event(), }) .is_err() - { - sim.monitor_events(false); - *profiler = None; - } + { + sim.monitor_events(false); + *profiler = None; } } @@ -838,7 +837,7 @@ pub enum DebugEvent { pub enum ProfileEvent { Start { - file_path: PathBuf, + info: Arc, }, Update { cycles: u32, diff --git a/src/emulator/cart.rs b/src/emulator/cart.rs index 8aec193..efc7730 100644 --- a/src/emulator/cart.rs +++ b/src/emulator/cart.rs @@ -4,21 +4,24 @@ use std::{ fs::{self, File}, io::{Read, Seek as _, SeekFrom, Write as _}, path::{Path, PathBuf}, + sync::Arc, }; -use crate::emulator::SimId; +use crate::emulator::{SimId, game_info::GameInfo}; pub struct Cart { pub file_path: PathBuf, pub rom: Vec, sram_file: File, pub sram: Vec, + pub info: Arc, } impl Cart { pub fn load(file_path: &Path, sim_id: SimId) -> Result { let rom = fs::read(file_path)?; - let rom = try_parse_elf(&rom).unwrap_or(rom); + let (rom, info) = + try_parse_elf(file_path, &rom).unwrap_or_else(|| (rom, GameInfo::empty(file_path))); let mut sram_file = File::options() .read(true) @@ -46,6 +49,7 @@ impl Cart { rom, sram_file, sram, + info: Arc::new(info), }) } @@ -56,18 +60,39 @@ impl Cart { } } -fn try_parse_elf(data: &[u8]) -> Option> { - let parsed = elf::ElfBytes::::minimal_parse(data).ok()?; +fn try_parse_elf(file_path: &Path, data: &[u8]) -> Option<(Vec, GameInfo)> { + use object::read::elf::FileHeader; + let program = match object::FileKind::parse(data).ok()? { + object::FileKind::Elf32 => { + let header = object::elf::FileHeader32::parse(data).ok()?; + parse_elf_program(header, data)? + } + object::FileKind::Elf64 => { + let header = object::elf::FileHeader64::parse(data).ok()?; + parse_elf_program(header, data)? + } + _ => return None, + }; + let info = GameInfo::new(file_path, data).unwrap_or_else(|_| GameInfo::empty(file_path)); + Some((program, info)) +} + +fn parse_elf_program>( + header: &Elf, + data: &[u8], +) -> Option> { + use object::read::elf::ProgramHeader; + let endian = header.endian().ok()?; let mut bytes = vec![]; let mut pstart = None; - for phdr in parsed.segments()? { - if phdr.p_filesz == 0 { + for phdr in header.program_headers(endian, data).ok()? { + if phdr.p_filesz(endian).into() == 0 { continue; } - let start = pstart.unwrap_or(phdr.p_paddr); + let start = pstart.unwrap_or(phdr.p_paddr(endian).into()); pstart = Some(start); - bytes.resize((phdr.p_paddr - start) as usize, 0); - let data = parsed.segment_data(&phdr).ok()?; + bytes.resize((phdr.p_paddr(endian).into() - start) as usize, 0); + let data = phdr.data(endian, data).ok()?; bytes.extend_from_slice(data); } Some(bytes) diff --git a/src/emulator/game_info.rs b/src/emulator/game_info.rs new file mode 100644 index 0000000..0ff679f --- /dev/null +++ b/src/emulator/game_info.rs @@ -0,0 +1,83 @@ +use std::{path::Path, sync::Arc}; + +use anyhow::Result; +use fxprof_processed_profile::{LibraryInfo, Symbol, SymbolTable, debugid::DebugId}; +use object::{Object, ObjectSymbol}; +use wholesym::samply_symbols::{DebugIdExt, demangle_any}; + +#[derive(Debug)] +pub struct GameInfo { + library_info: LibraryInfo, +} + +impl GameInfo { + pub fn new(file_path: &Path, input: &[u8]) -> Result { + let file = object::File::parse(input)?; + + let (name, path) = name_and_path(file_path); + let debug_id = file + .build_id()? + .map(|id| DebugId::from_identifier(id, true)) + .unwrap_or_default(); + let code_id = file.build_id()?.map(hex::encode); + let mut symbols = vec![]; + for sym in file.symbols() { + symbols.push(Symbol { + address: sym.address() as u32, + size: Some(sym.size() as u32), + name: demangle_any(sym.name()?), + }); + } + + let library_info = LibraryInfo { + name: name.clone(), + debug_name: name, + path: path.clone(), + debug_path: path, + debug_id, + code_id, + arch: None, + symbol_table: Some(Arc::new(SymbolTable::new(symbols))), + }; + + Ok(Self { library_info }) + } + + pub fn empty(file_path: &Path) -> Self { + let (name, path) = name_and_path(file_path); + let library_info = LibraryInfo { + name: name.clone(), + debug_name: name, + path: path.clone(), + debug_path: path, + debug_id: DebugId::default(), + code_id: None, + arch: None, + symbol_table: None, + }; + Self { library_info } + } + + pub fn name(&self) -> &str { + &self.library_info.name + } + + pub fn library_info(&self) -> &LibraryInfo { + &self.library_info + } +} + +fn name_and_path(file_path: &Path) -> (String, String) { + let normalized = normpath::PathExt::normalize(file_path); + let path = normalized + .as_ref() + .map(|n| n.as_path()) + .unwrap_or(file_path); + + let name = match path.file_stem() { + Some(s) => s.to_string_lossy().into_owned(), + None => "game".to_string(), + }; + let path = path.to_string_lossy().into_owned(); + (name, path) +} diff --git a/src/profiler.rs b/src/profiler.rs index 9705e8e..fe16404 100644 --- a/src/profiler.rs +++ b/src/profiler.rs @@ -1,5 +1,4 @@ use std::{ - path::PathBuf, sync::{Arc, Mutex}, thread, }; @@ -7,13 +6,12 @@ use std::{ use anyhow::Result; use tokio::{select, sync::mpsc}; -use crate::emulator::{EmulatorClient, EmulatorCommand, ProfileEvent, SimEvent, SimId}; +use crate::emulator::{EmulatorClient, EmulatorCommand, GameInfo, ProfileEvent, SimEvent, SimId}; use recording::Recording; use state::ProgramState; mod recording; mod state; -mod symbols; pub struct Profiler { sim_id: SimId, @@ -132,7 +130,7 @@ async fn run_profile( async fn handle_event(event: ProfileEvent, session: &mut ProfilerSession) -> Result<()> { match event { - ProfileEvent::Start { file_path } => session.start_profiling(file_path).await, + ProfileEvent::Start { info } => session.start_profiling(info).await, ProfileEvent::Update { cycles, event } => { session.track_elapsed_cycles(cycles); if let Some(event) = event { @@ -199,8 +197,8 @@ impl ProfilerSession { } } - async fn start_profiling(&mut self, file_path: PathBuf) { - let program = ProgramState::new(file_path).await; + async fn start_profiling(&mut self, info: Arc) { + let program = ProgramState::new(info).await; let recording = if self.recording.is_some() { Some(Recording::new(&program)) } else { diff --git a/src/profiler/recording.rs b/src/profiler/recording.rs index 2513330..0723be9 100644 --- a/src/profiler/recording.rs +++ b/src/profiler/recording.rs @@ -24,10 +24,8 @@ impl Recording { let process = profile.add_process(state.name(), 1, Timestamp::from_nanos_since_reference(0)); - if let Some(symbol_file) = state.symbol_file() { - let lib = profile.add_lib(symbol_file.library_info().clone()); - profile.add_lib_mapping(process, lib, 0x00000000, 0xffffffff, 0); - } + let lib = profile.add_lib(state.library_info().clone()); + profile.add_lib_mapping(process, lib, 0x00000000, 0xffffffff, 0); let mut me = Self { profile, diff --git a/src/profiler/state.rs b/src/profiler/state.rs index 5eb39e2..ed7e9e8 100644 --- a/src/profiler/state.rs +++ b/src/profiler/state.rs @@ -1,12 +1,12 @@ -use std::{collections::HashMap, path::PathBuf}; +use std::{collections::HashMap, sync::Arc}; use anyhow::{Result, bail}; +use fxprof_processed_profile::LibraryInfo; -use crate::profiler::symbols::SymbolFile; +use crate::emulator::GameInfo; pub struct ProgramState { - name: String, - symbol_file: Option, + info: Arc, call_stacks: HashMap>, context_stack: Vec, } @@ -17,17 +17,7 @@ pub struct StackFrame { pub const RESET_CODE: u16 = 0xfff0; impl ProgramState { - pub async fn new(file_path: PathBuf) -> Self { - let symbol_file = SymbolFile::load(&file_path).await.ok(); - let name = symbol_file - .as_ref() - .map(|f| f.name().to_string()) - .or_else(|| { - file_path - .file_stem() - .map(|s| s.to_string_lossy().into_owned()) - }) - .unwrap_or_else(|| "game".to_string()); + pub async fn new(info: Arc) -> Self { let mut call_stacks = HashMap::new(); call_stacks.insert( RESET_CODE, @@ -36,19 +26,18 @@ impl ProgramState { }], ); Self { - name, - symbol_file, + info, call_stacks, context_stack: vec![RESET_CODE], } } pub fn name(&self) -> &str { - &self.name + self.info.name() } - pub fn symbol_file(&self) -> Option<&SymbolFile> { - self.symbol_file.as_ref() + pub fn library_info(&self) -> &LibraryInfo { + self.info.library_info() } pub fn current_stack(&self) -> Option<(u16, &[StackFrame])> { diff --git a/src/profiler/symbols.rs b/src/profiler/symbols.rs deleted file mode 100644 index fd84211..0000000 --- a/src/profiler/symbols.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::{path::Path, sync::Arc}; - -use anyhow::Result; -use fxprof_processed_profile::{LibraryInfo, Symbol, SymbolTable}; -use wholesym::{SymbolManager, samply_symbols::demangle_any}; - -pub struct SymbolFile { - library_info: LibraryInfo, -} - -impl SymbolFile { - pub async fn load(file_path: &Path) -> Result { - let normalized = normpath::PathExt::normalize(file_path)?; - let library_info = - SymbolManager::library_info_for_binary_at_path(normalized.as_path(), None).await?; - - let symbol_manager = SymbolManager::with_config(Default::default()); - let symbol_map = symbol_manager - .load_symbol_map_for_binary_at_path(normalized.as_path(), None) - .await?; - - let name = library_info - .name - .or_else(|| { - normalized - .file_name() - .map(|n| n.to_string_lossy().into_owned()) - }) - .unwrap_or("game".to_string()); - let debug_name = library_info.debug_name.unwrap_or_else(|| name.clone()); - let path = library_info - .path - .unwrap_or_else(|| normalized.into_os_string().to_string_lossy().into_owned()); - let debug_path = library_info.debug_path.unwrap_or_else(|| path.clone()); - let debug_id = library_info.debug_id.unwrap_or_default(); - let code_id = library_info.code_id.map(|id| id.to_string()); - let arch = library_info.arch; - let symbols = symbol_map - .iter_symbols() - .map(|(address, name)| Symbol { - address: address + 0x07000000, - size: None, - name: demangle_any(&name), - }) - .collect(); - Ok(Self { - library_info: LibraryInfo { - name, - debug_name, - path, - debug_path, - debug_id, - code_id, - arch, - symbol_table: Some(Arc::new(SymbolTable::new(symbols))), - }, - }) - } - - pub fn name(&self) -> &str { - &self.library_info.name - } - - pub fn library_info(&self) -> &LibraryInfo { - &self.library_info - } -}