Skip to content

Commit d766ff5

Browse files
committed
Add skip_cfa_ranges option to config.yml
This allows configuring the function analyzer to skip certain problematic ranges in the program. Only for exceptional cases that prevents analysis from finishing. When using this feature, ensure that no `function` symbols exist for the affected range in symbols.txt, otherwise CFA will run for the defined functions, regardless of this setting.
1 parent 3434c06 commit d766ff5

File tree

3 files changed

+95
-41
lines changed

3 files changed

+95
-41
lines changed

src/analysis/cfa.rs

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,13 @@ use itertools::Itertools;
1111
use crate::{
1212
analysis::{
1313
executor::{ExecCbData, ExecCbResult, Executor},
14-
skip_alignment,
1514
slices::{FunctionSlices, TailCallResult},
1615
vm::{BranchTarget, GprValue, StepResult, VM},
1716
RelocationTarget,
1817
},
1918
obj::{
20-
ObjInfo, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags, ObjSymbolKind,
21-
SectionIndex,
19+
ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet, ObjSymbolFlags,
20+
ObjSymbolKind, SectionIndex,
2221
},
2322
util::config::create_auto_symbol_name,
2423
};
@@ -125,9 +124,21 @@ pub struct AnalyzerState {
125124
pub jump_tables: BTreeMap<SectionAddress, u32>,
126125
pub known_symbols: BTreeMap<SectionAddress, Vec<ObjSymbol>>,
127126
pub known_sections: BTreeMap<SectionIndex, String>,
127+
pub skip_ranges: BTreeMap<SectionAddress, SectionAddress>,
128128
}
129129

