Skip to content

Commit ba25896

Browse files
committed
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
1 parent 7bc0bc4 commit ba25896

File tree

1 file changed

+52
-19
lines changed

1 file changed

+52
-19
lines changed

src/analysis/slices.rs

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,36 +45,44 @@ type BlockRange = Range<SectionAddress>;
4545

4646
type InsCheck = dyn Fn(Ins) -> bool;
4747

48+
/// Stop searching for prologue/epilogue sequences if the next instruction
49+
/// is a branch or uses r0 or r1.
50+
fn is_end_of_seq(next: &Ins) -> bool {
51+
next.is_branch()
52+
|| next
53+
.defs()
54+
.iter()
55+
.chain(next.uses().iter())
56+
.any(|a| matches!(a, ppc750cl::Argument::GPR(ppc750cl::GPR(0 | 1))))
57+
}
58+
4859
#[inline(always)]
4960
fn check_sequence(
5061
section: &ObjSection,
5162
addr: SectionAddress,
5263
ins: Option<Ins>,
5364
sequence: &[(&InsCheck, &InsCheck)],
5465
) -> Result<bool> {
55-
let mut found = false;
66+
let ins = ins
67+
.or_else(|| disassemble(section, addr.address))
68+
.with_context(|| format!("Failed to disassemble instruction at {addr:#010X}"))?;
5669
for &(first, second) in sequence {
57-
let Some(ins) = ins.or_else(|| disassemble(section, addr.address)) else {
58-
continue;
59-
};
6070
if !first(ins) {
6171
continue;
6272
}
63-
let Some(next) = disassemble(section, addr.address + 4) else {
64-
continue;
65-
};
66-
if second(next)
67-
// Also check the following instruction, in case the scheduler
68-
// put something in between.
69-
|| (!next.is_branch()
70-
&& matches!(disassemble(section, addr.address + 8), Some(ins) if second(ins)))
71-
{
72-
found = true;
73-
break;
73+
let mut current_addr = addr.address + 4;
74+
while let Some(next) = disassemble(section, current_addr) {
75+
if second(next) {
76+
return Ok(true);
77+
}
78+
if is_end_of_seq(&next) {
79+
// If we hit a branch or an instruction that uses r0 or r1, stop searching.
80+
break;
81+
}
82+
current_addr += 4;
7483
}
7584
}
76-
77-
Ok(found)
85+
Ok(false)
7886
}
7987

8088
fn check_prologue_sequence(
@@ -97,7 +105,11 @@ fn check_prologue_sequence(
97105
// stw r0, d(r1)
98106
ins.op == Opcode::Stw && ins.field_rs() == 0 && ins.field_ra() == 1
99107
}
100-
check_sequence(section, addr, ins, &[(&is_stwu, &is_mflr), (&is_mflr, &is_stw)])
108+
check_sequence(section, addr, ins, &[
109+
(&is_stwu, &is_mflr),
110+
(&is_mflr, &is_stw),
111+
(&is_mflr, &is_stwu),
112+
])
101113
}
102114

103115
impl FunctionSlices {
@@ -148,7 +160,28 @@ impl FunctionSlices {
148160
}
149161
if check_prologue_sequence(section, addr, Some(ins))? {
150162
if let Some(prologue) = self.prologue {
151-
if prologue != addr && prologue != addr - 4 {
163+
let invalid_seq = if prologue == addr {
164+
false
165+
} else if prologue > addr {
166+
true
167+
} else {
168+
// Check if any instructions between the prologue and this address
169+
// are branches or use r0 or r1.
170+
let mut current_addr = prologue.address + 4;
171+
loop {
172+
if current_addr == addr.address {
173+
break false;
174+
}
175+
let next = disassemble(section, current_addr).with_context(|| {
176+
format!("Failed to disassemble {current_addr:#010X}")
177+
})?;
178+
if is_end_of_seq(&next) {
179+
break true;
180+
}
181+
current_addr += 4;
182+
}
183+
};
184+
if invalid_seq {
152185
bail!("Found multiple functions inside a symbol: {:#010X} and {:#010X}. Check symbols.txt?", prologue, addr)
153186
}
154187
} else {

0 commit comments

Comments
 (0)