mirror of
https://github.com/encounter/decomp-toolkit.git
synced 2025-06-16 11:33:38 +00:00
888 lines
34 KiB
Rust
888 lines
34 KiB
Rust
use std::num::NonZeroU32;
|
|
|
|
use ppc750cl::{Argument, Ins, Opcode, GPR};
|
|
|
|
use crate::{
|
|
analysis::{cfa::SectionAddress, relocation_target_for, RelocationTarget},
|
|
obj::{ObjInfo, ObjKind},
|
|
};
|
|
|
|
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
|
|
pub enum GprValue {
|
|
#[default]
|
|
/// GPR value is unknown
|
|
Unknown,
|
|
/// GPR value is a constant
|
|
Constant(u32),
|
|
/// GPR value is a known relocated address
|
|
Address(RelocationTarget),
|
|
/// Comparison result (CR field)
|
|
ComparisonResult(u8),
|
|
/// GPR value is within a range
|
|
Range { min: u32, max: u32, step: u32 },
|
|
/// GPR value is loaded from an address with a max offset (jump table)
|
|
LoadIndexed { address: RelocationTarget, max_offset: Option<NonZeroU32> },
|
|
}
|
|
|
|
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
|
|
pub struct Gpr {
|
|
/// The current calculated value
|
|
pub value: GprValue,
|
|
/// Address that loads the hi part of this GPR
|
|
pub hi_addr: Option<SectionAddress>,
|
|
/// Address that loads the lo part of this GPR
|
|
pub lo_addr: Option<SectionAddress>,
|
|
}
|
|
|
|
impl Gpr {
|
|
fn set_direct(&mut self, value: GprValue) {
|
|
self.value = value;
|
|
self.hi_addr = None;
|
|
self.lo_addr = None;
|
|
}
|
|
|
|
fn set_hi(&mut self, value: GprValue, addr: SectionAddress) {
|
|
self.value = value;
|
|
self.hi_addr = Some(addr);
|
|
self.lo_addr = None;
|
|
}
|
|
|
|
fn set_lo(&mut self, value: GprValue, addr: SectionAddress, hi_gpr: Gpr) {
|
|
self.value = value;
|
|
self.hi_addr = hi_gpr.hi_addr;
|
|
self.lo_addr = Some(hi_gpr.lo_addr.unwrap_or(addr));
|
|
}
|
|
|
|
fn address(&self, obj: &ObjInfo, ins_addr: SectionAddress) -> Option<RelocationTarget> {
|
|
match self.value {
|
|
GprValue::Constant(value) => section_address_for(obj, ins_addr, value),
|
|
GprValue::Address(target) => Some(target),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
|
pub struct Cr {
|
|
/// The left-hand value of this comparison
|
|
pub left: GprValue,
|
|
/// The right-hand value of this comparison
|
|
pub right: GprValue,
|
|
/// Whether this comparison is signed
|
|
pub signed: bool,
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
|
pub struct VM {
|
|
/// General purpose registers
|
|
pub gpr: [Gpr; 32],
|
|
/// Condition registers
|
|
pub cr: [Cr; 8],
|
|
/// Link register
|
|
pub lr: GprValue,
|
|
/// Count register
|
|
pub ctr: GprValue,
|
|
}
|
|
|
|
impl VM {
|
|
pub fn gpr_value(&self, reg: u8) -> GprValue { self.gpr[reg as usize].value }
|
|
}
|
|
|
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
pub enum BranchTarget {
|
|
/// Unknown branch target (CTR without known value)
|
|
Unknown,
|
|
/// Branch to LR
|
|
Return,
|
|
/// Branch to address
|
|
Address(RelocationTarget),
|
|
/// Branch to jump table
|
|
JumpTable { address: RelocationTarget, size: Option<NonZeroU32> },
|
|
}
|
|
|
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
pub struct Branch {
|
|
/// Branch target
|
|
pub target: BranchTarget,
|
|
/// Branch with link
|
|
pub link: bool,
|
|
/// VM state for this branch
|
|
pub vm: Box<VM>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
pub enum StepResult {
|
|
/// Continue normally
|
|
Continue,
|
|
/// Load from / store to
|
|
LoadStore { address: RelocationTarget, source: Gpr, source_reg: u8 },
|
|
/// Hit illegal instruction
|
|
Illegal,
|
|
/// Jump without affecting VM state
|
|
Jump(BranchTarget),
|
|
/// Branch with split VM states
|
|
Branch(Vec<Branch>),
|
|
}
|
|
|
|
pub fn section_address_for(
|
|
obj: &ObjInfo,
|
|
ins_addr: SectionAddress,
|
|
target_addr: u32,
|
|
) -> Option<RelocationTarget> {
|
|
if let Some(target) = relocation_target_for(obj, ins_addr, None).ok().flatten() {
|
|
return Some(target);
|
|
}
|
|
if obj.kind == ObjKind::Executable {
|
|
let (section_index, _) = obj.sections.at_address(target_addr).ok()?;
|
|
return Some(RelocationTarget::Address(SectionAddress::new(section_index, target_addr)));
|
|
}
|
|
if obj.sections[ins_addr.section].contains(target_addr) {
|
|
Some(RelocationTarget::Address(SectionAddress::new(ins_addr.section, target_addr)))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
impl VM {
|
|
#[inline]
|
|
pub fn new() -> Box<Self> { Box::default() }
|
|
|
|
#[inline]
|
|
pub fn new_from_obj(obj: &ObjInfo) -> Box<Self> {
|
|
Self::new_with_base(obj.sda2_base, obj.sda_base)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn new_with_base(sda2_base: Option<u32>, sda_base: Option<u32>) -> Box<Self> {
|
|
let mut vm = Self::new();
|
|
if let Some(value) = sda2_base {
|
|
vm.gpr[2].value = GprValue::Constant(value);
|
|
}
|
|
if let Some(value) = sda_base {
|
|
vm.gpr[13].value = GprValue::Constant(value);
|
|
}
|
|
vm
|
|
}
|
|
|
|
/// When calling a function, only preserve SDA bases
|
|
#[inline]
|
|
pub fn clone_for_link(&self) -> Box<Self> {
|
|
let mut vm = Self::new();
|
|
vm.gpr[2].value = self.gpr[2].value;
|
|
vm.gpr[13].value = self.gpr[13].value;
|
|
vm
|
|
}
|
|
|
|
/// When returning from a function call, only dedicated
|
|
/// and nonvolatile registers are preserved
|
|
#[inline]
|
|
pub fn clone_for_return(&self) -> Box<Self> {
|
|
let mut vm = Self::new();
|
|
// Dedicated registers
|
|
vm.gpr[1].value = self.gpr[1].value;
|
|
vm.gpr[2].value = self.gpr[2].value;
|
|
vm.gpr[13].value = self.gpr[13].value;
|
|
// Non-volatile registers
|
|
for i in 14..32 {
|
|
vm.gpr[i] = self.gpr[i];
|
|
}
|
|
vm
|
|
}
|
|
|
|
#[inline]
|
|
pub fn clone_all(&self) -> Box<Self> { Box::new(self.clone()) }
|
|
|
|
pub fn step(&mut self, obj: &ObjInfo, ins_addr: SectionAddress, ins: Ins) -> StepResult {
|
|
match ins.op {
|
|
Opcode::Illegal => {
|
|
return StepResult::Illegal;
|
|
}
|
|
// add rD, rA, rB
|
|
Opcode::Add => {
|
|
let left = self.gpr[ins.field_ra() as usize].value;
|
|
let right = self.gpr[ins.field_rb() as usize].value;
|
|
let value = match (left, right) {
|
|
(GprValue::Constant(left), GprValue::Constant(right)) => {
|
|
GprValue::Constant(left.wrapping_add(right))
|
|
}
|
|
(
|
|
GprValue::Address(RelocationTarget::Address(left)),
|
|
GprValue::Constant(right),
|
|
) => GprValue::Address(RelocationTarget::Address(left + right)),
|
|
(
|
|
GprValue::Constant(left),
|
|
GprValue::Address(RelocationTarget::Address(right)),
|
|
) => GprValue::Address(RelocationTarget::Address(right + left)),
|
|
_ => GprValue::Unknown,
|
|
};
|
|
self.gpr[ins.field_rd() as usize].set_direct(value);
|
|
}
|
|
// addis rD, rA, SIMM
|
|
Opcode::Addis => {
|
|
if let Some(target) =
|
|
relocation_target_for(obj, ins_addr, None /* TODO */).ok().flatten()
|
|
{
|
|
debug_assert_eq!(ins.field_ra(), 0);
|
|
self.gpr[ins.field_rd() as usize].set_hi(GprValue::Address(target), ins_addr);
|
|
} else {
|
|
let left = if ins.field_ra() == 0 {
|
|
GprValue::Constant(0)
|
|
} else {
|
|
self.gpr[ins.field_ra() as usize].value
|
|
};
|
|
let value = match left {
|
|
GprValue::Constant(value) => {
|
|
GprValue::Constant(value.wrapping_add((ins.field_simm() as u32) << 16))
|
|
}
|
|
_ => GprValue::Unknown,
|
|
};
|
|
if ins.field_ra() == 0 {
|
|
// lis rD, SIMM
|
|
self.gpr[ins.field_rd() as usize].set_hi(value, ins_addr);
|
|
} else {
|
|
self.gpr[ins.field_rd() as usize].set_direct(value);
|
|
}
|
|
}
|
|
}
|
|
// addi rD, rA, SIMM
|
|
// addic rD, rA, SIMM
|
|
// addic. rD, rA, SIMM
|
|
Opcode::Addi | Opcode::Addic | Opcode::Addic_ => {
|
|
if let Some(target) =
|
|
relocation_target_for(obj, ins_addr, None /* TODO */).ok().flatten()
|
|
{
|
|
self.gpr[ins.field_rd() as usize].set_lo(
|
|
GprValue::Address(target),
|
|
ins_addr,
|
|
self.gpr[ins.field_ra() as usize],
|
|
);
|
|
} else {
|
|
let left = if ins.field_ra() == 0 && ins.op == Opcode::Addi {
|
|
GprValue::Constant(0)
|
|
} else {
|
|
self.gpr[ins.field_ra() as usize].value
|
|
};
|
|
let value = match left {
|
|
GprValue::Constant(value) => {
|
|
GprValue::Constant(value.wrapping_add(ins.field_simm() as u32))
|
|
}
|
|
GprValue::Address(RelocationTarget::Address(address)) => GprValue::Address(
|
|
RelocationTarget::Address(address.offset(ins.field_simm() as i32)),
|
|
),
|
|
_ => GprValue::Unknown,
|
|
};
|
|
if ins.field_ra() == 0 {
|
|
// li rD, SIMM
|
|
self.gpr[ins.field_rd() as usize].set_direct(value);
|
|
} else {
|
|
self.gpr[ins.field_rd() as usize].set_lo(
|
|
value,
|
|
ins_addr,
|
|
self.gpr[ins.field_ra() as usize],
|
|
);
|
|
}
|
|
}
|
|
}
|
|
// subf rD, rA, rB
|
|
// subfc rD, rA, rB
|
|
Opcode::Subf | Opcode::Subfc => {
|
|
self.gpr[ins.field_rd() as usize].set_direct(
|
|
match (
|
|
self.gpr[ins.field_ra() as usize].value,
|
|
self.gpr[ins.field_rb() as usize].value,
|
|
) {
|
|
(GprValue::Constant(left), GprValue::Constant(right)) => {
|
|
GprValue::Constant((!left).wrapping_add(right).wrapping_add(1))
|
|
}
|
|
_ => GprValue::Unknown,
|
|
},
|
|
);
|
|
}
|
|
// subfic rD, rA, SIMM
|
|
Opcode::Subfic => {
|
|
self.gpr[ins.field_rd() as usize].set_direct(
|
|
match self.gpr[ins.field_ra() as usize].value {
|
|
GprValue::Constant(value) => GprValue::Constant(
|
|
(!value).wrapping_add(ins.field_simm() as u32).wrapping_add(1),
|
|
),
|
|
_ => GprValue::Unknown,
|
|
},
|
|
);
|
|
}
|
|
// ori rA, rS, UIMM
|
|
Opcode::Ori => {
|
|
if let Some(target) =
|
|
relocation_target_for(obj, ins_addr, None /* TODO */).ok().flatten()
|
|
{
|
|
self.gpr[ins.field_ra() as usize].set_lo(
|
|
GprValue::Address(target),
|
|
ins_addr,
|
|
self.gpr[ins.field_rs() as usize],
|
|
);
|
|
} else {
|
|
let value = match self.gpr[ins.field_rs() as usize].value {
|
|
GprValue::Constant(value) => {
|
|
GprValue::Constant(value | ins.field_uimm() as u32)
|
|
}
|
|
_ => GprValue::Unknown,
|
|
};
|
|
self.gpr[ins.field_ra() as usize].set_lo(
|
|
value,
|
|
ins_addr,
|
|
self.gpr[ins.field_rs() as usize],
|
|
);
|
|
}
|
|
}
|
|
// or rA, rS, rB
|
|
Opcode::Or => {
|
|
if ins.field_rs() == ins.field_rb() {
|
|
// Register copy
|
|
self.gpr[ins.field_ra() as usize] = self.gpr[ins.field_rs() as usize];
|
|
} else {
|
|
let left = self.gpr[ins.field_rs() as usize].value;
|
|
let right = self.gpr[ins.field_rb() as usize].value;
|
|
let value = match (left, right) {
|
|
(GprValue::Constant(left), GprValue::Constant(right)) => {
|
|
GprValue::Constant(left | right)
|
|
}
|
|
_ => GprValue::Unknown,
|
|
};
|
|
self.gpr[ins.field_ra() as usize].set_direct(value);
|
|
}
|
|
}
|
|
// cmp [crfD], [L], rA, rB
|
|
// cmpi [crfD], [L], rA, SIMM
|
|
// cmpl [crfD], [L], rA, rB
|
|
// cmpli [crfD], [L], rA, UIMM
|
|
Opcode::Cmp | Opcode::Cmpi | Opcode::Cmpl | Opcode::Cmpli => {
|
|
if ins.field_l() == 0 {
|
|
let left_reg = ins.field_ra() as usize;
|
|
let left = self.gpr[left_reg].value;
|
|
let (right, signed) = match ins.op {
|
|
Opcode::Cmp => (self.gpr[ins.field_rb() as usize].value, true),
|
|
Opcode::Cmpl => (self.gpr[ins.field_rb() as usize].value, false),
|
|
Opcode::Cmpi => (GprValue::Constant(ins.field_simm() as u32), true),
|
|
Opcode::Cmpli => (GprValue::Constant(ins.field_uimm() as u32), false),
|
|
_ => unreachable!(),
|
|
};
|
|
let crf = ins.field_crfd();
|
|
self.cr[crf as usize] = Cr { signed, left, right };
|
|
self.gpr[left_reg].value = GprValue::ComparisonResult(crf);
|
|
}
|
|
}
|
|
// rlwinm rA, rS, SH, MB, ME
|
|
// rlwnm rA, rS, rB, MB, ME
|
|
Opcode::Rlwinm | Opcode::Rlwnm => {
|
|
let value = if let Some(shift) = match ins.op {
|
|
Opcode::Rlwinm => Some(ins.field_sh() as u32),
|
|
Opcode::Rlwnm => match self.gpr[ins.field_rb() as usize].value {
|
|
GprValue::Constant(value) => Some(value),
|
|
_ => None,
|
|
},
|
|
_ => unreachable!(),
|
|
} {
|
|
let mask = mask_value(ins.field_mb() as u32, ins.field_me() as u32);
|
|
match self.gpr[ins.field_rs() as usize].value {
|
|
GprValue::Constant(value) => {
|
|
GprValue::Constant(value.rotate_left(shift) & mask)
|
|
}
|
|
GprValue::Range { min, max, step } => GprValue::Range {
|
|
min: min.rotate_left(shift) & mask,
|
|
max: max.rotate_left(shift) & mask,
|
|
step: step.rotate_left(shift),
|
|
},
|
|
_ => GprValue::Range { min: 0, max: mask, step: 1u32.rotate_left(shift) },
|
|
}
|
|
} else {
|
|
GprValue::Unknown
|
|
};
|
|
self.gpr[ins.field_ra() as usize].set_direct(value);
|
|
}
|
|
// b[l][a] target_addr
|
|
// b[c][l][a] BO, BI, target_addr
|
|
// b[c]ctr[l] BO, BI
|
|
// b[c]lr[l] BO, BI
|
|
Opcode::B | Opcode::Bc | Opcode::Bcctr | Opcode::Bclr => {
|
|
// HACK for `bla 0x60` in __OSDBJump
|
|
if ins.op == Opcode::B && ins.field_lk() && ins.field_aa() {
|
|
return StepResult::Jump(BranchTarget::Unknown);
|
|
}
|
|
|
|
let branch_target = match ins.op {
|
|
Opcode::Bcctr => {
|
|
match self.ctr {
|
|
GprValue::Constant(value) => {
|
|
// TODO only check valid target?
|
|
if let Some(target) = section_address_for(obj, ins_addr, value) {
|
|
BranchTarget::Address(target)
|
|
} else {
|
|
BranchTarget::Unknown
|
|
}
|
|
},
|
|
GprValue::Address(target) => BranchTarget::Address(target),
|
|
GprValue::LoadIndexed { address, max_offset }
|
|
// FIXME: avoids treating bctrl indirect calls as jump tables
|
|
if !ins.field_lk() => {
|
|
BranchTarget::JumpTable { address, size: max_offset.and_then(|n| n.checked_add(4)) }
|
|
}
|
|
_ => BranchTarget::Unknown,
|
|
}
|
|
}
|
|
Opcode::Bclr => BranchTarget::Return,
|
|
_ => {
|
|
let value = ins.branch_dest(ins_addr.address).unwrap();
|
|
if let Some(target) = section_address_for(obj, ins_addr, value) {
|
|
BranchTarget::Address(target)
|
|
} else {
|
|
BranchTarget::Unknown
|
|
}
|
|
}
|
|
};
|
|
|
|
// If branching with link, use function call semantics
|
|
if ins.field_lk() {
|
|
return StepResult::Branch(vec![
|
|
Branch {
|
|
target: BranchTarget::Address(RelocationTarget::Address(ins_addr + 4)),
|
|
link: false,
|
|
vm: self.clone_for_return(),
|
|
},
|
|
Branch { target: branch_target, link: true, vm: self.clone_for_link() },
|
|
]);
|
|
}
|
|
|
|
// Branch always
|
|
if ins.op == Opcode::B || ins.field_bo() & 0b10100 == 0b10100 {
|
|
return StepResult::Jump(branch_target);
|
|
}
|
|
|
|
// Branch conditionally
|
|
let mut branches = vec![
|
|
// Branch not taken
|
|
Branch {
|
|
target: BranchTarget::Address(RelocationTarget::Address(ins_addr + 4)),
|
|
link: false,
|
|
vm: self.clone_all(),
|
|
},
|
|
// Branch taken
|
|
Branch { target: branch_target, link: ins.field_lk(), vm: self.clone_all() },
|
|
];
|
|
|
|
// Use tracked CR to calculate new register values for branches
|
|
let crf = (ins.field_bi() >> 2) as usize;
|
|
let crb = ins.field_bi() & 3;
|
|
let (f_val, t_val) =
|
|
split_values_by_crb(crb, self.cr[crf].left, self.cr[crf].right);
|
|
if ins.field_bo() & 0b11110 == 0b00100 {
|
|
// Branch if false
|
|
branches[0].vm.set_comparison_result(t_val, crf);
|
|
branches[1].vm.set_comparison_result(f_val, crf);
|
|
} else if ins.field_bo() & 0b11110 == 0b01100 {
|
|
// Branch if true
|
|
branches[0].vm.set_comparison_result(f_val, crf);
|
|
branches[1].vm.set_comparison_result(t_val, crf);
|
|
}
|
|
|
|
return StepResult::Branch(branches);
|
|
}
|
|
// lwzx rD, rA, rB
|
|
Opcode::Lwzx => {
|
|
let left = self.gpr[ins.field_ra() as usize].address(obj, ins_addr);
|
|
let right = self.gpr[ins.field_rb() as usize].value;
|
|
let value = match (left, right) {
|
|
(Some(address), GprValue::Range { min: _, max, .. })
|
|
if /*min == 0 &&*/ max < u32::MAX - 4 && max & 3 == 0 =>
|
|
{
|
|
GprValue::LoadIndexed { address, max_offset: NonZeroU32::new(max) }
|
|
}
|
|
(Some(address), GprValue::Range { min: _, max, .. })
|
|
if /*min == 0 &&*/ max < u32::MAX - 4 && max & 3 == 0 =>
|
|
{
|
|
GprValue::LoadIndexed { address, max_offset: NonZeroU32::new(max) }
|
|
}
|
|
(Some(address), _) => {
|
|
GprValue::LoadIndexed { address, max_offset: None }
|
|
}
|
|
_ => GprValue::Unknown,
|
|
};
|
|
self.gpr[ins.field_rd() as usize].set_direct(value);
|
|
}
|
|
// mtspr SPR, rS
|
|
Opcode::Mtspr => match ins.field_spr() {
|
|
8 => self.lr = self.gpr[ins.field_rs() as usize].value,
|
|
9 => self.ctr = self.gpr[ins.field_rs() as usize].value,
|
|
_ => {}
|
|
},
|
|
// mfspr rD, SPR
|
|
Opcode::Mfspr => {
|
|
let value = match ins.field_spr() {
|
|
8 => self.lr,
|
|
9 => self.ctr,
|
|
_ => GprValue::Unknown,
|
|
};
|
|
self.gpr[ins.field_rd() as usize].set_direct(value);
|
|
}
|
|
// rfi
|
|
Opcode::Rfi => {
|
|
return StepResult::Jump(BranchTarget::Unknown);
|
|
}
|
|
op if is_load_store_op(op) => {
|
|
let source = ins.field_ra() as usize;
|
|
let mut result = StepResult::Continue;
|
|
if let GprValue::Address(target) = self.gpr[source].value {
|
|
if is_update_op(op) {
|
|
self.gpr[source].set_lo(
|
|
GprValue::Address(target),
|
|
ins_addr,
|
|
self.gpr[source],
|
|
);
|
|
}
|
|
result = StepResult::LoadStore {
|
|
address: target,
|
|
source: self.gpr[source],
|
|
source_reg: source as u8,
|
|
};
|
|
} else if let GprValue::Constant(base) = self.gpr[source].value {
|
|
let address = base.wrapping_add(ins.field_simm() as u32);
|
|
if let Some(target) = section_address_for(obj, ins_addr, address) {
|
|
if is_update_op(op) {
|
|
self.gpr[source].set_lo(
|
|
GprValue::Address(target),
|
|
ins_addr,
|
|
self.gpr[source],
|
|
);
|
|
}
|
|
result = StepResult::LoadStore {
|
|
address: target,
|
|
source: self.gpr[source],
|
|
source_reg: source as u8,
|
|
};
|
|
}
|
|
} else if is_update_op(op) {
|
|
self.gpr[source].set_direct(GprValue::Unknown);
|
|
}
|
|
if is_load_op(op) {
|
|
self.gpr[ins.field_rd() as usize].set_direct(GprValue::Unknown);
|
|
}
|
|
return result;
|
|
}
|
|
_ => {
|
|
for argument in ins.defs() {
|
|
if let Argument::GPR(GPR(reg)) = argument {
|
|
self.gpr[reg as usize].set_direct(GprValue::Unknown);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
StepResult::Continue
|
|
}
|
|
|
|
#[inline]
|
|
fn set_comparison_result(&mut self, value: GprValue, crf: usize) {
|
|
for gpr in &mut self.gpr {
|
|
if gpr.value == GprValue::ComparisonResult(crf as u8) {
|
|
gpr.value = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Given a condition register bit, calculate new register
|
|
/// values for each branch. (false / true)
|
|
fn split_values_by_crb(crb: u8, left: GprValue, right: GprValue) -> (GprValue, GprValue) {
|
|
match crb {
|
|
// lt
|
|
0 => match (left, right) {
|
|
(GprValue::Range { min, max, step }, GprValue::Constant(value)) => (
|
|
// left >= right
|
|
GprValue::Range {
|
|
min: std::cmp::max(min, value),
|
|
max: std::cmp::max(max, value),
|
|
step,
|
|
},
|
|
// left < right
|
|
GprValue::Range {
|
|
min: std::cmp::min(min, value.wrapping_sub(1)),
|
|
max: std::cmp::min(max, value.wrapping_sub(1)),
|
|
step,
|
|
},
|
|
),
|
|
(_, GprValue::Constant(value)) => (
|
|
// left >= right
|
|
GprValue::Range { min: value, max: u32::MAX, step: 1 },
|
|
// left < right
|
|
GprValue::Range { min: 0, max: value.wrapping_sub(1), step: 1 },
|
|
),
|
|
_ => (left, left),
|
|
},
|
|
// gt
|
|
1 => match (left, right) {
|
|
(GprValue::Range { min, max, step }, GprValue::Constant(value)) => (
|
|
// left <= right
|
|
GprValue::Range {
|
|
min: std::cmp::min(min, value),
|
|
max: std::cmp::min(max, value),
|
|
step,
|
|
},
|
|
// left > right
|
|
GprValue::Range {
|
|
min: std::cmp::max(min, value.wrapping_add(1)),
|
|
max: std::cmp::max(max, value.wrapping_add(1)),
|
|
step,
|
|
},
|
|
),
|
|
(_, GprValue::Constant(value)) => (
|
|
// left <= right
|
|
GprValue::Range { min: 0, max: value, step: 1 },
|
|
// left > right
|
|
GprValue::Range { min: value.wrapping_add(1), max: u32::MAX, step: 1 },
|
|
),
|
|
_ => (left, left),
|
|
},
|
|
// eq
|
|
2 => match (left, right) {
|
|
(GprValue::Constant(l), GprValue::Constant(r)) => (
|
|
// left != right
|
|
if l == r { GprValue::Unknown } else { left },
|
|
// left == right
|
|
GprValue::Constant(r),
|
|
),
|
|
(_, GprValue::Constant(value)) => (
|
|
// left != right
|
|
left,
|
|
// left == right
|
|
GprValue::Constant(value),
|
|
),
|
|
_ => (left, left),
|
|
},
|
|
// so
|
|
3 => (left, left),
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn mask_value(begin: u32, end: u32) -> u32 {
|
|
if begin <= end {
|
|
let mut mask = 0u32;
|
|
for bit in begin..=end {
|
|
mask |= 1 << (31 - bit);
|
|
}
|
|
mask
|
|
} else if begin == end + 1 {
|
|
u32::MAX
|
|
} else {
|
|
let mut mask = u32::MAX;
|
|
for bit in end + 1..begin {
|
|
mask &= !(1 << (31 - bit));
|
|
}
|
|
mask
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn is_load_op(op: Opcode) -> bool {
|
|
matches!(
|
|
op,
|
|
Opcode::Lbz
|
|
| Opcode::Lbzu
|
|
| Opcode::Lha
|
|
| Opcode::Lhau
|
|
| Opcode::Lhz
|
|
| Opcode::Lhzu
|
|
| Opcode::Lmw
|
|
| Opcode::Lwz
|
|
| Opcode::Lwzu
|
|
)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn is_loadf_op(op: Opcode) -> bool {
|
|
matches!(op, Opcode::Lfd | Opcode::Lfdu | Opcode::Lfs | Opcode::Lfsu)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn is_store_op(op: Opcode) -> bool {
|
|
matches!(
|
|
op,
|
|
Opcode::Stb
|
|
| Opcode::Stbu
|
|
| Opcode::Sth
|
|
| Opcode::Sthu
|
|
| Opcode::Stmw
|
|
| Opcode::Stw
|
|
| Opcode::Stwu
|
|
)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn is_storef_op(op: Opcode) -> bool {
|
|
matches!(op, Opcode::Stfd | Opcode::Stfdu | Opcode::Stfs | Opcode::Stfsu)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn is_load_store_op(op: Opcode) -> bool {
|
|
is_load_op(op) || is_loadf_op(op) || is_store_op(op) || is_storef_op(op)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn is_update_op(op: Opcode) -> bool {
|
|
matches!(
|
|
op,
|
|
Opcode::Lbzu
|
|
| Opcode::Lbzux
|
|
| Opcode::Lfdu
|
|
| Opcode::Lfdux
|
|
| Opcode::Lfsu
|
|
| Opcode::Lfsux
|
|
| Opcode::Lhau
|
|
| Opcode::Lhaux
|
|
| Opcode::Lhzu
|
|
| Opcode::Lhzux
|
|
| Opcode::Lwzu
|
|
| Opcode::Lwzux
|
|
| Opcode::Stbu
|
|
| Opcode::Stbux
|
|
| Opcode::Stfdu
|
|
| Opcode::Stfdux
|
|
| Opcode::Stfsu
|
|
| Opcode::Stfsux
|
|
| Opcode::Sthu
|
|
| Opcode::Sthux
|
|
| Opcode::Stwu
|
|
| Opcode::Stwux
|
|
)
|
|
}
|
|
|
|
// #[inline]
|
|
// fn is_indexed_load_op(op: Opcode) -> bool {
|
|
// matches!(
|
|
// op,
|
|
// Opcode::Lbzux
|
|
// | Opcode::Lbzx
|
|
// | Opcode::Lhax
|
|
// | Opcode::Lhaux
|
|
// | Opcode::Lhzx
|
|
// | Opcode::Lhzux
|
|
// | Opcode::Lwzx
|
|
// | Opcode::Lwzux
|
|
// )
|
|
// }
|
|
|
|
// #[cfg(test)]
|
|
// mod tests {
|
|
// use super::*;
|
|
//
|
|
// #[test]
|
|
// fn test_load_indexed_1() {
|
|
// let mut vm = VM::new();
|
|
// assert_eq!(vm.step(&Ins::new(0x3cc08052, 0x803dfe28)), StepResult::Continue); // lis r6, -0x7fae
|
|
// assert_eq!(vm.step(&Ins::new(0x38c60e18, 0x803dfe30)), StepResult::Continue); // addi r6, r6, 0xe18
|
|
// assert_eq!(vm.gpr[6].value, GprValue::Constant(0x80520e18));
|
|
// assert_eq!(vm.step(&Ins::new(0x550066fa, 0x803dfe34)), StepResult::Continue); // rlwinm r0, r8, 12, 27, 29
|
|
// assert_eq!(vm.gpr[0].value, GprValue::Range { min: 0, max: 28, step: 1 << 12 });
|
|
// assert_eq!(vm.step(&Ins::new(0x7d86002e, 0x803dfe3c)), StepResult::Continue); // lwzx r12, r6, r0
|
|
// assert_eq!(vm.gpr[12].value, GprValue::LoadIndexed {
|
|
// address: 0x80520e18,
|
|
// max_offset: NonZeroU32::new(28)
|
|
// });
|
|
// assert_eq!(vm.step(&Ins::new(0x7d8903a6, 0x803dfe4c)), StepResult::Continue); // mtspr CTR, r12
|
|
// assert_eq!(vm.ctr, GprValue::LoadIndexed {
|
|
// address: 0x80520e18,
|
|
// max_offset: NonZeroU32::new(28)
|
|
// });
|
|
// assert_eq!(
|
|
// vm.step(&Ins::new(0x4e800420, 0x803dfe50)), // bctr
|
|
// StepResult::Jump(BranchTarget::JumpTable {
|
|
// address: 0x80520e18,
|
|
// size: NonZeroU32::new(32)
|
|
// })
|
|
// );
|
|
// }
|
|
//
|
|
// #[test]
|
|
// fn test_load_indexed_2() {
|
|
// let mut vm = VM::new();
|
|
// assert_eq!(vm.step(&Ins::new(0x3c808057, 0x80465320)), StepResult::Continue); // lis r4, -0x7fa9
|
|
// assert_eq!(vm.step(&Ins::new(0x54600e7a, 0x80465324)), StepResult::Continue); // rlwinm r0, r3, 1, 25, 29
|
|
// assert_eq!(vm.gpr[0].value, GprValue::Range { min: 0, max: 124, step: 2 });
|
|
// assert_eq!(vm.step(&Ins::new(0x38840f70, 0x80465328)), StepResult::Continue); // addi r4, r4, 0xf70
|
|
// assert_eq!(vm.gpr[4].value, GprValue::Constant(0x80570f70));
|
|
// assert_eq!(vm.step(&Ins::new(0x7d84002e, 0x80465330)), StepResult::Continue); // lwzx r12, r4, r0
|
|
// assert_eq!(vm.gpr[12].value, GprValue::LoadIndexed {
|
|
// address: 0x80570f70,
|
|
// max_offset: NonZeroU32::new(124)
|
|
// });
|
|
// assert_eq!(vm.step(&Ins::new(0x7d8903a6, 0x80465340)), StepResult::Continue); // mtspr CTR, r12
|
|
// assert_eq!(vm.ctr, GprValue::LoadIndexed {
|
|
// address: 0x80570f70,
|
|
// max_offset: NonZeroU32::new(124)
|
|
// });
|
|
// assert_eq!(
|
|
// vm.step(&Ins::new(0x4e800420, 0x80465344)), // bctr
|
|
// StepResult::Jump(BranchTarget::JumpTable {
|
|
// address: 0x80570f70,
|
|
// size: NonZeroU32::new(128)
|
|
// })
|
|
// );
|
|
// }
|
|
//
|
|
// #[test]
|
|
// fn test_load_indexed_3() {
|
|
// let mut vm = VM::new();
|
|
// assert_eq!(vm.step(&Ins::new(0x28000127, 0x800ed458)), StepResult::Continue); // cmplwi r0, 0x127
|
|
// assert_eq!(vm.cr[0], Cr {
|
|
// signed: false,
|
|
// left: GprValue::Unknown,
|
|
// right: GprValue::Constant(295),
|
|
// });
|
|
//
|
|
// // When branch isn't taken, we know r0 is <= 295
|
|
// let mut false_vm = vm.clone();
|
|
// false_vm.gpr[0] =
|
|
// Gpr { value: GprValue::Range { min: 0, max: 295, step: 1 }, ..Default::default() };
|
|
// // When branch is taken, we know r0 is > 295
|
|
// let mut true_vm = vm.clone();
|
|
// true_vm.gpr[0] = Gpr {
|
|
// value: GprValue::Range { min: 296, max: u32::MAX, step: 1 },
|
|
// ..Default::default()
|
|
// };
|
|
// assert_eq!(
|
|
// vm.step(&Ins::new(0x418160bc, 0x800ed45c)), // bgt 0x60bc
|
|
// StepResult::Branch(vec![
|
|
// Branch {
|
|
// target: BranchTarget::Address(0x800ed460),
|
|
// link: false,
|
|
// vm: false_vm.clone()
|
|
// },
|
|
// Branch { target: BranchTarget::Address(0x800f3518), link: false, vm: true_vm }
|
|
// ])
|
|
// );
|
|
//
|
|
// // Take the false branch
|
|
// let mut vm = false_vm;
|
|
// assert_eq!(vm.step(&Ins::new(0x3c608053, 0x800ed460)), StepResult::Continue); // lis r3, -0x7fad
|
|
// assert_eq!(vm.step(&Ins::new(0x5400103a, 0x800ed464)), StepResult::Continue); // rlwinm r0, r0, 0x2, 0x0, 0x1d
|
|
// assert_eq!(vm.gpr[0].value, GprValue::Range { min: 0, max: 1180, step: 4 });
|
|
// assert_eq!(vm.step(&Ins::new(0x3863ef6c, 0x800ed468)), StepResult::Continue); // subi r3, r3, 0x1094
|
|
// assert_eq!(vm.gpr[3].value, GprValue::Constant(0x8052ef6c));
|
|
// assert_eq!(vm.step(&Ins::new(0x7c63002e, 0x800ed46c)), StepResult::Continue); // lwzx r3, r3, r0
|
|
// assert_eq!(vm.gpr[3].value, GprValue::LoadIndexed {
|
|
// address: 0x8052ef6c,
|
|
// max_offset: NonZeroU32::new(1180)
|
|
// });
|
|
// assert_eq!(vm.step(&Ins::new(0x7c6903a6, 0x800ed470)), StepResult::Continue); // mtspr CTR, r3
|
|
// assert_eq!(vm.ctr, GprValue::LoadIndexed {
|
|
// address: 0x8052ef6c,
|
|
// max_offset: NonZeroU32::new(1180)
|
|
// });
|
|
// assert_eq!(
|
|
// vm.step(&Ins::new(0x4e800420, 0x800ed474)), // bctr
|
|
// StepResult::Jump(BranchTarget::JumpTable {
|
|
// address: 0x8052ef6c,
|
|
// size: NonZeroU32::new(1184)
|
|
// })
|
|
// );
|
|
// }
|
|
// }
|