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 }, } #[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, /// Address that loads the lo part of this GPR pub lo_addr: Option, } 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 { 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 }, } #[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, } #[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), } pub fn section_address_for( obj: &ObjInfo, ins_addr: SectionAddress, target_addr: u32, ) -> Option { 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 { Box::default() } #[inline] pub fn new_from_obj(obj: &ObjInfo) -> Box { Self::new_with_base(obj.sda2_base, obj.sda_base) } #[inline] pub fn new_with_base(sda2_base: Option, sda_base: Option) -> Box { 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 { 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 { 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 { 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) // }) // ); // } // }