Compare commits

..

13 Commits
v1.6.0 ... main

Author SHA1 Message Date
89864dc76e
refactor: rename relocations field and flatten relocs (#115) 2025-09-13 08:17:36 +00:00
9c5210184d
Remove globalize_symbols test (#116) 2025-09-13 08:14:37 +00:00
31c42d58db
Fix clippy lints and address cargo deny advisories (#117) 2025-09-13 02:04:42 -06:00
Vi
d3596dbaa4
account for enum size in dwarf dump (#110) 2025-08-30 11:03:48 -06:00
b56b399201 Support loading Wii Menu (BootStage) DOL files
Also adds support to elf2dol to extract the inner DOL
from a BootStage DOL.
2025-06-23 18:21:42 -06:00
8620099731 Version v1.6.2 2025-06-09 22:45:48 -06:00
ae00c35ec3 Better jump table error context 2025-06-09 22:45:48 -06:00
ba2589646e 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
2025-06-09 22:45:48 -06:00
cadmic
7bc0bc474d
Continue analyzing functions after unknown jumps (#106)
* Continue analyzing functions after unknown jumps
2025-06-09 22:45:21 -06:00
cadmic
d969819b78
Guess endianness of "erased" DWARF info (#104) 2025-06-09 22:44:39 -06:00
f4a67ee619 Version v1.6.1 2025-06-04 22:04:30 -06:00
Robin Avery
d92a892c2b
Relax string size requirement for auto symbols (#102) 2025-06-04 20:01:39 -07:00
cadmic
5e33fea49f
Allow specifying replacement bytes in dtk extab clean (#103)
* Allow specifying replacement bytes in dtk extab clean

* Simplify extab padding replacement

* Reword log message

* clippy has bad taste

* Don't specify revision number for cwextab

---------

Co-authored-by: Amber Brault <celestialamber1@gmail.com>
2025-06-04 20:01:05 -07:00
26 changed files with 819 additions and 537 deletions

154
Cargo.lock generated
View File

@ -167,9 +167,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.16.0"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "byteorder"
@ -179,9 +179,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.7.2"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "bzip2"
@ -339,16 +339,16 @@ checksum = "c2e06f9bce634a3c898eb1e5cb949ff63133cbb218af93cc9b38b31d6f3ea285"
[[package]]
name = "cwextab"
version = "1.1.0"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "701f6867c92e1b64ddcc4b416194be3121b8f7ba5352a70ed5fd3295a7d8e0e1"
checksum = "9dd95393b8cc20937e4757d9c22b89d016613e934c60dcb073bd8a5aade79fcf"
dependencies = [
"thiserror 2.0.12",
]
[[package]]
name = "decomp-toolkit"
version = "1.6.0"
version = "1.7.0"
dependencies = [
"aes",
"anyhow",
@ -365,7 +365,7 @@ dependencies = [
"enable-ansi-support",
"encoding_rs",
"filetime",
"fixedbitset 0.5.7",
"fixedbitset",
"flagset",
"glob",
"hex",
@ -480,9 +480,9 @@ dependencies = [
[[package]]
name = "fastrand"
version = "2.1.1"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
[[package]]
name = "filetime"
@ -496,12 +496,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "fixedbitset"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "fixedbitset"
version = "0.5.7"
@ -697,10 +691,11 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.72"
version = "0.3.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738"
dependencies = [
"once_cell",
"wasm-bindgen",
]
@ -781,11 +776,11 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "matchers"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
dependencies = [
"regex-automata 0.1.10",
"regex-automata",
]
[[package]]
@ -911,12 +906,11 @@ dependencies = [
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
version = "0.50.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
dependencies = [
"overload",
"winapi",
"windows-sys 0.52.0",
]
[[package]]
@ -1018,12 +1012,6 @@ dependencies = [
"snafu",
]
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "owo-colors"
version = "4.1.0"
@ -1071,11 +1059,11 @@ dependencies = [
[[package]]
name = "petgraph"
version = "0.6.5"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
dependencies = [
"fixedbitset 0.4.2",
"fixedbitset",
"indexmap",
]
@ -1105,9 +1093,9 @@ checksum = "be8e5b9d48ab30323e8ece6d655d20fc16a570e4f1af0de6890d3e9ebd284ba0"
[[package]]
name = "prettyplease"
version = "0.2.22"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba"
checksum = "6837b9e10d61f45f987d50808f83d1ee3d206c66acf650c3e4ae2e1f6ddedf55"
dependencies = [
"proc-macro2",
"syn 2.0.101",
@ -1133,9 +1121,9 @@ dependencies = [
[[package]]
name = "prost"
version = "0.13.3"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f"
checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
dependencies = [
"bytes",
"prost-derive",
@ -1143,11 +1131,10 @@ dependencies = [
[[package]]
name = "prost-build"
version = "0.13.3"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15"
checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf"
dependencies = [
"bytes",
"heck",
"itertools",
"log",
@ -1164,9 +1151,9 @@ dependencies = [
[[package]]
name = "prost-derive"
version = "0.13.3"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5"
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
dependencies = [
"anyhow",
"itertools",
@ -1177,9 +1164,9 @@ dependencies = [
[[package]]
name = "prost-types"
version = "0.13.3"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670"
checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16"
dependencies = [
"prost",
]
@ -1252,17 +1239,8 @@ checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.8",
"regex-syntax 0.8.5",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
"regex-automata",
"regex-syntax",
]
[[package]]
@ -1273,15 +1251,9 @@ checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.8.5",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.8.5"
@ -1605,7 +1577,7 @@ dependencies = [
"flate2",
"fnv",
"once_cell",
"regex-syntax 0.8.5",
"regex-syntax",
"serde",
"serde_derive",
"serde_json",
@ -1695,9 +1667,9 @@ dependencies = [
[[package]]
name = "tracing"
version = "0.1.40"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"pin-project-lite",
"tracing-attributes",
@ -1706,9 +1678,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.27"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
dependencies = [
"proc-macro2",
"quote",
@ -1717,9 +1689,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.32"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
dependencies = [
"once_cell",
"valuable",
@ -1738,14 +1710,14 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex",
"regex-automata",
"sharded-slab",
"smallvec",
"thread_local",
@ -1756,9 +1728,9 @@ dependencies = [
[[package]]
name = "tsify-next"
version = "0.5.4"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f4a645dca4ee0800f5ab60ce166deba2db6a0315de795a2691e138a3d55d756"
checksum = "7d0f2208feeb5f7a6edb15a2389c14cd42480ef6417318316bb866da5806a61d"
dependencies = [
"serde",
"serde-wasm-bindgen",
@ -1768,9 +1740,9 @@ dependencies = [
[[package]]
name = "tsify-next-macros"
version = "0.5.4"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d5c06f8a51d759bb58129e30b2631739e7e1e4579fad1f30ac09a6c88e488a6"
checksum = "f81253930d0d388a3ab8fa4ae56da9973ab171ef833d1be2e9080fc3ce502bd6"
dependencies = [
"proc-macro2",
"quote",
@ -1853,24 +1825,25 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.95"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.95"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.101",
@ -1879,9 +1852,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.95"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -1889,9 +1862,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.95"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa"
dependencies = [
"proc-macro2",
"quote",
@ -1902,9 +1875,12 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.95"
version = "0.2.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1"
dependencies = [
"unicode-ident",
]
[[package]]
name = "winapi"
@ -1928,7 +1904,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]

View File

@ -3,7 +3,7 @@ name = "decomp-toolkit"
description = "Yet another GameCube/Wii decompilation toolkit."
authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0"
version = "1.6.0"
version = "1.7.0"
edition = "2021"
publish = false
repository = "https://github.com/encounter/decomp-toolkit"

View File

@ -297,6 +297,8 @@ Dumps DWARF 1.1 information from an ELF file. (Does **not** support DWARF 2+)
```shell
$ dtk dwarf dump input.elf
# or, to include data that was stripped by MWLD
$ dtk dwarf dump input.elf --include-erased
```
### elf disasm

View File

@ -75,12 +75,14 @@ ignore = [
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
{ id = "RUSTSEC-2024-0384", reason = "unmaintained transient dependency, will be updated in next nod version" },
{ id = "RUSTSEC-2025-0056", reason = "adler crate used via nodtool has no maintained alternative yet" },
{ id = "RUSTSEC-2025-0048", reason = "tsify-next is unmaintained but required by objdiff-core" },
]
# If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library.
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
# See Git Authentication for more information about setting up git authentication.
#git-fetch-with-cli = true
git-fetch-with-cli = true
# This section is considered when running `cargo deny check licenses`
# More documentation for the licenses section can be found here:

View File

@ -1,6 +1,6 @@
use std::{
cmp::min,
collections::BTreeMap,
collections::{BTreeMap, BTreeSet},
fmt::{Debug, Display, Formatter, UpperHex},
ops::{Add, AddAssign, BitAnd, Sub},
};
@ -572,6 +572,26 @@ pub fn locate_sda_bases(obj: &mut ObjInfo) -> Result<bool> {
Some((sda2_base, sda_base)) => {
obj.sda2_base = Some(sda2_base);
obj.sda_base = Some(sda_base);
obj.add_symbol(
ObjSymbol {
name: "_SDA2_BASE_".to_string(),
address: sda2_base as u64,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
..Default::default()
},
true,
)?;
obj.add_symbol(
ObjSymbol {
name: "_SDA_BASE_".to_string(),
address: sda_base as u64,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
..Default::default()
},
true,
)?;
Ok(true)
}
None => Ok(false),
@ -581,7 +601,7 @@ pub fn locate_sda_bases(obj: &mut ObjInfo) -> Result<bool> {
/// ProDG hardcodes .bss and .sbss section initialization in `entry`
/// This function locates the memset calls and returns a list of
/// (address, size) pairs for the .bss sections.
pub fn locate_bss_memsets(obj: &mut ObjInfo) -> Result<Vec<(u32, u32)>> {
pub fn locate_bss_memsets(obj: &ObjInfo) -> Result<Vec<(u32, u32)>> {
let mut bss_sections: Vec<(u32, u32)> = Vec::new();
let Some(entry) = obj.entry else {
return Ok(bss_sections);
@ -632,3 +652,50 @@ pub fn locate_bss_memsets(obj: &mut ObjInfo) -> Result<Vec<(u32, u32)>> {
)?;
Ok(bss_sections)
}
/// Execute VM from specified entry point following inner-section branches and function calls,
/// noting all branch targets outside the current section.
pub fn locate_cross_section_branch_targets(
obj: &ObjInfo,
entry: SectionAddress,
) -> Result<BTreeSet<SectionAddress>> {
let mut branch_targets = BTreeSet::<SectionAddress>::new();
let mut executor = Executor::new(obj);
executor.push(entry, VM::new(), false);
executor.run(
obj,
|ExecCbData { executor, vm, result, ins_addr, section: _, ins: _, block_start: _ }| {
match result {
StepResult::Continue | StepResult::LoadStore { .. } => {
Ok(ExecCbResult::<()>::Continue)
}
StepResult::Illegal => bail!("Illegal instruction @ {}", ins_addr),
StepResult::Jump(target) => {
if let BranchTarget::Address(RelocationTarget::Address(addr)) = target {
if addr.section == entry.section {
executor.push(addr, vm.clone_all(), true);
} else {
branch_targets.insert(addr);
}
}
Ok(ExecCbResult::EndBlock)
}
StepResult::Branch(branches) => {
for branch in branches {
if let BranchTarget::Address(RelocationTarget::Address(addr)) =
branch.target
{
if addr.section == entry.section {
executor.push(addr, branch.vm, true);
} else {
branch_targets.insert(addr);
}
}
}
Ok(ExecCbResult::Continue)
}
}
},
)?;
Ok(branch_targets)
}

View File

@ -244,7 +244,9 @@ pub fn uniq_jump_table_entries(
return Ok((BTreeSet::new(), 0));
}
let (entries, size) =
get_jump_table_entries(obj, addr, size, from, function_start, function_end)?;
get_jump_table_entries(obj, addr, size, from, function_start, function_end).with_context(
|| format!("While fetching jump table entries starting at {addr:#010X}"),
)?;
Ok((BTreeSet::from_iter(entries.iter().cloned()), size))
}

View File

@ -2,7 +2,7 @@ use anyhow::Result;
use crate::{
obj::{ObjDataKind, ObjInfo, ObjSectionKind, ObjSymbolKind, SymbolIndex},
util::split::is_linker_generated_label,
util::{config::is_auto_symbol, split::is_linker_generated_label},
};
pub fn detect_objects(obj: &mut ObjInfo) -> Result<()> {
@ -134,7 +134,9 @@ pub fn detect_strings(obj: &mut ObjInfo) -> Result<()> {
StringResult::None => {}
StringResult::String { length, terminated } => {
let size = if terminated { length + 1 } else { length };
if !symbol.size_known || symbol.size == size as u64 {
if symbol.size == size as u64
|| (is_auto_symbol(symbol) && symbol.size > size as u64)
{
let str = String::from_utf8_lossy(&data[..length]);
log::debug!("Found string '{}' @ {}", str, symbol.name);
symbols_set.push((symbol_idx, ObjDataKind::String, size));
@ -142,7 +144,9 @@ pub fn detect_strings(obj: &mut ObjInfo) -> Result<()> {
}
StringResult::WString { length, str } => {
let size = length + 2;
if !symbol.size_known || symbol.size == size as u64 {
if symbol.size == size as u64
|| (is_auto_symbol(symbol) && symbol.size > size as u64)
{
log::debug!("Found wide string '{}' @ {}", str, symbol.name);
symbols_set.push((symbol_idx, ObjDataKind::String16, size));
}

View File

@ -45,6 +45,17 @@ type BlockRange = Range<SectionAddress>;
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<Ins>,
sequence: &[(&InsCheck, &InsCheck)],
) -> Result<bool> {
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(
@ -89,15 +97,19 @@ fn check_prologue_sequence(
}
#[inline(always)]
fn is_stwu(ins: Ins) -> bool {
// stwu r1, d(r1)
ins.op == Opcode::Stwu && ins.field_rs() == 1 && ins.field_ra() == 1
// stwu[x] r1, d(r1)
matches!(ins.op, Opcode::Stwu | Opcode::Stwux) && 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, 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 {
@ -180,7 +213,11 @@ impl FunctionSlices {
ins.op == Opcode::Or && ins.field_rd() == 1
}
if check_sequence(section, addr, Some(ins), &[(&is_mtlr, &is_addi), (&is_or, &is_mtlr)])? {
if check_sequence(section, addr, Some(ins), &[
(&is_mtlr, &is_addi),
(&is_mtlr, &is_or),
(&is_or, &is_mtlr),
])? {
if let Some(epilogue) = self.epilogue {
if epilogue != addr {
bail!("Found duplicate epilogue: {:#010X} and {:#010X}", epilogue, addr)
@ -340,7 +377,14 @@ impl FunctionSlices {
function_end.or_else(|| self.end()),
)?;
log::debug!("-> size {}: {:?}", size, entries);
if (entries.contains(&next_address) || self.blocks.contains_key(&next_address))
let max_block = self
.blocks
.keys()
.next_back()
.copied()
.unwrap_or(next_address)
.max(next_address);
if entries.iter().any(|&addr| addr > function_start && addr <= max_block)
&& !entries.iter().any(|&addr| {
self.is_known_function(known_functions, addr)
.is_some_and(|fn_addr| fn_addr != function_start)
@ -703,7 +747,7 @@ impl FunctionSlices {
}
}
// If we discovered a function prologue, known tail call.
if slices.prologue.is_some() {
if slices.prologue.is_some() || slices.has_r1_load {
log::trace!("Prologue discovered; known tail call: {:#010X}", addr);
return TailCallResult::Is;
}

View File

@ -417,9 +417,13 @@ impl Tracker {
Ok(ExecCbResult::Continue)
}
StepResult::Jump(target) => match target {
BranchTarget::Return => Ok(ExecCbResult::EndBlock),
BranchTarget::Unknown
| BranchTarget::Return
| BranchTarget::JumpTable { address: RelocationTarget::External, .. } => {
let next_addr = ins_addr + 4;
if next_addr < function_end {
possible_missed_branches.insert(ins_addr + 4, vm.clone_all());
}
Ok(ExecCbResult::EndBlock)
}
BranchTarget::Address(addr) => {

View File

@ -234,6 +234,9 @@ pub struct ProjectConfig {
/// Marks all emitted symbols as "exported" to prevent the linker from removing them.
#[serde(default = "bool_true", skip_serializing_if = "is_true")]
pub export_all: bool,
/// Promotes local symbols referenced by other units to global.
#[serde(default = "bool_true", skip_serializing_if = "is_true")]
pub globalize_symbols: bool,
/// Optional base path for all object files.
#[serde(with = "unix_path_serde_option", default, skip_serializing_if = "is_default")]
pub object_base: Option<Utf8UnixPathBuf>,
@ -259,6 +262,7 @@ impl Default for ProjectConfig {
symbols_known: false,
fill_gaps: true,
export_all: true,
globalize_symbols: true,
object_base: None,
extract_objects: true,
}
@ -313,6 +317,10 @@ pub struct ExtractConfig {
/// Path is relative to `out_dir/include`.
#[serde(with = "unix_path_serde_option", default, skip_serializing_if = "Option::is_none")]
pub header: Option<Utf8UnixPathBuf>,
/// If specified, any relocations within the symbol will be written to the given file in JSON
/// format. Path is relative to `out_dir/bin`.
#[serde(with = "unix_path_serde_option", default, skip_serializing_if = "Option::is_none")]
pub relocations: Option<Utf8UnixPathBuf>,
/// The type for the extracted symbol in the header file. By default, the header will emit
/// a full symbol declaration (a.k.a. `symbol`), but this can be set to `raw` to emit the raw
/// data as a byte array. `none` avoids emitting a header entirely, in which case the `header`
@ -399,11 +407,24 @@ pub struct OutputExtract {
pub binary: Option<Utf8UnixPathBuf>,
#[serde(with = "unix_path_serde_option")]
pub header: Option<Utf8UnixPathBuf>,
#[serde(with = "unix_path_serde_option")]
pub relocations: Option<Utf8UnixPathBuf>,
pub header_type: String,
pub custom_type: Option<String>,
pub custom_data: Option<serde_json::Value>,
}
#[derive(Serialize)]
struct ExtractRelocInfo {
offset: u32,
#[serde(rename = "type")]
kind: ObjRelocKind,
target: String,
addend: i64,
#[serde(skip_serializing_if = "Option::is_none")]
module: Option<u32>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct OutputLink {
pub modules: Vec<String>,
@ -538,7 +559,9 @@ pub fn info(args: InfoArgs) -> Result<()> {
apply_selfile(&mut obj, file.map()?)?;
}
println!("{}:", obj.name);
if !obj.name.is_empty() {
println!("{}:", obj.name);
}
if let Some(entry) = obj.entry {
println!("Entry point: {entry:#010X}");
}
@ -838,7 +861,7 @@ fn load_dol_module(
};
if config.clean_extab.unwrap_or(false) {
log::debug!("Cleaning extab for {}", config.name());
clean_extab(&mut obj)?;
clean_extab(&mut obj, std::iter::empty())?;
}
Ok((obj, object_path))
}
@ -965,7 +988,7 @@ fn split_write_obj(
debug!("Splitting {} objects", module.obj.link_order.len());
let module_name = module.config.name().to_string();
let split_objs = split_obj(&module.obj, Some(module_name.as_str()))?;
let split_objs = split_obj(&module.obj, Some(module_name.as_str()), config.globalize_symbols)?;
debug!("Writing object files");
DirBuilder::new()
@ -1057,12 +1080,35 @@ fn split_write_obj(
}
}
if let Some(relocations) = &extract.relocations {
let start = symbol.address as u32 - section.address as u32;
let end = start + symbol.size as u32;
let mut reloc_entries = Vec::new();
for (addr, reloc) in section.relocations.range(start..end) {
let target_symbol = &module.obj.symbols[reloc.target_symbol];
reloc_entries.push(ExtractRelocInfo {
offset: addr - start,
kind: reloc.kind,
target: target_symbol.name.clone(),
addend: reloc.addend,
module: reloc.module,
});
}
let relocations_json = serde_json::to_vec_pretty(&reloc_entries)?;
let out_path = base_dir.join("bin").join(relocations.with_encoding());
if let Some(parent) = out_path.parent() {
DirBuilder::new().recursive(true).create(parent)?;
}
write_if_changed(&out_path, &relocations_json)?;
}
// Copy to output config
out_config.extract.push(OutputExtract {
symbol: symbol.name.clone(),
rename: extract.rename.clone(),
binary: extract.binary.clone(),
header: extract.header.clone(),
relocations: extract.relocations.clone(),
header_type: header_kind.to_string(),
custom_type: extract.custom_type.clone(),
custom_data: extract.custom_data.clone(),

View File

@ -136,7 +136,7 @@ fn disasm(args: DisasmArgs) -> Result<()> {
match obj.kind {
ObjKind::Executable => {
log::info!("Splitting {} objects", obj.link_order.len());
let split_objs = split_obj(&obj, None)?;
let split_objs = split_obj(&obj, None, false)?;
let asm_dir = args.out.join("asm");
let include_dir = args.out.join("include");

View File

@ -7,7 +7,6 @@ use typed_path::Utf8NativePathBuf;
use crate::{
util::{
alf::ALF_MAGIC,
dol::{process_dol, write_dol},
file::buf_writer,
path::native_path,
@ -16,11 +15,11 @@ use crate::{
};
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Converts an ELF (or ALF) file to a DOL file.
/// Converts an ELF, ALF, or BootStage file to a DOL file.
#[argp(subcommand, name = "elf2dol")]
pub struct Args {
#[argp(positional, from_str_fn(native_path))]
/// path to input ELF or ALF file
/// path to input ELF, ALF or BootStage file
elf_file: Utf8NativePathBuf,
#[argp(positional, from_str_fn(native_path))]
/// path to output DOL
@ -54,8 +53,8 @@ const MAX_DATA_SECTIONS: usize = 11;
pub fn run(args: Args) -> Result<()> {
let mut file = open_file(&args.elf_file, true)?;
let data = file.map()?;
if data.len() >= 4 && data[0..4] == ALF_MAGIC {
return convert_alf(args, data);
if data.len() >= 4 && data[0..4] != object::elf::ELFMAG {
return convert_dol_like(args, data);
}
let obj_file = object::read::File::parse(data)?;
@ -163,7 +162,8 @@ pub fn run(args: Args) -> Result<()> {
Ok(())
}
fn convert_alf(args: Args, data: &[u8]) -> Result<()> {
/// Converts a DOL-like format (ALF or BootStage) to a DOL file.
fn convert_dol_like(args: Args, data: &[u8]) -> Result<()> {
let obj = process_dol(data, "")?;
let mut out = buf_writer(&args.dol_file)?;
write_dol(&obj, &mut out)?;

View File

@ -30,15 +30,18 @@ enum SubCommand {
}
#[derive(FromArgs, PartialEq, Eq, Debug)]
/// Rewrites extab data in a DOL or ELF file, zeroing out any uninitialized padding bytes.
/// Rewrites extab data in a DOL or ELF file, replacing any uninitialized padding bytes.
#[argp(subcommand, name = "clean")]
pub struct CleanArgs {
#[argp(positional, from_str_fn(native_path))]
/// path to input file
/// Path to input file
input: Utf8NativePathBuf,
#[argp(positional, from_str_fn(native_path))]
/// path to output file
/// Path to output file
output: Utf8NativePathBuf,
#[argp(option, short = 'p')]
/// Data to replace padding bytes with, encoded as a hexadecimal string. If not specified, padding bytes will be zeroed instead.
padding: Option<String>,
}
pub fn run(args: Args) -> Result<()> {
@ -56,7 +59,13 @@ fn clean_extab(args: CleanArgs) -> Result<()> {
let name = args.input.file_stem().unwrap_or_default();
process_dol(file.map()?, name)?
};
let num_cleaned = util::extab::clean_extab(&mut obj)?;
let padding: Vec<u8> = match args.padding {
None => Vec::new(),
Some(padding_str) => {
hex::decode(padding_str).context("Failed to decode padding bytes from hex")?
}
};
let num_cleaned = util::extab::clean_extab(&mut obj, padding.iter().copied())?;
tracing::debug!("Cleaned {num_cleaned} extab symbols");
let mut out = buf_writer(&args.output)?;
if is_elf {

View File

@ -123,7 +123,7 @@ pub fn run(args: Args) -> Result<()> {
}
}
fn load_obj(buf: &[u8]) -> Result<File> {
fn load_obj(buf: &[u8]) -> Result<File<'_>> {
let obj = File::parse(buf)?;
match obj.architecture() {
Architecture::PowerPc => {}

View File

@ -4,10 +4,13 @@ use std::{
io::{Cursor, Read, Seek, SeekFrom, Write},
};
use anyhow::{anyhow, bail, ensure, Result};
use anyhow::{anyhow, bail, ensure, Context, Result};
use itertools::Itertools;
use crate::{
analysis::cfa::{locate_bss_memsets, locate_sda_bases, SectionAddress},
analysis::cfa::{
locate_bss_memsets, locate_cross_section_branch_targets, locate_sda_bases, SectionAddress,
},
array_ref,
obj::{
ObjArchitecture, ObjInfo, ObjKind, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlagSet,
@ -209,6 +212,13 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
Box::new(DolFile::from_reader(&mut reader, Endian::Big)?)
};
let mut entry_point = dol.entry_point();
let mut is_bootstage = false;
if entry_point & 0x80000000 == 0 {
entry_point |= 0x80000000;
is_bootstage = true;
}
// Locate _rom_copy_info
let first_rom_section = dol
.sections()
@ -216,20 +226,38 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
.find(|section| section.kind != DolSectionKind::Bss)
.ok_or_else(|| anyhow!("Failed to locate first rom section"))?;
let init_section = dol
.section_by_address(dol.entry_point())
.section_by_address(entry_point)
.ok_or_else(|| anyhow!("Failed to locate .init section"))?;
let first_data_section = dol
.sections()
.iter()
.find(|s| s.kind == DolSectionKind::Data)
.ok_or_else(|| anyhow!("Failed to locate .data section"))?;
let rom_copy_section = if is_bootstage { first_data_section } else { init_section };
if is_bootstage {
// Real entry point is stored at the end of the bootstage init section, always(?) 0x81330000
entry_point = read_u32(buf, dol.as_ref(), init_section.address + init_section.size - 4)?;
}
let rom_copy_info_addr = {
let mut addr = init_section.address + init_section.size
- MAX_ROM_COPY_INFO_SIZE as u32
- MAX_BSS_INIT_INFO_SIZE as u32;
let mut addr = if is_bootstage {
// Start searching from the beginning of the BootStage "data" section
rom_copy_section.address
} else {
// Start searching from the end of the .init section
rom_copy_section.address + rom_copy_section.size
- MAX_ROM_COPY_INFO_SIZE as u32
- MAX_BSS_INIT_INFO_SIZE as u32
};
loop {
let value = read_u32(buf, dol.as_ref(), addr)?;
if value == first_rom_section.address {
if value == first_rom_section.address || value == entry_point {
log::debug!("Found _rom_copy_info @ {addr:#010X}");
break Some(addr);
}
addr += 4;
if addr >= init_section.address + init_section.size {
if addr >= rom_copy_section.address + rom_copy_section.size {
log::warn!("Failed to locate _rom_copy_info");
break None;
}
@ -252,7 +280,7 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
log::debug!("Found _rom_copy_info end @ {addr:#010X}");
break Some(addr);
}
if addr >= init_section.address + init_section.size {
if addr >= rom_copy_section.address + rom_copy_section.size {
log::warn!("Failed to locate _rom_copy_info end");
break None;
}
@ -270,12 +298,14 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
let bss_init_info_addr = match rom_copy_info_end {
Some(mut addr) => loop {
let value = read_u32(buf, dol.as_ref(), addr)?;
if value == bss_section.address {
if is_bootstage
|| (value >= bss_section.address && value < bss_section.address + bss_section.size)
{
log::debug!("Found _bss_init_info @ {addr:#010X}");
break Some(addr);
}
addr += 4;
if addr >= init_section.address + init_section.size {
if addr >= rom_copy_section.address + rom_copy_section.size {
log::warn!("Failed to locate _bss_init_info");
break None;
}
@ -294,7 +324,7 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
log::debug!("Found _bss_init_info end @ {addr:#010X}");
break Some(addr);
}
if addr >= init_section.address + init_section.size {
if addr >= rom_copy_section.address + rom_copy_section.size {
log::warn!("Failed to locate _bss_init_info end");
break None;
}
@ -303,170 +333,105 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
None => None,
};
// Locate _eti_init_info
let num_text_sections =
dol.sections().iter().filter(|section| section.kind == DolSectionKind::Text).count();
let mut eti_entries: Vec<EtiEntry> = Vec::new();
let mut eti_init_info_range: Option<(u32, u32)> = None;
let mut extab_section: Option<SectionIndex> = None;
let mut extabindex_section: Option<SectionIndex> = None;
'outer: for dol_section in
dol.sections().iter().filter(|section| section.kind == DolSectionKind::Data)
{
// Use section size from _rom_copy_info
let dol_section_size = match rom_sections.get(&dol_section.address) {
Some(&size) => size,
None => dol_section.size,
};
let dol_section_end = dol_section.address + dol_section_size;
let eti_init_info_addr = {
let mut addr = dol_section_end - (ETI_INIT_INFO_SIZE * (num_text_sections + 1)) as u32;
loop {
let eti_init_info = read_eti_init_info(buf, dol.as_ref(), addr)?;
if validate_eti_init_info(
dol.as_ref(),
&eti_init_info,
dol_section,
dol_section_end,
&rom_sections,
)? {
log::debug!("Found _eti_init_info @ {addr:#010X}");
break addr;
}
addr += 4;
if addr > dol_section_end - ETI_INIT_INFO_SIZE as u32 {
continue 'outer;
}
}
};
let eti_init_info_end = {
let mut addr = eti_init_info_addr;
loop {
let eti_init_info = read_eti_init_info(buf, dol.as_ref(), addr)?;
addr += 16;
if eti_init_info.is_zero() {
break;
}
if addr > dol_section_end - ETI_INIT_INFO_SIZE as u32 {
bail!(
"Failed to locate _eti_init_info end (start @ {:#010X})",
eti_init_info_addr
);
}
if !validate_eti_init_info(
dol.as_ref(),
&eti_init_info,
dol_section,
dol_section_end,
&rom_sections,
)? {
bail!("Invalid _eti_init_info entry: {:#010X?}", eti_init_info);
}
for addr in (eti_init_info.eti_start..eti_init_info.eti_end).step_by(12) {
let eti_entry = read_eti_entry(buf, dol.as_ref(), addr)?;
let entry_section =
dol.section_by_address(eti_entry.extab_addr).ok_or_else(|| {
anyhow!(
"Failed to locate section for extab address {:#010X}",
eti_entry.extab_addr
)
})?;
if let Some(extab_section) = extab_section {
ensure!(
entry_section.index == extab_section,
"Mismatched sections for extabindex entries: {} != {}",
entry_section.index,
extab_section
);
} else {
extab_section = Some(entry_section.index);
}
eti_entries.push(eti_entry);
}
}
log::debug!("Found _eti_init_info end @ {addr:#010X}");
addr
};
eti_init_info_range = Some((eti_init_info_addr, eti_init_info_end));
extabindex_section = Some(dol_section.index);
break;
}
if eti_init_info_range.is_none() {
log::debug!("Failed to locate _eti_init_info");
}
// Add text and data sections
let mut sections = vec![];
for dol_section in dol.sections().iter() {
// We'll split .bss later
if dol_section.kind == DolSectionKind::Bss && dol.has_unified_bss() {
continue;
}
if is_bootstage {
// Create sections based on _rom_copy_info
for (idx, (&addr, &size)) in rom_sections.iter().enumerate() {
let dol_section = dol
.section_by_address(addr)
.ok_or_else(|| anyhow!("Failed to locate section for ROM address {addr:#010X}"))?;
let data = dol.virtual_data_at(buf, addr, size)?;
let (name, kind, known) = match dol_section.index {
idx if idx == init_section.index => (".init".to_string(), ObjSectionKind::Code, true),
idx if Some(idx) == extab_section => {
("extab".to_string(), ObjSectionKind::ReadOnlyData, true)
}
idx if Some(idx) == extabindex_section => {
("extabindex".to_string(), ObjSectionKind::ReadOnlyData, true)
}
_ if num_text_sections == 2 && dol_section.kind == DolSectionKind::Text => {
(".text".to_string(), ObjSectionKind::Code, true)
}
idx => match dol_section.kind {
DolSectionKind::Text => (format!(".text{idx}"), ObjSectionKind::Code, false),
DolSectionKind::Data => (format!(".data{idx}"), ObjSectionKind::Data, false),
DolSectionKind::Bss => (format!(".bss{idx}"), ObjSectionKind::Bss, false),
},
};
let (size, data): (u32, &[u8]) = if kind == ObjSectionKind::Bss {
(dol_section.size, &[])
} else {
// Use section size from _rom_copy_info
let size = match rom_sections.get(&dol_section.address) {
Some(&size) => size,
None => {
if !rom_sections.is_empty() {
log::warn!(
"Section {} ({:#010X}) doesn't exist in _rom_copy_info",
dol_section.index,
dol_section.address
);
}
dol_section.size
let (name, kind, known) = if entry_point >= addr && entry_point < addr + size {
(".init".to_string(), ObjSectionKind::Code, true)
} else {
match dol_section.kind {
DolSectionKind::Text => (format!(".text{idx}"), ObjSectionKind::Code, false),
DolSectionKind::Data => (format!(".data{idx}"), ObjSectionKind::Data, false),
DolSectionKind::Bss => (format!(".bss{idx}"), ObjSectionKind::Bss, false),
}
};
(size, dol.virtual_data_at(buf, dol_section.address, size)?)
};
sections.push(ObjSection {
name,
kind,
address: dol_section.address as u64,
size: size as u64,
data: data.to_vec(),
align: 0,
elf_index: 0,
relocations: Default::default(),
virtual_address: Some(dol_section.address as u64),
file_offset: dol_section.file_offset as u64,
section_known: known,
splits: Default::default(),
});
let file_offset = addr - dol_section.address + dol_section.file_offset;
sections.push(ObjSection {
name,
kind,
address: addr as u64,
size: size as u64,
data: data.to_vec(),
align: 0,
elf_index: 0,
relocations: Default::default(),
virtual_address: Some(addr as u64),
file_offset: file_offset as u64,
section_known: known,
splits: Default::default(),
});
}
} else {
for dol_section in dol.sections().iter() {
// We'll split .bss later
if dol_section.kind == DolSectionKind::Bss && dol.has_unified_bss() {
continue;
}
let (name, kind, known) = match dol_section.index {
idx if idx == init_section.index => {
(".init".to_string(), ObjSectionKind::Code, true)
}
idx => match dol_section.kind {
DolSectionKind::Text => (format!(".text{idx}"), ObjSectionKind::Code, false),
DolSectionKind::Data => (format!(".data{idx}"), ObjSectionKind::Data, false),
DolSectionKind::Bss => (format!(".bss{idx}"), ObjSectionKind::Bss, false),
},
};
let (size, data): (u32, &[u8]) = if kind == ObjSectionKind::Bss {
(dol_section.size, &[])
} else {
// Use section size from _rom_copy_info
let size = match rom_sections.get(&dol_section.address) {
Some(&size) => size,
None => {
if !rom_sections.is_empty() {
log::warn!(
"Section {} ({:#010X}) doesn't exist in _rom_copy_info",
dol_section.index,
dol_section.address
);
}
dol_section.size
}
};
(size, dol.virtual_data_at(buf, dol_section.address, size)?)
};
sections.push(ObjSection {
name,
kind,
address: dol_section.address as u64,
size: size as u64,
data: data.to_vec(),
align: 0,
elf_index: 0,
relocations: Default::default(),
virtual_address: Some(dol_section.address as u64),
file_offset: dol_section.file_offset as u64,
section_known: known,
splits: Default::default(),
});
}
}
if dol.has_unified_bss() {
// Add BSS sections from _bss_init_info
for (idx, (&addr, &size)) in bss_sections.iter().enumerate() {
ensure!(
addr >= bss_section.address
&& addr < bss_section.address + bss_section.size
&& addr + size <= bss_section.address + bss_section.size,
is_bootstage
|| (addr >= bss_section.address
&& addr < bss_section.address + bss_section.size
&& addr + size <= bss_section.address + bss_section.size),
"Invalid BSS range {:#010X}-{:#010X} (DOL BSS: {:#010X}-{:#010X})",
addr,
addr + size,
@ -515,8 +480,8 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
vec![],
temp_sections,
);
obj.entry = Some(dol.entry_point() as u64);
let bss_sections = locate_bss_memsets(&mut obj)?;
obj.entry = Some(entry_point as u64);
let bss_sections = locate_bss_memsets(&obj)?;
match bss_sections.len() {
0 => log::warn!("Failed to locate BSS sections"),
2 => {
@ -559,24 +524,10 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
}
// Apply section indices
let mut init_section_index: Option<SectionIndex> = None;
for (idx, section) in sections.iter_mut().enumerate() {
let idx = idx as SectionIndex;
match section.name.as_str() {
".init" => {
init_section_index = Some(idx);
}
"extab" => {
extab_section = Some(idx);
}
"extabindex" => {
extabindex_section = Some(idx);
}
_ => {}
}
// Assume the original ELF section index is +1
// ELF files start with a NULL section
section.elf_index = idx + 1;
section.elf_index = (idx as SectionIndex) + 1;
}
// Guess section alignment
@ -588,12 +539,13 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
align = (align + 1).next_power_of_two();
}
if align_up(last_section_end, align) != section_start {
bail!(
log::warn!(
"Couldn't determine alignment for section '{}' ({:#010X} -> {:#010X})",
section.name,
last_section_end,
section_start
);
align = 32;
}
last_section_end = section_start + section.size as u32;
section.align = align as u64;
@ -607,17 +559,18 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
vec![],
sections,
);
obj.entry = Some(dol.entry_point() as u64);
obj.entry = Some(entry_point as u64);
// Generate _rom_copy_info symbol
if let (Some(rom_copy_info_addr), Some(rom_copy_info_end)) =
(rom_copy_info_addr, rom_copy_info_end)
{
let (section_index, _) = obj.sections.at_address(rom_copy_info_addr)?;
obj.add_symbol(
ObjSymbol {
name: "_rom_copy_info".to_string(),
address: rom_copy_info_addr as u64,
section: init_section_index,
section: Some(section_index),
size: (rom_copy_info_end - rom_copy_info_addr) as u64,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
@ -632,11 +585,12 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
if let (Some(bss_init_info_addr), Some(bss_init_info_end)) =
(bss_init_info_addr, bss_init_info_end)
{
let (section_index, _) = obj.sections.at_address(bss_init_info_addr)?;
obj.add_symbol(
ObjSymbol {
name: "_bss_init_info".to_string(),
address: bss_init_info_addr as u64,
section: init_section_index,
section: Some(section_index),
size: (bss_init_info_end - bss_init_info_addr) as u64,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
@ -647,150 +601,24 @@ pub fn process_dol(buf: &[u8], name: &str) -> Result<ObjInfo> {
)?;
}
// Generate _eti_init_info symbol
if let Some((eti_init_info_addr, eti_init_info_end)) = eti_init_info_range {
obj.add_symbol(
ObjSymbol {
name: "_eti_init_info".to_string(),
address: eti_init_info_addr as u64,
section: extabindex_section,
size: (eti_init_info_end - eti_init_info_addr) as u64,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
kind: ObjSymbolKind::Object,
..Default::default()
},
true,
)?;
// Locate .text section
if let Err(e) = locate_text(&mut obj) {
log::warn!("Failed to locate .text section: {:?}", e);
}
// Generate symbols for extab & extabindex entries
if let (Some(extabindex_section_index), Some(extab_section_index)) =
(extabindex_section, extab_section)
{
let extab_section = &obj.sections[extab_section_index];
let extab_section_address = extab_section.address;
let extab_section_size = extab_section.size;
for entry in &eti_entries {
// Add functions from extabindex entries as known function bounds
let (section_index, _) = obj.sections.at_address(entry.function).map_err(|_| {
anyhow!(
"Failed to locate section for function {:#010X} (referenced from extabindex entry {:#010X})",
entry.function,
entry.address,
)
})?;
let addr = SectionAddress::new(section_index, entry.function);
if let Some(Some(old_value)) =
obj.known_functions.insert(addr, Some(entry.function_size))
{
if old_value != entry.function_size {
log::warn!(
"Conflicting sizes for {:#010X}: {:#X} != {:#X}",
entry.function,
entry.function_size,
old_value
);
}
}
obj.add_symbol(
ObjSymbol {
name: format!("@eti_{:08X}", entry.address),
address: entry.address as u64,
section: Some(extabindex_section_index),
size: 12,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Local | ObjSymbolFlags::Hidden),
kind: ObjSymbolKind::Object,
..Default::default()
},
false,
)?;
}
let mut entry_iter = eti_entries.iter().peekable();
loop {
let (addr, size) = match (entry_iter.next(), entry_iter.peek()) {
(Some(a), Some(&b)) => (a.extab_addr, b.extab_addr - a.extab_addr),
(Some(a), None) => (
a.extab_addr,
(extab_section_address + extab_section_size) as u32 - a.extab_addr,
),
_ => break,
};
obj.add_symbol(
ObjSymbol {
name: format!("@etb_{addr:08X}"),
address: addr as u64,
section: Some(extab_section_index),
size: size as u64,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Local | ObjSymbolFlags::Hidden),
kind: ObjSymbolKind::Object,
..Default::default()
},
false,
)?;
}
// Locate extab and extabindex sections
if let Err(e) = locate_extab_extabindex(&mut obj) {
log::warn!("Failed to locate extab/etabindex: {:?}", e);
}
// Add .ctors and .dtors functions to known functions if they exist
for (_, section) in obj.sections.iter() {
if section.size & 3 != 0 {
continue;
}
let mut entries = vec![];
let mut current_addr = section.address as u32;
for chunk in section.data.chunks_exact(4) {
let addr = u32::from_be_bytes(chunk.try_into()?);
if addr == 0 || addr & 3 != 0 {
break;
}
let Ok((section_index, section)) = obj.sections.at_address(addr) else {
break;
};
if section.kind != ObjSectionKind::Code {
break;
}
entries.push(SectionAddress::new(section_index, addr));
current_addr += 4;
}
// .ctors and .dtors end with a null pointer
if current_addr != (section.address + section.size) as u32 - 4
|| section.data_range(current_addr, 0)?.iter().any(|&b| b != 0)
{
continue;
}
obj.known_functions.extend(entries.into_iter().map(|addr| (addr, None)));
// Locate .ctors and .dtors sections
if let Err(e) = locate_ctors_dtors(&mut obj) {
log::warn!("Failed to locate .ctors/.dtors: {:?}", e);
}
// Locate _SDA2_BASE_ & _SDA_BASE_
match locate_sda_bases(&mut obj) {
Ok(true) => {
let sda2_base = obj.sda2_base.unwrap();
let sda_base = obj.sda_base.unwrap();
obj.add_symbol(
ObjSymbol {
name: "_SDA2_BASE_".to_string(),
address: sda2_base as u64,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
..Default::default()
},
true,
)?;
obj.add_symbol(
ObjSymbol {
name: "_SDA_BASE_".to_string(),
address: sda_base as u64,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
..Default::default()
},
true,
)?;
}
Ok(true) => {}
Ok(false) => {
log::warn!("Unable to locate SDA bases");
}
@ -830,39 +658,42 @@ struct EtiEntry {
extab_addr: u32,
}
fn read_eti_init_info(buf: &[u8], dol: &dyn DolLike, addr: u32) -> Result<EtiInitInfo> {
let eti_start = read_u32(buf, dol, addr)?;
let eti_end = read_u32(buf, dol, addr + 4)?;
let code_start = read_u32(buf, dol, addr + 8)?;
let code_size = read_u32(buf, dol, addr + 12)?;
fn read_u32_obj(obj: &ObjInfo, addr: u32) -> Result<u32> {
let (_, section) = obj.sections.at_address(addr)?;
let data = section.data_range(addr, addr + 4)?;
Ok(u32::from_be_bytes(data.try_into()?))
}
fn read_eti_init_info(obj: &ObjInfo, addr: u32) -> Result<EtiInitInfo> {
let eti_start = read_u32_obj(obj, addr)?;
let eti_end = read_u32_obj(obj, addr + 4)?;
let code_start = read_u32_obj(obj, addr + 8)?;
let code_size = read_u32_obj(obj, addr + 12)?;
Ok(EtiInitInfo { eti_start, eti_end, code_start, code_size })
}
fn read_eti_entry(buf: &[u8], dol: &dyn DolLike, address: u32) -> Result<EtiEntry> {
let function = read_u32(buf, dol, address)?;
let function_size = read_u32(buf, dol, address + 4)?;
let extab_addr = read_u32(buf, dol, address + 8)?;
fn read_eti_entry(obj: &ObjInfo, address: u32) -> Result<EtiEntry> {
let function = read_u32_obj(obj, address)?;
let function_size = read_u32_obj(obj, address + 4)?;
let extab_addr = read_u32_obj(obj, address + 8)?;
Ok(EtiEntry { address, function, function_size, extab_addr })
}
fn validate_eti_init_info(
dol: &dyn DolLike,
obj: &ObjInfo,
eti_init_info: &EtiInitInfo,
eti_section: &DolSection,
eti_section_addr: u32,
eti_section_end: u32,
rom_sections: &BTreeMap<u32, u32>,
) -> Result<bool> {
if eti_init_info.eti_start >= eti_section.address
if eti_init_info.eti_start >= eti_section_addr
&& eti_init_info.eti_start < eti_section_end
&& eti_init_info.eti_end >= eti_section.address
&& eti_init_info.eti_end >= eti_section_addr
&& eti_init_info.eti_end < eti_section_end
{
if let Some(code_section) = dol.section_by_address(eti_init_info.code_start) {
let code_section_size = match rom_sections.get(&code_section.address) {
Some(&size) => size,
None => code_section.size,
};
if eti_init_info.code_size <= code_section_size {
if let Ok((_, code_section)) = obj.sections.at_address(eti_init_info.code_start) {
if eti_init_info.code_start + eti_init_info.code_size
<= (code_section.address + code_section.size) as u32
{
return Ok(true);
}
}
@ -945,3 +776,261 @@ where T: Write + ?Sized {
}
Ok(())
}
fn rename_section(
obj: &mut ObjInfo,
index: SectionIndex,
name: &str,
kind: ObjSectionKind,
) -> Result<()> {
let section = &mut obj.sections[index];
ensure!(
!section.section_known,
"Section {} is already known, cannot rename to {}",
section.name,
name
);
section.name = name.to_string();
section.kind = kind;
section.section_known = true;
Ok(())
}
fn locate_text(obj: &mut ObjInfo) -> Result<()> {
// If we didn't find .text, infer from branch targets from .init section
if !obj.sections.iter().any(|(_, s)| s.section_known && s.name == ".text") {
let entry_point = obj.entry.ok_or_else(|| anyhow!("Missing entry point"))? as u32;
let (entry_section_index, _) = obj.sections.at_address(entry_point)?;
let entry = SectionAddress::new(entry_section_index, entry_point);
let branch_targets = locate_cross_section_branch_targets(obj, entry)?;
let mut section_indexes = branch_targets.iter().map(|s| s.section).dedup();
let text_section_index =
section_indexes.next().ok_or_else(|| anyhow!("Failed to locate .text section"))?;
ensure!(section_indexes.next().is_none(), "Multiple possible .text sections found");
rename_section(obj, text_section_index, ".text", ObjSectionKind::Code)?;
}
Ok(())
}
fn locate_ctors_dtors(obj: &mut ObjInfo) -> Result<()> {
// Add .ctors and .dtors functions to known functions if they exist
let mut ctors_section_index = None;
let mut dtors_section_index = None;
for (section_index, section) in obj.sections.iter() {
if section.size & 3 != 0 {
continue;
}
let mut entries = vec![];
let mut current_addr = section.address as u32;
for chunk in section.data.chunks_exact(4) {
let addr = u32::from_be_bytes(chunk.try_into()?);
if addr == 0 || addr & 3 != 0 {
break;
}
let Ok((section_index, section)) = obj.sections.at_address(addr) else {
break;
};
if section.kind != ObjSectionKind::Code {
break;
}
entries.push(SectionAddress::new(section_index, addr));
current_addr += 4;
}
// .ctors and .dtors end with a null pointer
if current_addr != (section.address + section.size) as u32 - 4
|| section.data_range(current_addr, 0)?.iter().any(|&b| b != 0)
{
continue;
}
obj.known_functions.extend(entries.into_iter().map(|addr| (addr, None)));
match (ctors_section_index, dtors_section_index) {
(None, None) => ctors_section_index = Some(section_index),
(Some(_), None) => dtors_section_index = Some(section_index),
_ => log::warn!("Multiple possible .ctors/.dtors sections found"),
}
}
if let Some(ctors_section) = ctors_section_index {
rename_section(obj, ctors_section, ".ctors", ObjSectionKind::ReadOnlyData)?;
}
if let Some(dtors_section) = dtors_section_index {
rename_section(obj, dtors_section, ".dtors", ObjSectionKind::ReadOnlyData)?;
}
Ok(())
}
fn locate_extab_extabindex(obj: &mut ObjInfo) -> Result<()> {
let num_text_sections =
obj.sections.iter().filter(|(_, section)| section.kind == ObjSectionKind::Code).count();
let mut eti_entries: Vec<EtiEntry> = Vec::new();
let mut eti_init_info_range: Option<(u32, u32)> = None;
let mut extab_section_index: Option<SectionIndex> = None;
let mut extabindex_section_index: Option<SectionIndex> = None;
'outer: for (dol_section_index, dol_section) in
obj.sections.iter().filter(|(_, section)| section.kind == ObjSectionKind::Data)
{
let dol_section_start = dol_section.address as u32;
let dol_section_end = dol_section_start + dol_section.size as u32;
let eti_init_info_addr = {
let mut addr = dol_section_end - (ETI_INIT_INFO_SIZE * (num_text_sections + 1)) as u32;
loop {
let eti_init_info = read_eti_init_info(obj, addr)?;
if validate_eti_init_info(obj, &eti_init_info, dol_section_start, dol_section_end)?
{
log::debug!("Found _eti_init_info @ {addr:#010X}");
break addr;
}
addr += 4;
if addr > dol_section_end - ETI_INIT_INFO_SIZE as u32 {
continue 'outer;
}
}
};
let eti_init_info_end = {
let mut addr = eti_init_info_addr;
loop {
let eti_init_info = read_eti_init_info(obj, addr)?;
addr += 16;
if eti_init_info.is_zero() {
break;
}
if addr > dol_section_end - ETI_INIT_INFO_SIZE as u32 {
bail!(
"Failed to locate _eti_init_info end (start @ {:#010X})",
eti_init_info_addr
);
}
if !validate_eti_init_info(obj, &eti_init_info, dol_section_start, dol_section_end)?
{
bail!("Invalid _eti_init_info entry: {:#010X?}", eti_init_info);
}
for addr in (eti_init_info.eti_start..eti_init_info.eti_end).step_by(12) {
let eti_entry = read_eti_entry(obj, addr)?;
let (entry_section_index, _) =
obj.sections.at_address(eti_entry.extab_addr).with_context(|| {
format!(
"Failed to locate section for extab address {:#010X}",
eti_entry.extab_addr
)
})?;
if let Some(extab_section) = extab_section_index {
ensure!(
entry_section_index == extab_section,
"Mismatched sections for extabindex entries: {} != {}",
entry_section_index,
extab_section
);
} else {
extab_section_index = Some(entry_section_index);
}
eti_entries.push(eti_entry);
}
}
log::debug!("Found _eti_init_info end @ {addr:#010X}");
addr
};
eti_init_info_range = Some((eti_init_info_addr, eti_init_info_end));
extabindex_section_index = Some(dol_section_index);
break;
}
if eti_init_info_range.is_none() {
log::debug!("Failed to locate _eti_init_info");
}
if let Some(extab_section_index) = extab_section_index {
rename_section(obj, extab_section_index, "extab", ObjSectionKind::ReadOnlyData)?;
}
if let Some(extabindex_section_index) = extabindex_section_index {
rename_section(obj, extabindex_section_index, "extabindex", ObjSectionKind::ReadOnlyData)?;
}
// Generate _eti_init_info symbol
if let Some((eti_init_info_addr, eti_init_info_end)) = eti_init_info_range {
obj.add_symbol(
ObjSymbol {
name: "_eti_init_info".to_string(),
address: eti_init_info_addr as u64,
section: extabindex_section_index,
size: (eti_init_info_end - eti_init_info_addr) as u64,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Global.into()),
kind: ObjSymbolKind::Object,
..Default::default()
},
true,
)?;
}
// Generate symbols for extab & extabindex entries
if let (Some(extabindex_section_index), Some(extab_section_index)) =
(extabindex_section_index, extab_section_index)
{
let extab_section = &obj.sections[extab_section_index];
let extab_section_address = extab_section.address;
let extab_section_size = extab_section.size;
for entry in &eti_entries {
// Add functions from extabindex entries as known function bounds
let (section_index, _) = obj.sections.at_address(entry.function).map_err(|_| {
anyhow!(
"Failed to locate section for function {:#010X} (referenced from extabindex entry {:#010X})",
entry.function,
entry.address,
)
})?;
let addr = SectionAddress::new(section_index, entry.function);
if let Some(Some(old_value)) =
obj.known_functions.insert(addr, Some(entry.function_size))
{
if old_value != entry.function_size {
log::warn!(
"Conflicting sizes for {:#010X}: {:#X} != {:#X}",
entry.function,
entry.function_size,
old_value
);
}
}
obj.add_symbol(
ObjSymbol {
name: format!("@eti_{:08X}", entry.address),
address: entry.address as u64,
section: Some(extabindex_section_index),
size: 12,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Local | ObjSymbolFlags::Hidden),
kind: ObjSymbolKind::Object,
..Default::default()
},
false,
)?;
}
let mut entry_iter = eti_entries.iter().peekable();
loop {
let (addr, size) = match (entry_iter.next(), entry_iter.peek()) {
(Some(a), Some(&b)) => (a.extab_addr, b.extab_addr - a.extab_addr),
(Some(a), None) => (
a.extab_addr,
(extab_section_address + extab_section_size) as u32 - a.extab_addr,
),
_ => break,
};
obj.add_symbol(
ObjSymbol {
name: format!("@etb_{addr:08X}"),
address: addr as u64,
section: Some(extab_section_index),
size: size as u64,
size_known: true,
flags: ObjSymbolFlagSet(ObjSymbolFlags::Local | ObjSymbolFlags::Hidden),
kind: ObjSymbolKind::Object,
..Default::default()
},
false,
)?;
}
}
Ok(())
}

View File

@ -358,6 +358,7 @@ pub struct Tag {
pub kind: TagKind,
pub is_erased: bool, // Tag was deleted but has been reconstructed
pub is_erased_root: bool, // Tag is erased and is the root of a tree of erased tags
pub data_endian: Endian, // Endianness of the tag data (could be different from the address endianness for erased tags)
pub attributes: Vec<Attribute>,
}
@ -554,6 +555,7 @@ where
kind: TagKind::Padding,
is_erased,
is_erased_root: false,
data_endian,
attributes: Vec::new(),
});
return Ok(tags);
@ -563,26 +565,42 @@ where
let tag = TagKind::try_from(tag_num).context("Unknown DWARF tag type")?;
if tag == TagKind::Padding {
if include_erased {
// Erased entries that have become padding are little-endian, and we
// have to guess the length and tag of the first entry. We assume
// the entry is either a variable or a function, and read until we
// find the high_pc attribute. Only MwGlobalRef will follow, and
// these are unlikely to be confused with the length of the next
// entry.
// Erased entries that have become padding could be either
// little-endian or big-endian, and we have to guess the length and
// tag of the first entry. We assume the entry is either a variable
// or a function, and read until we find the high_pc attribute. Only
// MwGlobalRef will follow, and these are unlikely to be confused
// with the length of the next entry.
let mut attributes = Vec::new();
let mut is_function = false;
// Guess endianness based on first attribute
let data_endian = if is_erased {
data_endian
} else {
// Peek next two bytes
let mut buf = [0u8; 2];
reader.read_exact(&mut buf)?;
let attr_tag = u16::from_reader(&mut Cursor::new(&buf), data_endian)?;
reader.seek(SeekFrom::Current(-2))?;
match AttributeKind::try_from(attr_tag) {
Ok(_) => data_endian,
Err(_) => data_endian.flip(),
}
};
while reader.stream_position()? < position + size as u64 {
// Peek next two bytes
let mut buf = [0u8; 2];
reader.read_exact(&mut buf)?;
let attr_tag = u16::from_reader(&mut Cursor::new(&buf), Endian::Little)?;
let attr_tag = u16::from_reader(&mut Cursor::new(&buf), data_endian)?;
reader.seek(SeekFrom::Current(-2))?;
if is_function && attr_tag != AttributeKind::MwGlobalRef as u16 {
break;
}
let attr = read_attribute(reader, Endian::Little, addr_endian)?;
let attr = read_attribute(reader, data_endian, addr_endian)?;
if attr.kind == AttributeKind::HighPc {
is_function = true;
}
@ -594,12 +612,13 @@ where
kind,
is_erased: true,
is_erased_root: true,
data_endian,
attributes,
});
// Read the rest of the tags
while reader.stream_position()? < position + size as u64 {
for tag in read_tags(reader, Endian::Little, addr_endian, include_erased, true)? {
for tag in read_tags(reader, data_endian, addr_endian, include_erased, true)? {
tags.push(tag);
}
}
@ -616,6 +635,7 @@ where
kind: tag,
is_erased,
is_erased_root: false,
data_endian,
attributes,
});
}
@ -2028,9 +2048,9 @@ fn process_array_tag(info: &DwarfInfo, tag: &Tag) -> Result<ArrayType> {
(AttributeKind::Sibling, _) => {}
(AttributeKind::SubscrData, AttributeValue::Block(data)) => {
subscr_data =
Some(process_array_subscript_data(data, info.e, tag.is_erased).with_context(
|| format!("Failed to process SubscrData for tag: {tag:?}"),
)?)
Some(process_array_subscript_data(data, info.e).with_context(|| {
format!("Failed to process SubscrData for tag: {tag:?}")
})?)
}
(AttributeKind::Ordering, val) => match val {
AttributeValue::Data2(d2) => {
@ -2056,11 +2076,7 @@ fn process_array_tag(info: &DwarfInfo, tag: &Tag) -> Result<ArrayType> {
Ok(ArrayType { element_type: Box::from(element_type), dimensions })
}
fn process_array_subscript_data(
data: &[u8],
e: Endian,
is_erased: bool,
) -> Result<(Type, Vec<ArrayDimension>)> {
fn process_array_subscript_data(data: &[u8], e: Endian) -> Result<(Type, Vec<ArrayDimension>)> {
let mut element_type = None;
let mut dimensions = Vec::new();
let mut data = data;
@ -2101,8 +2117,7 @@ fn process_array_subscript_data(
SubscriptFormat::ElementType => {
let mut cursor = Cursor::new(data);
// TODO: is this the right endianness to use for erased tags?
let type_attr =
read_attribute(&mut cursor, if is_erased { Endian::Little } else { e }, e)?;
let type_attr = read_attribute(&mut cursor, e, e)?;
element_type = Some(process_type(&type_attr, e)?);
data = &data[cursor.position() as usize..];
}
@ -2127,9 +2142,16 @@ fn process_enumeration_tag(info: &DwarfInfo, tag: &Tag) -> Result<EnumerationTyp
(AttributeKind::ElementList, AttributeValue::Block(data)) => {
let mut cursor = Cursor::new(data);
while cursor.position() < data.len() as u64 {
let value = i32::from_reader(&mut cursor, info.e)?;
let value = match byte_size {
Some(1) => Some(i8::from_reader(&mut cursor, info.e)? as i32),
Some(2) => Some(i16::from_reader(&mut cursor, info.e)? as i32),
Some(4) => Some(i32::from_reader(&mut cursor, info.e)?),
_ => None,
};
let name = read_string(&mut cursor)?;
members.push(EnumerationMember { name, value });
if let Some(value) = value {
members.push(EnumerationMember { name, value });
}
}
}
(AttributeKind::Member, &AttributeValue::Reference(_key)) => {
@ -2456,10 +2478,7 @@ fn process_subroutine_parameter_tag(info: &DwarfInfo, tag: &Tag) -> Result<Subro
) => kind = Some(process_type(attr, info.e)?),
(AttributeKind::Location, AttributeValue::Block(block)) => {
if !block.is_empty() {
location = Some(process_variable_location(
block,
if tag.is_erased { Endian::Little } else { info.e },
)?);
location = Some(process_variable_location(block, tag.data_endian)?);
}
}
(AttributeKind::MwDwarf2Location, AttributeValue::Block(_block)) => {
@ -2514,10 +2533,7 @@ fn process_local_variable_tag(info: &DwarfInfo, tag: &Tag) -> Result<SubroutineV
) => kind = Some(process_type(attr, info.e)?),
(AttributeKind::Location, AttributeValue::Block(block)) => {
if !block.is_empty() {
location = Some(process_variable_location(
block,
if tag.is_erased { Endian::Little } else { info.e },
)?);
location = Some(process_variable_location(block, tag.data_endian)?);
}
}
(AttributeKind::MwDwarf2Location, AttributeValue::Block(_block)) => {

View File

@ -3,7 +3,7 @@ use itertools::Itertools;
use crate::obj::ObjInfo;
pub fn clean_extab(obj: &mut ObjInfo) -> Result<usize> {
pub fn clean_extab(obj: &mut ObjInfo, mut padding: impl Iterator<Item = u8>) -> Result<usize> {
let (extab_section_index, extab_section) = obj
.sections
.iter_mut()
@ -27,19 +27,25 @@ pub fn clean_extab(obj: &mut ObjInfo) -> Result<usize> {
})?;
let mut updated = false;
for action in &decoded.exception_actions {
let section_offset =
(symbol.address - extab_section.address) as usize + action.action_offset as usize;
let clean_data = action.get_exaction_bytes(true);
let orig_data =
&mut extab_section.data[section_offset..section_offset + clean_data.len()];
if orig_data != clean_data {
updated = true;
// Check if the current action has padding
if let Some(padding_offset) = action.get_struct_padding_offset() {
let index = padding_offset as usize;
let section_offset = (symbol.address - extab_section.address) as usize
+ action.action_offset as usize;
let mut clean_data: Vec<u8> = action.get_exaction_bytes(false);
// Write the two padding bytes
clean_data[index] = padding.next().unwrap_or(0);
clean_data[index + 1] = padding.next().unwrap_or(0);
let orig_data =
&mut extab_section.data[section_offset..section_offset + clean_data.len()];
orig_data.copy_from_slice(&clean_data);
updated = true;
}
}
if updated {
tracing::debug!(
"Removed uninitialized bytes in {} (extab {:#010X}..{:#010X})",
"Replaced uninitialized bytes in {} (extab {:#010X}..{:#010X})",
symbol.name,
symbol.address,
symbol.address + symbol.size

View File

@ -144,7 +144,7 @@ pub fn touch(path: &Utf8NativePath) -> io::Result<()> {
}
}
pub fn decompress_if_needed(buf: &[u8]) -> Result<Bytes> {
pub fn decompress_if_needed(buf: &[u8]) -> Result<Bytes<'_>> {
if buf.len() > 4 {
match *array_ref!(buf, 0, 4) {
YAZ0_MAGIC => return decompress_yaz0(buf).map(Bytes::Owned),

View File

@ -200,7 +200,7 @@ impl<'a> RarcView<'a> {
}
/// Get a string from the string table at the given offset.
pub fn get_string(&self, offset: u32) -> Result<Cow<str>, String> {
pub fn get_string(&self, offset: u32) -> Result<Cow<'_, str>, String> {
let name_buf = self.string_table.get(offset as usize..).ok_or_else(|| {
format!(
"RARC: name offset {} out of bounds (string table size: {})",

View File

@ -20,6 +20,15 @@ impl From<object::Endianness> for Endian {
}
}
impl Endian {
pub fn flip(self) -> Self {
match self {
Endian::Big => Endian::Little,
Endian::Little => Endian::Big,
}
}
}
pub const DYNAMIC_SIZE: usize = 0;
pub const fn struct_size<const N: usize>(fields: [usize; N]) -> usize {

View File

@ -992,7 +992,11 @@ fn resolve_link_order(obj: &ObjInfo) -> Result<Vec<ObjUnit>> {
/// Split an object into multiple relocatable objects.
#[instrument(level = "debug", skip(obj))]
pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo>> {
pub fn split_obj(
obj: &ObjInfo,
module_name: Option<&str>,
globalize_symbols: bool,
) -> Result<Vec<ObjInfo>> {
let mut objects: Vec<ObjInfo> = vec![];
let mut object_symbols: Vec<Vec<Option<SymbolIndex>>> = vec![];
let mut name_to_obj: HashMap<String, usize> = HashMap::new();
@ -1215,7 +1219,7 @@ pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo
}
// Update relocations
let mut globalize_symbols = vec![];
let mut symbols_to_globalize = vec![];
for (obj_idx, out_obj) in objects.iter_mut().enumerate() {
let symbol_idxs = &mut object_symbols[obj_idx];
for (_section_index, section) in out_obj.sections.iter_mut() {
@ -1231,7 +1235,7 @@ pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo
// If the symbol is local, we'll upgrade the scope to global
// and rename it to avoid conflicts
if target_sym.flags.is_local() {
if globalize_symbols && target_sym.flags.is_local() {
let address_str = if obj.module_id == 0 {
format!("{:08X}", target_sym.address)
} else if let Some(section_index) = target_sym.section {
@ -1250,7 +1254,7 @@ pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo
} else {
format!("{}_{}", target_sym.name, address_str)
};
globalize_symbols.push((reloc.target_symbol, new_name));
symbols_to_globalize.push((reloc.target_symbol, new_name));
}
symbol_idxs[reloc.target_symbol as usize] = Some(out_sym_idx);
@ -1295,16 +1299,18 @@ pub fn split_obj(obj: &ObjInfo, module_name: Option<&str>) -> Result<Vec<ObjInfo
}
// Upgrade local symbols to global if necessary
for (obj, symbol_map) in objects.iter_mut().zip(&object_symbols) {
for (globalize_idx, new_name) in &globalize_symbols {
if let Some(symbol_idx) = symbol_map[*globalize_idx as usize] {
let mut symbol = obj.symbols[symbol_idx].clone();
symbol.name.clone_from(new_name);
if symbol.flags.is_local() {
log::debug!("Globalizing {} in {}", symbol.name, obj.name);
symbol.flags.set_scope(ObjSymbolScope::Global);
if globalize_symbols {
for (obj, symbol_map) in objects.iter_mut().zip(&object_symbols) {
for (globalize_idx, new_name) in &symbols_to_globalize {
if let Some(symbol_idx) = symbol_map[*globalize_idx as usize] {
let mut symbol = obj.symbols[symbol_idx].clone();
symbol.name.clone_from(new_name);
if symbol.flags.is_local() {
log::debug!("Globalizing {} in {}", symbol.name, obj.name);
symbol.flags.set_scope(ObjSymbolScope::Global);
}
obj.symbols.replace(symbol_idx, symbol)?;
}
obj.symbols.replace(symbol_idx, symbol)?;
}
}
}

View File

@ -121,10 +121,10 @@ impl<'a> U8View<'a> {
}
/// Iterate over the nodes in the U8 archive.
pub fn iter(&self) -> U8Iter { U8Iter { inner: self, idx: 1 } }
pub fn iter(&self) -> U8Iter<'_> { U8Iter { inner: self, idx: 1 } }
/// Get the name of a node.
pub fn get_name(&self, node: U8Node) -> Result<Cow<str>, String> {
pub fn get_name(&self, node: U8Node) -> Result<Cow<'_, str>, String> {
let name_buf = self.string_table.get(node.name_offset() as usize..).ok_or_else(|| {
format!(
"U8: name offset {} out of bounds (string table size: {})",

View File

@ -47,7 +47,7 @@ impl DiscFs {
Ok(Self { disc, base, meta, mtime })
}
fn find(&self, path: &Utf8UnixPath) -> VfsResult<DiscNode> {
fn find(&self, path: &Utf8UnixPath) -> VfsResult<DiscNode<'_>> {
let path = path.as_str().trim_matches('/');
let mut split = path.split('/');
let mut segment = next_non_empty(&mut split);

View File

@ -13,7 +13,7 @@ pub struct RarcFs {
impl RarcFs {
pub fn new(file: Box<dyn VfsFile>) -> io::Result<Self> { Ok(Self { file }) }
fn view(&mut self) -> io::Result<RarcView> {
fn view(&mut self) -> io::Result<RarcView<'_>> {
let data = self.file.map()?;
RarcView::new(data).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}

View File

@ -13,7 +13,7 @@ pub struct U8Fs {
impl U8Fs {
pub fn new(file: Box<dyn VfsFile>) -> io::Result<Self> { Ok(Self { file }) }
fn view(&mut self) -> io::Result<U8View> {
fn view(&mut self) -> io::Result<U8View<'_>> {
let data = self.file.map()?;
U8View::new(data).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}

View File

@ -41,7 +41,7 @@ impl WadFs {
Ok(Self { file, wad, mtime })
}
fn find(&self, path: &str) -> Option<WadFindResult> {
fn find(&self, path: &str) -> Option<WadFindResult<'_>> {
let filename = path.trim_start_matches('/');
if filename.contains('/') {
return None;