Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
b4c76e0
Mark r10 and r11 as pre-defined registers
AetiasHax Apr 25, 2026
2bc1c02
Support Thumb jump table case `bx {dest}`
AetiasHax Apr 25, 2026
566c47d
Add `eor pc, ...` as a possible return instruction
AetiasHax Apr 25, 2026
bd6602c
Only set last conditional destination if branch was conditional
AetiasHax Apr 26, 2026
7fd33bd
Add `add pc, ...` as a possible return instruction
AetiasHax Apr 26, 2026
48f92b9
Add all unknown function symbols and external labels before adding fu…
AetiasHax Apr 26, 2026
e9b9f5e
Don't create relocations to local symbols
AetiasHax Apr 26, 2026
fb0d8e4
Define disjoint overlays as static overlays
AetiasHax Apr 26, 2026
ed344e1
Find external labels and unknown functions earlier
AetiasHax Apr 26, 2026
c4a47c2
Mark branches into functions as return only if the instruction modes …
AetiasHax Apr 26, 2026
bf674c7
Add exception for `add pc, pc, r*, lsl #0x2` as return instruction
AetiasHax Apr 26, 2026
efac4f5
Always mark backwards branches as returns
AetiasHax Apr 26, 2026
149dad5
Add illegal code pattern
AetiasHax Apr 26, 2026
2f926da
Distinguish error messages
AetiasHax Apr 27, 2026
e8734d3
Support external label symbols when creating relocations for pool con…
AetiasHax Apr 27, 2026
eab42d6
check symbols: Skip label symbols
AetiasHax Apr 27, 2026
3027c50
check symbols: Clearer output
AetiasHax Apr 27, 2026
0d96ef4
test: Update error type for allowing unknown function calls
AetiasHax Apr 27, 2026
f13c92b
Treat jump table branches as conditional
AetiasHax Apr 27, 2026
7b2baff
Add ARM jump table case `ldmiahi` to return early if no case matched
AetiasHax Apr 27, 2026
d215a1b
Mark existing labels as external on `SymbolMap::add_external_label`
AetiasHax Apr 27, 2026
76e42e6
Skip consecutive pointers
AetiasHax Apr 28, 2026
8bde2f6
Handle branch to wrong instruction mode as illegal instruction
AetiasHax Apr 28, 2026
9d1b571
Don't add relocations to non-external labels
AetiasHax Apr 28, 2026
298aa5f
Treat Thumb NOP as illegal
AetiasHax Apr 28, 2026
c4cd2ea
Force ITCM .text to start at ITCM base address
AetiasHax Apr 28, 2026
ce3dcae
Mark branches outside of program as illegal
AetiasHax Apr 28, 2026
82566ee
Shorten max search distance for .text functions in ARM9 main
AetiasHax Apr 28, 2026
34b9986
Force ARM9 .text to end at next section start
AetiasHax May 16, 2026
b1b3991
Add `GetExceptix` function case in Thumb
AetiasHax May 16, 2026
dcdf2df
test: Print whether `--allow-unknown-function-calls` was used
AetiasHax May 16, 2026
118fb28
lcf: Allow spaces in directories leading up to .o files
AetiasHax May 16, 2026
6bde209
dis: Refuse functions in .bss sections
AetiasHax May 16, 2026
5efbe8f
Handle jump table case using `ldrb` instead of `ldrh`
AetiasHax May 17, 2026
5ed48f5
init: Don't set upper bound when pool constant is used for tail calls
AetiasHax May 17, 2026
e13d8fb
dump elf-function: New subcommand for inspecting functions after linking
AetiasHax May 17, 2026
db82e40
roundtrip: Build ROM and compare to base ROM
AetiasHax May 17, 2026
f51047a
check symbols: Update error message
AetiasHax May 17, 2026
872b18c
rom config: Use `object` name from config.yaml instead of assuming fi…
AetiasHax May 17, 2026
a5ee86e
rom config: Provide file names for unknown autoloads
AetiasHax May 17, 2026
bb72833
rom config: Create bin files if linker doesn't generate one
AetiasHax May 17, 2026
1807ab9
rom config: Optimize performance
AetiasHax May 17, 2026
1e64ab6
init: Treat out-of-bounds pool reads as illegal instructions
AetiasHax May 17, 2026
97ad8c1
init: Fix `signed: true` never getting added to config.yaml
AetiasHax May 17, 2026
83da00d
lcf: Rewrite overlay grouping algorithm
AetiasHax May 17, 2026
1a43daa
init: Fix incorrect start address for analyzing .text in ARM9
AetiasHax May 30, 2026
2a0083b
Clippy
AetiasHax May 30, 2026
529b756
Clippy
AetiasHax May 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ base64 = "0.22"
clap = { version = "4.5", features = ["derive"] }
cpp_demangle = "0.5"
ds-decomp = { path = "../lib" }
ds-rom = "0.7"
ds-rom = { git = "https://github.com/AetiasHax/ds-rom", branch = "0.7.1" }
env_logger = "0.11"
fxhash = "0.2"
log = "0.4"
Expand Down
225 changes: 114 additions & 111 deletions cli/src/analysis/data.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use ds_decomp::{
analysis::functions::Function,
analysis::functions::{CalledFunction, Function},
config::{
module::{AnalysisOptions, Module, ModuleKind},
module::{Module, ModuleKind},
relocations::{Relocation, RelocationFromModulesError, RelocationModule},
section::{SectionCodeError, SectionIndex, SectionKind},
symbol::{SymbolMapError, SymbolMaps},
symbol::{InstructionMode, SymFunction, SymLabel, SymbolKind, SymbolMapError, SymbolMaps},
},
};
use snafu::Snafu;
Expand All @@ -18,9 +18,18 @@ pub struct AnalyzeExternalReferencesOptions<'a> {
#[derive(Debug, Snafu)]
pub enum AnalyzeExternalReferencesError {
#[snafu(display(
"Local function call from {from:#010x} in {module_kind} to {to:#010x} leads to no function"
"Failed to add relocation for local function call from {from:#010x} in {module_kind} to {to:#010x} as it leads to no function"
))]
LocalFunctionNotFound { from: u32, to: u32, module_kind: ModuleKind },
#[snafu(display(
"Failed to add relocation for function call from {from:#010x} in {from_module} to {to:#010x} in {to_module} as it leads to a non-function symbol"
))]
InvalidCallDestinationSymbol {
from: u32,
to: u32,
from_module: ModuleKind,
to_module: ModuleKind,
},
#[snafu(transparent)]
SymbolMap { source: SymbolMapError },
#[snafu(transparent)]
Expand All @@ -30,153 +39,133 @@ pub enum AnalyzeExternalReferencesError {
}

