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:
276
crates/xenia-cpu/src/disasm.rs
Normal file
276
crates/xenia-cpu/src/disasm.rs
Normal file
@@ -0,0 +1,276 @@
|
||||
use crate::decoder::DecodedInstr;
|
||||
use crate::opcode::PpcOpcode;
|
||||
use std::fmt::Write;
|
||||
|
||||
/// Disassemble a decoded instruction into PPC assembly text.
|
||||
pub fn disassemble(instr: &DecodedInstr) -> String {
|
||||
let mut out = String::new();
|
||||
match instr.opcode {
|
||||
// Branch instructions
|
||||
PpcOpcode::bx => {
|
||||
let target = if instr.aa() {
|
||||
instr.li() as u32
|
||||
} else {
|
||||
instr.addr.wrapping_add(instr.li() as u32)
|
||||
};
|
||||
let mnemonic = if instr.lk() { "bl" } else { "b" };
|
||||
write!(out, "{} 0x{:08X}", mnemonic, target).unwrap();
|
||||
}
|
||||
PpcOpcode::bcx => {
|
||||
let bo = instr.bo();
|
||||
let bi = instr.bi();
|
||||
let target = if instr.aa() {
|
||||
instr.bd() as u32
|
||||
} else {
|
||||
instr.addr.wrapping_add(instr.bd() as u32)
|
||||
};
|
||||
let mnemonic = if instr.lk() { "bcl" } else { "bc" };
|
||||
write!(out, "{} {},{},0x{:08X}", mnemonic, bo, bi, target).unwrap();
|
||||
}
|
||||
PpcOpcode::bclrx => {
|
||||
let mnemonic = if instr.lk() { "bclrl" } else { "bclr" };
|
||||
write!(out, "{} {},{}", mnemonic, instr.bo(), instr.bi()).unwrap();
|
||||
}
|
||||
PpcOpcode::bcctrx => {
|
||||
let mnemonic = if instr.lk() { "bcctrl" } else { "bcctr" };
|
||||
write!(out, "{} {},{}", mnemonic, instr.bo(), instr.bi()).unwrap();
|
||||
}
|
||||
|
||||
// System call
|
||||
PpcOpcode::sc => {
|
||||
write!(out, "sc").unwrap();
|
||||
}
|
||||
|
||||
// D-form load/store
|
||||
PpcOpcode::lwz | PpcOpcode::lwzu | PpcOpcode::lbz | PpcOpcode::lbzu |
|
||||
PpcOpcode::lhz | PpcOpcode::lhzu | PpcOpcode::lha | PpcOpcode::lhau |
|
||||
PpcOpcode::lfs | PpcOpcode::lfsu | PpcOpcode::lfd | PpcOpcode::lfdu => {
|
||||
write!(out, "{:?} r{},{}(r{})", instr.opcode, instr.rd(), instr.d(), instr.ra()).unwrap();
|
||||
}
|
||||
PpcOpcode::stw | PpcOpcode::stwu | PpcOpcode::stb | PpcOpcode::stbu |
|
||||
PpcOpcode::sth | PpcOpcode::sthu |
|
||||
PpcOpcode::stfs | PpcOpcode::stfsu | PpcOpcode::stfd | PpcOpcode::stfdu => {
|
||||
write!(out, "{:?} r{},{}(r{})", instr.opcode, instr.rs(), instr.d(), instr.ra()).unwrap();
|
||||
}
|
||||
|
||||
// D-form immediate ALU
|
||||
PpcOpcode::addi | PpcOpcode::addis | PpcOpcode::addic | PpcOpcode::addicx |
|
||||
PpcOpcode::subficx | PpcOpcode::mulli => {
|
||||
write!(out, "{:?} r{},r{},{}", instr.opcode, instr.rd(), instr.ra(), instr.simm16()).unwrap();
|
||||
}
|
||||
|
||||
// D-form immediate logical
|
||||
PpcOpcode::ori | PpcOpcode::oris | PpcOpcode::xori | PpcOpcode::xoris |
|
||||
PpcOpcode::andix | PpcOpcode::andisx => {
|
||||
write!(out, "{:?} r{},r{},0x{:04X}", instr.opcode, instr.ra(), instr.rs(), instr.uimm16()).unwrap();
|
||||
}
|
||||
|
||||
// Compare
|
||||
PpcOpcode::cmpi => {
|
||||
write!(out, "cmp{}i cr{},r{},{}", if instr.l() { "d" } else { "w" },
|
||||
instr.crfd(), instr.ra(), instr.simm16()).unwrap();
|
||||
}
|
||||
PpcOpcode::cmpli => {
|
||||
write!(out, "cmpl{}i cr{},r{},0x{:04X}", if instr.l() { "d" } else { "w" },
|
||||
instr.crfd(), instr.ra(), instr.uimm16()).unwrap();
|
||||
}
|
||||
PpcOpcode::cmp => {
|
||||
write!(out, "cmp{} cr{},r{},r{}", if instr.l() { "d" } else { "w" },
|
||||
instr.crfd(), instr.ra(), instr.rb()).unwrap();
|
||||
}
|
||||
PpcOpcode::cmpl => {
|
||||
write!(out, "cmpl{} cr{},r{},r{}", if instr.l() { "d" } else { "w" },
|
||||
instr.crfd(), instr.ra(), instr.rb()).unwrap();
|
||||
}
|
||||
|
||||
// X-form ALU (3-register)
|
||||
PpcOpcode::addx | PpcOpcode::addcx | PpcOpcode::addex | PpcOpcode::addzex |
|
||||
PpcOpcode::addmex | PpcOpcode::subfx | PpcOpcode::subfcx | PpcOpcode::subfex |
|
||||
PpcOpcode::subfzex | PpcOpcode::subfmex | PpcOpcode::negx |
|
||||
PpcOpcode::mullwx | PpcOpcode::mulhwx | PpcOpcode::mulhwux |
|
||||
PpcOpcode::divwx | PpcOpcode::divwux |
|
||||
PpcOpcode::mulldx | PpcOpcode::mulhdx | PpcOpcode::mulhdux |
|
||||
PpcOpcode::divdx | PpcOpcode::divdux => {
|
||||
write!(out, "{:?} r{},r{},r{}", instr.opcode, instr.rd(), instr.ra(), instr.rb()).unwrap();
|
||||
}
|
||||
|
||||
// X-form logical
|
||||
PpcOpcode::andx | PpcOpcode::andcx | PpcOpcode::orx | PpcOpcode::orcx |
|
||||
PpcOpcode::xorx | PpcOpcode::norx | PpcOpcode::nandx | PpcOpcode::eqvx => {
|
||||
write!(out, "{:?} r{},r{},r{}", instr.opcode, instr.ra(), instr.rs(), instr.rb()).unwrap();
|
||||
}
|
||||
|
||||
// Shift/rotate
|
||||
PpcOpcode::slwx | PpcOpcode::srwx | PpcOpcode::srawx | PpcOpcode::sldx |
|
||||
PpcOpcode::srdx | PpcOpcode::sradx => {
|
||||
write!(out, "{:?} r{},r{},r{}", instr.opcode, instr.ra(), instr.rs(), instr.rb()).unwrap();
|
||||
}
|
||||
PpcOpcode::srawix => {
|
||||
write!(out, "srawi r{},r{},{}", instr.ra(), instr.rs(), instr.sh()).unwrap();
|
||||
}
|
||||
PpcOpcode::sradix => {
|
||||
write!(out, "sradi r{},r{},{}", instr.ra(), instr.rs(), instr.sh64()).unwrap();
|
||||
}
|
||||
|
||||
// Rotate
|
||||
PpcOpcode::rlwinmx => {
|
||||
write!(out, "rlwinm r{},r{},{},{},{}", instr.ra(), instr.rs(), instr.sh(), instr.mb(), instr.me()).unwrap();
|
||||
}
|
||||
PpcOpcode::rlwimix => {
|
||||
write!(out, "rlwimi r{},r{},{},{},{}", instr.ra(), instr.rs(), instr.sh(), instr.mb(), instr.me()).unwrap();
|
||||
}
|
||||
PpcOpcode::rlwnmx => {
|
||||
write!(out, "rlwnm r{},r{},r{},{},{}", instr.ra(), instr.rs(), instr.rb(), instr.mb(), instr.me()).unwrap();
|
||||
}
|
||||
|
||||
// Special register moves
|
||||
PpcOpcode::mfspr => {
|
||||
let spr_name = match instr.spr() {
|
||||
1 => "xer",
|
||||
8 => "lr",
|
||||
9 => "ctr",
|
||||
268 => "tbl",
|
||||
269 => "tbu",
|
||||
_ => "",
|
||||
};
|
||||
if spr_name.is_empty() {
|
||||
write!(out, "mfspr r{},{}", instr.rd(), instr.spr()).unwrap();
|
||||
} else {
|
||||
write!(out, "mf{} r{}", spr_name, instr.rd()).unwrap();
|
||||
}
|
||||
}
|
||||
PpcOpcode::mtspr => {
|
||||
let spr_name = match instr.spr() {
|
||||
1 => "xer",
|
||||
8 => "lr",
|
||||
9 => "ctr",
|
||||
_ => "",
|
||||
};
|
||||
if spr_name.is_empty() {
|
||||
write!(out, "mtspr {},r{}", instr.spr(), instr.rs()).unwrap();
|
||||
} else {
|
||||
write!(out, "mt{} r{}", spr_name, instr.rs()).unwrap();
|
||||
}
|
||||
}
|
||||
PpcOpcode::mfcr => {
|
||||
write!(out, "mfcr r{}", instr.rd()).unwrap();
|
||||
}
|
||||
PpcOpcode::mtcrf => {
|
||||
write!(out, "mtcrf 0x{:02X},r{}", instr.crm(), instr.rs()).unwrap();
|
||||
}
|
||||
|
||||
// Extend
|
||||
PpcOpcode::extsbx => write!(out, "extsb r{},r{}", instr.ra(), instr.rs()).unwrap(),
|
||||
PpcOpcode::extshx => write!(out, "extsh r{},r{}", instr.ra(), instr.rs()).unwrap(),
|
||||
PpcOpcode::extswx => write!(out, "extsw r{},r{}", instr.ra(), instr.rs()).unwrap(),
|
||||
PpcOpcode::cntlzwx => write!(out, "cntlzw r{},r{}", instr.ra(), instr.rs()).unwrap(),
|
||||
PpcOpcode::cntlzdx => write!(out, "cntlzd r{},r{}", instr.ra(), instr.rs()).unwrap(),
|
||||
|
||||
// X-form load/store
|
||||
PpcOpcode::lwzx | PpcOpcode::lwzux | PpcOpcode::lbzx | PpcOpcode::lbzux |
|
||||
PpcOpcode::lhzx | PpcOpcode::lhzux | PpcOpcode::lhax | PpcOpcode::lhaux |
|
||||
PpcOpcode::lwax | PpcOpcode::lwaux | PpcOpcode::ldx | PpcOpcode::ldux |
|
||||
PpcOpcode::lfsx | PpcOpcode::lfsux | PpcOpcode::lfdx | PpcOpcode::lfdux |
|
||||
PpcOpcode::lwbrx | PpcOpcode::lhbrx | PpcOpcode::ldbrx |
|
||||
PpcOpcode::lwarx | PpcOpcode::ldarx => {
|
||||
write!(out, "{:?} r{},r{},r{}", instr.opcode, instr.rd(), instr.ra(), instr.rb()).unwrap();
|
||||
}
|
||||
PpcOpcode::stwx | PpcOpcode::stwux | PpcOpcode::stbx | PpcOpcode::stbux |
|
||||
PpcOpcode::sthx | PpcOpcode::sthux | PpcOpcode::stdx | PpcOpcode::stdux |
|
||||
PpcOpcode::stfsx | PpcOpcode::stfsux | PpcOpcode::stfdx | PpcOpcode::stfdux |
|
||||
PpcOpcode::stwbrx | PpcOpcode::sthbrx | PpcOpcode::stdbrx |
|
||||
PpcOpcode::stwcx | PpcOpcode::stdcx | PpcOpcode::stfiwx => {
|
||||
write!(out, "{:?} r{},r{},r{}", instr.opcode, instr.rs(), instr.ra(), instr.rb()).unwrap();
|
||||
}
|
||||
|
||||
// Cache/sync ops (no-ops for interpreter)
|
||||
PpcOpcode::dcbf | PpcOpcode::dcbi | PpcOpcode::dcbst |
|
||||
PpcOpcode::dcbt | PpcOpcode::dcbtst | PpcOpcode::icbi => {
|
||||
write!(out, "{:?} r{},r{}", instr.opcode, instr.ra(), instr.rb()).unwrap();
|
||||
}
|
||||
PpcOpcode::dcbz | PpcOpcode::dcbz128 => {
|
||||
write!(out, "{:?} r{},r{}", instr.opcode, instr.ra(), instr.rb()).unwrap();
|
||||
}
|
||||
PpcOpcode::sync | PpcOpcode::eieio | PpcOpcode::isync => {
|
||||
write!(out, "{:?}", instr.opcode).unwrap();
|
||||
}
|
||||
|
||||
// Load/store multiple
|
||||
PpcOpcode::lmw => write!(out, "lmw r{},{}(r{})", instr.rd(), instr.d(), instr.ra()).unwrap(),
|
||||
PpcOpcode::stmw => write!(out, "stmw r{},{}(r{})", instr.rs(), instr.d(), instr.ra()).unwrap(),
|
||||
|
||||
// DS-form loads/stores
|
||||
PpcOpcode::ld | PpcOpcode::ldu | PpcOpcode::lwa => {
|
||||
write!(out, "{:?} r{},{}(r{})", instr.opcode, instr.rd(), instr.ds(), instr.ra()).unwrap();
|
||||
}
|
||||
PpcOpcode::std | PpcOpcode::stdu => {
|
||||
write!(out, "{:?} r{},{}(r{})", instr.opcode, instr.rs(), instr.ds(), instr.ra()).unwrap();
|
||||
}
|
||||
|
||||
// CR logical ops
|
||||
PpcOpcode::crand | PpcOpcode::crandc | PpcOpcode::creqv | PpcOpcode::crnand |
|
||||
PpcOpcode::crnor | PpcOpcode::cror | PpcOpcode::crorc | PpcOpcode::crxor => {
|
||||
write!(out, "{:?} {},{},{}", instr.opcode, instr.crbd(), instr.crba(), instr.crbb()).unwrap();
|
||||
}
|
||||
PpcOpcode::mcrf => {
|
||||
write!(out, "mcrf cr{},cr{}", instr.crfd(), instr.crfs()).unwrap();
|
||||
}
|
||||
|
||||
// Trap
|
||||
PpcOpcode::tdi => write!(out, "tdi {},r{},{}", instr.rd(), instr.ra(), instr.simm16()).unwrap(),
|
||||
PpcOpcode::twi => write!(out, "twi {},r{},{}", instr.rd(), instr.ra(), instr.simm16()).unwrap(),
|
||||
PpcOpcode::td => write!(out, "td {},r{},r{}", instr.rd(), instr.ra(), instr.rb()).unwrap(),
|
||||
PpcOpcode::tw => write!(out, "tw {},r{},r{}", instr.rd(), instr.ra(), instr.rb()).unwrap(),
|
||||
|
||||
// Default: just print opcode and raw hex
|
||||
_ => {
|
||||
write!(out, "{:?} [{:08X}]", instr.opcode, instr.raw).unwrap();
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Disassemble a range of instructions from a byte slice.
|
||||
pub fn disassemble_block(data: &[u8], base_addr: u32, count: usize) -> Vec<(u32, String)> {
|
||||
let mut result = Vec::new();
|
||||
for i in 0..count {
|
||||
let offset = i * 4;
|
||||
if offset + 4 > data.len() {
|
||||
break;
|
||||
}
|
||||
let raw = u32::from_be_bytes([
|
||||
data[offset],
|
||||
data[offset + 1],
|
||||
data[offset + 2],
|
||||
data[offset + 3],
|
||||
]);
|
||||
let addr = base_addr + offset as u32;
|
||||
let instr = crate::decode(raw, addr);
|
||||
let text = disassemble(&instr);
|
||||
result.push((addr, text));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::decoder::decode;
|
||||
|
||||
#[test]
|
||||
fn test_disasm_nop() {
|
||||
// ori r0, r0, 0 = NOP
|
||||
let instr = decode(0x60000000, 0);
|
||||
let text = disassemble(&instr);
|
||||
assert!(text.contains("ori"), "Expected 'ori', got: {}", text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disasm_addi() {
|
||||
let raw = (14u32 << 26) | (3 << 21) | (1 << 16) | 16;
|
||||
let instr = decode(raw, 0);
|
||||
let text = disassemble(&instr);
|
||||
assert!(text.contains("addi"), "Got: {}", text);
|
||||
assert!(text.contains("r3"), "Got: {}", text);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user