xenia-hid grows a guest-facing X_INPUT_GAMEPAD writer (big-endian on the wire, host-neutral GamepadState in memory) so XamInputGetState in the kernel and the UI input thread share one POD snapshot type. Adds the GUIDE button flag. xenia-debugger gains Debugger::wants_hooks(), a single-branch fast path the hot interpreter loop checks to skip the pre_step/post_step HashMap+match work when the debugger is in cold-run mode (no bps, no trace, StepMode::Run, not paused). Part of the Tier-3 perf landing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
141 lines
4.1 KiB
Rust
141 lines
4.1 KiB
Rust
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,
|
|
}
|
|
}
|
|
|
|
/// Tier-3 perf: single branch that the hot interpreter loop checks
|
|
/// before dispatching to [`pre_step`]/[`post_step`]. When the
|
|
/// debugger is in "cold run" mode (not paused, no breakpoints,
|
|
/// `StepMode::Run`, in-memory trace off), both hooks become dead
|
|
/// code and we can skip the HashMap lookup + step-mode match + Vec
|
|
/// maintenance entirely. The compiler reliably branch-predicts the
|
|
/// stable branch direction across millions of instructions.
|
|
#[inline]
|
|
pub fn wants_hooks(&self) -> bool {
|
|
self.trace_enabled
|
|
|| self.paused
|
|
|| self.break_pending
|
|
|| !matches!(self.step_mode, StepMode::Run)
|
|
|| !self.breakpoints.is_empty()
|
|
}
|
|
|
|
/// 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)
|
|
&& 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()
|
|
}
|
|
}
|