From 255123796e69523baf6ee57f94421068edc135a9 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 3 Jun 2024 20:31:06 -0600 Subject: [PATCH] Instruction disassembly in `dol diff` When a function diff is detected in `dol diff`, objdiff-core is used to print a detailed view highlighting any differences. Resolves #28 --- Cargo.lock | 249 ++++++++++++++++++++++++-- Cargo.toml | 7 +- src/cmd/dol.rs | 61 ++++++- src/obj/relocations.rs | 42 +++++ src/util/diff.rs | 384 +++++++++++++++++++++++++++++++++++++++++ src/util/elf.rs | 37 +--- src/util/mod.rs | 1 + 7 files changed, 730 insertions(+), 51 deletions(-) create mode 100644 src/util/diff.rs diff --git a/Cargo.lock b/Cargo.lock index e60b183..8b15402 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,6 +167,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + [[package]] name = "block-buffer" version = "0.10.4" @@ -255,7 +261,7 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", - "bitflags", + "bitflags 1.3.2", "strsim", "textwrap", "unicode-width", @@ -318,6 +324,31 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.5.0", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -336,13 +367,14 @@ checksum = "c2e06f9bce634a3c898eb1e5cb949ff63133cbb218af93cc9b38b31d6f3ea285" [[package]] name = "decomp-toolkit" -version = "0.8.3" +version = "0.9.0" dependencies = [ "anyhow", "ar", "argp", "base16ct", "base64", + "crossterm", "cwdemangle", "enable-ansi-support", "filetime", @@ -455,7 +487,7 @@ checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.4.1", "windows-sys 0.52.0", ] @@ -542,6 +574,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -687,6 +725,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.21" @@ -736,6 +784,18 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "multimap" version = "0.10.0" @@ -855,8 +915,8 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "objdiff-core" -version = "1.0.0" -source = "git+https://github.com/encounter/objdiff?rev=8b36fa4fc657d9edf767797af9d394bb32898711#8b36fa4fc657d9edf767797af9d394bb32898711" +version = "2.0.0-alpha.3" +source = "git+https://github.com/encounter/objdiff?rev=a5a6a3928e392d5af5d92826e73b77e074b8788c#a5a6a3928e392d5af5d92826e73b77e074b8788c" dependencies = [ "anyhow", "byteorder", @@ -870,6 +930,7 @@ dependencies = [ "ppc750cl", "serde", "similar", + "strum", ] [[package]] @@ -933,6 +994,29 @@ dependencies = [ "supports-color 2.1.0", ] +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.1", + "smallvec", + "windows-targets 0.52.4", +] + [[package]] name = "path-slash" version = "0.2.1" @@ -996,7 +1080,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" dependencies = [ - "bitflags", + "bitflags 1.3.2", "getopts", "memchr", "unicase", @@ -1047,7 +1131,16 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", +] + +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", ] [[package]] @@ -1106,6 +1199,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + [[package]] name = "ryu" version = "1.0.17" @@ -1127,6 +1226,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d36299972b96b8ae7e8f04ecbf75fb41a27bf3781af00abcf57609774cb911" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.199" @@ -1213,6 +1318,36 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "similar" version = "2.5.0" @@ -1246,7 +1381,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4b19911debfb8c2fb1107bc6cb2d61868aaf53a988449213959bb1b5b1ed95f" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "quote", "syn 2.0.52", @@ -1258,6 +1393,28 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7993a8e3a9e88a00351486baae9522c91b123a088f76469e5bd5cc17198ea87" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.52", +] + [[package]] name = "supports-color" version = "2.1.0" @@ -1306,7 +1463,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" dependencies = [ "bincode", - "bitflags", + "bitflags 1.3.2", "fancy-regex", "flate2", "fnv", @@ -1497,6 +1654,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "winapi" version = "0.3.9" @@ -1543,13 +1706,37 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -1573,6 +1760,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.4" @@ -1585,6 +1778,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.4" @@ -1597,6 +1796,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.4" @@ -1609,6 +1814,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.4" @@ -1621,6 +1832,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.4" @@ -1633,6 +1850,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.4" @@ -1645,6 +1868,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.4" diff --git a/Cargo.toml b/Cargo.toml index 1b428db..d68acdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,13 +3,13 @@ name = "decomp-toolkit" description = "Yet another GameCube/Wii decompilation toolkit." authors = ["Luke Street "] license = "MIT OR Apache-2.0" -version = "0.8.3" +version = "0.9.0" edition = "2021" publish = false repository = "https://github.com/encounter/decomp-toolkit" readme = "README.md" categories = ["command-line-utilities"] -rust-version = "1.70.0" +rust-version = "1.73.0" [[bin]] name = "dtk" @@ -29,6 +29,7 @@ ar = { git = "https://github.com/bjorn3/rust-ar.git", branch = "write_symbol_tab argp = "0.3.0" base16ct = "0.2.0" base64 = "0.22.1" +crossterm = "0.27.0" cwdemangle = "1.0.0" enable-ansi-support = "0.2.1" filetime = "0.2.23" @@ -47,7 +48,7 @@ nintendo-lz = "0.1.3" nodtool = { git = "https://github.com/encounter/nod-rs", rev = "03b83484cb17f94408fa0ef8e50d94951464d1b2" } #nodtool = { path = "../nod-rs/nodtool" } num_enum = "0.7.2" -objdiff-core = { git = "https://github.com/encounter/objdiff", rev = "8b36fa4fc657d9edf767797af9d394bb32898711", features = ["ppc"] } +objdiff-core = { git = "https://github.com/encounter/objdiff", rev = "a5a6a3928e392d5af5d92826e73b77e074b8788c", features = ["ppc"] } #objdiff-core = { path = "../objdiff/objdiff-core", features = ["ppc"] } object = { version = "0.35.0", features = ["read_core", "std", "elf", "write_std"], default-features = false } once_cell = "1.19.0" diff --git a/src/cmd/dol.rs b/src/cmd/dol.rs index 74073a9..42f8540 100644 --- a/src/cmd/dol.rs +++ b/src/cmd/dol.rs @@ -45,6 +45,7 @@ use crate::{ write_splits_file, write_symbols_file, SectionAddressRef, }, dep::DepFile, + diff::{calc_diff_ranges, print_diff, process_code}, dol::process_dol, elf::{process_elf, write_elf}, file::{ @@ -1503,6 +1504,31 @@ where P: AsRef { Ok(()) } +/// Check if two symbols' names match, allowing for differences in compiler-generated names, +/// like @1234 and @5678, or init$1234 and init$5678. +fn symbol_name_fuzzy_eq(a: &ObjSymbol, b: &ObjSymbol) -> bool { + if a.name == b.name { + return true; + } + // Match e.g. @1234 and @5678 + if a.name.starts_with('@') && b.name.starts_with('@') { + if let (Ok(_), Ok(_)) = (a.name[1..].parse::(), b.name[1..].parse::()) { + return true; + } + } + // Match e.g. init$1234 and init$5678 + if let (Some(a_dollar), Some(b_dollar)) = (a.name.rfind('$'), b.name.rfind('$')) { + if a.name[..a_dollar] == b.name[..b_dollar] { + if let (Ok(_), Ok(_)) = + (a.name[a_dollar + 1..].parse::(), b.name[b_dollar + 1..].parse::()) + { + return true; + } + } + } + false +} + fn diff(args: DiffArgs) -> Result<()> { log::info!("Loading {}", args.config.display()); let mut config_file = buf_reader(&args.config)?; @@ -1545,7 +1571,7 @@ fn diff(args: DiffArgs) -> Result<()> { }); let mut found = false; if let Some((_, linked_sym)) = linked_sym { - if linked_sym.name.starts_with(&orig_sym.name) { + if symbol_name_fuzzy_eq(linked_sym, orig_sym) { if linked_sym.size != orig_sym.size && // TODO validate common symbol sizes // (need to account for inflation bug) @@ -1643,8 +1669,37 @@ fn diff(args: DiffArgs) -> Result<()> { orig_sym.size, orig_sym.address ); - log::error!("Original: {}", hex::encode_upper(orig_data)); - log::error!("Linked: {}", hex::encode_upper(linked_data)); + + // Disassemble and print the diff using objdiff-core if it's a function + let mut handled = false; + if orig_sym.kind == ObjSymbolKind::Function + && orig_section.kind == ObjSectionKind::Code + && linked_sym.kind == ObjSymbolKind::Function + && linked_section.kind == ObjSectionKind::Code + { + let config = objdiff_core::diff::DiffObjConfig::default(); + let orig_code = process_code(&obj, orig_sym, orig_section, &config)?; + let linked_code = process_code(&linked_obj, linked_sym, linked_section, &config)?; + let (left_diff, right_diff) = objdiff_core::diff::code::diff_code( + &orig_code, + &linked_code, + objdiff_core::obj::SymbolRef::default(), + objdiff_core::obj::SymbolRef::default(), + &config, + )?; + let ranges = calc_diff_ranges(&left_diff.instructions, &right_diff.instructions, 3); + // objdiff may miss relocation differences, so fall back to printing the data diff + // if we don't have any instruction ranges to print + if !ranges.is_empty() { + print_diff(&left_diff, &right_diff, &ranges)?; + handled = true; + } + } + if !handled { + log::error!("Original: {}", hex::encode_upper(orig_data)); + log::error!("Linked: {}", hex::encode_upper(linked_data)); + } + std::process::exit(1); } } diff --git a/src/obj/relocations.rs b/src/obj/relocations.rs index 180717b..ac49501 100644 --- a/src/obj/relocations.rs +++ b/src/obj/relocations.rs @@ -6,6 +6,7 @@ use std::{ }; use anyhow::Result; +use object::elf; use serde::{Deserialize, Serialize}; use crate::obj::SymbolIndex; @@ -64,6 +65,47 @@ pub struct ObjReloc { pub module: Option, } +impl ObjReloc { + /// Calculates the ELF r_offset and r_type for a relocation. + pub fn to_elf(&self, addr: u32) -> (u64, u32) { + let mut r_offset = addr as u64; + let r_type = match self.kind { + ObjRelocKind::Absolute => { + if r_offset & 3 == 0 { + elf::R_PPC_ADDR32 + } else { + elf::R_PPC_UADDR32 + } + } + ObjRelocKind::PpcAddr16Hi => { + r_offset = (r_offset & !3) + 2; + elf::R_PPC_ADDR16_HI + } + ObjRelocKind::PpcAddr16Ha => { + r_offset = (r_offset & !3) + 2; + elf::R_PPC_ADDR16_HA + } + ObjRelocKind::PpcAddr16Lo => { + r_offset = (r_offset & !3) + 2; + elf::R_PPC_ADDR16_LO + } + ObjRelocKind::PpcRel24 => { + r_offset &= !3; + elf::R_PPC_REL24 + } + ObjRelocKind::PpcRel14 => { + r_offset &= !3; + elf::R_PPC_REL14 + } + ObjRelocKind::PpcEmbSda21 => { + r_offset &= !3; + elf::R_PPC_EMB_SDA21 + } + }; + (r_offset, r_type) + } +} + #[derive(Debug, Clone, Default)] pub struct ObjRelocations { relocations: BTreeMap, diff --git a/src/util/diff.rs b/src/util/diff.rs new file mode 100644 index 0000000..6ab7868 --- /dev/null +++ b/src/util/diff.rs @@ -0,0 +1,384 @@ +//! This includes helpers to convert between decomp-toolkit types and objdiff-core types. +//! Eventually it'd be nice to share [ObjInfo] and related types between decomp-toolkit and +//! objdiff-core to avoid this conversion. +use std::{ + io::{stdout, Write}, + ops::Range, +}; + +use anyhow::Result; +use crossterm::style::Color; +use itertools::Itertools; +use objdiff_core::{ + arch::{ObjArch, ProcessCodeResult}, + diff::{ + display::{display_diff, DiffText}, + DiffObjConfig, ObjInsDiff, ObjInsDiffKind, ObjSymbolDiff, + }, +}; +use object::RelocationFlags; + +use crate::obj::{ObjInfo, ObjReloc, ObjSection, ObjSymbol}; + +/// Processes code for a PPC function using objdiff-core. +/// Returns [ProcessCodeResult] for other objdiff-core functions to accept. +pub fn process_code( + obj: &ObjInfo, + symbol: &ObjSymbol, + section: &ObjSection, + config: &DiffObjConfig, +) -> Result { + let arch = objdiff_core::arch::ppc::ObjArchPpc {}; + let orig_relocs = section + .relocations + .range(symbol.address as u32..symbol.address as u32 + symbol.size as u32) + .map(|(a, r)| to_objdiff_reloc(obj, a, r)) + .collect_vec(); + let orig_data = + section.data_range(symbol.address as u32, symbol.address as u32 + symbol.size as u32)?; + arch.process_code( + symbol.address, + orig_data, + section.elf_index, + &orig_relocs, + &Default::default(), + config, + ) +} + +/// Calculates ranges of instructions to print, collapsing ranges of unchanged instructions. +/// (e.g. `grep -C`) +pub fn calc_diff_ranges( + left: &[ObjInsDiff], + right: &[ObjInsDiff], + collapse_lines: usize, +) -> Vec> { + enum State { + None, + DiffStart(usize), + DiffRange(Range), + } + let mut state = State::None; + let mut idx = 0usize; + let mut left_iter = left.iter(); + let mut right_iter = right.iter(); + let mut ranges = Vec::new(); + + // Left and right should always have the same number of instructions + while let (Some(left_ins), Some(right_ins)) = (left_iter.next(), right_iter.next()) { + match &state { + State::None => { + if left_ins.kind != ObjInsDiffKind::None || right_ins.kind != ObjInsDiffKind::None { + state = State::DiffStart(idx.saturating_sub(collapse_lines)); + } + } + State::DiffStart(start) => { + if left_ins.kind == ObjInsDiffKind::None && right_ins.kind == ObjInsDiffKind::None { + state = State::DiffRange(*start..idx); + } + } + State::DiffRange(range) => { + if left_ins.kind != ObjInsDiffKind::None || right_ins.kind != ObjInsDiffKind::None { + // Restart the range if we find a another diff + state = State::DiffStart(range.start); + } else if idx > range.end + collapse_lines * 2 { + // If we've gone collapse_lines * 2 instructions without a diff, add the range + ranges.push(range.start..range.end + collapse_lines); + state = State::None; + } + } + } + idx += 1; + } + + // Handle the last range + match state { + State::None => {} + State::DiffStart(start) => { + ranges.push(start..idx); + } + State::DiffRange(range) => { + ranges.push(range.start..idx.min(range.end + collapse_lines)); + } + } + + ranges +} + +pub fn print_diff( + left: &ObjSymbolDiff, + right: &ObjSymbolDiff, + ranges: &[Range], +) -> Result<()> { + let (w, _) = crossterm::terminal::size()?; + let mut stdout = stdout(); + for range in ranges { + if range.start > 0 { + crossterm::queue!(stdout, crossterm::style::Print("...\n"))?; + } + let left_ins = left.instructions[range.clone()].iter(); + let right_ins = right.instructions[range.clone()].iter(); + for (left_diff, right_diff) in left_ins.zip(right_ins) { + let left_line = print_line(left_diff, 0); + let right_line = print_line(right_diff, 0); + let mut x = 0; + let hw = (w as usize - 3) / 2; + for span in left_line { + if span.color != Color::Reset { + crossterm::queue!(stdout, crossterm::style::SetForegroundColor(span.color))?; + } + let len = (hw - x).min(span.text.len()); + crossterm::queue!(stdout, crossterm::style::Print(&span.text[..len]))?; + x += len; + } + if x < hw { + crossterm::queue!(stdout, crossterm::style::Print(" ".repeat(hw - x)))?; + } + if left_diff.kind != ObjInsDiffKind::None || right_diff.kind != ObjInsDiffKind::None { + crossterm::queue!( + stdout, + crossterm::style::ResetColor, + crossterm::style::Print(" | ") + )?; + } else { + crossterm::queue!( + stdout, + crossterm::style::ResetColor, + crossterm::style::Print(" ") + )?; + } + x = hw + 3; + for span in right_line { + if span.color != Color::Reset { + crossterm::queue!(stdout, crossterm::style::SetForegroundColor(span.color))?; + } + let len = (w as usize - x).min(span.text.len()); + crossterm::queue!(stdout, crossterm::style::Print(&span.text[..len]))?; + x += len; + } + crossterm::queue!(stdout, crossterm::style::ResetColor, crossterm::style::Print("\n"))?; + } + } + if matches!(ranges.last().map(|r| r.end), Some(n) if n != left.instructions.len()) { + crossterm::queue!(stdout, crossterm::style::Print("...\n"))?; + } + stdout.flush()?; + Ok(()) +} + +const COLOR_ROTATION: [Color; 7] = [ + Color::Magenta, + Color::Cyan, + Color::Green, + Color::Red, + Color::Yellow, + Color::Blue, + Color::Green, +]; + +struct Span { + text: String, + color: Color, +} + +fn print_line(ins_diff: &ObjInsDiff, base_addr: u64) -> Vec { + let mut line = Vec::new(); + display_diff(ins_diff, base_addr, |text| -> Result<()> { + let label_text; + let mut base_color = match ins_diff.kind { + ObjInsDiffKind::None | ObjInsDiffKind::OpMismatch | ObjInsDiffKind::ArgMismatch => { + Color::Grey + } + ObjInsDiffKind::Replace => Color::Cyan, + ObjInsDiffKind::Delete => Color::Red, + ObjInsDiffKind::Insert => Color::Green, + }; + let mut pad_to = 0; + match text { + DiffText::Basic(text) => { + label_text = text.to_string(); + } + DiffText::BasicColor(s, idx) => { + label_text = s.to_string(); + base_color = COLOR_ROTATION[idx % COLOR_ROTATION.len()]; + } + DiffText::Line(num) => { + label_text = format!("{num} "); + base_color = Color::DarkGrey; + pad_to = 5; + } + DiffText::Address(addr) => { + label_text = format!("{:x}:", addr); + pad_to = 5; + } + DiffText::Opcode(mnemonic, _op) => { + label_text = mnemonic.to_string(); + if ins_diff.kind == ObjInsDiffKind::OpMismatch { + base_color = Color::Blue; + } + pad_to = 8; + } + DiffText::Argument(arg, diff) => { + label_text = arg.to_string(); + if let Some(diff) = diff { + base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()] + } + } + DiffText::BranchDest(addr) => { + label_text = format!("{addr:x}"); + } + DiffText::Symbol(sym) => { + let name = sym.demangled_name.as_ref().unwrap_or(&sym.name); + label_text = name.clone(); + base_color = Color::White; + } + DiffText::Spacing(n) => { + line.push(Span { text: " ".repeat(n), color: Color::Reset }); + return Ok(()); + } + DiffText::Eol => { + return Ok(()); + } + } + let len = label_text.len(); + line.push(Span { text: label_text, color: base_color }); + if pad_to > len { + let pad = (pad_to - len) as u16; + line.push(Span { text: " ".repeat(pad as usize), color: Color::Reset }); + } + Ok(()) + }) + .unwrap(); + line +} + +/// Converts an [ObjReloc] to an [objdiff_core::obj::ObjReloc]. +fn to_objdiff_reloc(obj: &ObjInfo, address: u32, reloc: &ObjReloc) -> objdiff_core::obj::ObjReloc { + let target_symbol = &obj.symbols[reloc.target_symbol]; + let target_section = target_symbol.section.map(|i| &obj.sections[i]); + let (r_offset, r_type) = reloc.to_elf(address); + objdiff_core::obj::ObjReloc { + flags: RelocationFlags::Elf { r_type }, + address: r_offset, + target: to_objdiff_symbol(target_symbol, target_section, reloc.addend), + target_section: target_section.map(|s| s.name.clone()), + } +} + +/// Converts an [ObjSymbol] to an [objdiff_core::obj::ObjSymbol]. +fn to_objdiff_symbol( + symbol: &ObjSymbol, + section: Option<&ObjSection>, + addend: i64, +) -> objdiff_core::obj::ObjSymbol { + let mut flags = objdiff_core::obj::ObjSymbolFlagSet::default(); + if symbol.flags.is_global() { + flags.0 |= objdiff_core::obj::ObjSymbolFlags::Global; + } + if symbol.flags.is_local() { + flags.0 |= objdiff_core::obj::ObjSymbolFlags::Local; + } + if symbol.flags.is_weak() { + flags.0 |= objdiff_core::obj::ObjSymbolFlags::Weak; + } + if symbol.flags.is_common() { + flags.0 |= objdiff_core::obj::ObjSymbolFlags::Common; + } + if symbol.flags.is_hidden() { + flags.0 |= objdiff_core::obj::ObjSymbolFlags::Hidden; + } + objdiff_core::obj::ObjSymbol { + name: symbol.name.clone(), + demangled_name: symbol.demangled_name.clone(), + address: symbol.address, + section_address: symbol.address - section.map(|s| s.address).unwrap_or(0), + size: symbol.size, + size_known: symbol.size_known, + flags, + addend, + virtual_address: None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! ins_diff { + ($kind:expr) => { + ObjInsDiff { + ins: None, + kind: $kind, + branch_from: None, + branch_to: None, + arg_diff: vec![], + } + }; + } + + #[test] + fn test_get_diff_ranges() { + // Test single range + let diff = vec![ + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::Replace), + ins_diff!(ObjInsDiffKind::Replace), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::Replace), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ]; + assert_eq!(calc_diff_ranges(&diff, &diff, 3), vec![0..10]); + + // Test combining ranges + let diff = vec![ + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::Replace), + ins_diff!(ObjInsDiffKind::Replace), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::Replace), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + // This should be combined with the previous range, + // since it's within collapse_lines * 2 + 1 instructions + ins_diff!(ObjInsDiffKind::Replace), + ]; + assert_eq!(calc_diff_ranges(&diff, &diff, 3), vec![0..15]); + + // Test separating ranges + let diff = vec![ + // start range 1 + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::Replace), + ins_diff!(ObjInsDiffKind::Replace), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + // end range 1 + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + // start range 2 + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::None), + ins_diff!(ObjInsDiffKind::Replace), + ins_diff!(ObjInsDiffKind::Replace), + ins_diff!(ObjInsDiffKind::None), + // end range 2 + ]; + assert_eq!(calc_diff_ranges(&diff, &diff, 3), vec![0..7, 9..15]); + } +} diff --git a/src/util/elf.rs b/src/util/elf.rs index 5c95a3a..95a8022 100644 --- a/src/util/elf.rs +++ b/src/util/elf.rs @@ -727,41 +727,8 @@ pub fn write_elf(obj: &ObjInfo, export_all: bool) -> Result> { } writer.write_align_relocation(); ensure!(writer.len() == out_section.rela_offset); - for (reloc_address, reloc) in section.relocations.iter() { - let mut r_offset = reloc_address as u64; - let r_type = match reloc.kind { - ObjRelocKind::Absolute => { - if r_offset & 3 == 0 { - elf::R_PPC_ADDR32 - } else { - elf::R_PPC_UADDR32 - } - } - ObjRelocKind::PpcAddr16Hi => { - r_offset = (r_offset & !3) + 2; - elf::R_PPC_ADDR16_HI - } - ObjRelocKind::PpcAddr16Ha => { - r_offset = (r_offset & !3) + 2; - elf::R_PPC_ADDR16_HA - } - ObjRelocKind::PpcAddr16Lo => { - r_offset = (r_offset & !3) + 2; - elf::R_PPC_ADDR16_LO - } - ObjRelocKind::PpcRel24 => { - r_offset &= !3; - elf::R_PPC_REL24 - } - ObjRelocKind::PpcRel14 => { - r_offset &= !3; - elf::R_PPC_REL14 - } - ObjRelocKind::PpcEmbSda21 => { - r_offset &= !3; - elf::R_PPC_EMB_SDA21 - } - }; + for (addr, reloc) in section.relocations.iter() { + let (r_offset, r_type) = reloc.to_elf(addr); let r_sym = symbol_map[reloc.target_symbol] .ok_or_else(|| anyhow!("Relocation against stripped symbol"))?; writer.write_relocation(true, &Rel { r_offset, r_sym, r_type, r_addend: reloc.addend }); diff --git a/src/util/mod.rs b/src/util/mod.rs index c6b2dc9..facb8dc 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -6,6 +6,7 @@ pub mod bin2c; pub mod comment; pub mod config; pub mod dep; +pub mod diff; pub mod dol; pub mod dwarf; pub mod elf;