From ba2589646e983a4c9026567e0d28583e1f879ac7 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 9 Jun 2025 22:38:47 -0600 Subject: [PATCH] Relax prolog/epilog sequence checks Some games (e.g. Excite Truck) have very aggressive float scheduling that can create large gaps between prolog instructions. This allows arbitrary instructions in the sequence checks, provided they're not a branch and don't touch r0/r1. Resolves #105 --- src/analysis/slices.rs | 71 +++++++++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/src/analysis/slices.rs b/src/analysis/slices.rs index 70685ed..d37ecc3 100644 --- a/src/analysis/slices.rs +++ b/src/analysis/slices.rs @@ -45,6 +45,17 @@ type BlockRange = Range; type InsCheck = dyn Fn(Ins) -> bool; +/// Stop searching for prologue/epilogue sequences if the next instruction +/// is a branch or uses r0 or r1. +fn is_end_of_seq(next: &Ins) -> bool { + next.is_branch() + || next + .defs() + .iter() + .chain(next.uses().iter()) + .any(|a| matches!(a, ppc750cl::Argument::GPR(ppc750cl::GPR(0 | 1)))) +} + #[inline(always)] fn check_sequence( section: &ObjSection, @@ -52,29 +63,26 @@ fn check_sequence( ins: Option, sequence: &[(&InsCheck, &InsCheck)], ) -> Result { - let mut found = false; + let ins = ins + .or_else(|| disassemble(section, addr.address)) + .with_context(|| format!("Failed to disassemble instruction at {addr:#010X}"))?; for &(first, second) in sequence { - let Some(ins) = ins.or_else(|| disassemble(section, addr.address)) else { - continue; - }; if !first(ins) { continue; } - let Some(next) = disassemble(section, addr.address + 4) else { - continue; - }; - if second(next) - // Also check the following instruction, in case the scheduler - // put something in between. - || (!next.is_branch() - && matches!(disassemble(section, addr.address + 8), Some(ins) if second(ins))) - { - found = true; - break; + let mut current_addr = addr.address + 4; + while let Some(next) = disassemble(section, current_addr) { + if second(next) { + return Ok(true); + } + if is_end_of_seq(&next) { + // If we hit a branch or an instruction that uses r0 or r1, stop searching. + break; + } + current_addr += 4; } } - - Ok(found) + Ok(false) } fn check_prologue_sequence( @@ -97,7 +105,11 @@ fn check_prologue_sequence( // stw r0, d(r1) ins.op == Opcode::Stw && ins.field_rs() == 0 && ins.field_ra() == 1 } - check_sequence(section, addr, ins, &[(&is_stwu, &is_mflr), (&is_mflr, &is_stw)]) + check_sequence(section, addr, ins, &[ + (&is_stwu, &is_mflr), + (&is_mflr, &is_stw), + (&is_mflr, &is_stwu), + ]) } impl FunctionSlices { @@ -148,7 +160,28 @@ impl FunctionSlices { } if check_prologue_sequence(section, addr, Some(ins))? { if let Some(prologue) = self.prologue { - if prologue != addr && prologue != addr - 4 { + let invalid_seq = if prologue == addr { + false + } else if prologue > addr { + true + } else { + // Check if any instructions between the prologue and this address + // are branches or use r0 or r1. + let mut current_addr = prologue.address + 4; + loop { + if current_addr == addr.address { + break false; + } + let next = disassemble(section, current_addr).with_context(|| { + format!("Failed to disassemble {current_addr:#010X}") + })?; + if is_end_of_seq(&next) { + break true; + } + current_addr += 4; + } + }; + if invalid_seq { bail!("Found multiple functions inside a symbol: {:#010X} and {:#010X}. Check symbols.txt?", prologue, addr) } } else {