pub fn analyze_external_references(
options: AnalyzeExternalReferencesOptions,
analysis_options: &AnalysisOptions,
options: &mut AnalyzeExternalReferencesOptions,
) -> Result<RelocationResult, AnalyzeExternalReferencesError> {
let AnalyzeExternalReferencesOptions { modules, module_index, symbol_maps } = options;

let mut result = RelocationResult::new();
find_relocations_in_functions(
&mut result,
AnalyzeExternalReferencesOptions { modules, module_index, symbol_maps },
analysis_options,
)?;
find_external_references_in_sections(modules, module_index, &mut result)?;
find_relocations_in_functions(&mut result, options)?;
find_external_references_in_sections(options, &mut result)?;
Ok(result)
}

fn find_external_references_in_sections(
modules: &[Module],
module_index: usize,
options: &mut AnalyzeExternalReferencesOptions,
result: &mut RelocationResult,
) -> Result<(), AnalyzeExternalReferencesError> {
for section in modules[module_index].sections().iter() {
let o = options;
for section in o.modules[o.module_index].sections().iter() {
match section.kind() {
SectionKind::Data | SectionKind::Rodata => {}
SectionKind::Code | SectionKind::Bss => continue,
}

let code = section
.code(modules[module_index].code(), modules[module_index].base_address())?
.code(o.modules[o.module_index].code(), o.modules[o.module_index].base_address())?
.unwrap();
for word in section.iter_words(code, None) {
find_external_data(modules, module_index, word.address, word.value, result)?;
find_external_data(o, word.address, word.value, result)?;
}
}
Ok(())
}

fn find_relocations_in_functions(
result: &mut RelocationResult,
options: AnalyzeExternalReferencesOptions,
analysis_options: &AnalysisOptions,
options: &mut AnalyzeExternalReferencesOptions,
) -> Result<(), AnalyzeExternalReferencesError> {
let AnalyzeExternalReferencesOptions { modules, module_index, symbol_maps } = options;

for section in modules[module_index].sections().iter() {
for section in options.modules[options.module_index].sections().iter() {
for function in section.functions().values() {
add_function_calls_as_relocations(
function,
result,
AnalyzeExternalReferencesOptions { modules, module_index, symbol_maps },
analysis_options,
)?;
find_external_data_from_pools(modules, module_index, function, result)?;
add_function_calls_as_relocations(function, result, options)?;
find_external_data_from_pools(options, function, result)?;
}
}
Ok(())
}

fn iter_function_calls(function: &Function) -> impl Iterator<Item = (&u32, &CalledFunction)> {
function
.function_calls()
.iter()
// TODO: Condition code resets to AL for relocated call instructions
.filter(|(_, called_function)| !called_function.ins.is_conditional())
}

fn add_function_calls_as_relocations(
function: &Function,
result: &mut RelocationResult,
options: AnalyzeExternalReferencesOptions,
analysis_options: &AnalysisOptions,
options: &mut AnalyzeExternalReferencesOptions,
) -> Result<(), AnalyzeExternalReferencesError> {
let AnalyzeExternalReferencesOptions { modules, module_index, symbol_maps } = options;

for (&address, &called_function) in function.function_calls() {
if called_function.ins.is_conditional() {
// Dumb mwld linker bug removes the condition code from relocated call instructions
continue;
}

let local_module = &modules[module_index];
for (&address, &called_function) in iter_function_calls(function) {
let local_module = &modules[*module_index];
let is_local =
local_module.sections().get_by_contained_address(called_function.address).is_some();

let module: RelocationModule = if is_local {
let module_kind = local_module.kind();
let symbol_map = symbol_maps.get_mut(module_kind);
let symbol = match symbol_map.get_function_containing(called_function.address) {
let symbol = match symbol_map.by_address(called_function.address)? {
Some((_, symbol)) => symbol,
None => {
if !analysis_options.allow_unknown_function_calls {
let error = LocalFunctionNotFoundSnafu {
from: address,
to: called_function.address,
module_kind,
}
.build();
log::error!("{error}");
return Err(error);
} else {
log::warn!(
"Local function call from {:#010x} in {} to {:#010x} leads to no function, inserting an unknown function symbol",
address,
module_kind,
called_function.address
);

let thumb_bit = if called_function.thumb { 1 } else { 0 };
let function_address = called_function.address | thumb_bit;

if let Some((_, symbol)) = symbol_map.get_function(function_address)? {
symbol
} else {
let name = format!(
"{}{:08x}_unk",
local_module.default_func_prefix, function_address
);
let (_, symbol) = symbol_map.add_unknown_function(
name,
function_address,
called_function.thumb,
);
symbol
}
let error = LocalFunctionNotFoundSnafu {
from: address,
to: called_function.address,
module_kind,
}
.build();
log::error!("{error}");
return Err(error);
}
};
if called_function.address != symbol.addr {
log::warn!(
"Local function call from {:#010x} in {} to {:#010x} goes to middle of function '{}' at {:#010x}, adding an external label symbol",
address,
module_kind,
called_function.address,
symbol.name,
symbol.addr
);
symbol_map.add_external_label(called_function.address, called_function.thumb)?;
match &symbol.kind {
SymbolKind::Function(_) | SymbolKind::Label(SymLabel { external: true, .. }) => {}

SymbolKind::Label(SymLabel { external: false, .. })
| SymbolKind::Undefined
| SymbolKind::PoolConstant
| SymbolKind::JumpTable(_)
| SymbolKind::Data(_)
| SymbolKind::Bss(_) => {
return InvalidCallDestinationSymbolSnafu {
from: address,
to: called_function.address,
from_module: module_kind,
to_module: module_kind,
}
.fail();
}
}

module_kind.into()
} else {
let candidates = modules.iter().filter(|&module| {
let symbol_map = symbol_maps.get(module.kind()).unwrap();
let Some((function, _)) = symbol_map.get_function(called_function.address).unwrap()
let Some((_, symbol)) = symbol_map.by_address(called_function.address).unwrap()
else {
return false;
};
function.mode.into_thumb() == Some(called_function.thumb)

let mode = match &symbol.kind {
SymbolKind::Function(SymFunction { mode, .. })
| SymbolKind::Label(SymLabel { external: true, mode }) => mode,

SymbolKind::Label(SymLabel { external: false, .. })
| SymbolKind::Undefined
| SymbolKind::PoolConstant
| SymbolKind::JumpTable(_)
| SymbolKind::Data(_)
| SymbolKind::Bss(_) => return false,
};

mode.into_thumb() == Some(called_function.thumb)
});
RelocationModule::from_modules(candidates)?
};

if module == RelocationModule::None {
log::warn!(
"No functions from {address:#010x} in {} to {:#010x}:",
modules[module_index].kind(),
modules[*module_index].kind(),
called_function.address
);
}
Expand All @@ -201,44 +190,37 @@ fn add_function_calls_as_relocations(
}

fn find_external_data_from_pools(
modules: &[Module],
module_index: usize,
options: &mut AnalyzeExternalReferencesOptions,
function: &Function,
result: &mut RelocationResult,
) -> Result<(), AnalyzeExternalReferencesError> {
let module = &modules[module_index];
for pool_constant in function.iter_pool_constants(module.code(), module.base_address()) {
find_external_data(
modules,
module_index,
pool_constant.address,
pool_constant.value,
result,
)?;
for pool_constant in function.iter_pool_constants() {
find_external_data(options, pool_constant.address, pool_constant.value, result)?;
}
Ok(())
}

fn find_external_data(
modules: &[Module],
module_index: usize,
options: &mut AnalyzeExternalReferencesOptions,
address: u32,
pointer: u32,
result: &mut RelocationResult,
) -> Result<(), AnalyzeExternalReferencesError> {
let local_module = &modules[module_index];
let o = options;

let local_module = &o.modules[o.module_index];
let is_local = local_module.sections().get_by_contained_address(pointer).is_some();
if is_local {
return Ok(());
}

let candidates = find_symbol_candidates(modules, module_index, pointer);
let candidates = find_symbol_candidates(o, pointer);
if candidates.is_empty() {
// Probably not a pointer
return Ok(());
}

let candidate_modules = candidates.iter().map(|c| &modules[c.module_index]);
let candidate_modules = candidates.iter().map(|c| &o.modules[c.module_index]);
let module = RelocationModule::from_modules(candidate_modules)?;

result.relocations.push(Relocation::new_load(address, pointer, 0, module));
Expand All @@ -247,26 +229,47 @@ fn find_external_data(
}

fn find_symbol_candidates(
modules: &[Module],
module_index: usize,
options: &mut AnalyzeExternalReferencesOptions,
pointer: u32,
) -> Vec<SymbolCandidate> {
modules
options
.modules
.iter()
.enumerate()
.filter_map(|(index, module)| {
if index == module_index {
if index == options.module_index {
return None;
}
let (section_index, section) = module.sections().get_by_contained_address(pointer)?;
let symbol_map = options.symbol_maps.get(module.kind()).unwrap();
if section.kind() == SectionKind::Code {
let function = section.functions().get(&(pointer & !1))?;
let (_, symbol) = symbol_map.by_address(pointer & !1).unwrap()?;
let symbol_is_thumb = match &symbol.kind {
SymbolKind::Function(function) => function.mode == InstructionMode::Thumb,
SymbolKind::Label(SymLabel { external: true, mode }) => {
*mode == InstructionMode::Thumb
}
SymbolKind::Label(SymLabel { external: false, .. })
| SymbolKind::Undefined
| SymbolKind::PoolConstant
| SymbolKind::JumpTable(_)
| SymbolKind::Data(_)
| SymbolKind::Bss(_) => return None,
};

let thumb = (pointer & 1) != 0;
if function.is_thumb() != thumb {
if symbol_is_thumb != thumb {
return None;
}
}
Some(SymbolCandidate { module_index: index, section_index })
if let Some((_, symbol)) = symbol_map.by_address(pointer).unwrap()
&& symbol.local
{
// Existing symbol is local, so it can't be referred to by a relocation
None
} else {
Some(SymbolCandidate { module_index: index, section_index })
}
})
.collect::<Vec<_>>()
}
Expand Down
Loading
Loading