130130
impl AnalyzerState {
131+
pub fn new(skip_ranges: BTreeMap<SectionAddress, SectionAddress>) -> Self {
132+
Self {
133+
sda_bases: None,
134+
functions: BTreeMap::new(),
135+
jump_tables: BTreeMap::new(),
136+
known_symbols: BTreeMap::new(),
137+
known_sections: BTreeMap::new(),
138+
skip_ranges,
139+
}
140+
}
141+
131142
pub fn apply(&self, obj: &mut ObjInfo) -> Result<()> {
132143
for (&section_index, section_name) in &self.known_sections {
133144
obj.sections[section_index].rename(section_name.clone())?;
@@ -371,12 +382,7 @@ impl AnalyzerState {
371382
log::trace!("Finalizing {:#010X}", addr);
372383
slices.finalize(obj, &self.functions)?;
373384
for address in slices.function_references.iter().cloned() {
374-
// Only create functions for code sections
375-
// Some games use branches to data sections to prevent dead stripping (Mario Party)
376-
if matches!(obj.sections.get(address.section), Some(section) if section.kind == ObjSectionKind::Code)
377-
{
378-
self.functions.entry(address).or_default();
379-
}
385+
self.try_add_function(obj, address);
380386
}
381387
self.jump_tables.append(&mut slices.jump_table_references.clone());
382388
let end = slices.end();
@@ -390,6 +396,25 @@ impl AnalyzerState {
390396
Ok(finalized_any)
391397
}
392398

399+
fn try_add_function(&mut self, obj: &ObjInfo, address: SectionAddress) {
400+
// Only create functions for code sections
401+
// Some games use branches to data sections to prevent dead stripping (Mario Party)
402+
if !matches!(obj.sections.get(address.section), Some(section) if section.kind == ObjSectionKind::Code)
403+
// Avoid creating functions in skipped ranges
404+
|| self.in_skipped_range(address)
405+
{
406+
return;
407+
}
408+
self.functions.entry(address).or_default();
409+
}
410+
411+
fn in_skipped_range(&self, address: SectionAddress) -> bool {
412+
match self.skip_ranges.range(..=address).next_back() {
413+
Some((&start, &end)) => address >= start && address < end,
414+
None => false,
415+
}
416+
}
417+
393418
fn first_unbounded_function(&self) -> Option<SectionAddress> {
394419
self.functions.iter().find(|(_, info)| !info.is_analyzed()).map(|(&addr, _)| addr)
395420
}
@@ -414,12 +439,7 @@ impl AnalyzerState {
414439
pub fn process_function_at(&mut self, obj: &ObjInfo, addr: SectionAddress) -> Result<bool> {
415440
Ok(if let Some(mut slices) = self.process_function(obj, addr)? {
416441
for address in slices.function_references.iter().cloned() {
417-
// Only create functions for code sections
418-
// Some games use branches to data sections to prevent dead stripping (Mario Party)
419-
if matches!(obj.sections.get(address.section), Some(section) if section.kind == ObjSectionKind::Code)
420-
{
421-
self.functions.entry(address).or_default();
422-
}
442+
self.try_add_function(obj, address);
423443
}
424444
self.jump_tables.append(&mut slices.jump_table_references.clone());
425445
if slices.can_finalize() {
@@ -470,7 +490,7 @@ impl AnalyzerState {
470490
if first_end > second {
471491
bail!("Overlapping functions {}-{} -> {}", first, first_end, second);
472492
}
473-
let addr = match skip_alignment(section, first_end, second) {
493+
let addr = match self.skip_alignment(section, first_end, second) {
474494
Some(addr) => addr,
475495
None => continue,
476496
};
@@ -489,7 +509,7 @@ impl AnalyzerState {
489509
(Some((last, last_info)), None) => {
490510
let Some(last_end) = last_info.end else { continue };
491511
if last_end < section_end {
492-
let addr = match skip_alignment(section, last_end, section_end) {
512+
let addr = match self.skip_alignment(section, last_end, section_end) {
493513
Some(addr) => addr,
494514
None => continue,
495515
};
@@ -516,6 +536,33 @@ impl AnalyzerState {
516536
}
517537
Ok(found_new)
518538
}
539+
540+
fn skip_alignment(
541+
&self,
542+
section: &ObjSection,
543+
mut addr: SectionAddress,
544+
end: SectionAddress,
545+
) -> Option<SectionAddress> {
546+
loop {
547+
if let Some((&start, &end)) = self.skip_ranges.range(..=addr).next_back() {
548+
if addr >= start && addr < end {
549+
addr = end;
550+
}
551+
};
552+
if addr.address + 4 > end.address {
553+
break None;
554+
}
555+
let data = match section.data_range(addr.address, addr.address + 4) {
556+
Ok(data) => data,
557+
Err(_) => return None,
558+
};
559+
if data == [0u8; 4] {
560+
addr += 4;
561+
} else {
562+
break Some(addr);
563+
}
564+
}
565+
}
519566
}
520567

521568
/// Execute VM from entry point following branches and function calls

src/analysis/mod.rs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -249,25 +249,3 @@ pub fn uniq_jump_table_entries(
249249
)?;
250250
Ok((BTreeSet::from_iter(entries.iter().cloned()), size))
251251
}
252-
253-
pub fn skip_alignment(
254-
section: &ObjSection,
255-
mut addr: SectionAddress,
256-
end: SectionAddress,
257-
) -> Option<SectionAddress> {
258-
let mut data = match section.data_range(addr.address, end.address) {
259-
Ok(data) => data,
260-
Err(_) => return None,
261-
};
262-
loop {
263-
if data.is_empty() {
264-
break None;
265-
}
266-
if data[0..4] == [0u8; 4] {
267-
addr += 4;
268-
data = &data[4..];
269-
} else {
270-
break Some(addr);
271-
}
272-
}
273-
}

src/cmd/dol.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,8 @@ pub struct ModuleConfig {
301301
/// Process exception tables and zero out uninitialized data.
302302
#[serde(default, skip_serializing_if = "Option::is_none")]
303303
pub clean_extab: Option<bool>,
304+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
305+
pub skip_cfa_ranges: Vec<SkipCfaRangeConfig>,
304306
}
305307

306308
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
@@ -367,6 +369,16 @@ pub struct AddRelocationConfig {
367369
pub addend: i64,
368370
}
369371

372+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
373+
pub struct SkipCfaRangeConfig {
374+
/// The start address of the range to skip.
375+
/// Format: `section:address`, e.g. `.text:0x80001234`.
376+
pub start: SectionAddressRef,
377+
/// The end address of the range to skip.
378+
/// Format: `section:address`, e.g. `.text:0x80001234`.
379+
pub end: SectionAddressRef,
380+
}
381+
370382
impl ModuleConfig {
371383
pub fn file_name(&self) -> &str { self.object.file_name().unwrap_or(self.object.as_str()) }
372384

@@ -376,6 +388,19 @@ impl ModuleConfig {
376388
}
377389

378390
pub fn name(&self) -> &str { self.name.as_deref().unwrap_or_else(|| self.file_prefix()) }
391+
392+
pub fn skip_cfa_ranges(
393+
&self,
394+
obj: &ObjInfo,
395+
) -> Result<BTreeMap<SectionAddress, SectionAddress>> {
396+
let mut skip_cfa_ranges = BTreeMap::new();
397+
for range in &self.skip_cfa_ranges {
398+
let start = range.start.resolve(obj)?;
399+
let end = range.end.resolve(obj)?;
400+
skip_cfa_ranges.insert(start, end);
401+
}
402+
Ok(skip_cfa_ranges)
403+
}
379404
}
380405

381406
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -916,7 +941,9 @@ fn load_analyze_dol(config: &ProjectConfig, object_base: &ObjectBase) -> Result<
916941
apply_signatures(&mut obj)?;
917942

918943
if !config.quick_analysis {
919-
let mut state = AnalyzerState::default();
944+
let skip_ranges =
945+
config.base.skip_cfa_ranges(&obj).context("Resolving skip CFA ranges")?;
946+
let mut state = AnalyzerState::new(skip_ranges);
920947
debug!("Detecting function boundaries");
921948
FindSaveRestSleds::execute(&mut state, &obj)?;
922949
state.detect_functions(&obj)?;
@@ -1216,7 +1243,9 @@ fn load_analyze_rel(
12161243
if !config.symbols_known {
12171244
debug!("Analyzing module {}", module_obj.module_id);
12181245
if !config.quick_analysis {
1219-
let mut state = AnalyzerState::default();
1246+
let skip_ranges =
1247+
module_config.skip_cfa_ranges(&module_obj).context("Resolving skip CFA ranges")?;
1248+
let mut state = AnalyzerState::new(skip_ranges);
12201249
FindSaveRestSleds::execute(&mut state, &module_obj)?;
12211250
state.detect_functions(&module_obj)?;
12221251
FindRelCtorsDtors::execute(&mut state, &module_obj)?;

0 commit comments

Comments
 (0)