Initial commit: xenia-rs workspace for Xbox 360 RE
Rust reimplementation of the xenia Xbox 360 emulator targeting reverse- engineering and preservation, initially scoped to Project Sylpheed. Includes: - XEX2 loader (LZX decompression, AES decryption, PE parsing) - XISO / XGD2 disc image VFS - PPC interpreter with 200+ opcodes and VMX128 decoding - Static analyzer: functions, cross-references, labels, asm + SQLite output - HLE kernel covering the xboxkrnl/xam subset used by Sylpheed init - Debugger with in-memory and SQLite-backed execution tracing - `xenia-rs` CLI with extract/dis/exec commands that produce cumulative, superset SQLite databases and opt-in instruction/import/branch traces Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
7
crates/xenia-debugger/src/breakpoint.rs
Normal file
7
crates/xenia-debugger/src/breakpoint.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
/// A code breakpoint at a specific guest address.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Breakpoint {
|
||||
pub addr: u32,
|
||||
pub enabled: bool,
|
||||
pub condition: Option<String>,
|
||||
}
|
||||
125
crates/xenia-debugger/src/lib.rs
Normal file
125
crates/xenia-debugger/src/lib.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
pub mod breakpoint;
|
||||
pub mod trace;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use xenia_cpu::context::PpcContext;
|
||||
use xenia_memory::MemoryAccess;
|
||||
|
||||
pub use breakpoint::Breakpoint;
|
||||
pub use trace::TraceEntry;
|
||||
|
||||
/// The debugger. Hooks into every instruction step for observation.
|
||||
pub struct Debugger {
|
||||
pub breakpoints: HashMap<u32, Breakpoint>,
|
||||
pub trace_log: Vec<TraceEntry>,
|
||||
pub trace_enabled: bool,
|
||||
pub max_trace_entries: usize,
|
||||
pub paused: bool,
|
||||
pub step_mode: StepMode,
|
||||
break_pending: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum StepMode {
|
||||
/// Run freely until breakpoint or pause
|
||||
Run,
|
||||
/// Execute one instruction then pause
|
||||
StepInto,
|
||||
/// Run but break after current function returns (when LR changes)
|
||||
StepOver { return_addr: u32 },
|
||||
}
|
||||
|
||||
impl Debugger {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
breakpoints: HashMap::new(),
|
||||
trace_log: Vec::new(),
|
||||
trace_enabled: true,
|
||||
max_trace_entries: 100_000,
|
||||
paused: true, // Start paused for debugging
|
||||
step_mode: StepMode::StepInto,
|
||||
break_pending: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Called before each instruction executes.
|
||||
pub fn pre_step(&mut self, ctx: &PpcContext, _mem: &dyn MemoryAccess) {
|
||||
// Check breakpoints
|
||||
if let Some(bp) = self.breakpoints.get(&ctx.pc) {
|
||||
if bp.enabled {
|
||||
self.break_pending = true;
|
||||
tracing::info!("Breakpoint hit at {:#010x}", ctx.pc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Called after each instruction executes.
|
||||
pub fn post_step(&mut self, ctx: &PpcContext, _mem: &dyn MemoryAccess) {
|
||||
// Log to trace
|
||||
if self.trace_enabled {
|
||||
if self.trace_log.len() >= self.max_trace_entries {
|
||||
self.trace_log.remove(0);
|
||||
}
|
||||
self.trace_log.push(TraceEntry {
|
||||
pc: ctx.pc,
|
||||
cycle: ctx.cycle_count,
|
||||
gpr_snapshot: [ctx.gpr[0], ctx.gpr[1], ctx.gpr[3], ctx.gpr[4]],
|
||||
lr: ctx.lr,
|
||||
});
|
||||
}
|
||||
|
||||
// Handle step mode
|
||||
match self.step_mode {
|
||||
StepMode::StepInto => {
|
||||
self.break_pending = true;
|
||||
}
|
||||
StepMode::StepOver { return_addr } => {
|
||||
if ctx.pc == return_addr {
|
||||
self.break_pending = true;
|
||||
}
|
||||
}
|
||||
StepMode::Run => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Should we break execution?
|
||||
pub fn should_break(&self) -> bool {
|
||||
self.break_pending || self.paused
|
||||
}
|
||||
|
||||
/// Add a breakpoint at the given address.
|
||||
pub fn add_breakpoint(&mut self, addr: u32) {
|
||||
self.breakpoints.insert(addr, Breakpoint { addr, enabled: true, condition: None });
|
||||
}
|
||||
|
||||
/// Remove a breakpoint.
|
||||
pub fn remove_breakpoint(&mut self, addr: u32) {
|
||||
self.breakpoints.remove(&addr);
|
||||
}
|
||||
|
||||
/// Continue execution.
|
||||
pub fn continue_execution(&mut self) {
|
||||
self.paused = false;
|
||||
self.break_pending = false;
|
||||
self.step_mode = StepMode::Run;
|
||||
}
|
||||
|
||||
/// Step one instruction.
|
||||
pub fn step_into(&mut self) {
|
||||
self.paused = false;
|
||||
self.break_pending = false;
|
||||
self.step_mode = StepMode::StepInto;
|
||||
}
|
||||
|
||||
/// Clear break state after handling.
|
||||
pub fn acknowledge_break(&mut self) {
|
||||
self.break_pending = false;
|
||||
self.paused = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Debugger {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
9
crates/xenia-debugger/src/trace.rs
Normal file
9
crates/xenia-debugger/src/trace.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
/// A single entry in the instruction trace log.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TraceEntry {
|
||||
pub pc: u32,
|
||||
pub cycle: u64,
|
||||
/// Snapshot of key GPRs: [r0, r1(sp), r3(arg0/retval), r4(arg1)]
|
||||
pub gpr_snapshot: [u64; 4],
|
||||
pub lr: u64,
|
||||
}
|
||||
Reference in New Issue
Block a user