Profiling #7
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<GameInfo>,
|
||||
},
|
||||
Update {
|
||||
cycles: u32,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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])> {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue