mirror of
https://github.com/encounter/decomp-toolkit.git
synced 2025-12-13 07:06:16 +00:00
Reorganize files; start RSO support; config & split updates
This commit is contained in:
380
src/analysis/cfa.rs
Normal file
380
src/analysis/cfa.rs
Normal file
@@ -0,0 +1,380 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
|
||||
use crate::{
|
||||
analysis::{
|
||||
executor::{ExecCbData, ExecCbResult, Executor},
|
||||
skip_alignment,
|
||||
slices::{FunctionSlices, TailCallResult},
|
||||
vm::{BranchTarget, GprValue, StepResult, VM},
|
||||
},
|
||||
obj::{ObjInfo, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind},
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AnalyzerState {
|
||||
pub sda_bases: Option<(u32, u32)>,
|
||||
pub function_entries: BTreeSet<u32>,
|
||||
pub function_bounds: BTreeMap<u32, u32>,
|
||||
pub function_slices: BTreeMap<u32, FunctionSlices>,
|
||||
pub jump_tables: BTreeMap<u32, u32>,
|
||||
pub known_symbols: BTreeMap<u32, ObjSymbol>,
|
||||
pub non_finalized_functions: BTreeMap<u32, FunctionSlices>,
|
||||
}
|
||||
|
||||
impl AnalyzerState {
|
||||
pub fn apply(&self, obj: &mut ObjInfo) -> Result<()> {
|
||||
for (&start, &end) in &self.function_bounds {
|
||||
if end == 0 {
|
||||
continue;
|
||||
}
|
||||
if let Some(existing_symbol) = obj
|
||||
.symbols
|
||||
.iter_mut()
|
||||
.find(|sym| sym.address == start as u64 && sym.kind == ObjSymbolKind::Function)
|
||||
{
|
||||
let new_size = (end - start) as u64;
|
||||
if !existing_symbol.size_known || existing_symbol.size == 0 {
|
||||
existing_symbol.size = new_size;
|
||||
existing_symbol.size_known = true;
|
||||
} else if existing_symbol.size != new_size {
|
||||
log::warn!(
|
||||
"Conflicting size for {}: was {:#X}, now {:#X}",
|
||||
existing_symbol.name,
|
||||
existing_symbol.size,
|
||||
new_size
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let section = obj
|
||||
.sections
|
||||
.iter()
|
||||
.find(|section| {
|
||||
(start as u64) >= section.address
|
||||
&& (end as u64) <= section.address + section.size
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
anyhow!("Failed to locate section for function {:#010X}-{:#010X}", start, end)
|
||||
})?;
|
||||
obj.symbols.push(ObjSymbol {
|
||||
name: format!("fn_{:08X}", start),
|
||||
demangled_name: None,
|
||||
address: start as u64,
|
||||
section: Some(section.index),
|
||||
size: (end - start) as u64,
|
||||
size_known: true,
|
||||
flags: Default::default(),
|
||||
kind: ObjSymbolKind::Function,
|
||||
});
|
||||
}
|
||||
for (&addr, &size) in &self.jump_tables {
|
||||
let section = obj
|
||||
.sections
|
||||
.iter()
|
||||
.find(|section| {
|
||||
(addr as u64) >= section.address
|
||||
&& ((addr + size) as u64) <= section.address + section.size
|
||||
})
|
||||
.ok_or_else(|| anyhow!("Failed to locate section for jump table"))?;
|
||||
if let Some(existing_symbol) = obj
|
||||
.symbols
|
||||
.iter_mut()
|
||||
.find(|sym| sym.address == addr as u64 && sym.kind == ObjSymbolKind::Object)
|
||||
{
|
||||
let new_size = size as u64;
|
||||
if !existing_symbol.size_known || existing_symbol.size == 0 {
|
||||
existing_symbol.size = new_size;
|
||||
existing_symbol.size_known = true;
|
||||
// existing_symbol.flags.0 &= ObjSymbolFlags::Global;
|
||||
// existing_symbol.flags.0 |= ObjSymbolFlags::Local;
|
||||
} else if existing_symbol.size != new_size {
|
||||
log::warn!(
|
||||
"Conflicting size for {}: was {:#X}, now {:#X}",
|
||||
existing_symbol.name,
|
||||
existing_symbol.size,
|
||||
new_size
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
obj.symbols.push(ObjSymbol {
|
||||
name: format!("jumptable_{:08X}", addr),
|
||||
demangled_name: None,
|
||||
address: addr as u64,
|
||||
section: Some(section.index),
|
||||
size: size as u64,
|
||||
size_known: true,
|
||||
flags: ObjSymbolFlagSet(ObjSymbolFlags::Local.into()),
|
||||
kind: ObjSymbolKind::Object,
|
||||
});
|
||||
}
|
||||
for (&_addr, symbol) in &self.known_symbols {
|
||||
if let Some(existing_symbol) = obj
|
||||
.symbols
|
||||
.iter_mut()
|
||||
.find(|e| symbol.address == e.address && symbol.kind == e.kind)
|
||||
{
|
||||
*existing_symbol = symbol.clone();
|
||||
continue;
|
||||
}
|
||||
obj.symbols.push(symbol.clone());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn detect_functions(&mut self, obj: &ObjInfo) -> Result<()> {
|
||||
// Process known functions first
|
||||
let known_functions = self.function_entries.clone();
|
||||
for addr in known_functions {
|
||||
self.process_function_at(obj, addr)?;
|
||||
}
|
||||
// Locate entry function bounds
|
||||
self.process_function_at(obj, obj.entry as u32)?;
|
||||
// Locate bounds for referenced functions until none are left
|
||||
self.process_functions(obj)?;
|
||||
// Final pass(es)
|
||||
while self.finalize_functions(obj, true)? {
|
||||
self.process_functions(obj)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn finalize_functions(&mut self, obj: &ObjInfo, finalize: bool) -> Result<bool> {
|
||||
let mut finalized = Vec::new();
|
||||
for (&addr, slices) in &mut self.non_finalized_functions {
|
||||
// log::info!("Trying to finalize {:#010X}", addr);
|
||||
let function_start = slices.start();
|
||||
let function_end = slices.end();
|
||||
let mut current = 0;
|
||||
while let Some(&block) = slices.possible_blocks.range(current + 4..).next() {
|
||||
current = block;
|
||||
match slices.check_tail_call(
|
||||
obj,
|
||||
block,
|
||||
function_start,
|
||||
function_end,
|
||||
&self.function_entries,
|
||||
) {
|
||||
TailCallResult::Not => {
|
||||
log::trace!("Finalized block @ {:#010X}", block);
|
||||
slices.possible_blocks.remove(&block);
|
||||
slices.analyze(
|
||||
obj,
|
||||
block,
|
||||
function_start,
|
||||
Some(function_end),
|
||||
&self.function_entries,
|
||||
)?;
|
||||
}
|
||||
TailCallResult::Is => {
|
||||
log::trace!("Finalized tail call @ {:#010X}", block);
|
||||
slices.possible_blocks.remove(&block);
|
||||
slices.function_references.insert(block);
|
||||
}
|
||||
TailCallResult::Possible => {
|
||||
if finalize {
|
||||
log::trace!(
|
||||
"Still couldn't determine {:#010X}, assuming non-tail-call",
|
||||
block
|
||||
);
|
||||
slices.possible_blocks.remove(&block);
|
||||
slices.analyze(
|
||||
obj,
|
||||
block,
|
||||
function_start,
|
||||
Some(function_end),
|
||||
&self.function_entries,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if slices.can_finalize() {
|
||||
log::trace!("Finalizing {:#010X}", addr);
|
||||
slices.finalize(obj, &self.function_entries)?;
|
||||
self.function_entries.append(&mut slices.function_references.clone());
|
||||
self.jump_tables.append(&mut slices.jump_table_references.clone());
|
||||
let end = slices.end();
|
||||
self.function_bounds.insert(addr, end);
|
||||
self.function_slices.insert(addr, slices.clone());
|
||||
finalized.push(addr);
|
||||
}
|
||||
}
|
||||
let finalized_new = !finalized.is_empty();
|
||||
for addr in finalized {
|
||||
self.non_finalized_functions.remove(&addr);
|
||||
}
|
||||
Ok(finalized_new)
|
||||
}
|
||||
|
||||
fn first_unbounded_function(&self) -> Option<u32> {
|
||||
let mut entries_iter = self.function_entries.iter().cloned();
|
||||
let mut bounds_iter = self.function_bounds.keys().cloned();
|
||||
let mut entry = entries_iter.next();
|
||||
let mut bound = bounds_iter.next();
|
||||
loop {
|
||||
match (entry, bound) {
|
||||
(Some(a), Some(b)) => {
|
||||
if b < a {
|
||||
bound = bounds_iter.next();
|
||||
continue;
|
||||
} else if a != b {
|
||||
if self.non_finalized_functions.contains_key(&a) {
|
||||
entry = entries_iter.next();
|
||||
continue;
|
||||
} else {
|
||||
break Some(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
(Some(a), None) => {
|
||||
if self.non_finalized_functions.contains_key(&a) {
|
||||
entry = entries_iter.next();
|
||||
continue;
|
||||
} else {
|
||||
break Some(a);
|
||||
}
|
||||
}
|
||||
_ => break None,
|
||||
}
|
||||
entry = entries_iter.next();
|
||||
bound = bounds_iter.next();
|
||||
}
|
||||
}
|
||||
|
||||
fn process_functions(&mut self, obj: &ObjInfo) -> Result<()> {
|
||||
loop {
|
||||
match self.first_unbounded_function() {
|
||||
Some(addr) => {
|
||||
log::trace!("Processing {:#010X}", addr);
|
||||
self.process_function_at(&obj, addr)?;
|
||||
}
|
||||
None => {
|
||||
if !self.finalize_functions(obj, false)? {
|
||||
if !self.detect_new_functions(obj)? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn process_function_at(&mut self, obj: &ObjInfo, addr: u32) -> Result<bool> {
|
||||
if addr == 0 || addr == 0xFFFFFFFF {
|
||||
log::warn!("Tried to detect @ {:#010X}", addr);
|
||||
self.function_bounds.insert(addr, 0);
|
||||
return Ok(false);
|
||||
}
|
||||
Ok(if let Some(mut slices) = self.process_function(obj, addr)? {
|
||||
self.function_entries.insert(addr);
|
||||
self.function_entries.append(&mut slices.function_references.clone());
|
||||
self.jump_tables.append(&mut slices.jump_table_references.clone());
|
||||
if slices.can_finalize() {
|
||||
slices.finalize(obj, &self.function_entries)?;
|
||||
self.function_bounds.insert(addr, slices.end());
|
||||
self.function_slices.insert(addr, slices);
|
||||
} else {
|
||||
self.non_finalized_functions.insert(addr, slices);
|
||||
}
|
||||
true
|
||||
} else {
|
||||
log::debug!("Not a function @ {:#010X}", addr);
|
||||
self.function_bounds.insert(addr, 0);
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
fn process_function(&mut self, obj: &ObjInfo, start: u32) -> Result<Option<FunctionSlices>> {
|
||||
let mut slices = FunctionSlices::default();
|
||||
let function_end = self.function_bounds.get(&start).cloned();
|
||||
if start == 0x801FC300 {
|
||||
log::info!("Processing TRKExceptionHandler");
|
||||
}
|
||||
Ok(match slices.analyze(obj, start, start, function_end, &self.function_entries)? {
|
||||
true => Some(slices),
|
||||
false => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn detect_new_functions(&mut self, obj: &ObjInfo) -> Result<bool> {
|
||||
let mut found_new = false;
|
||||
let mut iter = self.function_bounds.iter().peekable();
|
||||
while let (Some((&first_begin, &first_end)), Some(&(&second_begin, &second_end))) =
|
||||
(iter.next(), iter.peek())
|
||||
{
|
||||
if first_end == 0 || first_end > second_begin {
|
||||
continue;
|
||||
}
|
||||
let addr = match skip_alignment(obj, first_end, second_begin) {
|
||||
Some(addr) => addr,
|
||||
None => continue,
|
||||
};
|
||||
if second_begin > addr && self.function_entries.insert(addr) {
|
||||
log::trace!(
|
||||
"Trying function @ {:#010X} (from {:#010X}-{:#010X} <-> {:#010X}-{:#010X})",
|
||||
addr,
|
||||
first_begin,
|
||||
first_end,
|
||||
second_begin,
|
||||
second_end,
|
||||
);
|
||||
found_new = true;
|
||||
}
|
||||
}
|
||||
Ok(found_new)
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute VM from entry point following branches and function calls
|
||||
/// until SDA bases are initialized (__init_registers)
|
||||
pub fn locate_sda_bases(obj: &mut ObjInfo) -> Result<bool> {
|
||||
let mut executor = Executor::new(obj);
|
||||
executor.push(obj.entry as u32, VM::new(), false);
|
||||
let result = executor.run(
|
||||
obj,
|
||||
|ExecCbData { executor, vm, result, section: _, ins, block_start: _ }| {
|
||||
match result {
|
||||
StepResult::Continue | StepResult::LoadStore { .. } => {
|
||||
return Ok(ExecCbResult::Continue);
|
||||
}
|
||||
StepResult::Illegal => bail!("Illegal instruction @ {:#010X}", ins.addr),
|
||||
StepResult::Jump(target) => match target {
|
||||
BranchTarget::Address(addr) => {
|
||||
return Ok(ExecCbResult::Jump(addr));
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
StepResult::Branch(branches) => {
|
||||
for branch in branches {
|
||||
match branch.target {
|
||||
BranchTarget::Address(addr) => {
|
||||
executor.push(addr, branch.vm, false);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let (GprValue::Constant(sda2_base), GprValue::Constant(sda_base)) =
|
||||
(vm.gpr_value(2), vm.gpr_value(13))
|
||||
{
|
||||
return Ok(ExecCbResult::End((sda2_base, sda_base)));
|
||||
}
|
||||
|
||||
Ok(ExecCbResult::EndBlock)
|
||||
},
|
||||
)?;
|
||||
match result {
|
||||
Some((sda2_base, sda_base)) => {
|
||||
obj.sda2_base = Some(sda2_base);
|
||||
obj.sda_base = Some(sda_base);
|
||||
Ok(true)
|
||||
}
|
||||
None => Ok(false),
|
||||
}
|
||||
}
|
||||
146
src/analysis/executor.rs
Normal file
146
src/analysis/executor.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
use anyhow::Result;
|
||||
use fixedbitset::FixedBitSet;
|
||||
use ppc750cl::Ins;
|
||||
|
||||
use crate::{
|
||||
analysis::{
|
||||
disassemble,
|
||||
vm::{StepResult, VM},
|
||||
},
|
||||
obj::{ObjInfo, ObjSection, ObjSectionKind},
|
||||
};
|
||||
|
||||
/// Space-efficient implementation for tracking visited code addresses
|
||||
struct VisitedAddresses {
|
||||
inner: Vec<FixedBitSet>,
|
||||
}
|
||||
|
||||
impl VisitedAddresses {
|
||||
pub fn new(obj: &ObjInfo) -> Self {
|
||||
let mut inner = Vec::with_capacity(obj.sections.len());
|
||||
for section in &obj.sections {
|
||||
if section.kind == ObjSectionKind::Code {
|
||||
let size = (section.size / 4) as usize;
|
||||
inner.push(FixedBitSet::with_capacity(size));
|
||||
} else {
|
||||
// Empty
|
||||
inner.push(FixedBitSet::new())
|
||||
}
|
||||
}
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
pub fn contains(&self, section: &ObjSection, address: u32) -> bool {
|
||||
self.inner[section.index].contains(Self::bit_for(section, address))
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, section: &ObjSection, address: u32) {
|
||||
self.inner[section.index].insert(Self::bit_for(section, address));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bit_for(section: &ObjSection, address: u32) -> usize {
|
||||
((address as u64 - section.address) / 4) as usize
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VMState {
|
||||
pub vm: Box<VM>,
|
||||
pub address: u32,
|
||||
}
|
||||
|
||||
/// Helper for branched VM execution, only visiting addresses once.
|
||||
pub struct Executor {
|
||||
vm_stack: Vec<VMState>,
|
||||
visited: VisitedAddresses,
|
||||
}
|
||||
|
||||
pub struct ExecCbData<'a> {
|
||||
pub executor: &'a mut Executor,
|
||||
pub vm: &'a mut VM,
|
||||
pub result: StepResult,
|
||||
pub section: &'a ObjSection,
|
||||
pub ins: &'a Ins,
|
||||
pub block_start: u32,
|
||||
}
|
||||
|
||||
pub enum ExecCbResult<T = ()> {
|
||||
Continue,
|
||||
Jump(u32),
|
||||
EndBlock,
|
||||
End(T),
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
pub fn new(obj: &ObjInfo) -> Self {
|
||||
Self { vm_stack: vec![], visited: VisitedAddresses::new(obj) }
|
||||
}
|
||||
|
||||
pub fn run<Cb, R>(&mut self, obj: &ObjInfo, mut cb: Cb) -> Result<Option<R>>
|
||||
where Cb: FnMut(ExecCbData) -> Result<ExecCbResult<R>> {
|
||||
while let Some(mut state) = self.vm_stack.pop() {
|
||||
let section = match obj.section_at(state.address) {
|
||||
Ok(section) => section,
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
// return Ok(None);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if section.kind != ObjSectionKind::Code {
|
||||
log::warn!("Attempted to visit non-code address {:#010X}", state.address);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Already visited block
|
||||
if self.visited.contains(section, state.address) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut block_start = state.address;
|
||||
loop {
|
||||
self.visited.insert(section, state.address);
|
||||
|
||||
let ins = match disassemble(section, state.address) {
|
||||
Some(ins) => ins,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let result = state.vm.step(&ins);
|
||||
match cb(ExecCbData {
|
||||
executor: self,
|
||||
vm: &mut state.vm,
|
||||
result,
|
||||
section,
|
||||
ins: &ins,
|
||||
block_start,
|
||||
})? {
|
||||
ExecCbResult::Continue => {
|
||||
state.address += 4;
|
||||
}
|
||||
ExecCbResult::Jump(addr) => {
|
||||
if self.visited.contains(section, addr) {
|
||||
break;
|
||||
}
|
||||
block_start = addr;
|
||||
state.address = addr;
|
||||
}
|
||||
ExecCbResult::EndBlock => break,
|
||||
ExecCbResult::End(result) => return Ok(Some(result)),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn push(&mut self, address: u32, vm: Box<VM>, sort: bool) {
|
||||
self.vm_stack.push(VMState { address, vm });
|
||||
if sort {
|
||||
// Sort lowest to highest, so we always go highest address first
|
||||
self.vm_stack.sort_by_key(|state| state.address);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn visited(&self, section: &ObjSection, address: u32) -> bool {
|
||||
self.visited.contains(section, address)
|
||||
}
|
||||
}
|
||||
107
src/analysis/mod.rs
Normal file
107
src/analysis/mod.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use std::{collections::BTreeSet, num::NonZeroU32};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use ppc750cl::Ins;
|
||||
|
||||
use crate::obj::{ObjInfo, ObjSection, ObjSectionKind};
|
||||
|
||||
pub mod cfa;
|
||||
pub mod executor;
|
||||
pub mod pass;
|
||||
pub mod slices;
|
||||
pub mod tracker;
|
||||
pub mod vm;
|
||||
|
||||
pub fn disassemble(section: &ObjSection, address: u32) -> Option<Ins> {
|
||||
read_u32(§ion.data, address, section.address as u32).map(|code| Ins::new(code, address))
|
||||
}
|
||||
|
||||
pub fn read_u32(data: &[u8], address: u32, section_address: u32) -> Option<u32> {
|
||||
let offset = (address - section_address) as usize;
|
||||
if data.len() < offset + 4 {
|
||||
return None;
|
||||
}
|
||||
Some(u32::from_be_bytes(data[offset..offset + 4].try_into().unwrap()))
|
||||
}
|
||||
|
||||
fn is_valid_jump_table_addr(obj: &ObjInfo, addr: u32) -> bool {
|
||||
matches!(obj.section_at(addr), Ok(section) if section.kind != ObjSectionKind::Bss)
|
||||
}
|
||||
|
||||
fn get_jump_table_entries(
|
||||
obj: &ObjInfo,
|
||||
addr: u32,
|
||||
size: Option<NonZeroU32>,
|
||||
from: u32,
|
||||
function_start: u32,
|
||||
function_end: u32,
|
||||
) -> Result<(Vec<u32>, u32)> {
|
||||
let section = obj.section_at(addr).with_context(|| {
|
||||
format!("Failed to get jump table entries @ {:#010X} size {:?}", addr, size)
|
||||
})?;
|
||||
let offset = (addr as u64 - section.address) as usize;
|
||||
if let Some(size) = size.map(|n| n.get()) {
|
||||
log::trace!(
|
||||
"Located jump table @ {:#010X} with entry count {} (from {:#010X})",
|
||||
addr,
|
||||
size / 4,
|
||||
from
|
||||
);
|
||||
let jt_data = §ion.data[offset..offset + size as usize];
|
||||
let entries =
|
||||
jt_data.chunks_exact(4).map(|c| u32::from_be_bytes(c.try_into().unwrap())).collect();
|
||||
Ok((entries, size))
|
||||
} else {
|
||||
let mut entries = Vec::new();
|
||||
let mut cur_addr = addr;
|
||||
while let Some(value) = read_u32(§ion.data, cur_addr, section.address as u32) {
|
||||
if value < function_start || value >= function_end {
|
||||
break;
|
||||
}
|
||||
entries.push(value);
|
||||
cur_addr += 4;
|
||||
}
|
||||
let size = cur_addr - addr;
|
||||
log::debug!(
|
||||
"Guessed jump table @ {:#010X} with entry count {} (from {:#010X})",
|
||||
addr,
|
||||
size / 4,
|
||||
from
|
||||
);
|
||||
Ok((entries, size))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uniq_jump_table_entries(
|
||||
obj: &ObjInfo,
|
||||
addr: u32,
|
||||
size: Option<NonZeroU32>,
|
||||
from: u32,
|
||||
function_start: u32,
|
||||
function_end: u32,
|
||||
) -> Result<(BTreeSet<u32>, u32)> {
|
||||
if !is_valid_jump_table_addr(obj, addr) {
|
||||
return Ok((BTreeSet::new(), 0));
|
||||
}
|
||||
let (entries, size) =
|
||||
get_jump_table_entries(obj, addr, size, from, function_start, function_end)?;
|
||||
Ok((BTreeSet::from_iter(entries.iter().cloned().filter(|&addr| addr != 0)), size))
|
||||
}
|
||||
|
||||
pub fn skip_alignment(obj: &ObjInfo, mut addr: u32, end: u32) -> Option<u32> {
|
||||
let mut data = match obj.section_data(addr, end) {
|
||||
Ok((_, data)) => data,
|
||||
Err(_) => return None,
|
||||
};
|
||||
loop {
|
||||
if data.is_empty() {
|
||||
break None;
|
||||
}
|
||||
if data[0..4] == [0u8; 4] {
|
||||
addr += 4;
|
||||
data = &data[4..];
|
||||
} else {
|
||||
break Some(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
117
src/analysis/pass.rs
Normal file
117
src/analysis/pass.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use anyhow::Result;
|
||||
use flagset::FlagSet;
|
||||
|
||||
use crate::{
|
||||
analysis::cfa::AnalyzerState,
|
||||
obj::{ObjInfo, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind},
|
||||
};
|
||||
|
||||
pub trait AnalysisPass {
|
||||
fn execute(state: &mut AnalyzerState, obj: &ObjInfo) -> Result<()>;
|
||||
}
|
||||
|
||||
pub struct FindTRKInterruptVectorTable {}
|
||||
|
||||
pub const TRK_TABLE_HEADER: &str = "Metrowerks Target Resident Kernel for PowerPC";
|
||||
pub const TRK_TABLE_SIZE: u32 = 0x1F34; // always?
|
||||
|
||||
// TRK_MINNOW_DOLPHIN.a __exception.s
|
||||
impl AnalysisPass for FindTRKInterruptVectorTable {
|
||||
fn execute(state: &mut AnalyzerState, obj: &ObjInfo) -> Result<()> {
|
||||
for (&start, _) in state.function_bounds.iter().filter(|&(_, &end)| end == 0) {
|
||||
let (section, data) = match obj.section_data(start, 0) {
|
||||
Ok((section, data)) => (section, data),
|
||||
Err(_) => continue,
|
||||
};
|
||||
if data.starts_with(TRK_TABLE_HEADER.as_bytes())
|
||||
&& data[TRK_TABLE_HEADER.as_bytes().len()] == 0
|
||||
{
|
||||
log::info!("Found gTRKInterruptVectorTable @ {:#010X}", start);
|
||||
state.known_symbols.insert(start, ObjSymbol {
|
||||
name: "gTRKInterruptVectorTable".to_string(),
|
||||
demangled_name: None,
|
||||
address: start as u64,
|
||||
section: Some(section.index),
|
||||
size: 0,
|
||||
size_known: true,
|
||||
flags: ObjSymbolFlagSet(FlagSet::from(ObjSymbolFlags::Global)),
|
||||
kind: ObjSymbolKind::Unknown,
|
||||
});
|
||||
let end = start + TRK_TABLE_SIZE;
|
||||
state.known_symbols.insert(end, ObjSymbol {
|
||||
name: "gTRKInterruptVectorTableEnd".to_string(),
|
||||
demangled_name: None,
|
||||
address: end as u64,
|
||||
section: Some(section.index),
|
||||
size: 0,
|
||||
size_known: true,
|
||||
flags: ObjSymbolFlagSet(FlagSet::from(ObjSymbolFlags::Global)),
|
||||
kind: ObjSymbolKind::Unknown,
|
||||
});
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
log::info!("gTRKInterruptVectorTable not found");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FindSaveRestSleds {}
|
||||
|
||||
const SLEDS: [([u8; 4], &'static str, &'static str); 4] = [
|
||||
([0xd9, 0xcb, 0xff, 0x70], "__save_fpr", "_savefpr_"),
|
||||
([0xc9, 0xcb, 0xff, 0x70], "__restore_fpr", "_restfpr_"),
|
||||
([0x91, 0xcb, 0xff, 0xb8], "__save_gpr", "_savegpr_"),
|
||||
([0x81, 0xcb, 0xff, 0xb8], "__restore_gpr", "_restgpr_"),
|
||||
];
|
||||
|
||||
// Runtime.PPCEABI.H.a runtime.c
|
||||
impl AnalysisPass for FindSaveRestSleds {
|
||||
fn execute(state: &mut AnalyzerState, obj: &ObjInfo) -> Result<()> {
|
||||
const SLED_SIZE: usize = 19 * 4; // registers 14-31 + blr
|
||||
let mut clear_ranges: Vec<Range<u32>> = vec![];
|
||||
for (&start, _) in state.function_bounds.iter().filter(|&(_, &end)| end != 0) {
|
||||
let (section, data) = obj.section_data(start, 0)?;
|
||||
for (needle, func, label) in &SLEDS {
|
||||
if data.starts_with(needle) {
|
||||
log::info!("Found {} @ {:#010X}", func, start);
|
||||
clear_ranges.push(start + 4..start + SLED_SIZE as u32);
|
||||
state.known_symbols.insert(start, ObjSymbol {
|
||||
name: func.to_string(),
|
||||
demangled_name: None,
|
||||
address: start as u64,
|
||||
section: Some(section.index),
|
||||
size: SLED_SIZE as u64,
|
||||
size_known: true,
|
||||
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
||||
kind: ObjSymbolKind::Function,
|
||||
});
|
||||
for i in 14..=31 {
|
||||
let addr = start + (i - 14) * 4;
|
||||
state.known_symbols.insert(addr, ObjSymbol {
|
||||
name: format!("{}{}", label, i),
|
||||
demangled_name: None,
|
||||
address: addr as u64,
|
||||
section: Some(section.index),
|
||||
size: 0,
|
||||
size_known: true,
|
||||
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
||||
kind: ObjSymbolKind::Unknown,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for range in clear_ranges {
|
||||
for addr in range.step_by(4) {
|
||||
state.function_entries.remove(&addr);
|
||||
state.function_bounds.remove(&addr);
|
||||
state.function_slices.remove(&addr);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
500
src/analysis/slices.rs
Normal file
500
src/analysis/slices.rs
Normal file
@@ -0,0 +1,500 @@
|
||||
use std::{
|
||||
collections::{btree_map, BTreeMap, BTreeSet},
|
||||
ops::Range,
|
||||
};
|
||||
|
||||
use anyhow::{bail, ensure, Context, Result};
|
||||
use ppc750cl::{Ins, Opcode};
|
||||
|
||||
use crate::{
|
||||
analysis::{
|
||||
disassemble,
|
||||
executor::{ExecCbData, ExecCbResult, Executor},
|
||||
uniq_jump_table_entries,
|
||||
vm::{BranchTarget, StepResult, VM},
|
||||
},
|
||||
obj::{ObjInfo, ObjSection},
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct FunctionSlices {
|
||||
pub blocks: BTreeMap<u32, u32>,
|
||||
pub branches: BTreeMap<u32, Vec<u32>>,
|
||||
pub function_references: BTreeSet<u32>,
|
||||
pub jump_table_references: BTreeMap<u32, u32>,
|
||||
pub prologue: Option<u32>,
|
||||
pub epilogue: Option<u32>,
|
||||
// Either a block or tail call
|
||||
pub possible_blocks: BTreeSet<u32>,
|
||||
pub has_conditional_blr: bool,
|
||||
pub has_rfi: bool,
|
||||
pub finalized: bool,
|
||||
}
|
||||
|
||||
pub enum TailCallResult {
|
||||
Not,
|
||||
Is,
|
||||
Possible,
|
||||
}
|
||||
|
||||
type BlockRange = Range<u32>;
|
||||
|
||||
impl FunctionSlices {
|
||||
pub fn end(&self) -> u32 { self.blocks.last_key_value().map(|(_, &end)| end).unwrap_or(0) }
|
||||
|
||||
pub fn start(&self) -> u32 {
|
||||
self.blocks.first_key_value().map(|(&start, _)| start).unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn add_block_start(&mut self, addr: u32) -> bool {
|
||||
// Slice previous block.
|
||||
if let Some((_, end)) = self.blocks.range_mut(..addr).last() {
|
||||
let last_end = *end;
|
||||
if last_end > addr {
|
||||
*end = addr;
|
||||
self.blocks.insert(addr, last_end);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Otherwise, insert with no end.
|
||||
match self.blocks.entry(addr) {
|
||||
btree_map::Entry::Vacant(e) => {
|
||||
e.insert(0);
|
||||
true
|
||||
}
|
||||
btree_map::Entry::Occupied(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_prologue(&mut self, section: &ObjSection, ins: &Ins) -> Result<()> {
|
||||
let next_ins = match disassemble(section, ins.addr + 4) {
|
||||
Some(ins) => ins,
|
||||
None => return Ok(()),
|
||||
};
|
||||
// stwu r1, d(r1)
|
||||
// mfspr r0, LR
|
||||
if ((ins.op == Opcode::Stwu && ins.field_rS() == 1 && ins.field_rA() == 1)
|
||||
&& (next_ins.op == Opcode::Mfspr
|
||||
&& next_ins.field_rD() == 0
|
||||
&& next_ins.field_spr() == 8))
|
||||
// mfspr r0, LR
|
||||
// stw r0, d(r1)
|
||||
|| ((ins.op == Opcode::Mfspr && ins.field_rD() == 0 && ins.field_spr() == 8)
|
||||
&& (next_ins.op == Opcode::Stw
|
||||
&& next_ins.field_rS() == 0
|
||||
&& next_ins.field_rA() == 1))
|
||||
{
|
||||
match self.prologue {
|
||||
Some(prologue) if prologue != ins.addr && prologue != ins.addr - 4 => {
|
||||
bail!("Found duplicate prologue: {:#010X} and {:#010X}", prologue, ins.addr)
|
||||
}
|
||||
_ => self.prologue = Some(ins.addr),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_epilogue(&mut self, section: &ObjSection, ins: &Ins) -> Result<()> {
|
||||
let next_ins = match disassemble(section, ins.addr + 4) {
|
||||
Some(ins) => ins,
|
||||
None => return Ok(()),
|
||||
};
|
||||
// mtspr SPR, r0
|
||||
// addi rD, rA, SIMM
|
||||
if ((ins.op == Opcode::Mtspr && ins.field_rS() == 0 && ins.field_spr() == 8)
|
||||
&& (next_ins.op == Opcode::Addi
|
||||
&& next_ins.field_rD() == 1
|
||||
&& next_ins.field_rA() == 1))
|
||||
// or r1, rA, rB
|
||||
// mtspr SPR, r0
|
||||
|| ((ins.op == Opcode::Or && ins.field_rA() == 1)
|
||||
&& (next_ins.op == Opcode::Mtspr
|
||||
&& next_ins.field_rS() == 0
|
||||
&& next_ins.field_spr() == 8))
|
||||
{
|
||||
match self.epilogue {
|
||||
Some(epilogue) if epilogue != ins.addr => {
|
||||
bail!("Found duplicate epilogue: {:#010X} and {:#010X}", epilogue, ins.addr)
|
||||
}
|
||||
_ => self.epilogue = Some(ins.addr),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn instruction_callback(
|
||||
&mut self,
|
||||
data: ExecCbData,
|
||||
obj: &ObjInfo,
|
||||
function_start: u32,
|
||||
function_end: Option<u32>,
|
||||
known_functions: &BTreeSet<u32>,
|
||||
) -> Result<ExecCbResult<bool>> {
|
||||
let ExecCbData { executor, vm, result, section, ins, block_start } = data;
|
||||
|
||||
// Track discovered prologue(s) and epilogue(s)
|
||||
self.check_prologue(section, ins)
|
||||
.with_context(|| format!("While processing {:#010X}", function_start))?;
|
||||
self.check_epilogue(section, ins)
|
||||
.with_context(|| format!("While processing {:#010X}", function_start))?;
|
||||
if !self.has_conditional_blr && is_conditional_blr(&ins) {
|
||||
self.has_conditional_blr = true;
|
||||
}
|
||||
if !self.has_rfi && ins.op == Opcode::Rfi {
|
||||
self.has_rfi = true;
|
||||
}
|
||||
// If control flow hits a block we thought may be a tail call,
|
||||
// we know it isn't.
|
||||
if self.possible_blocks.contains(&ins.addr) {
|
||||
self.possible_blocks.remove(&ins.addr);
|
||||
}
|
||||
|
||||
match result {
|
||||
StepResult::Continue | StepResult::LoadStore { .. } => {
|
||||
let next_address = ins.addr + 4;
|
||||
// If we already visited the next address, connect the blocks and end
|
||||
if executor.visited(section, next_address) {
|
||||
self.blocks.insert(block_start, next_address);
|
||||
self.branches.insert(ins.addr, vec![next_address]);
|
||||
Ok(ExecCbResult::EndBlock)
|
||||
} else {
|
||||
Ok(ExecCbResult::Continue)
|
||||
}
|
||||
}
|
||||
StepResult::Illegal => {
|
||||
log::debug!("Illegal instruction @ {:#010X}", ins.addr);
|
||||
Ok(ExecCbResult::End(false))
|
||||
}
|
||||
StepResult::Jump(target) => match target {
|
||||
BranchTarget::Unknown => {
|
||||
// Likely end of function
|
||||
let next_addr = ins.addr + 4;
|
||||
self.blocks.insert(block_start, next_addr);
|
||||
// If this function has a prologue but no epilogue, and this
|
||||
// instruction is a bctr, we can assume it's an unrecovered
|
||||
// jump table and continue analysis.
|
||||
if self.prologue.is_some() && self.epilogue.is_none() {
|
||||
log::debug!("Assuming unrecovered jump table {:#010X}", next_addr);
|
||||
self.branches.insert(ins.addr, vec![next_addr]);
|
||||
if self.add_block_start(next_addr) {
|
||||
executor.push(next_addr, vm.clone_for_return(), true);
|
||||
}
|
||||
}
|
||||
Ok(ExecCbResult::EndBlock)
|
||||
}
|
||||
BranchTarget::Return => {
|
||||
self.blocks.insert(block_start, ins.addr + 4);
|
||||
Ok(ExecCbResult::EndBlock)
|
||||
}
|
||||
BranchTarget::Address(addr) => {
|
||||
// End of block
|
||||
self.blocks.insert(block_start, ins.addr + 4);
|
||||
self.branches.insert(ins.addr, vec![addr]);
|
||||
if addr == ins.addr {
|
||||
// Infinite loop
|
||||
} else if addr >= function_start
|
||||
&& matches!(function_end, Some(known_end) if addr < known_end)
|
||||
{
|
||||
// If target is within known function bounds, jump
|
||||
if self.add_block_start(addr) {
|
||||
return Ok(ExecCbResult::Jump(addr));
|
||||
}
|
||||
} else if matches!(obj.section_data(ins.addr, ins.addr + 4), Ok((_, data)) if data == [0u8; 4])
|
||||
{
|
||||
// If this branch has zeroed padding after it, assume tail call.
|
||||
self.function_references.insert(addr);
|
||||
} else {
|
||||
self.possible_blocks.insert(addr);
|
||||
}
|
||||
Ok(ExecCbResult::EndBlock)
|
||||
}
|
||||
BranchTarget::JumpTable { address, size } => {
|
||||
// End of block
|
||||
let next_address = ins.addr + 4;
|
||||
self.blocks.insert(block_start, next_address);
|
||||
|
||||
let (mut entries, size) = uniq_jump_table_entries(
|
||||
obj,
|
||||
address,
|
||||
size,
|
||||
ins.addr,
|
||||
function_start,
|
||||
function_end.unwrap_or_else(|| self.end()),
|
||||
)?;
|
||||
if entries.contains(&next_address)
|
||||
&& !entries.iter().any(|addr| known_functions.contains(addr))
|
||||
{
|
||||
self.jump_table_references.insert(address, size);
|
||||
let mut branches = vec![];
|
||||
for addr in entries {
|
||||
branches.push(addr);
|
||||
if self.add_block_start(addr) {
|
||||
executor.push(addr, vm.clone_all(), true);
|
||||
}
|
||||
}
|
||||
self.branches.insert(ins.addr, branches);
|
||||
} else {
|
||||
// If the table doesn't contain the next address,
|
||||
// it could be a function jump table instead
|
||||
self.possible_blocks.append(&mut entries);
|
||||
}
|
||||
Ok(ExecCbResult::EndBlock)
|
||||
}
|
||||
},
|
||||
StepResult::Branch(branches) => {
|
||||
// End of block
|
||||
self.blocks.insert(block_start, ins.addr + 4);
|
||||
|
||||
let mut out_branches = vec![];
|
||||
for branch in branches {
|
||||
match branch.target {
|
||||
BranchTarget::Unknown | BranchTarget::Return => {
|
||||
continue;
|
||||
}
|
||||
BranchTarget::Address(addr) => {
|
||||
if branch.link || known_functions.contains(&addr) {
|
||||
self.function_references.insert(addr);
|
||||
} else {
|
||||
out_branches.push(addr);
|
||||
if self.add_block_start(addr) {
|
||||
executor.push(addr, branch.vm, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
BranchTarget::JumpTable { .. } => {
|
||||
bail!("Conditional jump table unsupported @ {:#010X}", ins.addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !out_branches.is_empty() {
|
||||
self.branches.insert(ins.addr, out_branches);
|
||||
}
|
||||
Ok(ExecCbResult::EndBlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn analyze(
|
||||
&mut self,
|
||||
obj: &ObjInfo,
|
||||
start: u32,
|
||||
function_start: u32,
|
||||
function_end: Option<u32>,
|
||||
known_functions: &BTreeSet<u32>,
|
||||
) -> Result<bool> {
|
||||
if !self.add_block_start(start) {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let mut executor = Executor::new(obj);
|
||||
executor.push(start, VM::new_from_obj(obj), false);
|
||||
let result = executor.run(obj, |data| {
|
||||
self.instruction_callback(data, obj, function_start, function_end, known_functions)
|
||||
})?;
|
||||
if matches!(result, Some(b) if !b) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Visit unreachable blocks
|
||||
while let Some((first, _)) = self.first_disconnected_block() {
|
||||
executor.push(first.end, VM::new_from_obj(obj), true);
|
||||
let result = executor.run(obj, |data| {
|
||||
self.instruction_callback(data, obj, function_start, function_end, known_functions)
|
||||
})?;
|
||||
if matches!(result, Some(b) if !b) {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Visit trailing blocks
|
||||
if let Some(known_end) = function_end {
|
||||
while self.end() < known_end {
|
||||
executor.push(self.end(), VM::new_from_obj(obj), true);
|
||||
let result = executor.run(obj, |data| {
|
||||
self.instruction_callback(
|
||||
data,
|
||||
obj,
|
||||
function_start,
|
||||
function_end,
|
||||
known_functions,
|
||||
)
|
||||
})?;
|
||||
if matches!(result, Some(b) if !b) {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
for (&start, &end) in &self.blocks {
|
||||
ensure!(end != 0, "Failed to finalize block @ {start:#010X}");
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub fn can_finalize(&self) -> bool { self.possible_blocks.is_empty() }
|
||||
|
||||
pub fn finalize(&mut self, obj: &ObjInfo, known_functions: &BTreeSet<u32>) -> Result<()> {
|
||||
ensure!(!self.finalized, "Already finalized");
|
||||
ensure!(self.can_finalize(), "Can't finalize");
|
||||
|
||||
match (self.prologue, self.epilogue) {
|
||||
(Some(_), Some(_)) | (None, None) => {}
|
||||
(Some(_), None) => {
|
||||
// Likely __noreturn
|
||||
}
|
||||
(None, Some(e)) => {
|
||||
log::info!("{:#010X?}", self);
|
||||
bail!("Unpaired epilogue {:#010X}", e);
|
||||
}
|
||||
}
|
||||
|
||||
let end = self.end();
|
||||
if let Ok(section) = obj.section_at(end) {
|
||||
// FIXME this is real bad
|
||||
if !self.has_conditional_blr {
|
||||
if let Some(ins) = disassemble(§ion, end - 4) {
|
||||
if ins.op == Opcode::B {
|
||||
if self.function_references.contains(&ins.branch_dest().unwrap()) {
|
||||
for (_, branches) in &self.branches {
|
||||
if branches.len() > 1
|
||||
&& branches.contains(self.blocks.last_key_value().unwrap().0)
|
||||
{
|
||||
self.has_conditional_blr = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MWCC optimization sometimes leaves an unreachable blr
|
||||
// after generating a conditional blr in the function.
|
||||
if self.has_conditional_blr {
|
||||
if matches!(disassemble(§ion, end - 4), Some(ins) if !ins.is_blr())
|
||||
&& matches!(disassemble(§ion, end), Some(ins) if ins.is_blr())
|
||||
&& !known_functions.contains(&end)
|
||||
{
|
||||
log::trace!("Found trailing blr @ {:#010X}, merging with function", end);
|
||||
self.blocks.insert(end, end + 4);
|
||||
}
|
||||
}
|
||||
|
||||
// Some functions with rfi also include a trailing nop
|
||||
if self.has_rfi {
|
||||
if matches!(disassemble(§ion, end), Some(ins) if is_nop(&ins))
|
||||
&& !known_functions.contains(&end)
|
||||
{
|
||||
log::trace!("Found trailing nop @ {:#010X}, merging with function", end);
|
||||
self.blocks.insert(end, end + 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.finalized = true;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_tail_call(
|
||||
&mut self,
|
||||
obj: &ObjInfo,
|
||||
addr: u32,
|
||||
function_start: u32,
|
||||
function_end: u32,
|
||||
known_functions: &BTreeSet<u32>,
|
||||
) -> TailCallResult {
|
||||
// If jump target is already a known block or within known function bounds, not a tail call.
|
||||
if self.blocks.contains_key(&addr) || (addr >= function_start && addr < function_end) {
|
||||
return TailCallResult::Not;
|
||||
}
|
||||
// If there's a prologue in the current function, not a tail call.
|
||||
if self.prologue.is_some() {
|
||||
return TailCallResult::Not;
|
||||
}
|
||||
// If jump target is before the start of the function, known tail call.
|
||||
if addr < function_start {
|
||||
return TailCallResult::Is;
|
||||
}
|
||||
// If the jump target has 0'd padding before it, known tail call.
|
||||
if matches!(obj.section_data(addr - 4, addr), Ok((_, data)) if data == [0u8; 4]) {
|
||||
return TailCallResult::Is;
|
||||
}
|
||||
// If we're not sure where the function ends yet, mark as possible tail call.
|
||||
// let end = self.end();
|
||||
if function_end == 0 {
|
||||
return TailCallResult::Possible;
|
||||
}
|
||||
// If jump target is known to be a function, or there's a function in between
|
||||
// this and the jump target, known tail call.
|
||||
log::trace!("Checking {:#010X}..={:#010X}", function_start + 4, addr);
|
||||
if self.function_references.range(function_start + 4..=addr).next().is_some()
|
||||
|| known_functions.range(function_start + 4..=addr).next().is_some()
|
||||
{
|
||||
return TailCallResult::Is;
|
||||
}
|
||||
// Perform CFA on jump target to determine more
|
||||
let mut slices = FunctionSlices::default();
|
||||
slices.function_references = self.function_references.clone();
|
||||
if let Ok(result) =
|
||||
slices.analyze(obj, addr, function_start, Some(function_end), known_functions)
|
||||
{
|
||||
// If analysis failed, assume tail call.
|
||||
if !result {
|
||||
log::warn!("Tail call analysis failed for {:#010X}", addr);
|
||||
return TailCallResult::Is;
|
||||
}
|
||||
// If control flow jumps below the entry point, not a tail call.
|
||||
let start = slices.start();
|
||||
if start < addr {
|
||||
log::trace!("Tail call possibility eliminated: {:#010X} < {:#010X}", start, addr);
|
||||
return TailCallResult::Not;
|
||||
}
|
||||
// If control flow includes another possible tail call, we know both are not tail calls.
|
||||
let end = slices.end();
|
||||
let other_blocks =
|
||||
self.possible_blocks.range(start + 4..end).cloned().collect::<Vec<u32>>();
|
||||
if !other_blocks.is_empty() {
|
||||
for other_addr in other_blocks {
|
||||
log::trace!("Logically eliminating {:#010X}", other_addr);
|
||||
self.possible_blocks.remove(&other_addr);
|
||||
// self.add_block_start(oth);
|
||||
}
|
||||
log::trace!("While analyzing {:#010X}", addr);
|
||||
return TailCallResult::Not;
|
||||
}
|
||||
// If we discovered a function prologue, known tail call.
|
||||
if slices.prologue.is_some() {
|
||||
log::trace!("Prologue discovered; known tail call: {:#010X}", addr);
|
||||
return TailCallResult::Is;
|
||||
}
|
||||
}
|
||||
TailCallResult::Possible
|
||||
}
|
||||
|
||||
pub fn first_disconnected_block(&self) -> Option<(BlockRange, BlockRange)> {
|
||||
let mut iter = self.blocks.iter().peekable();
|
||||
loop {
|
||||
let ((first_begin, first_end), (second_begin, second_end)) =
|
||||
match (iter.next(), iter.peek()) {
|
||||
(Some((&b1s, &b1e)), Some(&(&b2s, &b2e))) => ((b1s, b1e), (b2s, b2e)),
|
||||
_ => break None,
|
||||
};
|
||||
if second_begin > first_end {
|
||||
break Some((first_begin..first_end, second_begin..second_end));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_conditional_blr(ins: &Ins) -> bool {
|
||||
ins.op == Opcode::Bclr && ins.field_BO() & 0b10100 != 0b10100
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_nop(ins: &Ins) -> bool {
|
||||
// ori r0, r0, 0
|
||||
ins.code == 0x60000000
|
||||
}
|
||||
732
src/analysis/tracker.rs
Normal file
732
src/analysis/tracker.rs
Normal file
@@ -0,0 +1,732 @@
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
mem::take,
|
||||
};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use ppc750cl::Opcode;
|
||||
|
||||
use crate::{
|
||||
analysis::{
|
||||
executor::{ExecCbData, ExecCbResult, Executor},
|
||||
uniq_jump_table_entries,
|
||||
vm::{is_store_op, BranchTarget, GprValue, StepResult, VM},
|
||||
},
|
||||
obj::{ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolKind},
|
||||
util::nested::NestedVec,
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Relocation {
|
||||
Ha(u32),
|
||||
Hi(u32),
|
||||
Lo(u32),
|
||||
Sda21(u32),
|
||||
Rel14(u32),
|
||||
Rel24(u32),
|
||||
Absolute(u32),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum DataKind {
|
||||
Unknown = -1,
|
||||
Word,
|
||||
Half,
|
||||
Byte,
|
||||
Float,
|
||||
Double,
|
||||
// String,
|
||||
// String16,
|
||||
}
|
||||
|
||||
pub struct Tracker {
|
||||
processed_functions: BTreeSet<u32>,
|
||||
sda2_base: u32, // r2
|
||||
sda_base: u32, // r13
|
||||
pub relocations: BTreeMap<u32, Relocation>,
|
||||
data_types: BTreeMap<u32, DataKind>,
|
||||
stack_address: Option<u32>,
|
||||
stack_end: Option<u32>,
|
||||
db_stack_addr: Option<u32>,
|
||||
arena_lo: Option<u32>,
|
||||
arena_hi: Option<u32>,
|
||||
pub ignore_addresses: BTreeSet<u32>,
|
||||
pub known_relocations: BTreeSet<u32>,
|
||||
|
||||
stores_to: BTreeSet<u32>, // for determining data vs rodata, sdata(2)/sbss(2)
|
||||
sda_to: BTreeSet<u32>, // for determining data vs sdata
|
||||
hal_to: BTreeSet<u32>, // for determining data vs sdata
|
||||
}
|
||||
|
||||
impl Tracker {
|
||||
pub fn new(obj: &ObjInfo) -> Tracker {
|
||||
Self {
|
||||
processed_functions: Default::default(),
|
||||
sda2_base: obj.sda2_base.unwrap(),
|
||||
sda_base: obj.sda_base.unwrap(),
|
||||
relocations: Default::default(),
|
||||
data_types: Default::default(),
|
||||
stack_address: obj.stack_address,
|
||||
stack_end: obj.stack_end.or_else(|| {
|
||||
// Stack ends after all BSS sections
|
||||
obj.sections
|
||||
.iter()
|
||||
.rfind(|s| s.kind == ObjSectionKind::Bss)
|
||||
.map(|s| (s.address + s.size) as u32)
|
||||
}),
|
||||
db_stack_addr: obj.db_stack_addr,
|
||||
arena_lo: obj
|
||||
.arena_lo
|
||||
.or_else(|| obj.db_stack_addr.map(|db_stack_addr| (db_stack_addr + 0x1F) & !0x1F)),
|
||||
arena_hi: Some(obj.arena_hi.unwrap_or(0x81700000)),
|
||||
ignore_addresses: Default::default(),
|
||||
known_relocations: Default::default(),
|
||||
stores_to: Default::default(),
|
||||
sda_to: Default::default(),
|
||||
hal_to: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process(&mut self, obj: &ObjInfo) -> Result<()> {
|
||||
log::debug!("Processing code sections");
|
||||
self.process_code(obj)?;
|
||||
for section in &obj.sections {
|
||||
if matches!(section.kind, ObjSectionKind::Data | ObjSectionKind::ReadOnlyData) {
|
||||
log::debug!("Processing section {}, address {:#X}", section.index, section.address);
|
||||
self.process_data(obj, section)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// fn update_stack_address(&mut self, addr: u32) {
|
||||
// if let Some(db_stack_addr) = self.db_stack_addr {
|
||||
// if db_stack_addr == addr {
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// if let Some(stack_addr) = self.stack_address {
|
||||
// if stack_addr != addr {
|
||||
// log::error!("Stack address overridden from {:#010X} to {:#010X}", stack_addr, addr);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// log::debug!("Located stack address: {:08X}", addr);
|
||||
// self.stack_address = Some(addr);
|
||||
// let db_stack_addr = addr + 0x2000;
|
||||
// self.db_stack_addr = Some(db_stack_addr);
|
||||
// self.arena_lo = Some((db_stack_addr + 0x1F) & !0x1F);
|
||||
// // __ArenaHi is fixed (until it isn't?)
|
||||
// self.arena_hi = Some(0x81700000);
|
||||
// log::debug!("_stack_addr: {:#010X}", addr);
|
||||
// log::debug!("_stack_end: {:#010X}", self.stack_end.unwrap());
|
||||
// log::debug!("_db_stack_addr: {:#010X}", db_stack_addr);
|
||||
// log::debug!("__ArenaLo: {:#010X}", self.arena_lo.unwrap());
|
||||
// log::debug!("__ArenaHi: {:#010X}", self.arena_hi.unwrap());
|
||||
// }
|
||||
|
||||
fn process_code(&mut self, obj: &ObjInfo) -> Result<()> {
|
||||
let mut symbol_map = BTreeMap::new();
|
||||
for section in obj.sections.iter().filter(|s| s.kind == ObjSectionKind::Code) {
|
||||
symbol_map.append(&mut obj.build_symbol_map(section.index)?);
|
||||
}
|
||||
self.process_function_by_address(obj, &symbol_map, obj.entry as u32)?;
|
||||
'outer: for (&addr, symbols) in &symbol_map {
|
||||
if self.processed_functions.contains(&addr) {
|
||||
continue;
|
||||
}
|
||||
self.processed_functions.insert(addr);
|
||||
for &symbol_idx in symbols {
|
||||
let symbol = &obj.symbols[symbol_idx];
|
||||
if symbol.kind == ObjSymbolKind::Function && symbol.size_known {
|
||||
self.process_function(obj, symbol)?;
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Special handling for gTRKInterruptVectorTable
|
||||
// TODO
|
||||
// if let (Some(trk_interrupt_table), Some(trk_interrupt_vector_table_end)) = (
|
||||
// obj.symbols.iter().find(|sym| sym.name == "gTRKInterruptVectorTable"),
|
||||
// obj.symbols.iter().find(|sym| sym.name == "gTRKInterruptVectorTableEnd"),
|
||||
// ) {}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_function_by_address(
|
||||
&mut self,
|
||||
obj: &ObjInfo,
|
||||
symbol_map: &BTreeMap<u32, Vec<usize>>,
|
||||
addr: u32,
|
||||
) -> Result<()> {
|
||||
if self.processed_functions.contains(&addr) {
|
||||
return Ok(());
|
||||
}
|
||||
self.processed_functions.insert(addr);
|
||||
if let Some(symbols) = symbol_map.get(&addr) {
|
||||
for &symbol_idx in symbols {
|
||||
let symbol = &obj.symbols[symbol_idx];
|
||||
if symbol.kind == ObjSymbolKind::Function && symbol.size_known {
|
||||
self.process_function(obj, symbol)?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
log::warn!("Failed to locate function symbol @ {:#010X}", addr);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn instruction_callback(
|
||||
&mut self,
|
||||
data: ExecCbData,
|
||||
obj: &ObjInfo,
|
||||
function_start: u32,
|
||||
function_end: u32,
|
||||
possible_missed_branches: &mut BTreeMap<u32, Box<VM>>,
|
||||
) -> Result<ExecCbResult<()>> {
|
||||
let ExecCbData { executor, vm, result, section: _, ins, block_start: _ } = data;
|
||||
let is_function_addr = |addr: u32| addr >= function_start && addr < function_end;
|
||||
|
||||
match result {
|
||||
StepResult::Continue => {
|
||||
// if ins.addr == 0x8000ed0c || ins.addr == 0x8000ed08 || ins.addr == 0x8000ca50 {
|
||||
// println!("ok");
|
||||
// }
|
||||
match ins.op {
|
||||
Opcode::Addi | Opcode::Addic | Opcode::Addic_ => {
|
||||
// addi rD, rA, SIMM
|
||||
let source = ins.field_rA();
|
||||
let target = ins.field_rD();
|
||||
if let GprValue::Constant(value) = vm.gpr[target].value {
|
||||
if self.is_valid_address(obj, ins.addr, value) {
|
||||
if (source == 2
|
||||
&& vm.gpr[2].value == GprValue::Constant(self.sda2_base))
|
||||
|| (source == 13
|
||||
&& vm.gpr[13].value == GprValue::Constant(self.sda_base))
|
||||
{
|
||||
self.relocations.insert(ins.addr, Relocation::Sda21(value));
|
||||
self.sda_to.insert(value);
|
||||
} else if let (Some(hi_addr), Some(lo_addr)) =
|
||||
(vm.gpr[target].hi_addr, vm.gpr[target].lo_addr)
|
||||
{
|
||||
let hi_reloc = self.relocations.get(&hi_addr.get()).cloned();
|
||||
if hi_reloc.is_none() {
|
||||
self.relocations
|
||||
.insert(hi_addr.get(), Relocation::Ha(value));
|
||||
}
|
||||
let lo_reloc = self.relocations.get(&lo_addr.get()).cloned();
|
||||
if lo_reloc.is_none() {
|
||||
self.relocations
|
||||
.insert(lo_addr.get(), Relocation::Lo(value));
|
||||
}
|
||||
self.hal_to.insert(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Opcode::Ori => {
|
||||
// ori rA, rS, UIMM
|
||||
let target = ins.field_rA();
|
||||
if let GprValue::Constant(value) = vm.gpr[target].value {
|
||||
if self.is_valid_address(obj, ins.addr, value) {
|
||||
if let (Some(hi_addr), Some(lo_addr)) =
|
||||
(vm.gpr[target].hi_addr, vm.gpr[target].lo_addr)
|
||||
{
|
||||
let hi_reloc = self.relocations.get(&hi_addr.get()).cloned();
|
||||
if hi_reloc.is_none() {
|
||||
self.relocations
|
||||
.insert(hi_addr.get(), Relocation::Hi(value));
|
||||
}
|
||||
let lo_reloc = self.relocations.get(&lo_addr.get()).cloned();
|
||||
if lo_reloc.is_none() {
|
||||
self.relocations
|
||||
.insert(lo_addr.get(), Relocation::Lo(value));
|
||||
}
|
||||
self.hal_to.insert(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(ExecCbResult::Continue)
|
||||
}
|
||||
StepResult::LoadStore { address, source, source_reg } => {
|
||||
if self.is_valid_address(obj, ins.addr, address) {
|
||||
if (source_reg == 2 && source.value == GprValue::Constant(self.sda2_base))
|
||||
|| (source_reg == 13 && source.value == GprValue::Constant(self.sda_base))
|
||||
{
|
||||
self.relocations.insert(ins.addr, Relocation::Sda21(address));
|
||||
self.sda_to.insert(address);
|
||||
} else {
|
||||
match (source.hi_addr, source.lo_addr) {
|
||||
(Some(hi_addr), None) => {
|
||||
let hi_reloc = self.relocations.get(&hi_addr.get()).cloned();
|
||||
if hi_reloc.is_none() {
|
||||
self.relocations.insert(hi_addr.get(), Relocation::Ha(address));
|
||||
}
|
||||
if hi_reloc.is_none()
|
||||
|| matches!(hi_reloc, Some(Relocation::Ha(v)) if v == address)
|
||||
{
|
||||
self.relocations.insert(ins.addr, Relocation::Lo(address));
|
||||
}
|
||||
self.hal_to.insert(address);
|
||||
}
|
||||
(Some(hi_addr), Some(lo_addr)) => {
|
||||
let hi_reloc = self.relocations.get(&hi_addr.get()).cloned();
|
||||
if hi_reloc.is_none() {
|
||||
self.relocations.insert(hi_addr.get(), Relocation::Ha(address));
|
||||
}
|
||||
let lo_reloc = self.relocations.get(&lo_addr.get()).cloned();
|
||||
if lo_reloc.is_none() {
|
||||
self.relocations.insert(lo_addr.get(), Relocation::Lo(address));
|
||||
}
|
||||
self.hal_to.insert(address);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
self.data_types.insert(address, data_kind_from_op(ins.op));
|
||||
if is_store_op(ins.op) {
|
||||
self.stores_to.insert(address);
|
||||
}
|
||||
}
|
||||
Ok(ExecCbResult::Continue)
|
||||
}
|
||||
StepResult::Illegal => bail!(
|
||||
"Illegal instruction hit @ {:#010X} (function {:#010X}-{:#010X})",
|
||||
ins.addr,
|
||||
function_start,
|
||||
function_end
|
||||
),
|
||||
StepResult::Jump(target) => match target {
|
||||
BranchTarget::Unknown | BranchTarget::Return => Ok(ExecCbResult::EndBlock),
|
||||
BranchTarget::Address(addr) => {
|
||||
let next_addr = ins.addr + 4;
|
||||
if next_addr < function_end {
|
||||
possible_missed_branches.insert(ins.addr + 4, vm.clone_all());
|
||||
}
|
||||
if is_function_addr(addr) {
|
||||
Ok(ExecCbResult::Jump(addr))
|
||||
} else {
|
||||
self.relocations.insert(ins.addr, Relocation::Rel24(addr));
|
||||
Ok(ExecCbResult::EndBlock)
|
||||
}
|
||||
}
|
||||
BranchTarget::JumpTable { address, size } => {
|
||||
let (entries, _) = uniq_jump_table_entries(
|
||||
obj,
|
||||
address,
|
||||
size,
|
||||
ins.addr,
|
||||
function_start,
|
||||
function_end,
|
||||
)?;
|
||||
for target in entries {
|
||||
if is_function_addr(target) {
|
||||
executor.push(target, vm.clone_all(), true);
|
||||
}
|
||||
}
|
||||
Ok(ExecCbResult::EndBlock)
|
||||
}
|
||||
},
|
||||
StepResult::Branch(branches) => {
|
||||
for branch in branches {
|
||||
match branch.target {
|
||||
BranchTarget::Unknown | BranchTarget::Return => {}
|
||||
BranchTarget::Address(addr) => {
|
||||
if branch.link || !is_function_addr(addr) {
|
||||
self.relocations.insert(ins.addr, match ins.op {
|
||||
Opcode::B => Relocation::Rel24(addr),
|
||||
_ => Relocation::Rel14(addr),
|
||||
});
|
||||
} else if is_function_addr(addr) {
|
||||
executor.push(addr, branch.vm, true);
|
||||
}
|
||||
}
|
||||
BranchTarget::JumpTable { .. } => {
|
||||
bail!("Conditional jump table unsupported @ {:#010X}", ins.addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ExecCbResult::EndBlock)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_function(&mut self, obj: &ObjInfo, symbol: &ObjSymbol) -> Result<()> {
|
||||
let function_start = symbol.address as u32;
|
||||
let function_end = (symbol.address + symbol.size) as u32;
|
||||
|
||||
// The compiler can sometimes create impossible-to-reach branches,
|
||||
// but we still want to track them.
|
||||
let mut possible_missed_branches = BTreeMap::new();
|
||||
|
||||
let mut executor = Executor::new(obj);
|
||||
executor.push(
|
||||
symbol.address as u32,
|
||||
VM::new_with_base(self.sda2_base, self.sda_base),
|
||||
false,
|
||||
);
|
||||
loop {
|
||||
executor.run(obj, |data| -> Result<ExecCbResult<()>> {
|
||||
self.instruction_callback(
|
||||
data,
|
||||
obj,
|
||||
function_start,
|
||||
function_end,
|
||||
&mut possible_missed_branches,
|
||||
)
|
||||
})?;
|
||||
|
||||
if possible_missed_branches.is_empty() {
|
||||
break;
|
||||
}
|
||||
let mut added = false;
|
||||
for (addr, vm) in take(&mut possible_missed_branches) {
|
||||
let section = match obj.section_at(addr) {
|
||||
Ok(section) => section,
|
||||
Err(_) => continue,
|
||||
};
|
||||
if !executor.visited(section, addr) {
|
||||
executor.push(addr, vm, true);
|
||||
added = true;
|
||||
}
|
||||
}
|
||||
if !added {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_data(&mut self, obj: &ObjInfo, section: &ObjSection) -> Result<()> {
|
||||
let mut addr = section.address as u32;
|
||||
for chunk in section.data.chunks_exact(4) {
|
||||
let value = u32::from_be_bytes(chunk.try_into()?);
|
||||
if self.is_valid_address(obj, addr, value) {
|
||||
self.relocations.insert(addr, Relocation::Absolute(value));
|
||||
}
|
||||
addr += 4;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_valid_address(&self, obj: &ObjInfo, from: u32, addr: u32) -> bool {
|
||||
if self.ignore_addresses.contains(&addr) {
|
||||
return false;
|
||||
}
|
||||
if self.known_relocations.contains(&from) {
|
||||
return true;
|
||||
}
|
||||
if self.stack_address == Some(addr)
|
||||
|| self.stack_end == Some(addr)
|
||||
|| self.db_stack_addr == Some(addr)
|
||||
|| self.arena_lo == Some(addr)
|
||||
|| self.arena_hi == Some(addr)
|
||||
|| self.sda2_base == addr
|
||||
|| self.sda_base == addr
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// if addr > 0x80000000 && addr < 0x80003100 {
|
||||
// return true;
|
||||
// }
|
||||
for section in &obj.sections {
|
||||
if addr >= section.address as u32 && addr <= (section.address + section.size) as u32 {
|
||||
// References to code sections will never be unaligned
|
||||
return section.kind != ObjSectionKind::Code || addr & 3 == 0;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn special_symbol(
|
||||
&self,
|
||||
obj: &mut ObjInfo,
|
||||
addr: u32,
|
||||
reloc_kind: ObjRelocKind,
|
||||
) -> Option<usize> {
|
||||
if !matches!(reloc_kind, ObjRelocKind::PpcAddr16Ha | ObjRelocKind::PpcAddr16Lo) {
|
||||
return None;
|
||||
}
|
||||
// HACK for RSOStaticLocateObject
|
||||
for section in &obj.sections {
|
||||
if addr == section.address as u32 {
|
||||
let name = format!("_f_{}", section.name.trim_start_matches('.'));
|
||||
return Some(generate_special_symbol(obj, addr, &name));
|
||||
}
|
||||
}
|
||||
let mut check_symbol = |opt: Option<u32>, name: &str| -> Option<usize> {
|
||||
if let Some(value) = opt {
|
||||
if addr == value {
|
||||
return Some(generate_special_symbol(obj, value, name));
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
check_symbol(self.stack_address, "_stack_addr")
|
||||
.or_else(|| check_symbol(self.stack_end, "_stack_end"))
|
||||
.or_else(|| check_symbol(self.arena_lo, "__ArenaLo"))
|
||||
.or_else(|| check_symbol(self.arena_hi, "__ArenaHi"))
|
||||
.or_else(|| check_symbol(self.db_stack_addr, "_db_stack_addr"))
|
||||
.or_else(|| check_symbol(Some(self.sda2_base), "_SDA2_BASE_"))
|
||||
.or_else(|| check_symbol(Some(self.sda_base), "_SDA_BASE_"))
|
||||
}
|
||||
|
||||
pub fn apply(&self, obj: &mut ObjInfo, replace: bool) -> Result<()> {
|
||||
for section in &mut obj.sections {
|
||||
if !section.section_known {
|
||||
if section.kind == ObjSectionKind::Code {
|
||||
log::info!("Renaming {} to .text", section.name);
|
||||
section.name = ".text".to_string();
|
||||
continue;
|
||||
}
|
||||
let start = section.address as u32;
|
||||
let end = (section.address + section.size) as u32;
|
||||
if self.sda_to.range(start..end).next().is_some() {
|
||||
if self.stores_to.range(start..end).next().is_some() {
|
||||
if section.kind == ObjSectionKind::Bss {
|
||||
log::info!("Renaming {} to .sbss", section.name);
|
||||
section.name = ".sbss".to_string();
|
||||
} else {
|
||||
log::info!("Renaming {} to .sdata", section.name);
|
||||
section.name = ".sdata".to_string();
|
||||
}
|
||||
} else if section.kind == ObjSectionKind::Bss {
|
||||
log::info!("Renaming {} to .sbss2", section.name);
|
||||
section.name = ".sbss2".to_string();
|
||||
} else {
|
||||
log::info!("Renaming {} to .sdata2", section.name);
|
||||
section.name = ".sdata2".to_string();
|
||||
section.kind = ObjSectionKind::ReadOnlyData;
|
||||
}
|
||||
} else if self.hal_to.range(start..end).next().is_some() {
|
||||
if section.kind == ObjSectionKind::Bss {
|
||||
log::info!("Renaming {} to .bss", section.name);
|
||||
section.name = ".bss".to_string();
|
||||
} else if self.stores_to.range(start..end).next().is_some() {
|
||||
log::info!("Renaming {} to .data", section.name);
|
||||
section.name = ".data".to_string();
|
||||
} else {
|
||||
log::info!("Renaming {} to .rodata", section.name);
|
||||
section.name = ".rodata".to_string();
|
||||
section.kind = ObjSectionKind::ReadOnlyData;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut symbol_maps = Vec::new();
|
||||
for section in &obj.sections {
|
||||
symbol_maps.push(obj.build_symbol_map(section.index)?);
|
||||
}
|
||||
|
||||
for (addr, reloc) in &self.relocations {
|
||||
let addr = *addr;
|
||||
let (reloc_kind, target) = match *reloc {
|
||||
Relocation::Ha(v) => (ObjRelocKind::PpcAddr16Ha, v),
|
||||
Relocation::Hi(v) => (ObjRelocKind::PpcAddr16Hi, v),
|
||||
Relocation::Lo(v) => (ObjRelocKind::PpcAddr16Lo, v),
|
||||
Relocation::Sda21(v) => (ObjRelocKind::PpcEmbSda21, v),
|
||||
Relocation::Rel14(v) => (ObjRelocKind::PpcRel14, v),
|
||||
Relocation::Rel24(v) => (ObjRelocKind::PpcRel24, v),
|
||||
Relocation::Absolute(v) => (ObjRelocKind::Absolute, v),
|
||||
};
|
||||
let (target_symbol, addend) =
|
||||
if let Some(symbol) = self.special_symbol(obj, target, reloc_kind) {
|
||||
(symbol, 0)
|
||||
} else {
|
||||
let target_section = match obj.sections.iter().find(|s| {
|
||||
target >= s.address as u32 && target < (s.address + s.size) as u32
|
||||
}) {
|
||||
Some(v) => v,
|
||||
None => continue,
|
||||
};
|
||||
// Try to find a previous sized symbol that encompasses the target
|
||||
let sym_map = &mut symbol_maps[target_section.index];
|
||||
let target_symbol = {
|
||||
let mut result = None;
|
||||
for (_addr, symbol_idxs) in sym_map.range(..=target).rev() {
|
||||
let symbol_idx = if symbol_idxs.len() == 1 {
|
||||
symbol_idxs.first().cloned().unwrap()
|
||||
} else {
|
||||
let mut symbol_idxs = symbol_idxs.clone();
|
||||
symbol_idxs.sort_by_key(|&symbol_idx| {
|
||||
let symbol = &obj.symbols[symbol_idx];
|
||||
let mut rank = match symbol.kind {
|
||||
ObjSymbolKind::Function | ObjSymbolKind::Object => {
|
||||
match reloc_kind {
|
||||
ObjRelocKind::PpcAddr16Hi
|
||||
| ObjRelocKind::PpcAddr16Ha
|
||||
| ObjRelocKind::PpcAddr16Lo => 1,
|
||||
ObjRelocKind::Absolute
|
||||
| ObjRelocKind::PpcRel24
|
||||
| ObjRelocKind::PpcRel14
|
||||
| ObjRelocKind::PpcEmbSda21 => 2,
|
||||
}
|
||||
}
|
||||
// Label
|
||||
ObjSymbolKind::Unknown => match reloc_kind {
|
||||
ObjRelocKind::PpcAddr16Hi
|
||||
| ObjRelocKind::PpcAddr16Ha
|
||||
| ObjRelocKind::PpcAddr16Lo
|
||||
if !symbol.name.starts_with("..") =>
|
||||
{
|
||||
3
|
||||
}
|
||||
_ => 1,
|
||||
},
|
||||
ObjSymbolKind::Section => -1,
|
||||
};
|
||||
if symbol.size > 0 {
|
||||
rank += 1;
|
||||
}
|
||||
-rank
|
||||
});
|
||||
match symbol_idxs.first().cloned() {
|
||||
Some(v) => v,
|
||||
None => continue,
|
||||
}
|
||||
};
|
||||
let symbol = &obj.symbols[symbol_idx];
|
||||
if symbol.address == target as u64 {
|
||||
result = Some(symbol_idx);
|
||||
break;
|
||||
}
|
||||
if symbol.size > 0 {
|
||||
if symbol.address + symbol.size > target as u64 {
|
||||
result = Some(symbol_idx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
result
|
||||
};
|
||||
if let Some(symbol_idx) = target_symbol {
|
||||
let symbol = &obj.symbols[symbol_idx];
|
||||
(symbol_idx, target as i64 - symbol.address as i64)
|
||||
} else {
|
||||
// Create a new label
|
||||
let symbol_idx = obj.symbols.len();
|
||||
obj.symbols.push(ObjSymbol {
|
||||
name: format!("lbl_{:08X}", target),
|
||||
demangled_name: None,
|
||||
address: target as u64,
|
||||
section: Some(target_section.index),
|
||||
size: 0,
|
||||
size_known: false,
|
||||
flags: Default::default(),
|
||||
kind: Default::default(),
|
||||
});
|
||||
sym_map.nested_push(target, symbol_idx);
|
||||
(symbol_idx, 0)
|
||||
}
|
||||
};
|
||||
let reloc = ObjReloc { kind: reloc_kind, address: addr as u64, target_symbol, addend };
|
||||
let section = match obj
|
||||
.sections
|
||||
.iter_mut()
|
||||
.find(|s| addr >= s.address as u32 && addr < (s.address + s.size) as u32)
|
||||
{
|
||||
Some(v) => v,
|
||||
None => bail!(
|
||||
"Failed to locate source section for relocation @ {:#010X} {:#010X?}",
|
||||
addr,
|
||||
reloc
|
||||
),
|
||||
};
|
||||
match section.relocations.iter_mut().find(|r| r.address as u32 == addr) {
|
||||
Some(v) => {
|
||||
let iter_symbol = &obj.symbols[v.target_symbol];
|
||||
let reloc_symbol = &obj.symbols[reloc.target_symbol];
|
||||
if iter_symbol.address as i64 + v.addend
|
||||
!= reloc_symbol.address as i64 + reloc.addend
|
||||
{
|
||||
bail!(
|
||||
"Conflicting relocations (target {:#010X}): {:#010X?} != {:#010X?}",
|
||||
target,
|
||||
v,
|
||||
reloc
|
||||
);
|
||||
}
|
||||
if replace {
|
||||
*v = reloc;
|
||||
}
|
||||
}
|
||||
None => section.relocations.push(reloc),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn data_kind_from_op(op: Opcode) -> DataKind {
|
||||
match op {
|
||||
Opcode::Lbz => DataKind::Byte,
|
||||
Opcode::Lbzu => DataKind::Byte,
|
||||
Opcode::Lbzux => DataKind::Byte,
|
||||
Opcode::Lbzx => DataKind::Byte,
|
||||
Opcode::Lfd => DataKind::Double,
|
||||
Opcode::Lfdu => DataKind::Double,
|
||||
Opcode::Lfdux => DataKind::Double,
|
||||
Opcode::Lfdx => DataKind::Double,
|
||||
Opcode::Lfs => DataKind::Float,
|
||||
Opcode::Lfsu => DataKind::Float,
|
||||
Opcode::Lfsux => DataKind::Float,
|
||||
Opcode::Lfsx => DataKind::Float,
|
||||
Opcode::Lha => DataKind::Half,
|
||||
Opcode::Lhau => DataKind::Half,
|
||||
Opcode::Lhaux => DataKind::Half,
|
||||
Opcode::Lhax => DataKind::Half,
|
||||
Opcode::Lhbrx => DataKind::Half,
|
||||
Opcode::Lhz => DataKind::Half,
|
||||
Opcode::Lhzu => DataKind::Half,
|
||||
Opcode::Lhzux => DataKind::Half,
|
||||
Opcode::Lhzx => DataKind::Half,
|
||||
Opcode::Lwz => DataKind::Word,
|
||||
Opcode::Lwzu => DataKind::Word,
|
||||
Opcode::Lwzux => DataKind::Word,
|
||||
Opcode::Lwzx => DataKind::Word,
|
||||
Opcode::Stb => DataKind::Byte,
|
||||
Opcode::Stbu => DataKind::Byte,
|
||||
Opcode::Stbux => DataKind::Byte,
|
||||
Opcode::Stbx => DataKind::Byte,
|
||||
Opcode::Stfd => DataKind::Double,
|
||||
Opcode::Stfdu => DataKind::Double,
|
||||
Opcode::Stfdux => DataKind::Double,
|
||||
Opcode::Stfdx => DataKind::Double,
|
||||
Opcode::Stfiwx => DataKind::Float,
|
||||
Opcode::Stfs => DataKind::Float,
|
||||
Opcode::Stfsu => DataKind::Float,
|
||||
Opcode::Stfsux => DataKind::Float,
|
||||
Opcode::Stfsx => DataKind::Float,
|
||||
Opcode::Sth => DataKind::Half,
|
||||
Opcode::Sthbrx => DataKind::Half,
|
||||
Opcode::Sthu => DataKind::Half,
|
||||
Opcode::Sthux => DataKind::Half,
|
||||
Opcode::Sthx => DataKind::Half,
|
||||
Opcode::Stw => DataKind::Word,
|
||||
Opcode::Stwbrx => DataKind::Word,
|
||||
Opcode::Stwcx_ => DataKind::Word,
|
||||
Opcode::Stwu => DataKind::Word,
|
||||
Opcode::Stwux => DataKind::Word,
|
||||
Opcode::Stwx => DataKind::Word,
|
||||
_ => DataKind::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_special_symbol(obj: &mut ObjInfo, addr: u32, name: &str) -> usize {
|
||||
if let Some((symbol_idx, _)) =
|
||||
obj.symbols.iter().enumerate().find(|&(_, symbol)| symbol.name == name)
|
||||
{
|
||||
return symbol_idx;
|
||||
}
|
||||
let symbol_idx = obj.symbols.len();
|
||||
obj.symbols.push(ObjSymbol {
|
||||
name: name.to_string(),
|
||||
address: addr as u64,
|
||||
..Default::default()
|
||||
});
|
||||
symbol_idx
|
||||
}
|
||||
740
src/analysis/vm.rs
Normal file
740
src/analysis/vm.rs
Normal file
@@ -0,0 +1,740 @@
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
use ppc750cl::{Argument, Ins, Opcode, GPR};
|
||||
|
||||
use crate::obj::ObjInfo;
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum GprValue {
|
||||
#[default]
|
||||
/// GPR value is unknown
|
||||
Unknown,
|
||||
/// GPR value is a constant
|
||||
Constant(u32),
|
||||
/// 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: u32, 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<NonZeroU32>,
|
||||
/// Address that loads the lo part of this GPR
|
||||
pub lo_addr: Option<NonZeroU32>,
|
||||
}
|
||||
|
||||
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: u32) {
|
||||
self.value = value;
|
||||
self.hi_addr = NonZeroU32::new(addr);
|
||||
self.lo_addr = None;
|
||||
}
|
||||
|
||||
fn set_lo(&mut self, value: GprValue, addr: u32, hi_gpr: Gpr) {
|
||||
self.value = value;
|
||||
self.hi_addr = hi_gpr.hi_addr;
|
||||
self.lo_addr = hi_gpr.lo_addr.or_else(|| NonZeroU32::new(addr));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
||||
struct Cr {
|
||||
/// The left-hand value of this comparison
|
||||
left: GprValue,
|
||||
/// The right-hand value of this comparison
|
||||
right: GprValue,
|
||||
/// Whether this comparison is signed
|
||||
signed: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
||||
pub struct VM {
|
||||
/// General purpose registers
|
||||
pub gpr: [Gpr; 32],
|
||||
/// Condition registers
|
||||
cr: [Cr; 8],
|
||||
/// Count register
|
||||
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(u32),
|
||||
/// Branch to jump table
|
||||
JumpTable { address: u32, 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: u32, source: Gpr, source_reg: u8 },
|
||||
/// Hit illegal instruction
|
||||
Illegal,
|
||||
/// Jump without affecting VM state
|
||||
Jump(BranchTarget),
|
||||
/// Branch with split VM states
|
||||
Branch(Vec<Branch>),
|
||||
}
|
||||
|
||||
impl VM {
|
||||
#[inline]
|
||||
pub fn new() -> Box<Self> { Box::default() }
|
||||
|
||||
#[inline]
|
||||
pub fn new_from_obj(obj: &ObjInfo) -> Box<Self> {
|
||||
match (obj.sda2_base, obj.sda_base) {
|
||||
(Some(sda2_base), Some(sda_base)) => Self::new_with_base(sda2_base, sda_base),
|
||||
_ => Self::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_with_base(sda2_base: u32, sda_base: u32) -> Box<Self> {
|
||||
let mut vm = Self::new();
|
||||
vm.gpr[2].value = GprValue::Constant(sda2_base);
|
||||
vm.gpr[13].value = GprValue::Constant(sda_base);
|
||||
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, ins: &Ins) -> StepResult {
|
||||
match ins.op {
|
||||
Opcode::Illegal => {
|
||||
return StepResult::Illegal;
|
||||
}
|
||||
Opcode::Add => {
|
||||
// add rD, rA, rB
|
||||
let left = self.gpr[ins.field_rA()].value;
|
||||
let right = self.gpr[ins.field_rB()].value;
|
||||
let value = match (left, right) {
|
||||
(GprValue::Constant(left), GprValue::Constant(right)) => {
|
||||
GprValue::Constant(left.wrapping_add(right))
|
||||
}
|
||||
_ => GprValue::Unknown,
|
||||
};
|
||||
self.gpr[ins.field_rD()].set_direct(value);
|
||||
}
|
||||
Opcode::Addis => {
|
||||
// addis rD, rA, SIMM
|
||||
let left = if ins.field_rA() == 0 {
|
||||
GprValue::Constant(0)
|
||||
} else {
|
||||
self.gpr[ins.field_rA()].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()].set_hi(value, ins.addr);
|
||||
} else {
|
||||
self.gpr[ins.field_rD()].set_direct(value);
|
||||
}
|
||||
}
|
||||
Opcode::Addi | Opcode::Addic | Opcode::Addic_ => {
|
||||
// addi rD, rA, SIMM
|
||||
// addic rD, rA, SIMM
|
||||
// addic. rD, rA, SIMM
|
||||
let left = if ins.field_rA() == 0 && ins.op == Opcode::Addi {
|
||||
GprValue::Constant(0)
|
||||
} else {
|
||||
self.gpr[ins.field_rA()].value
|
||||
};
|
||||
let value = match left {
|
||||
GprValue::Constant(value) => {
|
||||
GprValue::Constant(value.wrapping_add(ins.field_simm() as u32))
|
||||
}
|
||||
_ => GprValue::Unknown,
|
||||
};
|
||||
if ins.field_rA() == 0 {
|
||||
// li rD, SIMM
|
||||
self.gpr[ins.field_rD()].set_direct(value);
|
||||
} else {
|
||||
self.gpr[ins.field_rD()].set_lo(value, ins.addr, self.gpr[ins.field_rA()]);
|
||||
}
|
||||
}
|
||||
Opcode::Ori => {
|
||||
// ori rA, rS, UIMM
|
||||
let value = match self.gpr[ins.field_rS()].value {
|
||||
GprValue::Constant(value) => {
|
||||
GprValue::Constant(value | ins.field_uimm() as u32)
|
||||
}
|
||||
_ => GprValue::Unknown,
|
||||
};
|
||||
self.gpr[ins.field_rA()].set_lo(value, ins.addr, self.gpr[ins.field_rS()]);
|
||||
}
|
||||
Opcode::Or => {
|
||||
// or rA, rS, rB
|
||||
if ins.field_rS() == ins.field_rB() {
|
||||
// Register copy
|
||||
self.gpr[ins.field_rA()] = self.gpr[ins.field_rS()];
|
||||
} else {
|
||||
let left = self.gpr[ins.field_rS()].value;
|
||||
let right = self.gpr[ins.field_rB()].value;
|
||||
let value = match (left, right) {
|
||||
(GprValue::Constant(left), GprValue::Constant(right)) => {
|
||||
GprValue::Constant(left | right)
|
||||
}
|
||||
_ => GprValue::Unknown,
|
||||
};
|
||||
self.gpr[ins.field_rA()].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();
|
||||
let left = self.gpr[left_reg].value;
|
||||
let (right, signed) = match ins.op {
|
||||
Opcode::Cmp => (self.gpr[ins.field_rB()].value, true),
|
||||
Opcode::Cmpl => (self.gpr[ins.field_rB()].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] = Cr { signed, left, right };
|
||||
self.gpr[left_reg].value = GprValue::ComparisonResult(crf as u8);
|
||||
}
|
||||
}
|
||||
// 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()].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()].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()].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) => BranchTarget::Address(value),
|
||||
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,
|
||||
_ => BranchTarget::Address(ins.branch_dest().unwrap()),
|
||||
};
|
||||
|
||||
// If branching with link, use function call semantics
|
||||
if ins.field_LK() {
|
||||
return StepResult::Branch(vec![
|
||||
Branch {
|
||||
target: BranchTarget::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(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;
|
||||
let crb = (ins.field_BI() & 3) as u8;
|
||||
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()].value;
|
||||
let right = self.gpr[ins.field_rB()].value;
|
||||
let value = match (left, right) {
|
||||
(GprValue::Constant(address), GprValue::Range { min: _, max, .. })
|
||||
if /*min == 0 &&*/ max < u32::MAX - 4 && max & 3 == 0 =>
|
||||
{
|
||||
GprValue::LoadIndexed { address, max_offset: NonZeroU32::new(max) }
|
||||
}
|
||||
(GprValue::Constant(address), _) => {
|
||||
GprValue::LoadIndexed { address, max_offset: None }
|
||||
}
|
||||
_ => GprValue::Unknown,
|
||||
};
|
||||
self.gpr[ins.field_rD()].set_direct(value);
|
||||
}
|
||||
// mtspr SPR, rS
|
||||
Opcode::Mtspr => {
|
||||
if ins.field_spr() == 9 {
|
||||
// CTR
|
||||
self.ctr = self.gpr[ins.field_rS()].value;
|
||||
}
|
||||
}
|
||||
// mfspr rD, SPR
|
||||
Opcode::Mfspr => {
|
||||
let value = if ins.field_spr() == 9 {
|
||||
// CTR
|
||||
self.ctr
|
||||
} else {
|
||||
GprValue::Unknown
|
||||
};
|
||||
self.gpr[ins.field_rD()].set_direct(value);
|
||||
}
|
||||
// rfi
|
||||
Opcode::Rfi => {
|
||||
return StepResult::Jump(BranchTarget::Unknown);
|
||||
}
|
||||
op if is_load_store_op(op) => {
|
||||
let source = ins.field_rA();
|
||||
let mut result = StepResult::Continue;
|
||||
if let GprValue::Constant(base) = self.gpr[source].value {
|
||||
let address = base.wrapping_add(ins.field_simm() as u32);
|
||||
if is_update_op(op) {
|
||||
self.gpr[source].set_lo(
|
||||
GprValue::Constant(address),
|
||||
ins.addr,
|
||||
self.gpr[source],
|
||||
);
|
||||
}
|
||||
result = StepResult::LoadStore {
|
||||
address,
|
||||
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()].set_direct(GprValue::Unknown);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
_ => {
|
||||
for field in ins.defs() {
|
||||
match field.argument() {
|
||||
Some(Argument::GPR(GPR(reg))) => {
|
||||
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 {
|
||||
let mut mask = 0u32;
|
||||
for bit in begin..=end {
|
||||
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)
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user