Profiling #7

Merged
SonicSwordcane merged 15 commits from profiling into main 2025-09-01 21:51:34 +00:00
9 changed files with 182 additions and 137 deletions
Showing only changes of commit 04c92c1454 - Show all commits

42
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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,19 +223,17 @@ 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
if let Some(profiler) = self.profilers[sim_id.to_index()].as_ref()
&& let Some(cart) = self.carts[index].as_ref()
&& profiler
.send(ProfileEvent::Start {
file_path: cart.file_path.clone(),
info: cart.info.clone(),
})
.is_ok()
{
sim.monitor_events(true);
profiling = true;
}
}
}
if !profiling {
sim.monitor_events(false);
}
@ -476,8 +476,8 @@ 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(),
})
@ -487,7 +487,6 @@ impl Emulator {
*profiler = None;
}
}
}
if state == EmulatorState::Stepping {
self.state.store(EmulatorState::Paused, Ordering::Release);
@ -838,7 +837,7 @@ pub enum DebugEvent {
pub enum ProfileEvent {
Start {
file_path: PathBuf,
info: Arc<GameInfo>,
},
Update {
cycles: u32,

View File

@ -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<u8>,
sram_file: File,
pub sram: Vec<u8>,
pub info: Arc<GameInfo>,
}
impl Cart {
pub fn load(file_path: &Path, sim_id: SimId) -> Result<Self> {
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<Vec<u8>> {
let parsed = elf::ElfBytes::<elf::endian::AnyEndian>::minimal_parse(data).ok()?;
fn try_parse_elf(file_path: &Path, data: &[u8]) -> Option<(Vec<u8>, 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<Elf: object::read::elf::FileHeader<Endian = object::Endianness>>(
header: &Elf,
data: &[u8],
) -> Option<Vec<u8>> {
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)

83
src/emulator/game_info.rs Normal file
View File

@ -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<Self> {
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)
}

View File

@ -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<GameInfo>) {
let program = ProgramState::new(info).await;
let recording = if self.recording.is_some() {
Some(Recording::new(&program))
} else {

View File

@ -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());
let lib = profile.add_lib(state.library_info().clone());
profile.add_lib_mapping(process, lib, 0x00000000, 0xffffffff, 0);
}
let mut me = Self {
profile,

View File

@ -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<SymbolFile>,
info: Arc<GameInfo>,
call_stacks: HashMap<u16, Vec<StackFrame>>,
context_stack: Vec<u16>,
}
@ -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<GameInfo>) -> 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])> {

View File

@ -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<Self> {
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
}
}