A lot more section-address-aware refactoring

This commit is contained in:
2023-08-23 23:13:12 -04:00
parent 5843ee021e
commit 3f63f1ef47
29 changed files with 10110 additions and 1206 deletions

View File

@@ -1,6 +1,10 @@
use std::collections::{BTreeMap, BTreeSet};
use std::{
collections::{BTreeMap, BTreeSet},
fmt::{Debug, Display, Formatter, UpperHex},
ops::{Add, AddAssign, BitAnd, Sub},
};
use anyhow::{bail, Context, Result};
use anyhow::{bail, ensure, Context, Result};
use crate::{
analysis::{
@@ -12,34 +16,101 @@ use crate::{
obj::{ObjInfo, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind},
};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SectionAddress {
pub section: usize,
pub address: u32,
}
impl Debug for SectionAddress {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{:#010X}", self.section as isize, self.address)
}
}
impl Display for SectionAddress {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{:#010X}", self.section as isize, self.address)
}
}
impl SectionAddress {
pub fn new(section: usize, address: u32) -> Self { Self { section, address } }
}
impl Add<u32> for SectionAddress {
type Output = Self;
fn add(self, rhs: u32) -> Self::Output {
Self { section: self.section, address: self.address + rhs }
}
}
impl Sub<u32> for SectionAddress {
type Output = Self;
fn sub(self, rhs: u32) -> Self::Output {
Self { section: self.section, address: self.address - rhs }
}
}
impl AddAssign<u32> for SectionAddress {
fn add_assign(&mut self, rhs: u32) { self.address += rhs; }
}
impl UpperHex for SectionAddress {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{:#010X}", self.section as isize, self.address)
}
}
impl BitAnd<u32> for SectionAddress {
type Output = u32;
fn bitand(self, rhs: u32) -> Self::Output { self.address & rhs }
}
#[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>,
pub function_entries: BTreeSet<SectionAddress>,
pub function_bounds: BTreeMap<SectionAddress, Option<SectionAddress>>,
pub function_slices: BTreeMap<SectionAddress, FunctionSlices>,
pub jump_tables: BTreeMap<SectionAddress, u32>,
pub known_symbols: BTreeMap<SectionAddress, ObjSymbol>,
pub known_sections: BTreeMap<usize, String>,
pub non_finalized_functions: BTreeMap<SectionAddress, FunctionSlices>,
}
impl AnalyzerState {
pub fn apply(&self, obj: &mut ObjInfo) -> Result<()> {
for (&section_index, section_name) in &self.known_sections {
obj.sections[section_index].rename(section_name.clone())?;
}
for (&start, &end) in &self.function_bounds {
if end == 0 {
continue;
}
let (section_index, _) = obj
.sections
.with_range(start..end)
.context("Failed to locate section for function")?;
let Some(end) = end else { continue };
let section = &obj.sections[start.section];
ensure!(
section.contains_range(start.address..end.address),
"Function {:#010X}..{:#010X} out of bounds of section {} {:#010X}..{:#010X}",
start.address,
end,
section.name,
section.address,
section.address + section.size
);
let name = if obj.module_id == 0 {
format!("fn_{:08X}", start.address)
} else {
format!("fn_{}_{:X}", obj.module_id, start.address)
};
obj.add_symbol(
ObjSymbol {
name: format!("fn_{:08X}", start),
name,
demangled_name: None,
address: start as u64,
section: Some(section_index),
size: (end - start) as u64,
address: start.address as u64,
section: Some(start.section),
size: (end.address - start.address) as u64,
size_known: true,
flags: Default::default(),
kind: ObjSymbolKind::Function,
@@ -50,16 +121,22 @@ impl AnalyzerState {
)?;
}
for (&addr, &size) in &self.jump_tables {
let (section_index, _) = obj
.sections
.with_range(addr..addr + size)
.context("Failed to locate section for jump table")?;
let section = &obj.sections[addr.section];
ensure!(
section.contains_range(addr.address..addr.address + size),
"Jump table {:#010X}..{:#010X} out of bounds of section {} {:#010X}..{:#010X}",
addr.address,
addr.address + size,
section.name,
section.address,
section.address + section.size
);
obj.add_symbol(
ObjSymbol {
name: format!("jumptable_{:08X}", addr),
name: format!("jumptable_{:08X}", addr.address),
demangled_name: None,
address: addr as u64,
section: Some(section_index),
address: addr.address as u64,
section: Some(addr.section),
size: size as u64,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Local.into()),
@@ -79,20 +156,27 @@ impl AnalyzerState {
pub fn detect_functions(&mut self, obj: &ObjInfo) -> Result<()> {
// Apply known functions from extab
for (&addr, &size) in &obj.known_functions {
self.function_entries.insert(addr);
self.function_bounds.insert(addr, addr + size);
let (section_index, _) = obj
.sections
.at_address(addr)
.context(format!("Function {:#010X} outside of any section", addr))?;
let addr_ref = SectionAddress::new(section_index, addr);
self.function_entries.insert(addr_ref);
self.function_bounds.insert(addr_ref, Some(addr_ref + size));
}
// Apply known functions from symbols
for (_, symbol) in obj.symbols.by_kind(ObjSymbolKind::Function) {
self.function_entries.insert(symbol.address as u32);
let Some(section_index) = symbol.section else { continue };
let addr_ref = SectionAddress::new(section_index, symbol.address as u32);
self.function_entries.insert(addr_ref);
if symbol.size_known {
self.function_bounds
.insert(symbol.address as u32, (symbol.address + symbol.size) as u32);
self.function_bounds.insert(addr_ref, Some(addr_ref + symbol.size as u32));
}
}
// Also check the beginning of every code section
for (_, section) in obj.sections.by_kind(ObjSectionKind::Code) {
self.function_entries.insert(section.address as u32);
for (section_index, section) in obj.sections.by_kind(ObjSectionKind::Code) {
self.function_entries
.insert(SectionAddress::new(section_index, section.address as u32));
}
// Process known functions first
@@ -100,8 +184,14 @@ impl AnalyzerState {
for addr in known_functions {
self.process_function_at(obj, addr)?;
}
// Locate entry function bounds
self.process_function_at(obj, obj.entry as u32)?;
if let Some(entry) = obj.entry.map(|n| n as u32) {
// Locate entry function bounds
let (section_index, _) = obj
.sections
.at_address(entry)
.context(format!("Entry point {:#010X} outside of any section", entry))?;
self.process_function_at(obj, SectionAddress::new(section_index, entry))?;
}
// Locate bounds for referenced functions until none are left
self.process_functions(obj)?;
// Final pass(es)
@@ -115,9 +205,11 @@ impl AnalyzerState {
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 Some(function_start) = slices.start() else {
bail!("Function slice without start @ {:#010X}", addr);
};
let function_end = slices.end();
let mut current = 0;
let mut current = SectionAddress::new(addr.section, 0);
while let Some(&block) = slices.possible_blocks.range(current + 4..).next() {
current = block;
match slices.check_tail_call(
@@ -134,7 +226,7 @@ impl AnalyzerState {
obj,
block,
function_start,
Some(function_end),
function_end,
&self.function_entries,
)?;
}
@@ -154,7 +246,7 @@ impl AnalyzerState {
obj,
block,
function_start,
Some(function_end),
function_end,
&self.function_entries,
)?;
}
@@ -180,7 +272,7 @@ impl AnalyzerState {
Ok(finalized_new)
}
fn first_unbounded_function(&self) -> Option<u32> {
fn first_unbounded_function(&self) -> Option<SectionAddress> {
let mut entries_iter = self.function_entries.iter().cloned();
let mut bounds_iter = self.function_bounds.keys().cloned();
let mut entry = entries_iter.next();
@@ -232,12 +324,12 @@ impl AnalyzerState {
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);
}
pub fn process_function_at(&mut self, obj: &ObjInfo, addr: SectionAddress) -> 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());
@@ -252,14 +344,18 @@ impl AnalyzerState {
true
} else {
log::debug!("Not a function @ {:#010X}", addr);
self.function_bounds.insert(addr, 0);
self.function_bounds.insert(addr, None);
false
})
}
fn process_function(&mut self, obj: &ObjInfo, start: u32) -> Result<Option<FunctionSlices>> {
fn process_function(
&mut self,
obj: &ObjInfo,
start: SectionAddress,
) -> Result<Option<FunctionSlices>> {
let mut slices = FunctionSlices::default();
let function_end = self.function_bounds.get(&start).cloned();
let function_end = self.function_bounds.get(&start).cloned().flatten();
Ok(match slices.analyze(obj, start, start, function_end, &self.function_entries)? {
true => Some(slices),
false => None,
@@ -268,14 +364,15 @@ impl AnalyzerState {
fn detect_new_functions(&mut self, obj: &ObjInfo) -> Result<bool> {
let mut found_new = false;
for (_, section) in obj.sections.by_kind(ObjSectionKind::Code) {
let section_start = section.address as u32;
let section_end = (section.address + section.size) as u32;
for (section_index, section) in obj.sections.by_kind(ObjSectionKind::Code) {
let section_start = SectionAddress::new(section_index, section.address as u32);
let section_end = section_start + section.size as u32;
let mut iter = self.function_bounds.range(section_start..section_end).peekable();
loop {
match (iter.next(), iter.peek()) {
(Some((&first_begin, &first_end)), Some(&(&second_begin, &second_end))) => {
if first_end == 0 || first_end > second_begin {
let Some(first_end) = first_end else { continue };
if first_end > second_begin {
continue;
}
let addr = match skip_alignment(section, first_end, second_begin) {
@@ -284,7 +381,7 @@ impl AnalyzerState {
};
if second_begin > addr && self.function_entries.insert(addr) {
log::trace!(
"Trying function @ {:#010X} (from {:#010X}-{:#010X} <-> {:#010X}-{:#010X})",
"Trying function @ {:#010X} (from {:#010X}-{:#010X} <-> {:#010X}-{:#010X?})",
addr,
first_begin,
first_end,
@@ -295,7 +392,8 @@ impl AnalyzerState {
}
}
(Some((&last_begin, &last_end)), None) => {
if last_end > 0 && last_end < section_end {
let Some(last_end) = last_end else { continue };
if last_end < section_end {
let addr = match skip_alignment(section, last_end, section_end) {
Some(addr) => addr,
None => continue,
@@ -323,11 +421,20 @@ impl AnalyzerState {
/// 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 Some(entry) = obj.entry else {
return Ok(false);
};
let (section_index, _) = obj
.sections
.at_address(entry as u32)
.context(format!("Entry point {:#010X} outside of any section", entry))?;
let entry_addr = SectionAddress::new(section_index, entry as u32);
let mut executor = Executor::new(obj);
executor.push(obj.entry as u32, VM::new(), false);
executor.push(entry_addr, VM::new(), false);
let result = executor.run(
obj,
|ExecCbData { executor, vm, result, section_index: _, section: _, ins, block_start: _ }| {
|ExecCbData { executor, vm, result, ins_addr: _, section: _, ins, block_start: _ }| {
match result {
StepResult::Continue | StepResult::LoadStore { .. } => {
return Ok(ExecCbResult::Continue);

View File

@@ -1,9 +1,10 @@
use anyhow::Result;
use anyhow::{ensure, Result};
use fixedbitset::FixedBitSet;
use ppc750cl::Ins;
use crate::{
analysis::{
cfa::SectionAddress,
disassemble,
vm::{StepResult, VM},
},
@@ -30,12 +31,12 @@ impl VisitedAddresses {
Self { inner }
}
pub fn contains(&self, section_index: usize, section_address: u32, address: u32) -> bool {
self.inner[section_index].contains(Self::bit_for(section_address, address))
pub fn contains(&self, section_address: u32, address: SectionAddress) -> bool {
self.inner[address.section].contains(Self::bit_for(section_address, address.address))
}
pub fn insert(&mut self, section_index: usize, section_address: u32, address: u32) {
self.inner[section_index].insert(Self::bit_for(section_address, address));
pub fn insert(&mut self, section_address: u32, address: SectionAddress) {
self.inner[address.section].insert(Self::bit_for(section_address, address.address));
}
#[inline]
@@ -46,7 +47,7 @@ impl VisitedAddresses {
pub struct VMState {
pub vm: Box<VM>,
pub address: u32,
pub address: SectionAddress,
}
/// Helper for branched VM execution, only visiting addresses once.
@@ -59,15 +60,15 @@ pub struct ExecCbData<'a> {
pub executor: &'a mut Executor,
pub vm: &'a mut VM,
pub result: StepResult,
pub section_index: usize,
pub ins_addr: SectionAddress,
pub section: &'a ObjSection,
pub ins: &'a Ins,
pub block_start: u32,
pub block_start: SectionAddress,
}
pub enum ExecCbResult<T = ()> {
Continue,
Jump(u32),
Jump(SectionAddress),
EndBlock,
End(T),
}
@@ -80,14 +81,15 @@ impl Executor {
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_index, section) = match obj.sections.at_address(state.address) {
Ok(ret) => ret,
Err(e) => {
log::error!("{}", e);
// return Ok(None);
continue;
}
};
let section = &obj.sections[state.address.section];
ensure!(
section.contains(state.address.address),
"Invalid address {:#010X} within section {} ({:#010X}-{:#010X})",
state.address.address,
section.name,
section.address,
section.address + section.size
);
if section.kind != ObjSectionKind::Code {
log::warn!("Attempted to visit non-code address {:#010X}", state.address);
continue;
@@ -95,24 +97,24 @@ impl Executor {
// Already visited block
let section_address = section.address as u32;
if self.visited.contains(section_index, section_address, state.address) {
if self.visited.contains(section_address, state.address) {
continue;
}
let mut block_start = state.address;
loop {
self.visited.insert(section_index, section_address, state.address);
self.visited.insert(section_address, state.address);
let ins = match disassemble(section, state.address) {
let ins = match disassemble(section, state.address.address) {
Some(ins) => ins,
None => return Ok(None),
};
let result = state.vm.step(&ins);
let result = state.vm.step(obj, state.address, &ins);
match cb(ExecCbData {
executor: self,
vm: &mut state.vm,
result,
section_index,
ins_addr: state.address,
section,
ins: &ins,
block_start,
@@ -121,7 +123,7 @@ impl Executor {
state.address += 4;
}
ExecCbResult::Jump(addr) => {
if self.visited.contains(section_index, section_address, addr) {
if self.visited.contains(section_address, addr) {
break;
}
block_start = addr;
@@ -135,7 +137,7 @@ impl Executor {
Ok(None)
}
pub fn push(&mut self, address: u32, vm: Box<VM>, sort: bool) {
pub fn push(&mut self, address: SectionAddress, 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
@@ -143,7 +145,7 @@ impl Executor {
}
}
pub fn visited(&self, section_index: usize, section_address: u32, address: u32) -> bool {
self.visited.contains(section_index, section_address, address)
pub fn visited(&self, section_address: u32, address: SectionAddress) -> bool {
self.visited.contains(section_address, address)
}
}

View File

@@ -1,11 +1,12 @@
use std::{collections::BTreeSet, num::NonZeroU32};
use anyhow::{Context, Result};
use anyhow::{anyhow, ensure, Context, Result};
use ppc750cl::Ins;
use crate::{
analysis::cfa::SectionAddress,
array_ref,
obj::{ObjInfo, ObjSection, ObjSectionKind},
obj::{ObjInfo, ObjKind, ObjRelocKind, ObjSection, ObjSectionKind},
};
pub mod cfa;
@@ -18,33 +19,119 @@ pub mod tracker;
pub mod vm;
pub fn disassemble(section: &ObjSection, address: u32) -> Option<Ins> {
read_u32(&section.data, address, section.address as u32).map(|code| Ins::new(code, address))
read_u32(section, address).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 {
pub fn read_u32(section: &ObjSection, address: u32) -> Option<u32> {
let offset = (address as u64 - section.address) as usize;
if section.data.len() < offset + 4 {
return None;
}
Some(u32::from_be_bytes(*array_ref!(data, offset, 4)))
Some(u32::from_be_bytes(*array_ref!(section.data, offset, 4)))
}
fn is_valid_jump_table_addr(obj: &ObjInfo, addr: u32) -> bool {
matches!(obj.sections.at_address(addr), Ok((_, section)) if section.kind != ObjSectionKind::Bss)
fn read_unresolved_relocation_address(
obj: &ObjInfo,
section: &ObjSection,
address: u32,
reloc_kind: Option<ObjRelocKind>,
) -> Result<Option<SectionAddress>> {
if let Some(reloc) = obj
.unresolved_relocations
.iter()
.find(|reloc| reloc.section as usize == section.elf_index && reloc.address == address)
{
ensure!(reloc.module_id == obj.module_id);
if let Some(reloc_kind) = reloc_kind {
ensure!(reloc.kind == reloc_kind);
}
let (target_section_index, target_section) =
obj.sections.get_elf_index(reloc.target_section as usize).ok_or_else(|| {
anyhow!(
"Failed to find target section {} for unresolved relocation",
reloc.target_section
)
})?;
Ok(Some(SectionAddress {
section: target_section_index,
address: target_section.address as u32 + reloc.addend,
}))
} else {
Ok(None)
}
}
fn read_relocation_address(
obj: &ObjInfo,
section: &ObjSection,
address: u32,
reloc_kind: Option<ObjRelocKind>,
) -> Result<Option<SectionAddress>> {
let Some(reloc) = section.relocations.at(address) else {
return Ok(None);
};
if let Some(reloc_kind) = reloc_kind {
ensure!(reloc.kind == reloc_kind);
}
let symbol = &obj.symbols[reloc.target_symbol];
let section_index = symbol.section.with_context(|| {
format!("Symbol '{}' @ {:#010X} missing section", symbol.name, symbol.address)
})?;
Ok(Some(SectionAddress {
section: section_index,
address: (symbol.address as i64 + reloc.addend) as u32,
}))
}
pub fn read_address(obj: &ObjInfo, section: &ObjSection, address: u32) -> Result<SectionAddress> {
if obj.kind == ObjKind::Relocatable {
let mut opt = read_relocation_address(obj, section, address, Some(ObjRelocKind::Absolute))?;
if opt.is_none() {
opt = read_unresolved_relocation_address(
obj,
section,
address,
Some(ObjRelocKind::Absolute),
)?;
}
opt.with_context(|| {
format!("Failed to find relocation for {:#010X} in section {}", address, section.name)
})
} else {
let offset = (address as u64 - section.address) as usize;
let address = u32::from_be_bytes(*array_ref!(section.data, offset, 4));
let (section_index, _) = obj.sections.at_address(address)?;
Ok(SectionAddress::new(section_index, address))
}
}
fn is_valid_jump_table_addr(obj: &ObjInfo, addr: SectionAddress) -> bool {
!matches!(obj.sections[addr.section].kind, ObjSectionKind::Code | ObjSectionKind::Bss)
}
#[inline(never)]
pub fn relocation_target_for(
obj: &ObjInfo,
addr: SectionAddress,
reloc_kind: Option<ObjRelocKind>,
) -> Result<Option<SectionAddress>> {
let section = &obj.sections[addr.section];
let mut opt = read_relocation_address(obj, section, addr.address, reloc_kind)?;
if opt.is_none() {
opt = read_unresolved_relocation_address(obj, section, addr.address, reloc_kind)?;
}
Ok(opt)
}
fn get_jump_table_entries(
obj: &ObjInfo,
addr: u32,
addr: SectionAddress,
size: Option<NonZeroU32>,
from: u32,
function_start: u32,
function_end: u32,
) -> Result<(Vec<u32>, u32)> {
let (_, section) = obj.sections.at_address(addr).with_context(|| {
format!("Failed to get jump table entries @ {:#010X} size {:?}", addr, size)
})?;
let offset = (addr as u64 - section.address) as usize;
from: SectionAddress,
function_start: SectionAddress,
function_end: Option<SectionAddress>,
) -> Result<(Vec<SectionAddress>, u32)> {
let section = &obj.sections[addr.section];
if let Some(size) = size.map(|n| n.get()) {
log::trace!(
"Located jump table @ {:#010X} with entry count {} (from {:#010X})",
@@ -52,21 +139,58 @@ fn get_jump_table_entries(
size / 4,
from
);
let jt_data = &section.data[offset..offset + size as usize];
let entries =
jt_data.chunks_exact(4).map(|c| u32::from_be_bytes(c.try_into().unwrap())).collect();
let mut entries = Vec::with_capacity(size as usize / 4);
let mut data = section.data_range(addr.address, addr.address + size)?;
let mut cur_addr = addr;
loop {
if data.is_empty() {
break;
}
if let Some(target) =
relocation_target_for(obj, cur_addr, Some(ObjRelocKind::Absolute))?
{
entries.push(target);
} else {
let entry_addr = u32::from_be_bytes(*array_ref!(data, 0, 4));
let (section_index, _) =
obj.sections.at_address(entry_addr).with_context(|| {
format!(
"Invalid jump table entry {:#010X} at {:#010X}",
entry_addr, cur_addr
)
})?;
entries.push(SectionAddress::new(section_index, entry_addr));
}
data = &data[4..];
cur_addr += 4;
}
Ok((entries, size))
} else {
let mut entries = Vec::new();
let mut cur_addr = addr;
while let Some(value) = read_u32(&section.data, cur_addr, section.address as u32) {
if value < function_start || value >= function_end {
loop {
let target = if let Some(target) =
relocation_target_for(obj, cur_addr, Some(ObjRelocKind::Absolute))?
{
target
} else if obj.kind == ObjKind::Executable {
let Some(value) = read_u32(section, cur_addr.address) else {
break;
};
let Ok((section_index, _)) = obj.sections.at_address(value) else {
break;
};
SectionAddress::new(section_index, value)
} else {
break;
};
if target < function_start || matches!(function_end, Some(end) if target >= end) {
break;
}
entries.push(value);
entries.push(target);
cur_addr += 4;
}
let size = cur_addr - addr;
let size = cur_addr.address - addr.address;
log::debug!(
"Guessed jump table @ {:#010X} with entry count {} (from {:#010X})",
addr,
@@ -79,22 +203,26 @@ fn get_jump_table_entries(
pub fn uniq_jump_table_entries(
obj: &ObjInfo,
addr: u32,
addr: SectionAddress,
size: Option<NonZeroU32>,
from: u32,
function_start: u32,
function_end: u32,
) -> Result<(BTreeSet<u32>, u32)> {
from: SectionAddress,
function_start: SectionAddress,
function_end: Option<SectionAddress>,
) -> Result<(BTreeSet<SectionAddress>, 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))
Ok((BTreeSet::from_iter(entries.iter().cloned()), size))
}
pub fn skip_alignment(section: &ObjSection, mut addr: u32, end: u32) -> Option<u32> {
let mut data = match section.data_range(addr, end) {
pub fn skip_alignment(
section: &ObjSection,
mut addr: SectionAddress,
end: SectionAddress,
) -> Option<SectionAddress> {
let mut data = match section.data_range(addr.address, end.address) {
Ok(data) => data,
Err(_) => return None,
};

View File

@@ -1,11 +1,15 @@
use std::ops::Range;
use anyhow::Result;
use anyhow::{bail, ensure, Result};
use flagset::FlagSet;
use itertools::Itertools;
use crate::{
analysis::cfa::AnalyzerState,
obj::{ObjInfo, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind},
analysis::cfa::{AnalyzerState, SectionAddress},
obj::{
ObjInfo, ObjKind, ObjRelocKind, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
ObjSymbolFlags, ObjSymbolKind,
},
};
pub trait AnalysisPass {
@@ -20,9 +24,9 @@ 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_index, section) = obj.sections.at_address(start)?;
let data = match section.data_range(start, 0) {
for (&start, _) in state.function_bounds.iter().filter(|&(_, &end)| end.is_none()) {
let section = &obj.sections[start.section];
let data = match section.data_range(start.address, 0) {
Ok(ret) => ret,
Err(_) => continue,
};
@@ -33,8 +37,8 @@ impl AnalysisPass for FindTRKInterruptVectorTable {
state.known_symbols.insert(start, ObjSymbol {
name: "gTRKInterruptVectorTable".to_string(),
demangled_name: None,
address: start as u64,
section: Some(section_index),
address: start.address as u64,
section: Some(start.section),
size: 0,
size_known: true,
flags: ObjSymbolFlagSet(FlagSet::from(ObjSymbolFlags::Global)),
@@ -46,8 +50,8 @@ impl AnalysisPass for FindTRKInterruptVectorTable {
state.known_symbols.insert(end, ObjSymbol {
name: "gTRKInterruptVectorTableEnd".to_string(),
demangled_name: None,
address: end as u64,
section: Some(section_index),
address: end.address as u64,
section: Some(start.section),
size: 0,
size_known: true,
flags: ObjSymbolFlagSet(FlagSet::from(ObjSymbolFlags::Global)),
@@ -77,10 +81,10 @@ const SLEDS: [([u8; 4], &str, &str); 4] = [
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_index, section) = obj.sections.at_address(start)?;
let data = match section.data_range(start, 0) {
let mut clear_ranges: Vec<Range<SectionAddress>> = vec![];
for (&start, _) in state.function_bounds.iter().filter(|&(_, &end)| end.is_some()) {
let section = &obj.sections[start.section];
let data = match section.data_range(start.address, 0) {
Ok(ret) => ret,
Err(_) => continue,
};
@@ -91,8 +95,8 @@ impl AnalysisPass for FindSaveRestSleds {
state.known_symbols.insert(start, ObjSymbol {
name: func.to_string(),
demangled_name: None,
address: start as u64,
section: Some(section_index),
address: start.address as u64,
section: Some(start.section),
size: SLED_SIZE as u64,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
@@ -105,8 +109,8 @@ impl AnalysisPass for FindSaveRestSleds {
state.known_symbols.insert(addr, ObjSymbol {
name: format!("{}{}", label, i),
demangled_name: None,
address: addr as u64,
section: Some(section_index),
address: addr.address as u64,
section: Some(start.section),
size: 0,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
@@ -119,12 +123,163 @@ impl AnalysisPass for FindSaveRestSleds {
}
}
for range in clear_ranges {
for addr in range.step_by(4) {
let mut addr = range.start;
while addr < range.end {
state.function_entries.remove(&addr);
state.function_bounds.remove(&addr);
state.function_slices.remove(&addr);
addr += 4;
}
}
Ok(())
}
}
pub struct FindRelCtorsDtors {}
impl AnalysisPass for FindRelCtorsDtors {
fn execute(state: &mut AnalyzerState, obj: &ObjInfo) -> Result<()> {
ensure!(obj.kind == ObjKind::Relocatable);
ensure!(!obj.unresolved_relocations.is_empty());
match (obj.sections.by_name(".ctors")?, obj.sections.by_name(".dtors")?) {
(Some(_), Some(_)) => return Ok(()),
(None, None) => {}
_ => bail!("Only one of .ctors and .dtors has been found?"),
}
let possible_sections = obj
.sections
.iter()
.filter(|&(_, section)| {
if section.section_known
|| !matches!(section.kind, ObjSectionKind::Data | ObjSectionKind::ReadOnlyData)
|| section.size < 4
{
return false;
}
let mut current_address = section.address as u32;
let section_end = current_address + section.size as u32;
// Check that each word has a relocation to a function
// And the section ends with a null pointer
while let Some(reloc) = obj.unresolved_relocations.iter().find(|reloc| {
reloc.module_id == obj.module_id
&& reloc.section == section.elf_index as u8
&& reloc.address == current_address
&& reloc.kind == ObjRelocKind::Absolute
}) {
let Some((target_section_index, target_section)) = obj
.sections
.iter()
.find(|(_, section)| section.elf_index == reloc.target_section as usize)
else {
return false;
};
if target_section.kind != ObjSectionKind::Code
|| !state
.function_bounds
.contains_key(&SectionAddress::new(target_section_index, reloc.addend))
{
return false;
}
current_address += 4;
if current_address >= section_end {
return false;
}
}
if current_address + 4 != section_end {
return false;
}
section.data_range(section_end - 4, section_end).ok() == Some(&[0; 4])
})
.collect_vec();
if possible_sections.len() != 2 {
log::warn!("Failed to find .ctors and .dtors");
return Ok(());
}
log::debug!("Found .ctors and .dtors: {:?}", possible_sections);
let ctors_section_index = possible_sections[0].0;
state.known_sections.insert(ctors_section_index, ".ctors".to_string());
state.known_symbols.insert(SectionAddress::new(ctors_section_index, 0), ObjSymbol {
name: "_ctors".to_string(),
demangled_name: None,
address: 0,
section: Some(ctors_section_index),
size: 0,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
kind: Default::default(),
align: None,
data_kind: Default::default(),
});
let dtors_section_index = possible_sections[1].0;
state.known_sections.insert(dtors_section_index, ".dtors".to_string());
state.known_symbols.insert(SectionAddress::new(dtors_section_index, 0), ObjSymbol {
name: "_dtors".to_string(),
demangled_name: None,
address: 0,
section: Some(dtors_section_index),
size: 0,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
kind: Default::default(),
align: None,
data_kind: Default::default(),
});
// Check for duplicate entries in .dtors, indicating __destroy_global_chain_reference
// let mut dtors_entries = vec![];
// let mut current_address = obj.sections[dtors_section_index].address as u32;
// let section_end = current_address + obj.sections[dtors_section_index].size as u32;
// while let Some(reloc) = obj.unresolved_relocations.iter().find(|reloc| {
// reloc.module_id == obj.module_id
// && reloc.section == obj.sections[dtors_section_index].elf_index as u8
// && reloc.address == current_address
// && reloc.kind == ObjRelocKind::Absolute
// }) {
// let Some((target_section_index, target_section)) = obj
// .sections
// .iter()
// .find(|(_, section)| section.elf_index == reloc.target_section as usize)
// else {
// bail!("Failed to find target section for .dtors entry");
// };
// if target_section.kind != ObjSectionKind::Code
// || !state
// .function_bounds
// .contains_key(&SectionAddress::new(target_section_index, reloc.addend))
// {
// bail!("Failed to find target function for .dtors entry");
// }
// dtors_entries.push(SectionAddress::new(target_section_index, reloc.addend));
// current_address += 4;
// if current_address >= section_end {
// bail!("Failed to find null terminator for .dtors");
// }
// }
// if current_address + 4 != section_end {
// bail!("Failed to find null terminator for .dtors");
// }
// if dtors_entries.len() != dtors_entries.iter().unique().count() {
// log::debug!("Found __destroy_global_chain_reference");
// state.known_symbols.insert(SectionAddress::new(dtors_section_index, 0), ObjSymbol {
// name: "__destroy_global_chain_reference".to_string(),
// demangled_name: None,
// address: 0,
// section: Some(dtors_section_index),
// size: 4,
// size_known: true,
// flags: ObjSymbolFlagSet(ObjSymbolFlags::Local.into()),
// kind: ObjSymbolKind::Object,
// align: None,
// data_kind: Default::default(),
// });
// }
Ok(())
}
}

View File

@@ -1,7 +1,10 @@
use anyhow::{anyhow, Result};
use anyhow::{anyhow, bail, Result};
use crate::{
analysis::{cfa::AnalyzerState, read_u32},
analysis::{
cfa::{AnalyzerState, SectionAddress},
read_address,
},
obj::{
ObjInfo, ObjSectionKind, ObjSplit, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags,
ObjSymbolKind,
@@ -189,189 +192,230 @@ const SIGNATURES: &[(&str, &str)] = &[
];
const POST_SIGNATURES: &[(&str, &str)] = &[
("RSOStaticLocateObject", include_str!("../../assets/signatures/RSOStaticLocateObject.yml")),
// ("GXInit", include_str!("../../assets/signatures/GXInit.yml")),
("GXInit", include_str!("../../assets/signatures/GXInit.yml")),
("__register_fragment", include_str!("../../assets/signatures/__register_fragment.yml")),
("__unregister_fragment", include_str!("../../assets/signatures/__unregister_fragment.yml")),
("__register_atexit", include_str!("../../assets/signatures/__register_atexit.yml")),
(
"__register_global_object",
include_str!("../../assets/signatures/__register_global_object.yml"),
),
];
fn apply_signature_for_symbol(obj: &mut ObjInfo, name: &str, sig_str: &str) -> Result<()> {
let Some((_, symbol)) = obj.symbols.by_name(name)? else {
return Ok(());
};
let Some(section_index) = symbol.section else {
return Ok(());
};
let addr = symbol.address as u32;
let section = &obj.sections[section_index];
if let Some(signature) = check_signatures_str(section, addr, sig_str)? {
apply_signature(obj, SectionAddress::new(section_index, addr), &signature)?;
}
Ok(())
}
fn apply_ctors_signatures(obj: &mut ObjInfo) -> Result<()> {
let Some((_, symbol)) = obj.symbols.by_name("_ctors")? else {
return Ok(());
};
// First entry of ctors is __init_cpp_exceptions
let ctors_section_index =
symbol.section.ok_or_else(|| anyhow!("Missing _ctors symbol section"))?;
let ctors_section = &obj.sections[ctors_section_index];
// __init_cpp_exceptions_reference + null pointer
if ctors_section.size < 8 {
return Ok(());
}
let Some(target) = read_address(obj, ctors_section, symbol.address as u32).ok() else {
return Ok(());
};
let Some(signature) = check_signatures_str(
&obj.sections[target.section],
target.address,
include_str!("../../assets/signatures/__init_cpp_exceptions.yml"),
)?
else {
return Ok(());
};
let address = symbol.address;
apply_signature(obj, target, &signature)?;
obj.symbols.add(
ObjSymbol {
name: "__init_cpp_exceptions_reference".to_string(),
demangled_name: None,
address,
section: Some(ctors_section_index),
size: 4,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global | ObjSymbolFlags::RelocationIgnore),
kind: ObjSymbolKind::Object,
align: None,
data_kind: Default::default(),
},
true,
)?;
if obj.sections[ctors_section_index].splits.for_address(address as u32).is_none() {
obj.add_split(ctors_section_index, address as u32, ObjSplit {
unit: "__init_cpp_exceptions.cpp".to_string(),
end: address as u32 + 4,
align: None,
common: false,
autogenerated: true,
})?;
}
Ok(())
}
fn apply_dtors_signatures(obj: &mut ObjInfo) -> Result<()> {
let Some((_, symbol)) = obj.symbols.by_name("_dtors")? else {
for symbol in obj.symbols.iter() {
println!("{:?} {:#010X} {}", symbol.section, symbol.address, symbol.name);
}
bail!("Missing _dtors symbol");
// return Ok(());
};
let dtors_section_index =
symbol.section.ok_or_else(|| anyhow!("Missing _dtors symbol section"))?;
let dtors_section = &obj.sections[dtors_section_index];
// __destroy_global_chain_reference + null pointer
if dtors_section.size < 8 {
return Ok(());
}
let address = symbol.address;
let dgc_target = read_address(obj, dtors_section, address as u32).ok();
let fce_target = read_address(obj, dtors_section, address as u32 + 4).ok();
let mut found_dgc = false;
let mut found_fce = false;
// First entry of dtors is __destroy_global_chain
if let Some(dgc_target) = dgc_target {
if let Some(signature) = check_signatures_str(
&obj.sections[dgc_target.section],
dgc_target.address,
include_str!("../../assets/signatures/__destroy_global_chain.yml"),
)? {
apply_signature(obj, dgc_target, &signature)?;
obj.add_symbol(
ObjSymbol {
name: "__destroy_global_chain_reference".to_string(),
demangled_name: None,
address,
section: Some(dtors_section_index),
size: 4,
size_known: true,
flags: ObjSymbolFlagSet(
ObjSymbolFlags::Global | ObjSymbolFlags::RelocationIgnore,
),
kind: ObjSymbolKind::Object,
align: None,
data_kind: Default::default(),
},
true,
)?;
found_dgc = true;
} else {
log::warn!("Failed to match __destroy_global_chain signature ({:#010X})", dgc_target);
}
}
// Second entry of dtors is __fini_cpp_exceptions
if let Some(fce_target) = fce_target {
if let Some(signature) = check_signatures_str(
&obj.sections[fce_target.section],
fce_target.address,
include_str!("../../assets/signatures/__fini_cpp_exceptions.yml"),
)? {
apply_signature(obj, fce_target, &signature)?;
obj.add_symbol(
ObjSymbol {
name: "__fini_cpp_exceptions_reference".to_string(),
demangled_name: None,
address: address + 4,
section: Some(dtors_section_index),
size: 4,
size_known: true,
flags: ObjSymbolFlagSet(
ObjSymbolFlags::Global | ObjSymbolFlags::RelocationIgnore,
),
kind: ObjSymbolKind::Object,
align: None,
data_kind: Default::default(),
},
true,
)?;
found_fce = true;
}
}
if found_dgc {
let mut end = address as u32 + 4;
if found_fce {
end += 4;
}
if obj.sections[dtors_section_index].splits.for_address(address as u32).is_none() {
obj.add_split(dtors_section_index, address as u32, ObjSplit {
unit: "__init_cpp_exceptions.cpp".to_string(),
end,
align: None,
common: false,
autogenerated: true,
})?;
}
}
Ok(())
}
fn apply_init_user_signatures(obj: &mut ObjInfo) -> Result<()> {
let Some((_, symbol)) = obj.symbols.by_name("__init_user")? else {
return Ok(());
};
let Some(section_index) = symbol.section else {
return Ok(());
};
// __init_user can be overridden, but we can still look for __init_cpp from it
let mut analyzer = AnalyzerState::default();
analyzer.process_function_at(obj, SectionAddress::new(section_index, symbol.address as u32))?;
for addr in analyzer.function_entries {
let section = &obj.sections[addr.section];
if let Some(signature) = check_signatures_str(
section,
addr.address,
include_str!("../../assets/signatures/__init_cpp.yml"),
)? {
apply_signature(obj, SectionAddress::new(section_index, addr.address), &signature)?;
break;
}
}
Ok(())
}
pub fn apply_signatures(obj: &mut ObjInfo) -> Result<()> {
let entry = obj.entry as u32;
let (entry_section_index, entry_section) = obj.sections.at_address(entry)?;
if let Some(signature) = check_signatures_str(
entry_section,
entry,
include_str!("../../assets/signatures/__start.yml"),
)? {
apply_signature(obj, entry_section_index, entry, &signature)?;
if let Some(entry) = obj.entry.map(|n| n as u32) {
let (entry_section_index, entry_section) = obj.sections.at_address(entry)?;
if let Some(signature) = check_signatures_str(
entry_section,
entry,
include_str!("../../assets/signatures/__start.yml"),
)? {
apply_signature(obj, SectionAddress::new(entry_section_index, entry), &signature)?;
}
}
for &(name, sig_str) in SIGNATURES {
if let Some((_, symbol)) = obj.symbols.by_name(name)? {
let addr = symbol.address as u32;
let section_index =
symbol.section.ok_or_else(|| anyhow!("Symbol '{}' missing section", name))?;
let section = &obj.sections[section_index];
if let Some(signature) = check_signatures_str(section, addr, sig_str)? {
apply_signature(obj, section_index, addr, &signature)?;
}
}
}
if let Some((_, symbol)) = obj.symbols.by_name("__init_user")? {
// __init_user can be overridden, but we can still look for __init_cpp from it
let mut analyzer = AnalyzerState::default();
analyzer.process_function_at(obj, symbol.address as u32)?;
for addr in analyzer.function_entries {
let (section_index, section) = obj.sections.at_address(addr)?;
if let Some(signature) = check_signatures_str(
section,
addr,
include_str!("../../assets/signatures/__init_cpp.yml"),
)? {
apply_signature(obj, section_index, addr, &signature)?;
break;
}
}
}
if let Some((_, symbol)) = obj.symbols.by_name("_ctors")? {
// First entry of ctors is __init_cpp_exceptions
let ctors_section_index =
symbol.section.ok_or_else(|| anyhow!("Missing _ctors symbol section"))?;
let ctors_section = &obj.sections[ctors_section_index];
let target =
read_u32(&ctors_section.data, symbol.address as u32, ctors_section.address as u32)
.ok_or_else(|| anyhow!("Failed to read _ctors data"))?;
if target != 0 {
let (target_section_index, target_section) = obj.sections.at_address(target)?;
if let Some(signature) = check_signatures_str(
target_section,
target,
include_str!("../../assets/signatures/__init_cpp_exceptions.yml"),
)? {
let address = symbol.address;
apply_signature(obj, target_section_index, target, &signature)?;
obj.symbols.add(
ObjSymbol {
name: "__init_cpp_exceptions_reference".to_string(),
demangled_name: None,
address,
section: Some(ctors_section_index),
size: 4,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
kind: ObjSymbolKind::Object,
align: None,
data_kind: Default::default(),
},
true,
)?;
if obj.sections[ctors_section_index].splits.for_address(address as u32).is_none() {
obj.add_split(ctors_section_index, address as u32, ObjSplit {
unit: "__init_cpp_exceptions.cpp".to_string(),
end: address as u32 + 4,
align: None,
common: false,
autogenerated: true,
})?;
}
}
}
}
if let Some((_, symbol)) = obj.symbols.by_name("_dtors")? {
let dtors_section_index =
symbol.section.ok_or_else(|| anyhow!("Missing _dtors symbol section"))?;
let dtors_section = &obj.sections[dtors_section_index];
let address = symbol.address;
let section_address = dtors_section.address;
// First entry of dtors is __destroy_global_chain
let dgc_target = read_u32(&dtors_section.data, address as u32, section_address as u32)
.ok_or_else(|| anyhow!("Failed to read _dtors data"))?;
let fce_target = read_u32(&dtors_section.data, address as u32 + 4, section_address as u32)
.ok_or_else(|| anyhow!("Failed to read _dtors data"))?;
let mut found_dgc = false;
let mut found_fce = false;
if dgc_target != 0 {
let (target_section_index, target_section) = obj.sections.at_address(dgc_target)?;
if let Some(signature) = check_signatures_str(
target_section,
dgc_target,
include_str!("../../assets/signatures/__destroy_global_chain.yml"),
)? {
apply_signature(obj, target_section_index, dgc_target, &signature)?;
obj.add_symbol(
ObjSymbol {
name: "__destroy_global_chain_reference".to_string(),
demangled_name: None,
address,
section: Some(dtors_section_index),
size: 4,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
kind: ObjSymbolKind::Object,
align: None,
data_kind: Default::default(),
},
true,
)?;
found_dgc = true;
} else {
log::warn!(
"Failed to match __destroy_global_chain signature ({:#010X})",
dgc_target
);
}
}
// Second entry of dtors is __fini_cpp_exceptions
if fce_target != 0 {
let (target_section_index, target_section) = obj.sections.at_address(fce_target)?;
if let Some(signature) = check_signatures_str(
target_section,
fce_target,
include_str!("../../assets/signatures/__fini_cpp_exceptions.yml"),
)? {
apply_signature(obj, target_section_index, fce_target, &signature)?;
obj.add_symbol(
ObjSymbol {
name: "__fini_cpp_exceptions_reference".to_string(),
demangled_name: None,
address: address + 4,
section: Some(dtors_section_index),
size: 4,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
kind: ObjSymbolKind::Object,
align: None,
data_kind: Default::default(),
},
true,
)?;
found_fce = true;
}
}
if found_dgc {
let mut end = address as u32 + 4;
if found_fce {
end += 4;
}
if obj.sections[dtors_section_index].splits.for_address(address as u32).is_none() {
obj.add_split(dtors_section_index, address as u32, ObjSplit {
unit: "__init_cpp_exceptions.cpp".to_string(),
end,
align: None,
common: false,
autogenerated: true,
})?;
}
}
apply_signature_for_symbol(obj, name, sig_str)?
}
apply_init_user_signatures(obj)?;
apply_ctors_signatures(obj)?;
apply_dtors_signatures(obj)?;
Ok(())
}
pub fn apply_signatures_post(obj: &mut ObjInfo) -> Result<()> {
log::info!("Checking post CFA signatures...");
log::debug!("Checking post CFA signatures");
for &(_name, sig_str) in POST_SIGNATURES {
let signatures = parse_signatures(sig_str)?;
let mut found_signature = None;
@@ -391,13 +435,10 @@ pub fn apply_signatures_post(obj: &mut ObjInfo) -> Result<()> {
}
if let Some((symbol_index, signature)) = found_signature {
let symbol = &obj.symbols[symbol_index];
let section_index = symbol
.section
.ok_or_else(|| anyhow!("Symbol '{}' missing section", symbol.name))?;
let address = symbol.address as u32;
apply_signature(obj, section_index, address, &signature)?;
let symbol_addr = SectionAddress::new(symbol.section.unwrap(), symbol.address as u32);
apply_signature(obj, symbol_addr, &signature)?;
}
}
log::info!("Done!");
log::debug!("Done!");
Ok(())
}

View File

@@ -8,24 +8,25 @@ use ppc750cl::{Ins, Opcode};
use crate::{
analysis::{
cfa::SectionAddress,
disassemble,
executor::{ExecCbData, ExecCbResult, Executor},
uniq_jump_table_entries,
vm::{BranchTarget, StepResult, VM},
vm::{section_address_for, BranchTarget, StepResult, VM},
},
obj::{ObjInfo, ObjSection},
obj::{ObjInfo, ObjKind, 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>,
pub blocks: BTreeMap<SectionAddress, Option<SectionAddress>>,
pub branches: BTreeMap<SectionAddress, Vec<SectionAddress>>,
pub function_references: BTreeSet<SectionAddress>,
pub jump_table_references: BTreeMap<SectionAddress, u32>,
pub prologue: Option<SectionAddress>,
pub epilogue: Option<SectionAddress>,
// Either a block or tail call
pub possible_blocks: BTreeSet<u32>,
pub possible_blocks: BTreeSet<SectionAddress>,
pub has_conditional_blr: bool,
pub has_rfi: bool,
pub finalized: bool,
@@ -38,7 +39,7 @@ pub enum TailCallResult {
Error(anyhow::Error),
}
type BlockRange = Range<u32>;
type BlockRange = Range<SectionAddress>;
#[inline(always)]
fn next_ins(section: &ObjSection, ins: &Ins) -> Option<Ins> { disassemble(section, ins.addr + 4) }
@@ -72,33 +73,41 @@ fn check_sequence(
}
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 end(&self) -> Option<SectionAddress> {
self.blocks.last_key_value().map(|(_, &end)| end).flatten()
}
pub fn add_block_start(&mut self, addr: u32) -> bool {
pub fn start(&self) -> Option<SectionAddress> {
self.blocks.first_key_value().map(|(&start, _)| start)
}
pub fn add_block_start(&mut self, addr: SectionAddress) -> bool {
// Slice previous block.
if let Some((_, end)) = self.blocks.range_mut(..addr).next_back() {
let last_end = *end;
if last_end > addr {
*end = addr;
self.blocks.insert(addr, last_end);
return false;
if let Some(last_end) = *end {
if last_end > addr {
*end = Some(addr);
self.blocks.insert(addr, Some(last_end));
return false;
}
}
}
// Otherwise, insert with no end.
match self.blocks.entry(addr) {
btree_map::Entry::Vacant(e) => {
e.insert(0);
e.insert(None);
true
}
btree_map::Entry::Occupied(_) => false,
}
}
fn check_prologue(&mut self, section: &ObjSection, ins: &Ins) -> Result<()> {
fn check_prologue(
&mut self,
section: &ObjSection,
addr: SectionAddress,
ins: &Ins,
) -> Result<()> {
#[inline(always)]
fn is_mflr(ins: &Ins) -> bool {
// mfspr r0, LR
@@ -117,17 +126,22 @@ impl FunctionSlices {
if check_sequence(section, ins, &[(&is_stwu, &is_mflr), (&is_mflr, &is_stw)])? {
if let Some(prologue) = self.prologue {
if prologue != ins.addr && prologue != ins.addr - 4 {
bail!("Found duplicate prologue: {:#010X} and {:#010X}", prologue, ins.addr)
if prologue != addr && prologue != addr - 4 {
bail!("Found duplicate prologue: {:#010X} and {:#010X}", prologue, addr)
}
} else {
self.prologue = Some(ins.addr);
self.prologue = Some(addr);
}
}
Ok(())
}
fn check_epilogue(&mut self, section: &ObjSection, ins: &Ins) -> Result<()> {
fn check_epilogue(
&mut self,
section: &ObjSection,
addr: SectionAddress,
ins: &Ins,
) -> Result<()> {
#[inline(always)]
fn is_mtlr(ins: &Ins) -> bool {
// mtspr LR, r0
@@ -146,11 +160,11 @@ impl FunctionSlices {
if check_sequence(section, ins, &[(&is_mtlr, &is_addi), (&is_or, &is_mtlr)])? {
if let Some(epilogue) = self.epilogue {
if epilogue != ins.addr {
bail!("Found duplicate epilogue: {:#010X} and {:#010X}", epilogue, ins.addr)
if epilogue != addr {
bail!("Found duplicate epilogue: {:#010X} and {:#010X}", epilogue, addr)
}
} else {
self.epilogue = Some(ins.addr);
self.epilogue = Some(addr);
}
}
Ok(())
@@ -160,16 +174,16 @@ impl FunctionSlices {
&mut self,
data: ExecCbData,
obj: &ObjInfo,
function_start: u32,
function_end: Option<u32>,
known_functions: &BTreeSet<u32>,
function_start: SectionAddress,
function_end: Option<SectionAddress>,
known_functions: &BTreeSet<SectionAddress>,
) -> Result<ExecCbResult<bool>> {
let ExecCbData { executor, vm, result, section_index, section, ins, block_start } = data;
let ExecCbData { executor, vm, result, ins_addr, section, ins, block_start } = data;
// Track discovered prologue(s) and epilogue(s)
self.check_prologue(section, ins)
self.check_prologue(section, ins_addr, ins)
.with_context(|| format!("While processing {:#010X}", function_start))?;
self.check_epilogue(section, ins)
self.check_epilogue(section, ins_addr, ins)
.with_context(|| format!("While processing {:#010X}", function_start))?;
if !self.has_conditional_blr && is_conditional_blr(ins) {
self.has_conditional_blr = true;
@@ -179,37 +193,37 @@ impl FunctionSlices {
}
// 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);
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;
let next_address = ins_addr + 4;
// If we already visited the next address, connect the blocks and end
if executor.visited(section_index, section.address as u32, next_address) {
self.blocks.insert(block_start, next_address);
self.branches.insert(ins.addr, vec![next_address]);
if executor.visited(section.address as u32, next_address) {
self.blocks.insert(block_start, Some(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);
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);
let next_addr = ins_addr + 4;
self.blocks.insert(block_start, Some(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]);
self.branches.insert(ins_addr, vec![next_addr]);
if self.add_block_start(next_addr) {
executor.push(next_addr, vm.clone_for_return(), true);
}
@@ -217,14 +231,14 @@ impl FunctionSlices {
Ok(ExecCbResult::EndBlock)
}
BranchTarget::Return => {
self.blocks.insert(block_start, ins.addr + 4);
self.blocks.insert(block_start, Some(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 {
self.blocks.insert(block_start, Some(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)
@@ -233,7 +247,7 @@ impl FunctionSlices {
if self.add_block_start(addr) {
return Ok(ExecCbResult::Jump(addr));
}
} else if matches!(section.data_range(ins.addr, ins.addr + 4), Ok(data) if data == [0u8; 4])
} else if matches!(section.data_range(ins_addr.address, ins_addr.address + 4), Ok(data) if data == [0u8; 4])
{
// If this branch has zeroed padding after it, assume tail call.
self.function_references.insert(addr);
@@ -244,16 +258,16 @@ impl FunctionSlices {
}
BranchTarget::JumpTable { address, size } => {
// End of block
let next_address = ins.addr + 4;
self.blocks.insert(block_start, next_address);
let next_address = ins_addr + 4;
self.blocks.insert(block_start, Some(next_address));
let (mut entries, size) = uniq_jump_table_entries(
obj,
address,
size,
ins.addr,
ins_addr,
function_start,
function_end.unwrap_or_else(|| self.end()),
function_end.or_else(|| self.end()),
)?;
if entries.contains(&next_address)
&& !entries.iter().any(|addr| known_functions.contains(addr))
@@ -266,7 +280,7 @@ impl FunctionSlices {
executor.push(addr, vm.clone_all(), true);
}
}
self.branches.insert(ins.addr, branches);
self.branches.insert(ins_addr, branches);
} else {
// If the table doesn't contain the next address,
// it could be a function jump table instead
@@ -277,7 +291,7 @@ impl FunctionSlices {
},
StepResult::Branch(branches) => {
// End of block
self.blocks.insert(block_start, ins.addr + 4);
self.blocks.insert(block_start, Some(ins_addr + 4));
let mut out_branches = vec![];
for branch in branches {
@@ -296,12 +310,12 @@ impl FunctionSlices {
}
}
BranchTarget::JumpTable { .. } => {
bail!("Conditional jump table unsupported @ {:#010X}", ins.addr);
bail!("Conditional jump table unsupported @ {:#010X}", ins_addr);
}
}
}
if !out_branches.is_empty() {
self.branches.insert(ins.addr, out_branches);
self.branches.insert(ins_addr, out_branches);
}
Ok(ExecCbResult::EndBlock)
}
@@ -311,10 +325,10 @@ impl FunctionSlices {
pub fn analyze(
&mut self,
obj: &ObjInfo,
start: u32,
function_start: u32,
function_end: Option<u32>,
known_functions: &BTreeSet<u32>,
start: SectionAddress,
function_start: SectionAddress,
function_end: Option<SectionAddress>,
known_functions: &BTreeSet<SectionAddress>,
) -> Result<bool> {
if !self.add_block_start(start) {
return Ok(true);
@@ -342,8 +356,15 @@ impl FunctionSlices {
// 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);
loop {
let Some(end) = self.end() else {
log::warn!("Trailing block analysis failed @ {:#010X}", function_start);
break;
};
if end >= known_end {
break;
}
executor.push(end, VM::new_from_obj(obj), true);
let result = executor.run(obj, |data| {
self.instruction_callback(
data,
@@ -361,7 +382,7 @@ impl FunctionSlices {
// Sanity check
for (&start, &end) in &self.blocks {
ensure!(end != 0, "Failed to finalize block @ {start:#010X}");
ensure!(end.is_some(), "Failed to finalize block @ {start:#010X}");
}
Ok(true)
@@ -369,7 +390,11 @@ impl FunctionSlices {
pub fn can_finalize(&self) -> bool { self.possible_blocks.is_empty() }
pub fn finalize(&mut self, obj: &ObjInfo, known_functions: &BTreeSet<u32>) -> Result<()> {
pub fn finalize(
&mut self,
obj: &ObjInfo,
known_functions: &BTreeSet<SectionAddress>,
) -> Result<()> {
ensure!(!self.finalized, "Already finalized");
ensure!(self.can_finalize(), "Can't finalize");
@@ -384,49 +409,64 @@ impl FunctionSlices {
}
}
let end = self.end();
match (obj.sections.at_address(end), obj.sections.at_address(end - 4)) {
(Ok((section_index, section)), Ok((other_section_index, _other_section)))
if section_index == other_section_index =>
{
// FIXME this is real bad
if !self.has_conditional_blr {
if let Some(ins) = disassemble(section, end - 4) {
if ins.op == Opcode::B
&& self.function_references.contains(&ins.branch_dest().unwrap())
{
for branches in self.branches.values() {
if branches.len() > 1
&& branches.contains(self.blocks.last_key_value().unwrap().0)
let Some(end) = self.end() else {
bail!("Can't finalize function without known end: {:#010X?}", self.start())
};
// TODO: rework to make compatible with relocatable objects
if obj.kind == ObjKind::Executable {
match (
(end.section, &obj.sections[end.section]),
obj.sections.at_address(end.address - 4),
) {
((section_index, section), Ok((other_section_index, _other_section)))
if section_index == other_section_index =>
{
// FIXME this is real bad
if !self.has_conditional_blr {
if let Some(ins) = disassemble(section, end.address - 4) {
if ins.op == Opcode::B {
if let Some(target) = ins
.branch_dest()
.and_then(|addr| section_address_for(obj, end - 4, addr))
{
self.has_conditional_blr = true;
if self.function_references.contains(&target) {
for branches in self.branches.values() {
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
&& matches!(disassemble(section, end - 4), Some(ins) if !ins.is_blr())
&& matches!(disassemble(section, 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);
}
// MWCC optimization sometimes leaves an unreachable blr
// after generating a conditional blr in the function.
if self.has_conditional_blr
&& matches!(disassemble(section, end.address - 4), Some(ins) if !ins.is_blr())
&& matches!(disassemble(section, end.address), Some(ins) if ins.is_blr())
&& !known_functions.contains(&end)
{
log::trace!("Found trailing blr @ {:#010X}, merging with function", end);
self.blocks.insert(end, Some(end + 4));
}
// Some functions with rfi also include a trailing nop
if self.has_rfi
&& matches!(disassemble(section, 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);
// Some functions with rfi also include a trailing nop
if self.has_rfi
&& matches!(disassemble(section, end.address), Some(ins) if is_nop(&ins))
&& !known_functions.contains(&end)
{
log::trace!("Found trailing nop @ {:#010X}, merging with function", end);
self.blocks.insert(end, Some(end + 4));
}
}
_ => {}
}
_ => {}
}
self.finalized = true;
@@ -437,15 +477,20 @@ impl FunctionSlices {
pub fn check_tail_call(
&mut self,
obj: &ObjInfo,
addr: u32,
function_start: u32,
function_end: u32,
known_functions: &BTreeSet<u32>,
addr: SectionAddress,
function_start: SectionAddress,
function_end: Option<SectionAddress>,
known_functions: &BTreeSet<SectionAddress>,
) -> 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) {
if self.blocks.contains_key(&addr) {
return TailCallResult::Not;
}
if let Some(function_end) = function_end {
if 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;
@@ -455,20 +500,18 @@ impl FunctionSlices {
return TailCallResult::Is;
}
// If the jump target is in a different section, known tail call.
let (_, target_section) = match obj.sections.at_address(addr) {
Ok(section) => section,
Err(e) => return TailCallResult::Error(e),
};
if !target_section.contains(function_start) {
if addr.section != function_start.section {
return TailCallResult::Is;
}
// If the jump target has 0'd padding before it, known tail call.
if matches!(target_section.data_range(addr - 4, addr), Ok(data) if data == [0u8; 4]) {
let target_section = &obj.sections[addr.section];
if matches!(target_section.data_range(addr.address - 4, addr.address), 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 {
if function_end.is_none() {
return TailCallResult::Possible;
}
// If jump target is known to be a function, or there's a function in between
@@ -483,8 +526,7 @@ impl FunctionSlices {
function_references: self.function_references.clone(),
..Default::default()
};
if let Ok(result) =
slices.analyze(obj, addr, function_start, Some(function_end), known_functions)
if let Ok(result) = slices.analyze(obj, addr, function_start, function_end, known_functions)
{
// If analysis failed, assume tail call.
if !result {
@@ -492,23 +534,28 @@ impl FunctionSlices {
return TailCallResult::Is;
}
// If control flow jumps below the entry point, not a tail call.
let start = slices.start();
let start = slices.start().unwrap();
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);
if let Some(end) = slices.end() {
// TODO idk if wrapping this is right
let other_blocks = self
.possible_blocks
.range(start + 4..end)
.cloned()
.collect::<Vec<SectionAddress>>();
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;
}
log::trace!("While analyzing {:#010X}", addr);
return TailCallResult::Not;
}
// If we discovered a function prologue, known tail call.
if slices.prologue.is_some() {
@@ -524,7 +571,10 @@ impl FunctionSlices {
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)),
(Some((&b1s, &Some(b1e))), Some(&(&b2s, &Some(b2e)))) => {
((b1s, b1e), (b2s, b2e))
}
(Some(_), Some(_)) => continue,
_ => break None,
};
if second_begin > first_end {

View File

@@ -1,5 +1,5 @@
use std::{
collections::{btree_map::Entry, BTreeMap, BTreeSet},
collections::{BTreeMap, BTreeSet},
mem::take,
};
@@ -8,25 +8,26 @@ use ppc750cl::Opcode;
use crate::{
analysis::{
cfa::SectionAddress,
executor::{ExecCbData, ExecCbResult, Executor},
uniq_jump_table_entries,
relocation_target_for, uniq_jump_table_entries,
vm::{is_store_op, BranchTarget, GprValue, StepResult, VM},
},
obj::{
ObjDataKind, ObjInfo, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind, ObjSymbol,
ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
ObjDataKind, ObjInfo, ObjKind, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind,
ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
},
};
#[derive(Debug, Copy, Clone)]
pub enum Relocation {
Ha(u32),
Hi(u32),
Lo(u32),
Sda21(u32),
Rel14(u32),
Rel24(u32),
Absolute(u32),
Ha(SectionAddress),
Hi(SectionAddress),
Lo(SectionAddress),
Sda21(SectionAddress),
Rel14(SectionAddress),
Rel24(SectionAddress),
Absolute(SectionAddress),
}
#[derive(Debug)]
@@ -42,30 +43,29 @@ pub enum DataKind {
}
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>,
processed_functions: BTreeSet<SectionAddress>,
sda2_base: Option<u32>, // r2
sda_base: Option<u32>, // r13
pub relocations: BTreeMap<SectionAddress, Relocation>,
data_types: BTreeMap<SectionAddress, 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>,
pub known_relocations: BTreeSet<SectionAddress>,
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
stores_to: BTreeSet<SectionAddress>, // for determining data vs rodata, sdata(2)/sbss(2)
sda_to: BTreeSet<SectionAddress>, // for determining data vs sdata
hal_to: BTreeSet<SectionAddress>, // 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(),
sda2_base: obj.sda2_base,
sda_base: obj.sda_base,
relocations: Default::default(),
data_types: Default::default(),
stack_address: obj.stack_address,
@@ -81,7 +81,6 @@ impl Tracker {
.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(),
@@ -98,20 +97,24 @@ impl Tracker {
.filter(|(_, s)| matches!(s.kind, ObjSectionKind::Data | ObjSectionKind::ReadOnlyData))
{
log::debug!("Processing section {}, address {:#X}", section_index, section.address);
self.process_data(obj, section)?;
self.process_data(obj, section_index, section)?;
}
Ok(())
}
fn process_code(&mut self, obj: &ObjInfo) -> Result<()> {
self.process_function_by_address(obj, obj.entry as u32)?;
if let Some(entry) = obj.entry {
let (section_index, _) = obj.sections.at_address(entry as u32)?;
let entry_addr = SectionAddress::new(section_index, entry as u32);
self.process_function_by_address(obj, entry_addr)?;
}
for (section_index, _) in obj.sections.by_kind(ObjSectionKind::Code) {
for (_, symbol) in obj
.symbols
.for_section(section_index)
.filter(|(_, symbol)| symbol.kind == ObjSymbolKind::Function && symbol.size_known)
{
let addr = symbol.address as u32;
let addr = SectionAddress::new(section_index, symbol.address as u32);
if !self.processed_functions.insert(addr) {
continue;
}
@@ -121,15 +124,14 @@ impl Tracker {
Ok(())
}
fn process_function_by_address(&mut self, obj: &ObjInfo, addr: u32) -> Result<()> {
fn process_function_by_address(&mut self, obj: &ObjInfo, addr: SectionAddress) -> Result<()> {
if self.processed_functions.contains(&addr) {
return Ok(());
}
self.processed_functions.insert(addr);
let (section_index, _) = obj.sections.at_address(addr)?;
if let Some((_, symbol)) = obj
.symbols
.at_section_address(section_index, addr)
.at_section_address(addr.section, addr.address)
.find(|(_, symbol)| symbol.kind == ObjSymbolKind::Function && symbol.size_known)
{
self.process_function(obj, symbol)?;
@@ -143,13 +145,12 @@ impl Tracker {
&mut self,
data: ExecCbData,
obj: &ObjInfo,
function_start: u32,
function_end: u32,
possible_missed_branches: &mut BTreeMap<u32, Box<VM>>,
function_start: SectionAddress,
function_end: SectionAddress,
possible_missed_branches: &mut BTreeMap<SectionAddress, Box<VM>>,
) -> Result<ExecCbResult<()>> {
let ExecCbData { executor, vm, result, section_index: _, section: _, ins, block_start: _ } =
data;
let is_function_addr = |addr: u32| addr >= function_start && addr < function_end;
let ExecCbData { executor, vm, result, ins_addr, section: _, ins, block_start: _ } = data;
let is_function_addr = |addr: SectionAddress| addr >= function_start && addr < function_end;
match result {
StepResult::Continue => {
@@ -159,26 +160,25 @@ impl Tracker {
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 let Some(value) = self.is_valid_address(obj, ins_addr, value) {
if (source == 2
&& vm.gpr[2].value == GprValue::Constant(self.sda2_base))
&& matches!(self.sda2_base, Some(v) if vm.gpr[2].value == GprValue::Constant(v)))
|| (source == 13
&& vm.gpr[13].value == GprValue::Constant(self.sda_base))
&& matches!(self.sda_base, Some(v) if vm.gpr[13].value == GprValue::Constant(v)))
{
self.relocations.insert(ins.addr, Relocation::Sda21(value));
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();
let hi_reloc = self.relocations.get(&hi_addr).cloned();
if hi_reloc.is_none() {
self.relocations
.insert(hi_addr.get(), Relocation::Ha(value));
debug_assert_ne!(value, SectionAddress::new(usize::MAX, 0));
self.relocations.insert(hi_addr, Relocation::Ha(value));
}
let lo_reloc = self.relocations.get(&lo_addr.get()).cloned();
let lo_reloc = self.relocations.get(&lo_addr).cloned();
if lo_reloc.is_none() {
self.relocations
.insert(lo_addr.get(), Relocation::Lo(value));
self.relocations.insert(lo_addr, Relocation::Lo(value));
}
self.hal_to.insert(value);
}
@@ -189,19 +189,17 @@ impl Tracker {
Opcode::Ori => {
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(value) = 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();
let hi_reloc = self.relocations.get(&hi_addr).cloned();
if hi_reloc.is_none() {
self.relocations
.insert(hi_addr.get(), Relocation::Hi(value));
self.relocations.insert(hi_addr, Relocation::Hi(value));
}
let lo_reloc = self.relocations.get(&lo_addr.get()).cloned();
let lo_reloc = self.relocations.get(&lo_addr).cloned();
if lo_reloc.is_none() {
self.relocations
.insert(lo_addr.get(), Relocation::Lo(value));
self.relocations.insert(lo_addr, Relocation::Lo(value));
}
self.hal_to.insert(value);
}
@@ -213,34 +211,38 @@ impl Tracker {
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))
if let Some(address) = self.is_valid_address(obj, ins_addr, address.address) {
if (source_reg == 2
&& matches!(self.sda2_base, Some(v) if source.value == GprValue::Constant(v)))
|| (source_reg == 13
&& matches!(self.sda_base, Some(v) if source.value == GprValue::Constant(v)))
{
self.relocations.insert(ins.addr, Relocation::Sda21(address));
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();
let hi_reloc = self.relocations.get(&hi_addr).cloned();
if hi_reloc.is_none() {
self.relocations.insert(hi_addr.get(), Relocation::Ha(address));
debug_assert_ne!(address, SectionAddress::new(usize::MAX, 0));
self.relocations.insert(hi_addr, 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.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();
let hi_reloc = self.relocations.get(&hi_addr).cloned();
if hi_reloc.is_none() {
self.relocations.insert(hi_addr.get(), Relocation::Ha(address));
debug_assert_ne!(address, SectionAddress::new(usize::MAX, 0));
self.relocations.insert(hi_addr, Relocation::Ha(address));
}
let lo_reloc = self.relocations.get(&lo_addr.get()).cloned();
let lo_reloc = self.relocations.get(&lo_addr).cloned();
if lo_reloc.is_none() {
self.relocations.insert(lo_addr.get(), Relocation::Lo(address));
self.relocations.insert(lo_addr, Relocation::Lo(address));
}
self.hal_to.insert(address);
}
@@ -256,22 +258,22 @@ impl Tracker {
}
StepResult::Illegal => bail!(
"Illegal instruction hit @ {:#010X} (function {:#010X}-{:#010X})",
ins.addr,
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;
let next_addr = ins_addr + 4;
if next_addr < function_end {
possible_missed_branches.insert(ins.addr + 4, vm.clone_all());
possible_missed_branches.insert(ins_addr + 4, vm.clone_all());
}
if is_function_addr(addr) {
Ok(ExecCbResult::Jump(addr))
} else {
if ins.is_direct_branch() {
self.relocations.insert(ins.addr, Relocation::Rel24(addr));
self.relocations.insert(ins_addr, Relocation::Rel24(addr));
}
Ok(ExecCbResult::EndBlock)
}
@@ -281,9 +283,9 @@ impl Tracker {
obj,
address,
size,
ins.addr,
ins_addr,
function_start,
function_end,
Some(function_end),
)?;
for target in entries {
if is_function_addr(target) {
@@ -299,7 +301,7 @@ impl Tracker {
BranchTarget::Unknown | BranchTarget::Return => {}
BranchTarget::Address(addr) => {
if branch.link || !is_function_addr(addr) {
self.relocations.insert(ins.addr, match ins.op {
self.relocations.insert(ins_addr, match ins.op {
Opcode::B => Relocation::Rel24(addr),
Opcode::Bc => Relocation::Rel14(addr),
_ => continue,
@@ -309,7 +311,7 @@ impl Tracker {
}
}
BranchTarget::JumpTable { .. } => {
bail!("Conditional jump table unsupported @ {:#010X}", ins.addr)
bail!("Conditional jump table unsupported @ {:#010X}", ins_addr)
}
}
}
@@ -319,19 +321,18 @@ impl Tracker {
}
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;
let Some(section_index) = symbol.section else {
bail!("Function '{}' missing section", symbol.name)
};
let function_start = SectionAddress::new(section_index, symbol.address as u32);
let function_end = function_start + 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,
);
executor.push(function_start, VM::new_with_base(self.sda2_base, self.sda_base), false);
loop {
executor.run(obj, |data| -> Result<ExecCbResult<()>> {
self.instruction_callback(
@@ -348,11 +349,8 @@ impl Tracker {
}
let mut added = false;
for (addr, vm) in take(&mut possible_missed_branches) {
let (section_index, section) = match obj.sections.at_address(addr) {
Ok(section) => section,
Err(_) => continue,
};
if !executor.visited(section_index, section.address as u32, addr) {
let section = &obj.sections[addr.section];
if !executor.visited(section.address as u32, addr) {
executor.push(addr, vm, true);
added = true;
}
@@ -364,11 +362,16 @@ impl Tracker {
Ok(())
}
fn process_data(&mut self, obj: &ObjInfo, section: &ObjSection) -> Result<()> {
let mut addr = section.address as u32;
fn process_data(
&mut self,
obj: &ObjInfo,
section_index: usize,
section: &ObjSection,
) -> Result<()> {
let mut addr = SectionAddress::new(section_index, 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) {
if let Some(value) = self.is_valid_address(obj, addr, value) {
self.relocations.insert(addr, Relocation::Absolute(value));
}
addr += 4;
@@ -376,36 +379,53 @@ impl Tracker {
Ok(())
}
fn is_valid_address(&self, obj: &ObjInfo, from: u32, addr: u32) -> bool {
if self.ignore_addresses.contains(&addr) {
return false;
}
if let Some((&start, &end)) = obj.blocked_ranges.range(..=from).next_back() {
if from >= start && from < end {
return false;
fn is_valid_address(
&self,
obj: &ObjInfo,
from: SectionAddress,
addr: u32,
) -> Option<SectionAddress> {
if let Some((&start, &end)) = obj.blocked_ranges.range(..=from.address).next_back() {
if from.address >= start && from.address < end {
return None;
}
}
// Check for an existing relocation
if let Some(target) = relocation_target_for(obj, from, None).ok().flatten() {
if obj.kind == ObjKind::Executable {
debug_assert_eq!(target.address, addr);
}
return Some(target);
}
// Remainder of this function is for executable objects only
if obj.kind == ObjKind::Relocatable {
return None;
}
if self.known_relocations.contains(&from) {
return true;
let section_index =
obj.sections.at_address(addr).ok().map(|(idx, _)| idx).unwrap_or(usize::MAX);
return Some(SectionAddress::new(section_index, addr));
}
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
|| self.sda2_base == Some(addr)
|| self.sda_base == Some(addr)
{
return true;
return Some(SectionAddress::new(usize::MAX, addr));
}
// if addr > 0x80000000 && addr < 0x80003100 {
// return true;
// }
if let Ok((_, section)) = obj.sections.at_address(addr) {
if let Ok((section_index, section)) = obj.sections.at_address(addr) {
// References to code sections will never be unaligned
return section.kind != ObjSectionKind::Code || addr & 3 == 0;
if section.kind != ObjSectionKind::Code || addr & 3 == 0 {
return Some(SectionAddress::new(section_index, addr));
}
}
false
None
}
fn special_symbol(
@@ -437,8 +457,8 @@ impl Tracker {
.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_"))
.or_else(|| check_symbol(self.sda2_base, "_SDA2_BASE_"))
.or_else(|| check_symbol(self.sda_base, "_SDA_BASE_"))
}
pub fn apply(&self, obj: &mut ObjInfo, replace: bool) -> Result<()> {
@@ -454,14 +474,14 @@ impl Tracker {
section.name = new_name;
}
for (_, section) in obj.sections.iter_mut() {
for (section_index, section) in obj.sections.iter_mut() {
if !section.section_known {
if section.kind == ObjSectionKind::Code {
apply_section_name(section, ".text");
continue;
}
let start = section.address as u32;
let end = (section.address + section.size) as u32;
let start = SectionAddress::new(section_index, section.address as u32);
let end = start + 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 {
@@ -488,11 +508,6 @@ impl Tracker {
}
}
let mut relocation_maps = Vec::new();
for (_, section) in obj.sections.iter() {
relocation_maps.push(section.build_relocation_map()?);
}
for (addr, reloc) in &self.relocations {
let addr = *addr;
let (reloc_kind, target) = match *reloc {
@@ -516,89 +531,67 @@ impl Tracker {
DataKind::Double => ObjDataKind::Double,
})
.unwrap_or_default();
let (target_symbol, addend) =
if let Some(symbol) = self.special_symbol(obj, target, reloc_kind) {
(symbol, 0)
} else {
let (target_section_index, _) = match obj.sections.iter().find(|&(_, s)| {
target >= s.address as u32 && target < (s.address + s.size) as u32
}) {
Some(v) => v,
None => continue,
};
if let Some((symbol_idx, symbol)) =
obj.symbols.for_relocation(target, reloc_kind)?
{
let symbol_address = symbol.address;
// TODO meh
if data_kind != ObjDataKind::Unknown
&& symbol.data_kind == ObjDataKind::Unknown
&& symbol_address as u32 == target
{
obj.symbols
.replace(symbol_idx, ObjSymbol { data_kind, ..symbol.clone() })?;
}
(symbol_idx, target as i64 - symbol_address as i64)
} else {
// Create a new label
let symbol_idx = obj.symbols.add_direct(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(),
align: None,
data_kind,
})?;
(symbol_idx, 0)
}
};
let reloc = ObjReloc {
kind: reloc_kind,
address: addr as u64,
target_symbol,
addend,
module: None,
};
let (section_index, section) =
match obj.sections.iter_mut().find(|(_, s)| s.contains(addr)) {
Some(v) => v,
None => bail!(
"Failed to locate source section for relocation @ {:#010X} {:#010X?}",
addr,
reloc
),
};
let reloc_map = &mut relocation_maps[section_index];
match reloc_map.entry(addr) {
Entry::Vacant(e) => {
e.insert(section.relocations.len());
section.relocations.push(reloc);
let (target_symbol, addend) = if let Some(symbol) =
self.special_symbol(obj, target.address, reloc_kind)
{
(symbol, 0)
} else if let Some((symbol_idx, symbol)) =
obj.symbols.for_relocation(target, reloc_kind)?
{
let symbol_address = symbol.address;
// TODO meh
if data_kind != ObjDataKind::Unknown
&& symbol.data_kind == ObjDataKind::Unknown
&& symbol_address as u32 == target.address
{
obj.symbols.replace(symbol_idx, ObjSymbol { data_kind, ..symbol.clone() })?;
}
Entry::Occupied(e) => {
let reloc_symbol = &obj.symbols[reloc.target_symbol];
if reloc_symbol.name != "_unresolved" {
let v = &mut section.relocations[*e.get()];
let iter_symbol = &obj.symbols[v.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,
iter_symbol.name,
reloc,
reloc_symbol.name
);
}
if replace {
*v = reloc;
}
(symbol_idx, target.address as i64 - symbol_address as i64)
} else {
// Create a new label
let name = if obj.module_id == 0 {
format!("lbl_{:08X}", target.address)
} else {
format!(
"lbl_{}_{}_{:X}",
obj.module_id,
obj.sections[target.section].name.trim_start_matches('.'),
target.address
)
};
let symbol_idx = obj.symbols.add_direct(ObjSymbol {
name,
demangled_name: None,
address: target.address as u64,
section: Some(target.section),
size: 0,
size_known: false,
flags: Default::default(),
kind: Default::default(),
align: None,
data_kind,
})?;
(symbol_idx, 0)
};
let reloc = ObjReloc { kind: reloc_kind, target_symbol, addend, module: None };
let section = &mut obj.sections[addr.section];
if replace {
section.relocations.replace(addr.address, reloc);
} else if let Err(e) = section.relocations.insert(addr.address, reloc.clone()) {
let reloc_symbol = &obj.symbols[target_symbol];
if reloc_symbol.name != "_unresolved" {
let iter_symbol = &obj.symbols[e.value.target_symbol];
if iter_symbol.address as i64 + e.value.addend
!= reloc_symbol.address as i64 + addend
{
bail!(
"Conflicting relocations (target {:#010X}): {:#010X?} ({}) != {:#010X?} ({})",
target,
e.value,
iter_symbol.name,
reloc,
reloc_symbol.name
);
}
}
}

View File

@@ -2,7 +2,10 @@ use std::num::NonZeroU32;
use ppc750cl::{Argument, Ins, Opcode, GPR};
use crate::obj::ObjInfo;
use crate::{
analysis::{cfa::SectionAddress, relocation_target_for},
obj::{ObjInfo, ObjKind},
};
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
pub enum GprValue {
@@ -11,6 +14,8 @@ pub enum GprValue {
Unknown,
/// GPR value is a constant
Constant(u32),
/// GPR value is a known relocated address
Address(SectionAddress),
/// Comparison result (CR field)
ComparisonResult(u8),
/// GPR value is within a range
@@ -24,9 +29,9 @@ pub struct Gpr {
/// The current calculated value
pub value: GprValue,
/// Address that loads the hi part of this GPR
pub hi_addr: Option<NonZeroU32>,
pub hi_addr: Option<SectionAddress>,
/// Address that loads the lo part of this GPR
pub lo_addr: Option<NonZeroU32>,
pub lo_addr: Option<SectionAddress>,
}
impl Gpr {
@@ -36,16 +41,16 @@ impl Gpr {
self.lo_addr = None;
}
fn set_hi(&mut self, value: GprValue, addr: u32) {
fn set_hi(&mut self, value: GprValue, addr: SectionAddress) {
self.value = value;
self.hi_addr = NonZeroU32::new(addr);
self.hi_addr = Some(addr);
self.lo_addr = None;
}
fn set_lo(&mut self, value: GprValue, addr: u32, hi_gpr: Gpr) {
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 = hi_gpr.lo_addr.or_else(|| NonZeroU32::new(addr));
self.lo_addr = Some(hi_gpr.lo_addr.unwrap_or(addr));
}
}
@@ -80,9 +85,9 @@ pub enum BranchTarget {
/// Branch to LR
Return,
/// Branch to address
Address(u32),
Address(SectionAddress),
/// Branch to jump table
JumpTable { address: u32, size: Option<NonZeroU32> },
JumpTable { address: SectionAddress, size: Option<NonZeroU32> },
}
#[derive(Debug, Clone, Eq, PartialEq)]
@@ -100,7 +105,7 @@ pub enum StepResult {
/// Continue normally
Continue,
/// Load from / store to
LoadStore { address: u32, source: Gpr, source_reg: u8 },
LoadStore { address: SectionAddress, source: Gpr, source_reg: u8 },
/// Hit illegal instruction
Illegal,
/// Jump without affecting VM state
@@ -109,23 +114,40 @@ pub enum StepResult {
Branch(Vec<Branch>),
}
pub fn section_address_for(
obj: &ObjInfo,
ins_addr: SectionAddress,
target_addr: u32,
) -> Option<SectionAddress> {
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(SectionAddress::new(section_index, target_addr));
}
// TODO: relative jumps within relocatable objects?
None
}
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(),
}
Self::new_with_base(obj.sda2_base, obj.sda_base)
}
#[inline]
pub fn new_with_base(sda2_base: u32, sda_base: u32) -> Box<Self> {
pub fn new_with_base(sda2_base: Option<u32>, sda_base: Option<u32>) -> Box<Self> {
let mut vm = Self::new();
vm.gpr[2].value = GprValue::Constant(sda2_base);
vm.gpr[13].value = GprValue::Constant(sda_base);
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
}
@@ -157,7 +179,13 @@ impl VM {
#[inline]
pub fn clone_all(&self) -> Box<Self> { Box::new(self.clone()) }
pub fn step(&mut self, ins: &Ins) -> StepResult {
pub fn step(&mut self, obj: &ObjInfo, ins_addr: SectionAddress, ins: &Ins) -> StepResult {
let relocation_target = relocation_target_for(obj, ins_addr, None).ok().flatten();
if let Some(_target) = relocation_target {
let _defs = ins.defs();
// TODO
}
match ins.op {
Opcode::Illegal => {
return StepResult::Illegal;
@@ -189,7 +217,7 @@ impl VM {
};
if ins.field_rA() == 0 {
// lis rD, SIMM
self.gpr[ins.field_rD()].set_hi(value, ins.addr);
self.gpr[ins.field_rD()].set_hi(value, ins_addr);
} else {
self.gpr[ins.field_rD()].set_direct(value);
}
@@ -213,7 +241,7 @@ impl VM {
// 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()]);
self.gpr[ins.field_rD()].set_lo(value, ins_addr, self.gpr[ins.field_rA()]);
}
}
// ori rA, rS, UIMM
@@ -224,7 +252,7 @@ impl VM {
}
_ => GprValue::Unknown,
};
self.gpr[ins.field_rA()].set_lo(value, ins.addr, self.gpr[ins.field_rS()]);
self.gpr[ins.field_rA()].set_lo(value, ins_addr, self.gpr[ins.field_rS()]);
}
// or rA, rS, rB
Opcode::Or => {
@@ -304,24 +332,41 @@ impl VM {
let branch_target = match ins.op {
Opcode::Bcctr => {
match self.ctr {
GprValue::Constant(value) => BranchTarget::Address(value),
GprValue::Constant(value) => {
if let Some(target) = section_address_for(obj, ins_addr, value) {
BranchTarget::Address(target)
} else {
BranchTarget::Unknown
}
},
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)) }
if let Some(target) = section_address_for(obj, ins_addr, address) {
BranchTarget::JumpTable { address: target, size: max_offset.and_then(|n| n.checked_add(4)) }
} else {
BranchTarget::Unknown
}
}
_ => BranchTarget::Unknown,
}
}
Opcode::Bclr => BranchTarget::Return,
_ => BranchTarget::Address(ins.branch_dest().unwrap()),
_ => {
let value = ins.branch_dest().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(ins.addr + 4),
target: BranchTarget::Address(ins_addr + 4),
link: false,
vm: self.clone_for_return(),
},
@@ -338,7 +383,7 @@ impl VM {
let mut branches = vec![
// Branch not taken
Branch {
target: BranchTarget::Address(ins.addr + 4),
target: BranchTarget::Address(ins_addr + 4),
link: false,
vm: self.clone_all(),
},
@@ -409,15 +454,17 @@ impl VM {
if is_update_op(op) {
self.gpr[source].set_lo(
GprValue::Constant(address),
ins.addr,
ins_addr,
self.gpr[source],
);
}
result = StepResult::LoadStore {
address,
source: self.gpr[source],
source_reg: source as u8,
};
if let Some(target) = section_address_for(obj, ins_addr, address) {
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);
}
@@ -619,119 +666,119 @@ pub fn is_update_op(op: Opcode) -> bool {
// )
// }
#[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)
})
);
}
}
// #[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)
// })
// );
// }
// }