mirror of
https://github.com/encounter/decomp-toolkit.git
synced 2025-12-12 22:56:28 +00:00
Analyzer fixes galore
- Transparent NLZSS decompression (add `:nlzss` to path) - Overhaul portions of the analyzer to support more games - Reject some invalid data relocations automatically - Jump table analysis fixes
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
cmp::min,
|
||||
collections::BTreeMap,
|
||||
fmt::{Debug, Display, Formatter, UpperHex},
|
||||
ops::{Add, AddAssign, BitAnd, Sub},
|
||||
};
|
||||
|
||||
use anyhow::{bail, ensure, Context, Result};
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::{
|
||||
analysis::{
|
||||
@@ -12,6 +14,7 @@ use crate::{
|
||||
skip_alignment,
|
||||
slices::{FunctionSlices, TailCallResult},
|
||||
vm::{BranchTarget, GprValue, StepResult, VM},
|
||||
RelocationTarget,
|
||||
},
|
||||
obj::{ObjInfo, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind},
|
||||
};
|
||||
@@ -36,6 +39,20 @@ impl Display for SectionAddress {
|
||||
|
||||
impl SectionAddress {
|
||||
pub fn new(section: usize, address: u32) -> Self { Self { section, address } }
|
||||
|
||||
pub fn offset(self, offset: i32) -> Self {
|
||||
Self { section: self.section, address: self.address.wrapping_add_signed(offset) }
|
||||
}
|
||||
|
||||
pub fn align_up(self, align: u32) -> Self {
|
||||
Self { section: self.section, address: (self.address + align - 1) & !(align - 1) }
|
||||
}
|
||||
|
||||
pub fn align_down(self, align: u32) -> Self {
|
||||
Self { section: self.section, address: self.address & !(align - 1) }
|
||||
}
|
||||
|
||||
pub fn is_aligned(self, align: u32) -> bool { self.address & (align - 1) == 0 }
|
||||
}
|
||||
|
||||
impl Add<u32> for SectionAddress {
|
||||
@@ -70,16 +87,36 @@ impl BitAnd<u32> for SectionAddress {
|
||||
fn bitand(self, rhs: u32) -> Self::Output { self.address & rhs }
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct FunctionInfo {
|
||||
pub analyzed: bool,
|
||||
pub end: Option<SectionAddress>,
|
||||
pub slices: Option<FunctionSlices>,
|
||||
}
|
||||
|
||||
impl FunctionInfo {
|
||||
pub fn is_analyzed(&self) -> bool { self.analyzed }
|
||||
|
||||
pub fn is_function(&self) -> bool {
|
||||
self.analyzed && self.end.is_some() && self.slices.is_some()
|
||||
}
|
||||
|
||||
pub fn is_non_function(&self) -> bool {
|
||||
self.analyzed && self.end.is_none() && self.slices.is_none()
|
||||
}
|
||||
|
||||
pub fn is_unfinalized(&self) -> bool {
|
||||
self.analyzed && self.end.is_none() && self.slices.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct AnalyzerState {
|
||||
pub sda_bases: Option<(u32, u32)>,
|
||||
pub function_entries: BTreeSet<SectionAddress>,
|
||||
pub function_bounds: BTreeMap<SectionAddress, Option<SectionAddress>>,
|
||||
pub function_slices: BTreeMap<SectionAddress, FunctionSlices>,
|
||||
pub functions: BTreeMap<SectionAddress, FunctionInfo>,
|
||||
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 {
|
||||
@@ -87,7 +124,7 @@ impl AnalyzerState {
|
||||
for (§ion_index, section_name) in &self.known_sections {
|
||||
obj.sections[section_index].rename(section_name.clone())?;
|
||||
}
|
||||
for (&start, &end) in &self.function_bounds {
|
||||
for (&start, FunctionInfo { end, .. }) in self.functions.iter() {
|
||||
let Some(end) = end else { continue };
|
||||
let section = &obj.sections[start.section];
|
||||
ensure!(
|
||||
@@ -120,7 +157,14 @@ impl AnalyzerState {
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
for (&addr, &size) in &self.jump_tables {
|
||||
let mut iter = self.jump_tables.iter().peekable();
|
||||
while let Some((&addr, &(mut size))) = iter.next() {
|
||||
// Truncate overlapping jump tables
|
||||
if let Some((&next_addr, _)) = iter.peek() {
|
||||
if next_addr.section == addr.section {
|
||||
size = min(size, next_addr.address - addr.address);
|
||||
}
|
||||
}
|
||||
let section = &obj.sections[addr.section];
|
||||
ensure!(
|
||||
section.contains_range(addr.address..addr.address + size),
|
||||
@@ -166,27 +210,31 @@ 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, Some(addr + size));
|
||||
self.functions.insert(addr, FunctionInfo {
|
||||
analyzed: false,
|
||||
end: size.map(|size| addr + size),
|
||||
slices: None,
|
||||
});
|
||||
}
|
||||
// Apply known functions from symbols
|
||||
for (_, symbol) in obj.symbols.by_kind(ObjSymbolKind::Function) {
|
||||
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(addr_ref, Some(addr_ref + symbol.size as u32));
|
||||
}
|
||||
self.functions.insert(addr_ref, FunctionInfo {
|
||||
analyzed: false,
|
||||
end: if symbol.size_known { Some(addr_ref + symbol.size as u32) } else { None },
|
||||
slices: None,
|
||||
});
|
||||
}
|
||||
// Also check the beginning of every code section
|
||||
for (section_index, section) in obj.sections.by_kind(ObjSectionKind::Code) {
|
||||
self.function_entries
|
||||
.insert(SectionAddress::new(section_index, section.address as u32));
|
||||
self.functions
|
||||
.entry(SectionAddress::new(section_index, section.address as u32))
|
||||
.or_default();
|
||||
}
|
||||
|
||||
// Process known functions first
|
||||
let known_functions = self.function_entries.clone();
|
||||
for addr in known_functions {
|
||||
for addr in self.functions.keys().cloned().collect_vec() {
|
||||
self.process_function_at(obj, addr)?;
|
||||
}
|
||||
if let Some(entry) = obj.entry.map(|n| n as u32) {
|
||||
@@ -203,26 +251,46 @@ impl AnalyzerState {
|
||||
while self.finalize_functions(obj, true)? {
|
||||
self.process_functions(obj)?;
|
||||
}
|
||||
if self.functions.iter().any(|(_, i)| i.is_unfinalized()) {
|
||||
log::error!("Failed to finalize functions:");
|
||||
for (addr, _) in self.functions.iter().filter(|(_, i)| i.is_unfinalized()) {
|
||||
log::error!(" {:#010X}", addr);
|
||||
}
|
||||
bail!("Failed to finalize functions");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn finalize_functions(&mut self, obj: &ObjInfo, finalize: bool) -> Result<bool> {
|
||||
let mut finalized = Vec::new();
|
||||
for (&addr, slices) in &mut self.non_finalized_functions {
|
||||
let mut finalized_any = false;
|
||||
let unfinalized = self
|
||||
.functions
|
||||
.iter()
|
||||
.filter_map(|(&addr, info)| {
|
||||
if info.is_unfinalized() {
|
||||
info.slices.clone().map(|s| (addr, s))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect_vec();
|
||||
for (addr, mut slices) in unfinalized {
|
||||
// log::info!("Trying to finalize {:#010X}", addr);
|
||||
let Some(function_start) = slices.start() else {
|
||||
bail!("Function slice without start @ {:#010X}", addr);
|
||||
};
|
||||
let function_end = slices.end();
|
||||
let mut current = SectionAddress::new(addr.section, 0);
|
||||
while let Some(&block) = slices.possible_blocks.range(current + 4..).next() {
|
||||
current = block;
|
||||
while let Some((&block, vm)) = slices.possible_blocks.range(current..).next() {
|
||||
current = block + 4;
|
||||
let vm = vm.clone();
|
||||
match slices.check_tail_call(
|
||||
obj,
|
||||
block,
|
||||
function_start,
|
||||
function_end,
|
||||
&self.function_entries,
|
||||
&self.functions,
|
||||
Some(vm.clone()),
|
||||
) {
|
||||
TailCallResult::Not => {
|
||||
log::trace!("Finalized block @ {:#010X}", block);
|
||||
@@ -232,7 +300,8 @@ impl AnalyzerState {
|
||||
block,
|
||||
function_start,
|
||||
function_end,
|
||||
&self.function_entries,
|
||||
&self.functions,
|
||||
Some(vm),
|
||||
)?;
|
||||
}
|
||||
TailCallResult::Is => {
|
||||
@@ -252,7 +321,8 @@ impl AnalyzerState {
|
||||
block,
|
||||
function_start,
|
||||
function_end,
|
||||
&self.function_entries,
|
||||
&self.functions,
|
||||
Some(vm),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
@@ -261,55 +331,24 @@ impl AnalyzerState {
|
||||
}
|
||||
if slices.can_finalize() {
|
||||
log::trace!("Finalizing {:#010X}", addr);
|
||||
slices.finalize(obj, &self.function_entries)?;
|
||||
self.function_entries.append(&mut slices.function_references.clone());
|
||||
slices.finalize(obj, &self.functions)?;
|
||||
for address in slices.function_references.iter().cloned() {
|
||||
self.functions.entry(address).or_default();
|
||||
}
|
||||
self.jump_tables.append(&mut slices.jump_table_references.clone());
|
||||
let end = slices.end();
|
||||
self.function_bounds.insert(addr, end);
|
||||
self.function_slices.insert(addr, slices.clone());
|
||||
finalized.push(addr);
|
||||
let info = self.functions.get_mut(&addr).unwrap();
|
||||
info.analyzed = true;
|
||||
info.end = end;
|
||||
info.slices = Some(slices.clone());
|
||||
finalized_any = true;
|
||||
}
|
||||
}
|
||||
let finalized_new = !finalized.is_empty();
|
||||
for addr in finalized {
|
||||
self.non_finalized_functions.remove(&addr);
|
||||
}
|
||||
Ok(finalized_new)
|
||||
Ok(finalized_any)
|
||||
}
|
||||
|
||||
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();
|
||||
let mut bound = bounds_iter.next();
|
||||
loop {
|
||||
match (entry, bound) {
|
||||
(Some(a), Some(b)) => {
|
||||
if b < a {
|
||||
bound = bounds_iter.next();
|
||||
continue;
|
||||
} else if a != b {
|
||||
if self.non_finalized_functions.contains_key(&a) {
|
||||
entry = entries_iter.next();
|
||||
continue;
|
||||
} else {
|
||||
break Some(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
(Some(a), None) => {
|
||||
if self.non_finalized_functions.contains_key(&a) {
|
||||
entry = entries_iter.next();
|
||||
continue;
|
||||
} else {
|
||||
break Some(a);
|
||||
}
|
||||
}
|
||||
_ => break None,
|
||||
}
|
||||
entry = entries_iter.next();
|
||||
bound = bounds_iter.next();
|
||||
}
|
||||
self.functions.iter().find(|(_, info)| !info.is_analyzed()).map(|(&addr, _)| addr)
|
||||
}
|
||||
|
||||
fn process_functions(&mut self, obj: &ObjInfo) -> Result<()> {
|
||||
@@ -330,26 +369,29 @@ impl AnalyzerState {
|
||||
}
|
||||
|
||||
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());
|
||||
for address in slices.function_references.iter().cloned() {
|
||||
self.functions.entry(address).or_default();
|
||||
}
|
||||
self.jump_tables.append(&mut slices.jump_table_references.clone());
|
||||
if slices.can_finalize() {
|
||||
slices.finalize(obj, &self.function_entries)?;
|
||||
self.function_bounds.insert(addr, slices.end());
|
||||
self.function_slices.insert(addr, slices);
|
||||
slices.finalize(obj, &self.functions)?;
|
||||
let info = self.functions.entry(addr).or_default();
|
||||
info.analyzed = true;
|
||||
info.end = slices.end();
|
||||
info.slices = Some(slices);
|
||||
} else {
|
||||
self.non_finalized_functions.insert(addr, slices);
|
||||
let info = self.functions.entry(addr).or_default();
|
||||
info.analyzed = true;
|
||||
info.end = None;
|
||||
info.slices = Some(slices);
|
||||
}
|
||||
true
|
||||
} else {
|
||||
log::debug!("Not a function @ {:#010X}", addr);
|
||||
self.function_bounds.insert(addr, None);
|
||||
let info = self.functions.entry(addr).or_default();
|
||||
info.analyzed = true;
|
||||
info.end = None;
|
||||
false
|
||||
})
|
||||
}
|
||||
@@ -360,58 +402,58 @@ impl AnalyzerState {
|
||||
start: SectionAddress,
|
||||
) -> Result<Option<FunctionSlices>> {
|
||||
let mut slices = FunctionSlices::default();
|
||||
let function_end = self.function_bounds.get(&start).cloned().flatten();
|
||||
Ok(match slices.analyze(obj, start, start, function_end, &self.function_entries)? {
|
||||
let function_end = self.functions.get(&start).and_then(|info| info.end);
|
||||
Ok(match slices.analyze(obj, start, start, function_end, &self.functions, None)? {
|
||||
true => Some(slices),
|
||||
false => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn detect_new_functions(&mut self, obj: &ObjInfo) -> Result<bool> {
|
||||
let mut found_new = false;
|
||||
let mut new_functions = vec![];
|
||||
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();
|
||||
let mut iter = self.functions.range(section_start..section_end).peekable();
|
||||
loop {
|
||||
match (iter.next(), iter.peek()) {
|
||||
(Some((&first_begin, &first_end)), Some(&(&second_begin, &second_end))) => {
|
||||
let Some(first_end) = first_end else { continue };
|
||||
if first_end > second_begin {
|
||||
continue;
|
||||
(Some((&first, first_info)), Some(&(&second, second_info))) => {
|
||||
let Some(first_end) = first_info.end else { continue };
|
||||
if first_end > second {
|
||||
bail!("Overlapping functions {}-{} -> {}", first, first_end, second);
|
||||
}
|
||||
let addr = match skip_alignment(section, first_end, second_begin) {
|
||||
let addr = match skip_alignment(section, first_end, second) {
|
||||
Some(addr) => addr,
|
||||
None => continue,
|
||||
};
|
||||
if second_begin > addr && self.function_entries.insert(addr) {
|
||||
if second > addr {
|
||||
log::trace!(
|
||||
"Trying function @ {:#010X} (from {:#010X}-{:#010X} <-> {:#010X}-{:#010X?})",
|
||||
addr,
|
||||
first_begin,
|
||||
first.address,
|
||||
first_end,
|
||||
second_begin,
|
||||
second_end,
|
||||
second.address,
|
||||
second_info.end,
|
||||
);
|
||||
found_new = true;
|
||||
new_functions.push(addr);
|
||||
}
|
||||
}
|
||||
(Some((&last_begin, &last_end)), None) => {
|
||||
let Some(last_end) = last_end else { continue };
|
||||
(Some((last, last_info)), None) => {
|
||||
let Some(last_end) = last_info.end else { continue };
|
||||
if last_end < section_end {
|
||||
let addr = match skip_alignment(section, last_end, section_end) {
|
||||
Some(addr) => addr,
|
||||
None => continue,
|
||||
};
|
||||
if addr < section_end && self.function_entries.insert(addr) {
|
||||
log::debug!(
|
||||
if addr < section_end {
|
||||
log::trace!(
|
||||
"Trying function @ {:#010X} (from {:#010X}-{:#010X} <-> {:#010X})",
|
||||
addr,
|
||||
last_begin,
|
||||
last.address,
|
||||
last_end,
|
||||
section_end,
|
||||
);
|
||||
found_new = true;
|
||||
new_functions.push(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -419,6 +461,11 @@ impl AnalyzerState {
|
||||
}
|
||||
}
|
||||
}
|
||||
let found_new = !new_functions.is_empty();
|
||||
for addr in new_functions {
|
||||
let opt = self.functions.insert(addr, FunctionInfo::default());
|
||||
ensure!(opt.is_none(), "Attempted to detect duplicate function @ {:#010X}", addr);
|
||||
}
|
||||
Ok(found_new)
|
||||
}
|
||||
}
|
||||
@@ -446,13 +493,15 @@ pub fn locate_sda_bases(obj: &mut ObjInfo) -> Result<bool> {
|
||||
}
|
||||
StepResult::Illegal => bail!("Illegal instruction @ {:#010X}", ins.addr),
|
||||
StepResult::Jump(target) => {
|
||||
if let BranchTarget::Address(addr) = target {
|
||||
if let BranchTarget::Address(RelocationTarget::Address(addr)) = target {
|
||||
return Ok(ExecCbResult::Jump(addr));
|
||||
}
|
||||
}
|
||||
StepResult::Branch(branches) => {
|
||||
for branch in branches {
|
||||
if let BranchTarget::Address(addr) = branch.target {
|
||||
if let BranchTarget::Address(RelocationTarget::Address(addr)) =
|
||||
branch.target
|
||||
{
|
||||
executor.push(addr, branch.vm, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{collections::BTreeSet, num::NonZeroU32};
|
||||
|
||||
use anyhow::{anyhow, ensure, Context, Result};
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
use ppc750cl::Ins;
|
||||
|
||||
use crate::{
|
||||
@@ -35,13 +35,15 @@ fn read_unresolved_relocation_address(
|
||||
section: &ObjSection,
|
||||
address: u32,
|
||||
reloc_kind: Option<ObjRelocKind>,
|
||||
) -> Result<Option<SectionAddress>> {
|
||||
) -> Result<Option<RelocationTarget>> {
|
||||
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 reloc.module_id != obj.module_id {
|
||||
return Ok(Some(RelocationTarget::External));
|
||||
}
|
||||
if let Some(reloc_kind) = reloc_kind {
|
||||
ensure!(reloc.kind == reloc_kind);
|
||||
}
|
||||
@@ -52,10 +54,10 @@ fn read_unresolved_relocation_address(
|
||||
reloc.target_section
|
||||
)
|
||||
})?;
|
||||
Ok(Some(SectionAddress {
|
||||
Ok(Some(RelocationTarget::Address(SectionAddress {
|
||||
section: target_section_index,
|
||||
address: target_section.address as u32 + reloc.addend,
|
||||
}))
|
||||
})))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
@@ -66,7 +68,7 @@ fn read_relocation_address(
|
||||
section: &ObjSection,
|
||||
address: u32,
|
||||
reloc_kind: Option<ObjRelocKind>,
|
||||
) -> Result<Option<SectionAddress>> {
|
||||
) -> Result<Option<RelocationTarget>> {
|
||||
let Some(reloc) = section.relocations.at(address) else {
|
||||
return Ok(None);
|
||||
};
|
||||
@@ -74,13 +76,13 @@ fn read_relocation_address(
|
||||
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 {
|
||||
let Some(section_index) = symbol.section else {
|
||||
return Ok(Some(RelocationTarget::External));
|
||||
};
|
||||
Ok(Some(RelocationTarget::Address(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> {
|
||||
@@ -94,7 +96,11 @@ pub fn read_address(obj: &ObjInfo, section: &ObjSection, address: u32) -> Result
|
||||
Some(ObjRelocKind::Absolute),
|
||||
)?;
|
||||
}
|
||||
opt.with_context(|| {
|
||||
opt.and_then(|t| match t {
|
||||
RelocationTarget::Address(addr) => Some(addr),
|
||||
RelocationTarget::External => None,
|
||||
})
|
||||
.with_context(|| {
|
||||
format!("Failed to find relocation for {:#010X} in section {}", address, section.name)
|
||||
})
|
||||
} else {
|
||||
@@ -109,12 +115,18 @@ fn is_valid_jump_table_addr(obj: &ObjInfo, addr: SectionAddress) -> bool {
|
||||
!matches!(obj.sections[addr.section].kind, ObjSectionKind::Code | ObjSectionKind::Bss)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RelocationTarget {
|
||||
Address(SectionAddress),
|
||||
External,
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn relocation_target_for(
|
||||
obj: &ObjInfo,
|
||||
addr: SectionAddress,
|
||||
reloc_kind: Option<ObjRelocKind>,
|
||||
) -> Result<Option<SectionAddress>> {
|
||||
) -> Result<Option<RelocationTarget>> {
|
||||
let section = &obj.sections[addr.section];
|
||||
let mut opt = read_relocation_address(obj, section, addr.address, reloc_kind)?;
|
||||
if opt.is_none() {
|
||||
@@ -149,17 +161,24 @@ fn get_jump_table_entries(
|
||||
if let Some(target) =
|
||||
relocation_target_for(obj, cur_addr, Some(ObjRelocKind::Absolute))?
|
||||
{
|
||||
entries.push(target);
|
||||
match target {
|
||||
RelocationTarget::Address(addr) => entries.push(addr),
|
||||
RelocationTarget::External => {
|
||||
bail!("Jump table entry at {:#010X} points to external symbol", cur_addr)
|
||||
}
|
||||
}
|
||||
} 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));
|
||||
if entry_addr > 0 {
|
||||
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;
|
||||
@@ -172,7 +191,10 @@ fn get_jump_table_entries(
|
||||
let target = if let Some(target) =
|
||||
relocation_target_for(obj, cur_addr, Some(ObjRelocKind::Absolute))?
|
||||
{
|
||||
target
|
||||
match target {
|
||||
RelocationTarget::Address(addr) => addr,
|
||||
RelocationTarget::External => break,
|
||||
}
|
||||
} else if obj.kind == ObjKind::Executable {
|
||||
let Some(value) = read_u32(section, cur_addr.address) else {
|
||||
break;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
use flagset::FlagSet;
|
||||
use itertools::Itertools;
|
||||
use memchr::memmem;
|
||||
|
||||
use crate::{
|
||||
analysis::cfa::{AnalyzerState, SectionAddress},
|
||||
analysis::cfa::{AnalyzerState, FunctionInfo, SectionAddress},
|
||||
obj::{
|
||||
ObjInfo, ObjKind, ObjRelocKind, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
|
||||
ObjSymbolFlags, ObjSymbolKind,
|
||||
@@ -24,7 +23,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.is_none()) {
|
||||
for (&start, _) in
|
||||
state.functions.iter().filter(|(_, info)| info.analyzed && info.end.is_none())
|
||||
{
|
||||
let section = &obj.sections[start.section];
|
||||
let data = match section.data_range(start.address, 0) {
|
||||
Ok(ret) => ret,
|
||||
@@ -70,67 +71,58 @@ impl AnalysisPass for FindTRKInterruptVectorTable {
|
||||
|
||||
pub struct FindSaveRestSleds {}
|
||||
|
||||
const SLEDS: [([u8; 4], &str, &str); 4] = [
|
||||
([0xd9, 0xcb, 0xff, 0x70], "__save_fpr", "_savefpr_"),
|
||||
([0xc9, 0xcb, 0xff, 0x70], "__restore_fpr", "_restfpr_"),
|
||||
([0x91, 0xcb, 0xff, 0xb8], "__save_gpr", "_savegpr_"),
|
||||
([0x81, 0xcb, 0xff, 0xb8], "__restore_gpr", "_restgpr_"),
|
||||
const SLEDS: [([u8; 8], &str, &str); 4] = [
|
||||
([0xd9, 0xcb, 0xff, 0x70, 0xd9, 0xeb, 0xff, 0x78], "__save_fpr", "_savefpr_"),
|
||||
([0xc9, 0xcb, 0xff, 0x70, 0xc9, 0xeb, 0xff, 0x78], "__restore_fpr", "_restfpr_"),
|
||||
([0x91, 0xcb, 0xff, 0xb8, 0x91, 0xeb, 0xff, 0xbc], "__save_gpr", "_savegpr_"),
|
||||
([0x81, 0xcb, 0xff, 0xb8, 0x81, 0xeb, 0xff, 0xbc], "__restore_gpr", "_restgpr_"),
|
||||
];
|
||||
|
||||
// Runtime.PPCEABI.H.a runtime.c
|
||||
impl AnalysisPass for FindSaveRestSleds {
|
||||
fn execute(state: &mut AnalyzerState, obj: &ObjInfo) -> Result<()> {
|
||||
const SLED_SIZE: usize = 19 * 4; // registers 14-31 + blr
|
||||
let mut clear_ranges: Vec<Range<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,
|
||||
};
|
||||
for (section_index, section) in obj.sections.by_kind(ObjSectionKind::Code) {
|
||||
for (needle, func, label) in &SLEDS {
|
||||
if data.starts_with(needle) {
|
||||
log::debug!("Found {} @ {:#010X}", func, start);
|
||||
clear_ranges.push(start + 4..start + SLED_SIZE as u32);
|
||||
state.known_symbols.insert(start, ObjSymbol {
|
||||
name: func.to_string(),
|
||||
let Some(pos) = memmem::find(§ion.data, needle) else {
|
||||
continue;
|
||||
};
|
||||
let start = SectionAddress::new(section_index, section.address as u32 + pos as u32);
|
||||
log::debug!("Found {} @ {:#010X}", func, start);
|
||||
state.functions.insert(start, FunctionInfo {
|
||||
analyzed: false,
|
||||
end: Some(start + SLED_SIZE as u32),
|
||||
slices: None,
|
||||
});
|
||||
state.known_symbols.insert(start, ObjSymbol {
|
||||
name: func.to_string(),
|
||||
demangled_name: None,
|
||||
address: start.address as u64,
|
||||
section: Some(start.section),
|
||||
size: SLED_SIZE as u64,
|
||||
size_known: true,
|
||||
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
||||
kind: ObjSymbolKind::Function,
|
||||
align: None,
|
||||
data_kind: Default::default(),
|
||||
});
|
||||
for i in 14..=31 {
|
||||
let addr = start + (i - 14) * 4;
|
||||
state.known_symbols.insert(addr, ObjSymbol {
|
||||
name: format!("{}{}", label, i),
|
||||
demangled_name: None,
|
||||
address: start.address as u64,
|
||||
address: addr.address as u64,
|
||||
section: Some(start.section),
|
||||
size: SLED_SIZE as u64,
|
||||
size: 0,
|
||||
size_known: true,
|
||||
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
||||
kind: ObjSymbolKind::Function,
|
||||
kind: ObjSymbolKind::Unknown,
|
||||
align: None,
|
||||
data_kind: Default::default(),
|
||||
});
|
||||
for i in 14..=31 {
|
||||
let addr = start + (i - 14) * 4;
|
||||
state.known_symbols.insert(addr, ObjSymbol {
|
||||
name: format!("{}{}", label, i),
|
||||
demangled_name: None,
|
||||
address: addr.address as u64,
|
||||
section: Some(start.section),
|
||||
size: 0,
|
||||
size_known: true,
|
||||
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
|
||||
kind: ObjSymbolKind::Unknown,
|
||||
align: None,
|
||||
data_kind: Default::default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for range in clear_ranges {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -179,7 +171,7 @@ impl AnalysisPass for FindRelCtorsDtors {
|
||||
};
|
||||
if target_section.kind != ObjSectionKind::Code
|
||||
|| !state
|
||||
.function_bounds
|
||||
.functions
|
||||
.contains_key(&SectionAddress::new(target_section_index, reloc.addend))
|
||||
{
|
||||
return false;
|
||||
@@ -197,7 +189,7 @@ impl AnalysisPass for FindRelCtorsDtors {
|
||||
.collect_vec();
|
||||
|
||||
if possible_sections.len() != 2 {
|
||||
log::warn!("Failed to find .ctors and .dtors");
|
||||
log::debug!("Failed to find .ctors and .dtors");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -311,7 +303,7 @@ impl AnalysisPass for FindRelRodataData {
|
||||
.collect_vec();
|
||||
|
||||
if possible_sections.len() != 2 {
|
||||
log::warn!("Failed to find .rodata and .data");
|
||||
log::debug!("Failed to find .rodata and .data");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
@@ -382,7 +382,7 @@ fn apply_init_user_signatures(obj: &mut ObjInfo) -> Result<()> {
|
||||
// __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 {
|
||||
for (addr, _) in analyzer.functions {
|
||||
let section = &obj.sections[addr.section];
|
||||
if let Some(signature) = check_signatures_str(
|
||||
section,
|
||||
|
||||
@@ -8,11 +8,12 @@ use ppc750cl::{Ins, Opcode};
|
||||
|
||||
use crate::{
|
||||
analysis::{
|
||||
cfa::SectionAddress,
|
||||
cfa::{FunctionInfo, SectionAddress},
|
||||
disassemble,
|
||||
executor::{ExecCbData, ExecCbResult, Executor},
|
||||
uniq_jump_table_entries,
|
||||
vm::{section_address_for, BranchTarget, StepResult, VM},
|
||||
RelocationTarget,
|
||||
},
|
||||
obj::{ObjInfo, ObjKind, ObjSection},
|
||||
};
|
||||
@@ -26,10 +27,11 @@ pub struct FunctionSlices {
|
||||
pub prologue: Option<SectionAddress>,
|
||||
pub epilogue: Option<SectionAddress>,
|
||||
// Either a block or tail call
|
||||
pub possible_blocks: BTreeSet<SectionAddress>,
|
||||
pub possible_blocks: BTreeMap<SectionAddress, Box<VM>>,
|
||||
pub has_conditional_blr: bool,
|
||||
pub has_rfi: bool,
|
||||
pub finalized: bool,
|
||||
pub has_r1_load: bool, // Possibly instead of a prologue
|
||||
}
|
||||
|
||||
pub enum TailCallResult {
|
||||
@@ -72,6 +74,25 @@ fn check_sequence(
|
||||
Ok(found)
|
||||
}
|
||||
|
||||
fn check_prologue_sequence(section: &ObjSection, ins: &Ins) -> Result<bool> {
|
||||
#[inline(always)]
|
||||
fn is_mflr(ins: &Ins) -> bool {
|
||||
// mfspr r0, LR
|
||||
ins.op == Opcode::Mfspr && ins.field_rD() == 0 && ins.field_spr() == 8
|
||||
}
|
||||
#[inline(always)]
|
||||
fn is_stwu(ins: &Ins) -> bool {
|
||||
// stwu r1, d(r1)
|
||||
ins.op == Opcode::Stwu && ins.field_rS() == 1 && ins.field_rA() == 1
|
||||
}
|
||||
#[inline(always)]
|
||||
fn is_stw(ins: &Ins) -> bool {
|
||||
// stw r0, d(r1)
|
||||
ins.op == Opcode::Stw && ins.field_rS() == 0 && ins.field_rA() == 1
|
||||
}
|
||||
check_sequence(section, ins, &[(&is_stwu, &is_mflr), (&is_mflr, &is_stw)])
|
||||
}
|
||||
|
||||
impl FunctionSlices {
|
||||
pub fn end(&self) -> Option<SectionAddress> {
|
||||
self.blocks.last_key_value().and_then(|(_, &end)| end)
|
||||
@@ -109,22 +130,16 @@ impl FunctionSlices {
|
||||
ins: &Ins,
|
||||
) -> Result<()> {
|
||||
#[inline(always)]
|
||||
fn is_mflr(ins: &Ins) -> bool {
|
||||
// mfspr r0, LR
|
||||
ins.op == Opcode::Mfspr && ins.field_rD() == 0 && ins.field_spr() == 8
|
||||
}
|
||||
#[inline(always)]
|
||||
fn is_stwu(ins: &Ins) -> bool {
|
||||
// stwu r1, d(r1)
|
||||
ins.op == Opcode::Stwu && ins.field_rS() == 1 && ins.field_rA() == 1
|
||||
}
|
||||
#[inline(always)]
|
||||
fn is_stw(ins: &Ins) -> bool {
|
||||
// stw r0, d(r1)
|
||||
ins.op == Opcode::Stw && ins.field_rS() == 0 && ins.field_rA() == 1
|
||||
fn is_lwz(ins: &Ins) -> bool {
|
||||
// lwz r1, d(r)
|
||||
ins.op == Opcode::Lwz && ins.field_rD() == 1
|
||||
}
|
||||
|
||||
if check_sequence(section, ins, &[(&is_stwu, &is_mflr), (&is_mflr, &is_stw)])? {
|
||||
if is_lwz(ins) {
|
||||
self.has_r1_load = true;
|
||||
return Ok(()); // Possibly instead of a prologue
|
||||
}
|
||||
if check_prologue_sequence(section, ins)? {
|
||||
if let Some(prologue) = self.prologue {
|
||||
if prologue != addr && prologue != addr - 4 {
|
||||
bail!("Found duplicate prologue: {:#010X} and {:#010X}", prologue, addr)
|
||||
@@ -170,21 +185,37 @@ impl FunctionSlices {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_known_function(
|
||||
&self,
|
||||
known_functions: &BTreeMap<SectionAddress, FunctionInfo>,
|
||||
addr: SectionAddress,
|
||||
) -> Option<SectionAddress> {
|
||||
if self.function_references.contains(&addr) {
|
||||
return Some(addr);
|
||||
}
|
||||
if let Some((&fn_addr, info)) = known_functions.range(..=addr).next_back() {
|
||||
if fn_addr == addr || info.end.is_some_and(|end| addr < end) {
|
||||
return Some(fn_addr);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn instruction_callback(
|
||||
&mut self,
|
||||
data: ExecCbData,
|
||||
obj: &ObjInfo,
|
||||
function_start: SectionAddress,
|
||||
function_end: Option<SectionAddress>,
|
||||
known_functions: &BTreeSet<SectionAddress>,
|
||||
known_functions: &BTreeMap<SectionAddress, FunctionInfo>,
|
||||
) -> Result<ExecCbResult<bool>> {
|
||||
let ExecCbData { executor, vm, result, ins_addr, section, ins, block_start } = data;
|
||||
|
||||
// Track discovered prologue(s) and epilogue(s)
|
||||
self.check_prologue(section, ins_addr, ins)
|
||||
.with_context(|| format!("While processing {:#010X}", function_start))?;
|
||||
.with_context(|| format!("While processing {:#010X}: {:#?}", function_start, self))?;
|
||||
self.check_epilogue(section, ins_addr, ins)
|
||||
.with_context(|| format!("While processing {:#010X}", function_start))?;
|
||||
.with_context(|| format!("While processing {:#010X}: {:#?}", function_start, self))?;
|
||||
if !self.has_conditional_blr && is_conditional_blr(ins) {
|
||||
self.has_conditional_blr = true;
|
||||
}
|
||||
@@ -193,9 +224,20 @@ 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) {
|
||||
if self.possible_blocks.contains_key(&ins_addr) {
|
||||
self.possible_blocks.remove(&ins_addr);
|
||||
}
|
||||
if let Some(fn_addr) = self.is_known_function(known_functions, ins_addr) {
|
||||
if fn_addr != function_start {
|
||||
log::warn!(
|
||||
"Control flow from {} hit known function {} (instruction: {})",
|
||||
function_start,
|
||||
fn_addr,
|
||||
ins_addr
|
||||
);
|
||||
return Ok(ExecCbResult::End(false));
|
||||
}
|
||||
}
|
||||
|
||||
match result {
|
||||
StepResult::Continue | StepResult::LoadStore { .. } => {
|
||||
@@ -214,7 +256,9 @@ impl FunctionSlices {
|
||||
Ok(ExecCbResult::End(false))
|
||||
}
|
||||
StepResult::Jump(target) => match target {
|
||||
BranchTarget::Unknown => {
|
||||
BranchTarget::Unknown
|
||||
| BranchTarget::Address(RelocationTarget::External)
|
||||
| BranchTarget::JumpTable { address: RelocationTarget::External, .. } => {
|
||||
// Likely end of function
|
||||
let next_addr = ins_addr + 4;
|
||||
self.blocks.insert(block_start, Some(next_addr));
|
||||
@@ -234,34 +278,41 @@ impl FunctionSlices {
|
||||
self.blocks.insert(block_start, Some(ins_addr + 4));
|
||||
Ok(ExecCbResult::EndBlock)
|
||||
}
|
||||
BranchTarget::Address(addr) => {
|
||||
BranchTarget::Address(RelocationTarget::Address(addr)) => {
|
||||
// End of block
|
||||
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)
|
||||
&& (matches!(function_end, Some(known_end) if addr < known_end)
|
||||
|| matches!(self.end(), Some(end) if addr < end)
|
||||
|| addr < ins_addr)
|
||||
{
|
||||
// If target is within known function bounds, jump
|
||||
if self.add_block_start(addr) {
|
||||
return Ok(ExecCbResult::Jump(addr));
|
||||
}
|
||||
} else if matches!(section.data_range(ins_addr.address, ins_addr.address + 4), Ok(data) if data == [0u8; 4])
|
||||
{
|
||||
} else if let Some(fn_addr) = self.is_known_function(known_functions, addr) {
|
||||
ensure!(fn_addr != function_start); // Sanity check
|
||||
self.function_references.insert(fn_addr);
|
||||
} else if addr.section != ins_addr.section
|
||||
// If this branch has zeroed padding after it, assume tail call.
|
||||
|| matches!(section.data_range(ins_addr.address, ins_addr.address + 4), Ok(data) if data == [0u8; 4])
|
||||
{
|
||||
self.function_references.insert(addr);
|
||||
} else {
|
||||
self.possible_blocks.insert(addr);
|
||||
self.possible_blocks.insert(addr, vm.clone_all());
|
||||
}
|
||||
Ok(ExecCbResult::EndBlock)
|
||||
}
|
||||
BranchTarget::JumpTable { address, size } => {
|
||||
BranchTarget::JumpTable { address: RelocationTarget::Address(address), size } => {
|
||||
// End of block
|
||||
let next_address = ins_addr + 4;
|
||||
self.blocks.insert(block_start, Some(next_address));
|
||||
|
||||
let (mut entries, size) = uniq_jump_table_entries(
|
||||
log::debug!("Fetching jump table entries @ {} with size {:?}", address, size);
|
||||
let (entries, size) = uniq_jump_table_entries(
|
||||
obj,
|
||||
address,
|
||||
size,
|
||||
@@ -269,8 +320,12 @@ impl FunctionSlices {
|
||||
function_start,
|
||||
function_end.or_else(|| self.end()),
|
||||
)?;
|
||||
log::debug!("-> size {}: {:?}", size, entries);
|
||||
if entries.contains(&next_address)
|
||||
&& !entries.iter().any(|addr| known_functions.contains(addr))
|
||||
&& !entries.iter().any(|&addr| {
|
||||
self.is_known_function(known_functions, addr)
|
||||
.is_some_and(|fn_addr| fn_addr != function_start)
|
||||
})
|
||||
{
|
||||
self.jump_table_references.insert(address, size);
|
||||
let mut branches = vec![];
|
||||
@@ -284,7 +339,8 @@ impl FunctionSlices {
|
||||
} else {
|
||||
// If the table doesn't contain the next address,
|
||||
// it could be a function jump table instead
|
||||
self.possible_blocks.append(&mut entries);
|
||||
self.possible_blocks
|
||||
.extend(entries.into_iter().map(|addr| (addr, vm.clone_all())));
|
||||
}
|
||||
Ok(ExecCbResult::EndBlock)
|
||||
}
|
||||
@@ -296,11 +352,15 @@ impl FunctionSlices {
|
||||
let mut out_branches = vec![];
|
||||
for branch in branches {
|
||||
match branch.target {
|
||||
BranchTarget::Unknown | BranchTarget::Return => {
|
||||
continue;
|
||||
}
|
||||
BranchTarget::Address(addr) => {
|
||||
if branch.link || known_functions.contains(&addr) {
|
||||
BranchTarget::Address(RelocationTarget::Address(addr)) => {
|
||||
let known = self.is_known_function(known_functions, addr);
|
||||
if let Some(fn_addr) = known {
|
||||
if fn_addr != function_start {
|
||||
self.function_references.insert(fn_addr);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if branch.link {
|
||||
self.function_references.insert(addr);
|
||||
} else {
|
||||
out_branches.push(addr);
|
||||
@@ -310,8 +370,14 @@ impl FunctionSlices {
|
||||
}
|
||||
}
|
||||
BranchTarget::JumpTable { address, size } => {
|
||||
bail!("Conditional jump table unsupported @ {:#010X} -> {:#010X} size {:#X?}", ins_addr, address, size);
|
||||
bail!(
|
||||
"Conditional jump table unsupported @ {:#010X} -> {:?} size {:#X?}",
|
||||
ins_addr,
|
||||
address,
|
||||
size
|
||||
);
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
if !out_branches.is_empty() {
|
||||
@@ -328,14 +394,15 @@ impl FunctionSlices {
|
||||
start: SectionAddress,
|
||||
function_start: SectionAddress,
|
||||
function_end: Option<SectionAddress>,
|
||||
known_functions: &BTreeSet<SectionAddress>,
|
||||
known_functions: &BTreeMap<SectionAddress, FunctionInfo>,
|
||||
vm: Option<Box<VM>>,
|
||||
) -> Result<bool> {
|
||||
if !self.add_block_start(start) {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let mut executor = Executor::new(obj);
|
||||
executor.push(start, VM::new_from_obj(obj), false);
|
||||
executor.push(start, vm.unwrap_or_else(|| VM::new_from_obj(obj)), false);
|
||||
let result = executor.run(obj, |data| {
|
||||
self.instruction_callback(data, obj, function_start, function_end, known_functions)
|
||||
})?;
|
||||
@@ -345,7 +412,8 @@ impl FunctionSlices {
|
||||
|
||||
// Visit unreachable blocks
|
||||
while let Some((first, _)) = self.first_disconnected_block() {
|
||||
executor.push(first.end, VM::new_from_obj(obj), true);
|
||||
let vm = self.possible_blocks.remove(&first.start);
|
||||
executor.push(first.end, vm.unwrap_or_else(|| VM::new_from_obj(obj)), true);
|
||||
let result = executor.run(obj, |data| {
|
||||
self.instruction_callback(data, obj, function_start, function_end, known_functions)
|
||||
})?;
|
||||
@@ -356,13 +424,25 @@ impl FunctionSlices {
|
||||
|
||||
// Visit trailing blocks
|
||||
if let Some(known_end) = function_end {
|
||||
loop {
|
||||
let Some(end) = self.end() else {
|
||||
'outer: loop {
|
||||
let Some(mut end) = self.end() else {
|
||||
log::warn!("Trailing block analysis failed @ {:#010X}", function_start);
|
||||
break;
|
||||
};
|
||||
if end >= known_end {
|
||||
break;
|
||||
loop {
|
||||
if end >= known_end {
|
||||
break 'outer;
|
||||
}
|
||||
// Skip nops
|
||||
match disassemble(&obj.sections[end.section], end.address) {
|
||||
Some(ins) => {
|
||||
if !is_nop(&ins) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
end += 4;
|
||||
}
|
||||
executor.push(end, VM::new_from_obj(obj), true);
|
||||
let result = executor.run(obj, |data| {
|
||||
@@ -393,20 +473,23 @@ impl FunctionSlices {
|
||||
pub fn finalize(
|
||||
&mut self,
|
||||
obj: &ObjInfo,
|
||||
known_functions: &BTreeSet<SectionAddress>,
|
||||
known_functions: &BTreeMap<SectionAddress, FunctionInfo>,
|
||||
) -> Result<()> {
|
||||
ensure!(!self.finalized, "Already finalized");
|
||||
ensure!(self.can_finalize(), "Can't finalize");
|
||||
|
||||
match (self.prologue, self.epilogue) {
|
||||
(Some(_), Some(_)) | (None, None) => {}
|
||||
(Some(_), None) => {
|
||||
match (self.prologue, self.epilogue, self.has_r1_load) {
|
||||
(Some(_), Some(_), _) | (None, None, _) => {}
|
||||
(Some(_), None, _) => {
|
||||
// Likely __noreturn
|
||||
}
|
||||
(None, Some(e)) => {
|
||||
(None, Some(e), false) => {
|
||||
log::warn!("{:#010X?}", self);
|
||||
bail!("Unpaired epilogue {:#010X}", e);
|
||||
}
|
||||
(None, Some(_), true) => {
|
||||
// Possible stack setup
|
||||
}
|
||||
}
|
||||
|
||||
let Some(end) = self.end() else {
|
||||
@@ -425,7 +508,7 @@ impl FunctionSlices {
|
||||
if !self.has_conditional_blr {
|
||||
if let Some(ins) = disassemble(section, end.address - 4) {
|
||||
if ins.op == Opcode::B {
|
||||
if let Some(target) = ins
|
||||
if let Some(RelocationTarget::Address(target)) = ins
|
||||
.branch_dest()
|
||||
.and_then(|addr| section_address_for(obj, end - 4, addr))
|
||||
{
|
||||
@@ -450,7 +533,7 @@ impl FunctionSlices {
|
||||
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)
|
||||
&& !known_functions.contains_key(&end)
|
||||
{
|
||||
log::trace!("Found trailing blr @ {:#010X}, merging with function", end);
|
||||
self.blocks.insert(end, Some(end + 4));
|
||||
@@ -459,7 +542,7 @@ impl FunctionSlices {
|
||||
// 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)
|
||||
&& !known_functions.contains_key(&end)
|
||||
{
|
||||
log::trace!("Found trailing nop @ {:#010X}, merging with function", end);
|
||||
self.blocks.insert(end, Some(end + 4));
|
||||
@@ -480,7 +563,8 @@ impl FunctionSlices {
|
||||
addr: SectionAddress,
|
||||
function_start: SectionAddress,
|
||||
function_end: Option<SectionAddress>,
|
||||
known_functions: &BTreeSet<SectionAddress>,
|
||||
known_functions: &BTreeMap<SectionAddress, FunctionInfo>,
|
||||
vm: Option<Box<VM>>,
|
||||
) -> TailCallResult {
|
||||
// If jump target is already a known block or within known function bounds, not a tail call.
|
||||
if self.blocks.contains_key(&addr) {
|
||||
@@ -521,12 +605,37 @@ impl FunctionSlices {
|
||||
{
|
||||
return TailCallResult::Is;
|
||||
}
|
||||
// If we haven't discovered a prologue yet, and one exists between the function
|
||||
// start and the jump target, known tail call.
|
||||
if self.prologue.is_none() {
|
||||
let mut current_address = function_start;
|
||||
while current_address < addr {
|
||||
let ins = disassemble(target_section, current_address.address).unwrap();
|
||||
match check_prologue_sequence(target_section, &ins) {
|
||||
Ok(true) => {
|
||||
log::debug!(
|
||||
"Prologue discovered @ {}; known tail call: {}",
|
||||
current_address,
|
||||
addr
|
||||
);
|
||||
return TailCallResult::Is;
|
||||
}
|
||||
Ok(false) => {}
|
||||
Err(e) => {
|
||||
log::warn!("Error while checking prologue sequence: {}", e);
|
||||
return TailCallResult::Error(e);
|
||||
}
|
||||
}
|
||||
current_address += 4;
|
||||
}
|
||||
}
|
||||
// Perform CFA on jump target to determine more
|
||||
let mut slices = FunctionSlices {
|
||||
function_references: self.function_references.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
if let Ok(result) = slices.analyze(obj, addr, function_start, function_end, known_functions)
|
||||
if let Ok(result) =
|
||||
slices.analyze(obj, addr, function_start, function_end, known_functions, vm)
|
||||
{
|
||||
// If analysis failed, assume tail call.
|
||||
if !result {
|
||||
@@ -545,7 +654,7 @@ impl FunctionSlices {
|
||||
let other_blocks = self
|
||||
.possible_blocks
|
||||
.range(start + 4..end)
|
||||
.cloned()
|
||||
.map(|(&addr, _)| addr)
|
||||
.collect::<Vec<SectionAddress>>();
|
||||
if !other_blocks.is_empty() {
|
||||
for other_addr in other_blocks {
|
||||
@@ -563,6 +672,7 @@ impl FunctionSlices {
|
||||
return TailCallResult::Is;
|
||||
}
|
||||
}
|
||||
// If all else fails, try again later.
|
||||
TailCallResult::Possible
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ use crate::{
|
||||
executor::{ExecCbData, ExecCbResult, Executor},
|
||||
relocation_target_for, uniq_jump_table_entries,
|
||||
vm::{is_store_op, BranchTarget, GprValue, StepResult, VM},
|
||||
RelocationTarget,
|
||||
},
|
||||
obj::{
|
||||
ObjDataKind, ObjInfo, ObjKind, ObjReloc, ObjRelocKind, ObjSection, ObjSectionKind,
|
||||
@@ -23,13 +24,31 @@ use crate::{
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Relocation {
|
||||
Ha(SectionAddress),
|
||||
Hi(SectionAddress),
|
||||
Lo(SectionAddress),
|
||||
Sda21(SectionAddress),
|
||||
Rel14(SectionAddress),
|
||||
Rel24(SectionAddress),
|
||||
Absolute(SectionAddress),
|
||||
Ha(RelocationTarget),
|
||||
Hi(RelocationTarget),
|
||||
Lo(RelocationTarget),
|
||||
Sda21(RelocationTarget),
|
||||
Rel14(RelocationTarget),
|
||||
Rel24(RelocationTarget),
|
||||
Absolute(RelocationTarget),
|
||||
}
|
||||
|
||||
impl Relocation {
|
||||
fn kind_and_address(&self) -> Option<(ObjRelocKind, SectionAddress)> {
|
||||
let (reloc_kind, target) = match self {
|
||||
Relocation::Ha(v) => (ObjRelocKind::PpcAddr16Ha, v),
|
||||
Relocation::Hi(v) => (ObjRelocKind::PpcAddr16Hi, v),
|
||||
Relocation::Lo(v) => (ObjRelocKind::PpcAddr16Lo, v),
|
||||
Relocation::Sda21(v) => (ObjRelocKind::PpcEmbSda21, v),
|
||||
Relocation::Rel14(v) => (ObjRelocKind::PpcRel14, v),
|
||||
Relocation::Rel24(v) => (ObjRelocKind::PpcRel24, v),
|
||||
Relocation::Absolute(v) => (ObjRelocKind::Absolute, v),
|
||||
};
|
||||
match *target {
|
||||
RelocationTarget::Address(address) => Some((reloc_kind, address)),
|
||||
RelocationTarget::External => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -93,13 +112,37 @@ impl Tracker {
|
||||
#[instrument(name = "tracker", skip(self, obj))]
|
||||
pub fn process(&mut self, obj: &ObjInfo) -> Result<()> {
|
||||
self.process_code(obj)?;
|
||||
for (section_index, section) in obj
|
||||
.sections
|
||||
.iter()
|
||||
.filter(|(_, s)| matches!(s.kind, ObjSectionKind::Data | ObjSectionKind::ReadOnlyData))
|
||||
{
|
||||
log::debug!("Processing section {}, address {:#X}", section_index, section.address);
|
||||
self.process_data(obj, section_index, section)?;
|
||||
if obj.kind == ObjKind::Executable {
|
||||
for (section_index, section) in obj.sections.iter().filter(|(_, s)| {
|
||||
matches!(s.kind, ObjSectionKind::Data | ObjSectionKind::ReadOnlyData)
|
||||
}) {
|
||||
log::debug!("Processing section {}, address {:#X}", section_index, section.address);
|
||||
self.process_data(obj, section_index, section)?;
|
||||
}
|
||||
}
|
||||
self.reject_invalid_relocations(obj)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove data relocations that point to an unaligned address if the aligned address has a
|
||||
/// relocation. A relocation will never point to the middle of an address.
|
||||
fn reject_invalid_relocations(&mut self, obj: &ObjInfo) -> Result<()> {
|
||||
let mut to_reject = vec![];
|
||||
for (&address, reloc) in &self.relocations {
|
||||
let section = &obj.sections[address.section];
|
||||
if !matches!(section.kind, ObjSectionKind::Data | ObjSectionKind::ReadOnlyData) {
|
||||
continue;
|
||||
}
|
||||
let Some((_, target)) = reloc.kind_and_address() else {
|
||||
continue;
|
||||
};
|
||||
if !target.is_aligned(4) && self.relocations.contains_key(&target.align_down(4)) {
|
||||
log::debug!("Rejecting invalid relocation @ {} -> {}", address, target);
|
||||
to_reject.push(address);
|
||||
}
|
||||
}
|
||||
for address in to_reject {
|
||||
self.relocations.remove(&address);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -143,6 +186,22 @@ impl Tracker {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn gpr_address(
|
||||
&self,
|
||||
obj: &ObjInfo,
|
||||
ins_addr: SectionAddress,
|
||||
value: &GprValue,
|
||||
) -> Option<RelocationTarget> {
|
||||
match *value {
|
||||
GprValue::Constant(value) => {
|
||||
self.is_valid_address(obj, ins_addr, value).map(RelocationTarget::Address)
|
||||
}
|
||||
GprValue::Address(address) => Some(address),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn instruction_callback(
|
||||
&mut self,
|
||||
data: ExecCbData,
|
||||
@@ -162,28 +221,37 @@ impl Tracker {
|
||||
Opcode::Addi | Opcode::Addic | Opcode::Addic_ => {
|
||||
let source = ins.field_rA();
|
||||
let target = ins.field_rD();
|
||||
if let GprValue::Constant(value) = vm.gpr[target].value {
|
||||
if let Some(value) = self.is_valid_address(obj, ins_addr, value) {
|
||||
if (source == 2
|
||||
&& matches!(self.sda2_base, Some(v) if vm.gpr[2].value == GprValue::Constant(v)))
|
||||
|| (source == 13
|
||||
&& matches!(self.sda_base, Some(v) if vm.gpr[13].value == GprValue::Constant(v)))
|
||||
{
|
||||
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).cloned();
|
||||
if hi_reloc.is_none() {
|
||||
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).cloned();
|
||||
if lo_reloc.is_none() {
|
||||
self.relocations.insert(lo_addr, Relocation::Lo(value));
|
||||
}
|
||||
self.hal_to.insert(value);
|
||||
if let Some(value) = self.gpr_address(obj, ins_addr, &vm.gpr[target].value)
|
||||
{
|
||||
if (source == 2
|
||||
&& matches!(self.sda2_base, Some(v) if vm.gpr[2].value == GprValue::Constant(v)))
|
||||
|| (source == 13
|
||||
&& matches!(self.sda_base, Some(v) if vm.gpr[13].value == GprValue::Constant(v)))
|
||||
{
|
||||
self.relocations.insert(ins_addr, Relocation::Sda21(value));
|
||||
if let RelocationTarget::Address(address) = value {
|
||||
self.sda_to.insert(address);
|
||||
}
|
||||
} 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).cloned();
|
||||
if hi_reloc.is_none() {
|
||||
debug_assert_ne!(
|
||||
value,
|
||||
RelocationTarget::Address(SectionAddress::new(
|
||||
usize::MAX,
|
||||
0
|
||||
))
|
||||
);
|
||||
self.relocations.insert(hi_addr, Relocation::Ha(value));
|
||||
}
|
||||
let lo_reloc = self.relocations.get(&lo_addr).cloned();
|
||||
if lo_reloc.is_none() {
|
||||
self.relocations.insert(lo_addr, Relocation::Lo(value));
|
||||
}
|
||||
if let RelocationTarget::Address(address) = value {
|
||||
self.hal_to.insert(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -191,20 +259,21 @@ impl Tracker {
|
||||
// ori rA, rS, UIMM
|
||||
Opcode::Ori => {
|
||||
let target = ins.field_rA();
|
||||
if let GprValue::Constant(value) = vm.gpr[target].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).cloned();
|
||||
if hi_reloc.is_none() {
|
||||
self.relocations.insert(hi_addr, Relocation::Hi(value));
|
||||
}
|
||||
let lo_reloc = self.relocations.get(&lo_addr).cloned();
|
||||
if lo_reloc.is_none() {
|
||||
self.relocations.insert(lo_addr, Relocation::Lo(value));
|
||||
}
|
||||
self.hal_to.insert(value);
|
||||
if let Some(value) = self.gpr_address(obj, ins_addr, &vm.gpr[target].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).cloned();
|
||||
if hi_reloc.is_none() {
|
||||
self.relocations.insert(hi_addr, Relocation::Hi(value));
|
||||
}
|
||||
let lo_reloc = self.relocations.get(&lo_addr).cloned();
|
||||
if lo_reloc.is_none() {
|
||||
self.relocations.insert(lo_addr, Relocation::Lo(value));
|
||||
}
|
||||
if let RelocationTarget::Address(address) = value {
|
||||
self.hal_to.insert(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,20 +283,28 @@ impl Tracker {
|
||||
Ok(ExecCbResult::Continue)
|
||||
}
|
||||
StepResult::LoadStore { address, source, source_reg } => {
|
||||
if let Some(address) = self.is_valid_address(obj, ins_addr, address.address) {
|
||||
if self.is_valid_section_address(obj, ins_addr) {
|
||||
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.sda_to.insert(address);
|
||||
if let RelocationTarget::Address(address) = 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).cloned();
|
||||
if hi_reloc.is_none() {
|
||||
debug_assert_ne!(address, SectionAddress::new(usize::MAX, 0));
|
||||
debug_assert_ne!(
|
||||
address,
|
||||
RelocationTarget::Address(SectionAddress::new(
|
||||
usize::MAX,
|
||||
0
|
||||
))
|
||||
);
|
||||
self.relocations.insert(hi_addr, Relocation::Ha(address));
|
||||
}
|
||||
if hi_reloc.is_none()
|
||||
@@ -235,26 +312,38 @@ impl Tracker {
|
||||
{
|
||||
self.relocations.insert(ins_addr, Relocation::Lo(address));
|
||||
}
|
||||
self.hal_to.insert(address);
|
||||
if let RelocationTarget::Address(address) = address {
|
||||
self.hal_to.insert(address);
|
||||
}
|
||||
}
|
||||
(Some(hi_addr), Some(lo_addr)) => {
|
||||
let hi_reloc = self.relocations.get(&hi_addr).cloned();
|
||||
if hi_reloc.is_none() {
|
||||
debug_assert_ne!(address, SectionAddress::new(usize::MAX, 0));
|
||||
debug_assert_ne!(
|
||||
address,
|
||||
RelocationTarget::Address(SectionAddress::new(
|
||||
usize::MAX,
|
||||
0
|
||||
))
|
||||
);
|
||||
self.relocations.insert(hi_addr, Relocation::Ha(address));
|
||||
}
|
||||
let lo_reloc = self.relocations.get(&lo_addr).cloned();
|
||||
if lo_reloc.is_none() {
|
||||
self.relocations.insert(lo_addr, Relocation::Lo(address));
|
||||
}
|
||||
self.hal_to.insert(address);
|
||||
if let RelocationTarget::Address(address) = address {
|
||||
self.hal_to.insert(address);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
self.data_types.insert(address, data_kind_from_op(ins.op));
|
||||
if is_store_op(ins.op) {
|
||||
self.stores_to.insert(address);
|
||||
if let RelocationTarget::Address(address) = address {
|
||||
self.data_types.insert(address, data_kind_from_op(ins.op));
|
||||
if is_store_op(ins.op) {
|
||||
self.stores_to.insert(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ExecCbResult::Continue)
|
||||
@@ -266,22 +355,27 @@ impl Tracker {
|
||||
function_end
|
||||
),
|
||||
StepResult::Jump(target) => match target {
|
||||
BranchTarget::Unknown | BranchTarget::Return => Ok(ExecCbResult::EndBlock),
|
||||
BranchTarget::Unknown
|
||||
| BranchTarget::Return
|
||||
| BranchTarget::JumpTable { address: RelocationTarget::External, .. } => {
|
||||
Ok(ExecCbResult::EndBlock)
|
||||
}
|
||||
BranchTarget::Address(addr) => {
|
||||
let next_addr = ins_addr + 4;
|
||||
if next_addr < function_end {
|
||||
possible_missed_branches.insert(ins_addr + 4, vm.clone_all());
|
||||
}
|
||||
if is_function_addr(addr) {
|
||||
Ok(ExecCbResult::Jump(addr))
|
||||
} else {
|
||||
if ins.is_direct_branch() {
|
||||
self.relocations.insert(ins_addr, Relocation::Rel24(addr));
|
||||
if let RelocationTarget::Address(addr) = addr {
|
||||
if is_function_addr(addr) {
|
||||
return Ok(ExecCbResult::Jump(addr));
|
||||
}
|
||||
Ok(ExecCbResult::EndBlock)
|
||||
}
|
||||
if ins.is_direct_branch() {
|
||||
self.relocations.insert(ins_addr, Relocation::Rel24(addr));
|
||||
}
|
||||
Ok(ExecCbResult::EndBlock)
|
||||
}
|
||||
BranchTarget::JumpTable { address, size } => {
|
||||
BranchTarget::JumpTable { address: RelocationTarget::Address(address), size } => {
|
||||
let (entries, _) = uniq_jump_table_entries(
|
||||
obj,
|
||||
address,
|
||||
@@ -301,19 +395,30 @@ impl Tracker {
|
||||
StepResult::Branch(branches) => {
|
||||
for branch in branches {
|
||||
match branch.target {
|
||||
BranchTarget::Unknown | BranchTarget::Return => {}
|
||||
BranchTarget::Address(addr) => {
|
||||
if branch.link || !is_function_addr(addr) {
|
||||
BranchTarget::Unknown
|
||||
| BranchTarget::Return
|
||||
| BranchTarget::JumpTable { address: RelocationTarget::External, .. } => {}
|
||||
BranchTarget::Address(target) => {
|
||||
let (addr, is_fn_addr) = if let RelocationTarget::Address(addr) = target
|
||||
{
|
||||
(addr, is_function_addr(addr))
|
||||
} else {
|
||||
(SectionAddress::new(usize::MAX, 0), false)
|
||||
};
|
||||
if branch.link || !is_fn_addr {
|
||||
self.relocations.insert(ins_addr, match ins.op {
|
||||
Opcode::B => Relocation::Rel24(addr),
|
||||
Opcode::Bc => Relocation::Rel14(addr),
|
||||
Opcode::B => Relocation::Rel24(target),
|
||||
Opcode::Bc => Relocation::Rel14(target),
|
||||
_ => continue,
|
||||
});
|
||||
} else if is_function_addr(addr) {
|
||||
} else if is_fn_addr {
|
||||
executor.push(addr, branch.vm, true);
|
||||
}
|
||||
}
|
||||
BranchTarget::JumpTable { address, size } => {
|
||||
BranchTarget::JumpTable {
|
||||
address: RelocationTarget::Address(address),
|
||||
size,
|
||||
} => {
|
||||
let (entries, _) = uniq_jump_table_entries(
|
||||
obj,
|
||||
address,
|
||||
@@ -390,13 +495,24 @@ impl Tracker {
|
||||
for chunk in section.data.chunks_exact(4) {
|
||||
let value = u32::from_be_bytes(chunk.try_into()?);
|
||||
if let Some(value) = self.is_valid_address(obj, addr, value) {
|
||||
self.relocations.insert(addr, Relocation::Absolute(value));
|
||||
self.relocations
|
||||
.insert(addr, Relocation::Absolute(RelocationTarget::Address(value)));
|
||||
}
|
||||
addr += 4;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_valid_section_address(&self, obj: &ObjInfo, from: SectionAddress) -> bool {
|
||||
if let Some((&start, &end)) = obj.blocked_ranges.range(..=from).next_back() {
|
||||
if from.section == start.section && from.address >= start.address && from.address < end
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn is_valid_address(
|
||||
&self,
|
||||
obj: &ObjInfo,
|
||||
@@ -410,11 +526,12 @@ impl Tracker {
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
if cfg!(debug_assertions) {
|
||||
let relocation_target = relocation_target_for(obj, from, None).ok().flatten();
|
||||
if !matches!(relocation_target, None | Some(RelocationTarget::External)) {
|
||||
// VM should have already handled this
|
||||
panic!("Relocation already exists for {:#010X} (from {:#010X})", addr, from);
|
||||
}
|
||||
return Some(target);
|
||||
}
|
||||
// Remainder of this function is for executable objects only
|
||||
if obj.kind == ObjKind::Relocatable {
|
||||
@@ -530,17 +647,27 @@ impl Tracker {
|
||||
}
|
||||
}
|
||||
|
||||
for (addr, reloc) in &self.relocations {
|
||||
let addr = *addr;
|
||||
let (reloc_kind, target) = match *reloc {
|
||||
Relocation::Ha(v) => (ObjRelocKind::PpcAddr16Ha, v),
|
||||
Relocation::Hi(v) => (ObjRelocKind::PpcAddr16Hi, v),
|
||||
Relocation::Lo(v) => (ObjRelocKind::PpcAddr16Lo, v),
|
||||
Relocation::Sda21(v) => (ObjRelocKind::PpcEmbSda21, v),
|
||||
Relocation::Rel14(v) => (ObjRelocKind::PpcRel14, v),
|
||||
Relocation::Rel24(v) => (ObjRelocKind::PpcRel24, v),
|
||||
Relocation::Absolute(v) => (ObjRelocKind::Absolute, v),
|
||||
for (&addr, reloc) in &self.relocations {
|
||||
let Some((reloc_kind, target)) = reloc.kind_and_address() else {
|
||||
// Skip external relocations, they already exist
|
||||
continue;
|
||||
};
|
||||
if obj.kind == ObjKind::Relocatable {
|
||||
// Sanity check: relocatable objects already have relocations,
|
||||
// did our analyzer find one that isn't real?
|
||||
let section = &obj.sections[addr.section];
|
||||
if section.relocations.at(addr.address).is_none()
|
||||
// We _do_ want to rebuild missing R_PPC_REL24 relocations
|
||||
&& !matches!(reloc_kind, ObjRelocKind::PpcRel24)
|
||||
{
|
||||
bail!(
|
||||
"Found invalid relocation {} {:#?} (target {}) in relocatable object",
|
||||
addr,
|
||||
reloc,
|
||||
target
|
||||
);
|
||||
}
|
||||
}
|
||||
let data_kind = self
|
||||
.data_types
|
||||
.get(&target)
|
||||
@@ -607,12 +734,14 @@ impl Tracker {
|
||||
!= reloc_symbol.address as i64 + addend
|
||||
{
|
||||
bail!(
|
||||
"Conflicting relocations (target {:#010X}): {:#010X?} ({}) != {:#010X?} ({})",
|
||||
"Conflicting relocations (target {:#010X}): {:#010X?} ({} {:#X}) != {:#010X?} ({} {:#X})",
|
||||
target,
|
||||
e.value,
|
||||
iter_symbol.name,
|
||||
iter_symbol.address as i64 + e.value.addend,
|
||||
reloc,
|
||||
reloc_symbol.name
|
||||
reloc_symbol.name,
|
||||
reloc_symbol.address as i64 + addend,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::num::NonZeroU32;
|
||||
use ppc750cl::{Argument, Ins, Opcode, GPR};
|
||||
|
||||
use crate::{
|
||||
analysis::{cfa::SectionAddress, relocation_target_for},
|
||||
analysis::{cfa::SectionAddress, relocation_target_for, RelocationTarget},
|
||||
obj::{ObjInfo, ObjKind},
|
||||
};
|
||||
|
||||
@@ -15,13 +15,13 @@ pub enum GprValue {
|
||||
/// GPR value is a constant
|
||||
Constant(u32),
|
||||
/// GPR value is a known relocated address
|
||||
Address(SectionAddress),
|
||||
Address(RelocationTarget),
|
||||
/// Comparison result (CR field)
|
||||
ComparisonResult(u8),
|
||||
/// GPR value is within a range
|
||||
Range { min: u32, max: u32, step: u32 },
|
||||
/// GPR value is loaded from an address with a max offset (jump table)
|
||||
LoadIndexed { address: u32, max_offset: Option<NonZeroU32> },
|
||||
LoadIndexed { address: RelocationTarget, max_offset: Option<NonZeroU32> },
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
|
||||
@@ -52,6 +52,14 @@ impl Gpr {
|
||||
self.hi_addr = hi_gpr.hi_addr;
|
||||
self.lo_addr = Some(hi_gpr.lo_addr.unwrap_or(addr));
|
||||
}
|
||||
|
||||
fn address(&self, obj: &ObjInfo, ins_addr: SectionAddress) -> Option<RelocationTarget> {
|
||||
match self.value {
|
||||
GprValue::Constant(value) => section_address_for(obj, ins_addr, value),
|
||||
GprValue::Address(target) => Some(target),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
||||
@@ -85,9 +93,9 @@ pub enum BranchTarget {
|
||||
/// Branch to LR
|
||||
Return,
|
||||
/// Branch to address
|
||||
Address(SectionAddress),
|
||||
Address(RelocationTarget),
|
||||
/// Branch to jump table
|
||||
JumpTable { address: SectionAddress, size: Option<NonZeroU32> },
|
||||
JumpTable { address: RelocationTarget, size: Option<NonZeroU32> },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
@@ -105,7 +113,7 @@ pub enum StepResult {
|
||||
/// Continue normally
|
||||
Continue,
|
||||
/// Load from / store to
|
||||
LoadStore { address: SectionAddress, source: Gpr, source_reg: u8 },
|
||||
LoadStore { address: RelocationTarget, source: Gpr, source_reg: u8 },
|
||||
/// Hit illegal instruction
|
||||
Illegal,
|
||||
/// Jump without affecting VM state
|
||||
@@ -118,16 +126,16 @@ pub fn section_address_for(
|
||||
obj: &ObjInfo,
|
||||
ins_addr: SectionAddress,
|
||||
target_addr: u32,
|
||||
) -> Option<SectionAddress> {
|
||||
) -> Option<RelocationTarget> {
|
||||
if let Some(target) = relocation_target_for(obj, ins_addr, None).ok().flatten() {
|
||||
return Some(target);
|
||||
}
|
||||
if obj.kind == ObjKind::Executable {
|
||||
let (section_index, _) = obj.sections.at_address(target_addr).ok()?;
|
||||
return Some(SectionAddress::new(section_index, target_addr));
|
||||
return Some(RelocationTarget::Address(SectionAddress::new(section_index, target_addr)));
|
||||
}
|
||||
if obj.sections[ins_addr.section].contains(target_addr) {
|
||||
Some(SectionAddress::new(ins_addr.section, target_addr))
|
||||
Some(RelocationTarget::Address(SectionAddress::new(ins_addr.section, target_addr)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -183,12 +191,6 @@ impl VM {
|
||||
pub fn clone_all(&self) -> Box<Self> { Box::new(self.clone()) }
|
||||
|
||||
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;
|
||||
@@ -201,61 +203,99 @@ impl VM {
|
||||
(GprValue::Constant(left), GprValue::Constant(right)) => {
|
||||
GprValue::Constant(left.wrapping_add(right))
|
||||
}
|
||||
(
|
||||
GprValue::Address(RelocationTarget::Address(left)),
|
||||
GprValue::Constant(right),
|
||||
) => GprValue::Address(RelocationTarget::Address(left + right)),
|
||||
(
|
||||
GprValue::Constant(left),
|
||||
GprValue::Address(RelocationTarget::Address(right)),
|
||||
) => GprValue::Address(RelocationTarget::Address(right + left)),
|
||||
_ => GprValue::Unknown,
|
||||
};
|
||||
self.gpr[ins.field_rD()].set_direct(value);
|
||||
}
|
||||
// addis rD, rA, SIMM
|
||||
Opcode::Addis => {
|
||||
let left = if ins.field_rA() == 0 {
|
||||
GprValue::Constant(0)
|
||||
if let Some(target) =
|
||||
relocation_target_for(obj, ins_addr, None /* TODO */).ok().flatten()
|
||||
{
|
||||
debug_assert_eq!(ins.field_rA(), 0);
|
||||
self.gpr[ins.field_rD()].set_hi(GprValue::Address(target), ins_addr);
|
||||
} else {
|
||||
self.gpr[ins.field_rA()].value
|
||||
};
|
||||
let value = match left {
|
||||
GprValue::Constant(value) => {
|
||||
GprValue::Constant(value.wrapping_add((ins.field_simm() as u32) << 16))
|
||||
let left = if ins.field_rA() == 0 {
|
||||
GprValue::Constant(0)
|
||||
} else {
|
||||
self.gpr[ins.field_rA()].value
|
||||
};
|
||||
let value = match left {
|
||||
GprValue::Constant(value) => {
|
||||
GprValue::Constant(value.wrapping_add((ins.field_simm() as u32) << 16))
|
||||
}
|
||||
_ => GprValue::Unknown,
|
||||
};
|
||||
if ins.field_rA() == 0 {
|
||||
// lis rD, SIMM
|
||||
self.gpr[ins.field_rD()].set_hi(value, ins_addr);
|
||||
} else {
|
||||
self.gpr[ins.field_rD()].set_direct(value);
|
||||
}
|
||||
_ => GprValue::Unknown,
|
||||
};
|
||||
if ins.field_rA() == 0 {
|
||||
// lis rD, SIMM
|
||||
self.gpr[ins.field_rD()].set_hi(value, ins_addr);
|
||||
} else {
|
||||
self.gpr[ins.field_rD()].set_direct(value);
|
||||
}
|
||||
}
|
||||
// addi rD, rA, SIMM
|
||||
// addic rD, rA, SIMM
|
||||
// addic. rD, rA, SIMM
|
||||
Opcode::Addi | Opcode::Addic | Opcode::Addic_ => {
|
||||
let left = if ins.field_rA() == 0 && ins.op == Opcode::Addi {
|
||||
GprValue::Constant(0)
|
||||
if let Some(target) =
|
||||
relocation_target_for(obj, ins_addr, None /* TODO */).ok().flatten()
|
||||
{
|
||||
self.gpr[ins.field_rD()].set_lo(
|
||||
GprValue::Address(target),
|
||||
ins_addr,
|
||||
self.gpr[ins.field_rA()],
|
||||
);
|
||||
} else {
|
||||
self.gpr[ins.field_rA()].value
|
||||
};
|
||||
let value = match left {
|
||||
GprValue::Constant(value) => {
|
||||
GprValue::Constant(value.wrapping_add(ins.field_simm() as u32))
|
||||
let left = if ins.field_rA() == 0 && ins.op == Opcode::Addi {
|
||||
GprValue::Constant(0)
|
||||
} else {
|
||||
self.gpr[ins.field_rA()].value
|
||||
};
|
||||
let value = match left {
|
||||
GprValue::Constant(value) => {
|
||||
GprValue::Constant(value.wrapping_add(ins.field_simm() as u32))
|
||||
}
|
||||
GprValue::Address(RelocationTarget::Address(address)) => GprValue::Address(
|
||||
RelocationTarget::Address(address.offset(ins.field_simm() as i32)),
|
||||
),
|
||||
_ => GprValue::Unknown,
|
||||
};
|
||||
if ins.field_rA() == 0 {
|
||||
// li rD, SIMM
|
||||
self.gpr[ins.field_rD()].set_direct(value);
|
||||
} else {
|
||||
self.gpr[ins.field_rD()].set_lo(value, ins_addr, self.gpr[ins.field_rA()]);
|
||||
}
|
||||
_ => GprValue::Unknown,
|
||||
};
|
||||
if ins.field_rA() == 0 {
|
||||
// li rD, SIMM
|
||||
self.gpr[ins.field_rD()].set_direct(value);
|
||||
} else {
|
||||
self.gpr[ins.field_rD()].set_lo(value, ins_addr, self.gpr[ins.field_rA()]);
|
||||
}
|
||||
}
|
||||
// ori rA, rS, UIMM
|
||||
Opcode::Ori => {
|
||||
let value = match self.gpr[ins.field_rS()].value {
|
||||
GprValue::Constant(value) => {
|
||||
GprValue::Constant(value | ins.field_uimm() as u32)
|
||||
}
|
||||
_ => GprValue::Unknown,
|
||||
};
|
||||
self.gpr[ins.field_rA()].set_lo(value, ins_addr, self.gpr[ins.field_rS()]);
|
||||
if let Some(target) =
|
||||
relocation_target_for(obj, ins_addr, None /* TODO */).ok().flatten()
|
||||
{
|
||||
self.gpr[ins.field_rA()].set_lo(
|
||||
GprValue::Address(target),
|
||||
ins_addr,
|
||||
self.gpr[ins.field_rS()],
|
||||
);
|
||||
} else {
|
||||
let value = match self.gpr[ins.field_rS()].value {
|
||||
GprValue::Constant(value) => {
|
||||
GprValue::Constant(value | ins.field_uimm() as u32)
|
||||
}
|
||||
_ => GprValue::Unknown,
|
||||
};
|
||||
self.gpr[ins.field_rA()].set_lo(value, ins_addr, self.gpr[ins.field_rS()]);
|
||||
}
|
||||
}
|
||||
// or rA, rS, rB
|
||||
Opcode::Or => {
|
||||
@@ -336,20 +376,18 @@ impl VM {
|
||||
Opcode::Bcctr => {
|
||||
match self.ctr {
|
||||
GprValue::Constant(value) => {
|
||||
// TODO only check valid target?
|
||||
if let Some(target) = section_address_for(obj, ins_addr, value) {
|
||||
BranchTarget::Address(target)
|
||||
} else {
|
||||
BranchTarget::Unknown
|
||||
}
|
||||
},
|
||||
GprValue::Address(target) => BranchTarget::Address(target),
|
||||
GprValue::LoadIndexed { address, max_offset }
|
||||
// FIXME: avoids treating bctrl indirect calls as jump tables
|
||||
if !ins.field_LK() => {
|
||||
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::JumpTable { address, size: max_offset.and_then(|n| n.checked_add(4)) }
|
||||
}
|
||||
_ => BranchTarget::Unknown,
|
||||
}
|
||||
@@ -369,7 +407,7 @@ impl VM {
|
||||
if ins.field_LK() {
|
||||
return StepResult::Branch(vec![
|
||||
Branch {
|
||||
target: BranchTarget::Address(ins_addr + 4),
|
||||
target: BranchTarget::Address(RelocationTarget::Address(ins_addr + 4)),
|
||||
link: false,
|
||||
vm: self.clone_for_return(),
|
||||
},
|
||||
@@ -386,7 +424,7 @@ impl VM {
|
||||
let mut branches = vec![
|
||||
// Branch not taken
|
||||
Branch {
|
||||
target: BranchTarget::Address(ins_addr + 4),
|
||||
target: BranchTarget::Address(RelocationTarget::Address(ins_addr + 4)),
|
||||
link: false,
|
||||
vm: self.clone_all(),
|
||||
},
|
||||
@@ -413,15 +451,20 @@ impl VM {
|
||||
}
|
||||
// lwzx rD, rA, rB
|
||||
Opcode::Lwzx => {
|
||||
let left = self.gpr[ins.field_rA()].value;
|
||||
let left = self.gpr[ins.field_rA()].address(obj, ins_addr);
|
||||
let right = self.gpr[ins.field_rB()].value;
|
||||
let value = match (left, right) {
|
||||
(GprValue::Constant(address), GprValue::Range { min: _, max, .. })
|
||||
(Some(address), GprValue::Range { min: _, max, .. })
|
||||
if /*min == 0 &&*/ max < u32::MAX - 4 && max & 3 == 0 =>
|
||||
{
|
||||
GprValue::LoadIndexed { address, max_offset: NonZeroU32::new(max) }
|
||||
}
|
||||
(GprValue::Constant(address), _) => {
|
||||
(Some(address), GprValue::Range { min: _, max, .. })
|
||||
if /*min == 0 &&*/ max < u32::MAX - 4 && max & 3 == 0 =>
|
||||
{
|
||||
GprValue::LoadIndexed { address, max_offset: NonZeroU32::new(max) }
|
||||
}
|
||||
(Some(address), _) => {
|
||||
GprValue::LoadIndexed { address, max_offset: None }
|
||||
}
|
||||
_ => GprValue::Unknown,
|
||||
@@ -452,16 +495,29 @@ impl VM {
|
||||
op if is_load_store_op(op) => {
|
||||
let source = ins.field_rA();
|
||||
let mut result = StepResult::Continue;
|
||||
if let GprValue::Constant(base) = self.gpr[source].value {
|
||||
let address = base.wrapping_add(ins.field_simm() as u32);
|
||||
if let GprValue::Address(target) = self.gpr[source].value {
|
||||
if is_update_op(op) {
|
||||
self.gpr[source].set_lo(
|
||||
GprValue::Constant(address),
|
||||
GprValue::Address(target),
|
||||
ins_addr,
|
||||
self.gpr[source],
|
||||
);
|
||||
}
|
||||
result = StepResult::LoadStore {
|
||||
address: target,
|
||||
source: self.gpr[source],
|
||||
source_reg: source as u8,
|
||||
};
|
||||
} else if let GprValue::Constant(base) = self.gpr[source].value {
|
||||
let address = base.wrapping_add(ins.field_simm() as u32);
|
||||
if let Some(target) = section_address_for(obj, ins_addr, address) {
|
||||
if is_update_op(op) {
|
||||
self.gpr[source].set_lo(
|
||||
GprValue::Address(target),
|
||||
ins_addr,
|
||||
self.gpr[source],
|
||||
);
|
||||
}
|
||||
result = StepResult::LoadStore {
|
||||
address: target,
|
||||
source: self.gpr[source],
|
||||
@@ -573,11 +629,21 @@ fn split_values_by_crb(crb: u8, left: GprValue, right: GprValue) -> (GprValue, G
|
||||
|
||||
#[inline]
|
||||
fn mask_value(begin: u32, end: u32) -> u32 {
|
||||
let mut mask = 0u32;
|
||||
for bit in begin..=end {
|
||||
mask |= 1 << (31 - bit);
|
||||
if begin <= end {
|
||||
let mut mask = 0u32;
|
||||
for bit in begin..=end {
|
||||
mask |= 1 << (31 - bit);
|
||||
}
|
||||
mask
|
||||
} else if begin == end + 1 {
|
||||
u32::MAX
|
||||
} else {
|
||||
let mut mask = u32::MAX;
|
||||
for bit in end + 1..begin {
|
||||
mask &= !(1 << (31 - bit));
|
||||
}
|
||||
mask
|
||||
}
|
||||
mask
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
||||
Reference in New Issue
Block a user