Compare commits

...

17 Commits

Author SHA1 Message Date
Luke Street c65e87c382 Version 2.5.0 2024-12-08 21:48:21 -07:00
Luke Street 1756b9f6c5 Repaint after view action 2024-12-08 21:42:33 -07:00
Luke Street 303f2938a2 Update dependencies 2024-12-08 21:40:13 -07:00
Luke Street 526e031251 Experimental objdiff-cli diff auto-rebuild 2024-12-08 21:40:13 -07:00
LagoLunatic 10b2a9c129 PPC: Display data values on hover for pools as well (#140)
* Fix missing dependency feature for objdiff-gui

* Update .gitignore

* PPC: Display data values on hover for pools as well

* Tooltip data display: Format floats and doubles better

Floats and doubles will now always be displayed with a decimal point and one digit after it, even if they are whole numbers. Floats will also have the f suffix. This is so you can tell the data type just by glancing at the value.

* Move big functions to bottom ppc.rs

* Clear pool relocs in volatile registers on function call

This fixes some false positives.

* Revert ObjArch API changes, add fake target symbol hack

Because we no longer have access to the actual symbol name via sections, guess_data_type can no longer detect the String data type for pooled references.

* Add hack to detect strings via the addi opcode

* Move hack to resolve placeholder symbol into process_code_symbol

* Merge reloc and fake_pool_reloc fields of ObjIns
2024-12-03 22:50:05 -07:00
LagoLunatic abe68ef2f2 objdiff-gui: Implement keyboard shortcuts (#139)
* Fix missing dependency feature for objdiff-gui

* Update .gitignore

* Add enter and back hotkeys

* Add scroll hotkeys

* Add hotkeys to select the next symbol above/below the current one in the listing

* Do not clear highlighted symbol when backing out of diff view

* Do not clear highlighted symbol when hovering mouse over an unpaired symbol

* Auto-scroll the keyboard-selected symbols into view if offscreen

* Fix some hotkeys stealing input from focused widgets

e.g. The symbol list was stealing the W/S key presses when typing into the symbol filter text edit.

If the user actually wants to use these shortcuts while a widget is focused, they can simply press the escape key to unfocus all widgets and then press the shortcut.

* Add Ctrl+F/S shortcuts for focusing the object and symbol filter text edits

* Add space as alternative to enter hotkey

This is for consistency with egui's builtint enter/space hotkey for interacting with the focused widget.

* Add hotkeys to change target and base functions

* Split function diff view: Enable PageUp/PageDown/Home/End for scrolling

* Add escape as an alternative to back hotkey

* Fix auto-scrolling to highlighted symbol only working for the left side

The flag is cleared after one scroll to avoid doing it continuously, but this breaks when we need to scroll to both the left and the right symbol at the same time. So now each side has its own flag to keep track of this state independently.

* Simplify clearing of the autoscroll flag, remove &mut State

* Found a better place to clear the autoscroll flag

DiffViewState::post_update is where the flag gets set, so clearing it right before that at the start of the function seems to make the most sense, instead of doing it in App::update.
2024-12-02 21:51:37 -07:00
LagoLunatic 304df96411 Display decoded rlwinm info to hover tooltip (#141)
* Fix missing dependency feature for objdiff-gui

* Update .gitignore

* Display decoded rlwinm info to hover tooltip

* Remove trailing newline when displaying decoded rlwinm info

* Change variable name

* Also update variable name in rlwinm.rs
2024-12-02 21:40:05 -07:00
Luke Street 7aa878b48e Update all dependencies & clippy fixes 2024-12-01 22:22:35 -07:00
Luke Street a119d9a6dd Add scratch preset_id field for decomp.me
Resolves #133
2024-11-07 09:27:13 -07:00
robojumper ebf653816a Combine nested otherwise empty directories in objects view (#137) 2024-11-07 08:21:39 -07:00
Luke Street 424434edd6 Experimental ARM64 support
Based on yaxpeax-arm, but with a heavy dose of
custom code to work around its limitations.

Please report any issues or unhandled relocations.
2024-10-31 17:39:12 -06:00
Luke Street 7f14b684bf Ignore PlainText segments when diffing 2024-10-31 17:27:27 -06:00
Luke Street c5da7f7dd5 Show diff color when symbols differ 2024-10-31 17:26:59 -06:00
Luke Street 2fd655850a Ignore Absolute relocations and log warning 2024-10-31 17:24:49 -06:00
Luke Street 79bd7317c1 Match BranchDest->Reloc with relaxed relocation diffs 2024-10-31 17:24:33 -06:00
Luke Street 21f8f2407c Relax symbol comparison logic
The Ghidra delinker plugin emits functions with type STT_OBJECT,
rather than STT_FUNC. The current logic was preventing these from
being compared based on their symbol type. Relax this condition
for now.
2024-10-29 22:46:02 -06:00
Luke Street d2b7a9ef25 Fix missing common BSS symbols
Resolves #128
2024-10-28 17:54:49 -06:00
51 changed files with 5919 additions and 1937 deletions
+1 -1
View File
@@ -18,4 +18,4 @@ android.keystore
*.frag
*.vert
*.metal
.vscode/launch.json
.vscode/
Generated
+819 -445
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -13,7 +13,7 @@ strip = "debuginfo"
codegen-units = 1
[workspace.package]
version = "2.3.3"
version = "2.5.0"
authors = ["Luke Street <luke@street.dev>"]
edition = "2021"
license = "MIT OR Apache-2.0"
+1
View File
@@ -20,6 +20,7 @@ Supports:
- MIPS (N64, PS1, PS2, PSP)
- x86 (COFF only at the moment)
- ARM (GBA, DS, 3DS)
- ARM64 (Switch, experimental)
See [Usage](#usage) for more information.
+2 -1
View File
@@ -73,6 +73,7 @@ ignore = [
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
#"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 indirect dependency" },
]
# 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.
@@ -97,7 +98,7 @@ allow = [
"BSL-1.0",
"CC0-1.0",
"MPL-2.0",
"Unicode-DFS-2016",
"Unicode-3.0",
"Zlib",
"0BSD",
"OFL-1.1",
+2 -2
View File
@@ -14,13 +14,13 @@ publish = false
[dependencies]
anyhow = "1.0"
argp = "0.3"
argp = "0.4"
crossterm = "0.28"
enable-ansi-support = "0.2"
memmap2 = "0.9"
objdiff-core = { path = "../objdiff-core", features = ["all"] }
prost = "0.13"
ratatui = "0.28"
ratatui = "0.29"
rayon = "1.10"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
File diff suppressed because it is too large Load Diff
+1
View File
@@ -1,6 +1,7 @@
mod argp_version;
mod cmd;
mod util;
mod views;
// musl's allocator is very slow, so use mimalloc when targeting musl.
// Otherwise, use the system allocator to avoid extra code size.
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
use anyhow::Result;
use crossterm::event::Event;
use ratatui::Frame;
use crate::cmd::diff::AppState;
pub mod function_diff;
#[derive(Default)]
pub struct EventResult {
pub redraw: bool,
pub click_xy: Option<(u16, u16)>,
}
pub enum EventControlFlow {
Break,
Continue(EventResult),
Reload,
}
pub trait UiView {
fn draw(&mut self, state: &AppState, f: &mut Frame, result: &mut EventResult);
fn handle_event(&mut self, state: &mut AppState, event: Event) -> EventControlFlow;
fn reload(&mut self, state: &AppState) -> Result<()>;
}
+29 -2
View File
@@ -16,15 +16,17 @@ documentation = "https://docs.rs/objdiff-core"
crate-type = ["cdylib", "rlib"]
[features]
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "bindings"]
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "arm64", "bindings", "build"]
any-arch = ["config", "dep:bimap", "dep:strum", "dep:similar", "dep:flagset", "dep:log", "dep:memmap2", "dep:byteorder", "dep:num-traits"] # Implicit, used to check if any arch is enabled
bindings = ["dep:serde_json", "dep:prost", "dep:pbjson", "dep:serde", "dep:prost-build", "dep:pbjson-build"]
build = ["dep:shell-escape", "dep:path-slash", "dep:winapi", "dep:notify", "dep:notify-debouncer-full", "dep:reqwest", "dep:self_update", "dep:tempfile", "dep:time"]
config = ["dep:bimap", "dep:globset", "dep:semver", "dep:serde_json", "dep:serde_yaml", "dep:serde", "dep:filetime"]
dwarf = ["dep:gimli"]
mips = ["any-arch", "dep:rabbitizer"]
ppc = ["any-arch", "dep:cwdemangle", "dep:cwextab", "dep:ppc750cl"]
x86 = ["any-arch", "dep:cpp_demangle", "dep:iced-x86", "dep:msvc-demangler"]
arm = ["any-arch", "dep:cpp_demangle", "dep:unarm", "dep:arm-attr"]
bindings = ["dep:serde_json", "dep:prost", "dep:pbjson", "dep:serde", "dep:prost-build", "dep:pbjson-build"]
arm64 = ["any-arch", "dep:cpp_demangle", "dep:yaxpeax-arch", "dep:yaxpeax-arm"]
wasm = ["bindings", "any-arch", "dep:console_error_panic_hook", "dep:console_log", "dep:wasm-bindgen", "dep:tsify-next", "dep:log"]
[package.metadata.docs.rs]
@@ -76,6 +78,31 @@ msvc-demangler = { version = "0.10", optional = true }
unarm = { version = "1.6", optional = true }
arm-attr = { version = "0.1", optional = true }
# arm64
yaxpeax-arch = { version = "0.3", default-features = false, features = ["std"], optional = true }
yaxpeax-arm = { version = "0.3", default-features = false, features = ["std"], optional = true }
# build
notify = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2", optional = true }
notify-debouncer-full = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2", optional = true }
shell-escape = { version = "0.1", optional = true }
tempfile = { version = "3.14", optional = true }
time = { version = "0.3", optional = true }
[target.'cfg(windows)'.dependencies]
path-slash = { version = "0.2", optional = true }
winapi = { version = "0.3", optional = true }
# For Linux static binaries, use rustls
[target.'cfg(target_os = "linux")'.dependencies]
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "rustls-tls"], optional = true }
self_update = { version = "0.41", default-features = false, features = ["rustls"], optional = true }
# For all other platforms, use native TLS
[target.'cfg(not(target_os = "linux"))'.dependencies]
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "default-tls"], optional = true }
self_update = { version = "0.41", optional = true }
[build-dependencies]
prost-build = { version = "0.13", optional = true }
pbjson-build = { version = "0.7", optional = true }
+1
View File
@@ -11,4 +11,5 @@ objdiff-core contains the core functionality of [objdiff](https://github.com/enc
- **`ppc`**: Enables the PowerPC backend powered by [ppc750cl](https://github.com/encounter/ppc750cl).
- **`x86`**: Enables the x86 backend powered by [iced-x86](https://crates.io/crates/iced-x86).
- **`arm`**: Enables the ARM backend powered by [unarm](https://github.com/AetiasHax/unarm).
- **`arm64`**: Enables the ARM64 backend powered by [yaxpeax-arm](https://github.com/iximeow/yaxpeax-arm).
- **`bindings`**: Enables serialization and deserialization of objdiff data structures.
+5 -7
View File
@@ -124,11 +124,9 @@ impl ObjArch for ObjArchArm {
.get(&SectionIndex(section_index))
.map(|x| x.as_slice())
.unwrap_or(&fallback_mappings);
let first_mapping_idx =
match mapping_symbols.binary_search_by_key(&start_addr, |x| x.address) {
Ok(idx) => idx,
Err(idx) => idx - 1,
};
let first_mapping_idx = mapping_symbols
.binary_search_by_key(&start_addr, |x| x.address)
.unwrap_or_else(|idx| idx - 1);
let first_mapping = mapping_symbols[first_mapping_idx].mapping;
let mut mappings_iter =
@@ -215,7 +213,7 @@ impl ObjArch for ObjArchArm {
address: address as u64,
size: (parser.address - address) as u8,
op: ins.opcode_id(),
mnemonic: parsed_ins.mnemonic.to_string(),
mnemonic: Cow::Borrowed(parsed_ins.mnemonic),
args,
reloc,
branch_dest,
@@ -234,7 +232,7 @@ impl ObjArch for ObjArchArm {
section: &ObjSection,
address: u64,
reloc: &Relocation,
) -> anyhow::Result<i64> {
) -> Result<i64> {
let address = address as usize;
Ok(match reloc.flags() {
// ARM calls
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -119,7 +119,7 @@ impl ObjArch for ObjArchMips {
let op = instruction.unique_id as u16;
ops.push(op);
let mnemonic = instruction.opcode_name().to_string();
let mnemonic = instruction.opcode_name();
let is_branch = instruction.is_branch();
let branch_offset = instruction.branch_offset();
let mut branch_dest = if is_branch {
@@ -202,7 +202,7 @@ impl ObjArch for ObjArchMips {
address: cur_addr as u64,
size: 4,
op,
mnemonic,
mnemonic: Cow::Borrowed(mnemonic),
args,
reloc: reloc.cloned(),
branch_dest,
+20 -7
View File
@@ -12,6 +12,8 @@ use crate::{
#[cfg(feature = "arm")]
mod arm;
#[cfg(feature = "arm64")]
mod arm64;
#[cfg(feature = "mips")]
pub mod mips;
#[cfg(feature = "ppc")]
@@ -34,13 +36,22 @@ pub enum DataType {
impl DataType {
pub fn display_bytes<Endian: ByteOrder>(&self, bytes: &[u8]) -> Option<String> {
// TODO: Attempt to interpret large symbols as arrays of a smaller type,
// fallback to intrepreting it as bytes.
// https://github.com/encounter/objdiff/issues/124
if self.required_len().is_some_and(|l| bytes.len() != l) {
log::warn!("Failed to display a symbol value for a symbol whose size doesn't match the instruction referencing it.");
if self.required_len().is_some_and(|l| bytes.len() < l) {
log::warn!("Failed to display a symbol value for a symbol whose size is too small for instruction referencing it.");
return None;
}
let mut bytes = bytes;
if self.required_len().is_some_and(|l| bytes.len() > l) {
// If the symbol's size is larger a single instance of this data type, we take just the
// bytes necessary for one of them in order to display the first element of the array.
bytes = &bytes[0..self.required_len().unwrap()];
// TODO: Attempt to interpret large symbols as arrays of a smaller type and show all
// elements of the array instead. https://github.com/encounter/objdiff/issues/124
// However, note that the stride of an array can not always be determined just by the
// data type guessed by the single instruction accessing it. There can also be arrays of
// structs that contain multiple elements of different types, so if other elements after
// the first one were to be displayed in this manner, they may be inaccurate.
}
match self {
DataType::Int8 => {
@@ -84,10 +95,10 @@ impl DataType {
}
}
DataType::Float => {
format!("Float: {}", Endian::read_f32(bytes))
format!("Float: {:?}f", Endian::read_f32(bytes))
}
DataType::Double => {
format!("Double: {}", Endian::read_f64(bytes))
format!("Double: {:?}", Endian::read_f64(bytes))
}
DataType::Bytes => {
format!("Bytes: {:#?}", bytes)
@@ -165,6 +176,8 @@ pub fn new_arch(object: &File) -> Result<Box<dyn ObjArch>> {
Architecture::I386 | Architecture::X86_64 => Box::new(x86::ObjArchX86::new(object)?),
#[cfg(feature = "arm")]
Architecture::Arm => Box::new(arm::ObjArchArm::new(object)?),
#[cfg(feature = "arm64")]
Architecture::Aarch64 => Box::new(arm64::ObjArchArm64::new(object)?),
arch => bail!("Unsupported architecture: {arch:?}"),
})
}
+215 -20
View File
@@ -1,4 +1,7 @@
use std::{borrow::Cow, collections::BTreeMap};
use std::{
borrow::Cow,
collections::{BTreeMap, HashMap},
};
use anyhow::{bail, ensure, Result};
use byteorder::BigEndian;
@@ -7,7 +10,7 @@ use object::{
elf, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, RelocationTarget,
Symbol, SymbolKind,
};
use ppc750cl::{Argument, InsIter, Opcode, GPR};
use ppc750cl::{Argument, InsIter, Opcode, ParsedIns, GPR};
use crate::{
arch::{DataType, ObjArch, ProcessCodeResult},
@@ -49,6 +52,8 @@ impl ObjArch for ObjArchPpc {
let ins_count = code.len() / 4;
let mut ops = Vec::<u16>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
let fake_pool_reloc_for_addr =
generate_fake_pool_reloc_for_addr_mapping(address, code, relocations);
for (cur_addr, mut ins) in InsIter::new(code, address as u32) {
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
if let Some(reloc) = reloc {
@@ -143,9 +148,9 @@ impl ObjArch for ObjArchPpc {
insts.push(ObjIns {
address: cur_addr as u64,
size: 4,
mnemonic: simplified.mnemonic.to_string(),
mnemonic: Cow::Borrowed(simplified.mnemonic),
args,
reloc: reloc.cloned(),
reloc: reloc.or(fake_pool_reloc_for_addr.get(&cur_addr)).cloned(),
op: ins.op as u16,
branch_dest,
line,
@@ -173,6 +178,7 @@ impl ObjArch for ObjArchPpc {
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
match flags {
RelocationFlags::Elf { r_type } => match r_type {
elf::R_PPC_NONE => Cow::Borrowed("R_PPC_NONE"), // We use this for fake pool relocs
elf::R_PPC_ADDR16_LO => Cow::Borrowed("R_PPC_ADDR16_LO"),
elf::R_PPC_ADDR16_HI => Cow::Borrowed("R_PPC_ADDR16_HI"),
elf::R_PPC_ADDR16_HA => Cow::Borrowed("R_PPC_ADDR16_HA"),
@@ -188,26 +194,22 @@ impl ObjArch for ObjArchPpc {
}
fn guess_data_type(&self, instruction: &ObjIns) -> Option<super::DataType> {
// Always shows the first string of the table. Not ideal, but it's really hard to find
// the actual string being referenced.
if instruction.reloc.as_ref().is_some_and(|r| r.target.name.starts_with("@stringBase")) {
return Some(DataType::String);
}
match Opcode::from(instruction.op as u8) {
Opcode::Lbz | Opcode::Lbzu | Opcode::Lbzux | Opcode::Lbzx => Some(DataType::Int8),
Opcode::Lhz | Opcode::Lhzu | Opcode::Lhzux | Opcode::Lhzx => Some(DataType::Int16),
Opcode::Lha | Opcode::Lhau | Opcode::Lhaux | Opcode::Lhax => Some(DataType::Int16),
Opcode::Lwz | Opcode::Lwzu | Opcode::Lwzux | Opcode::Lwzx => Some(DataType::Int32),
Opcode::Lfs | Opcode::Lfsu | Opcode::Lfsux | Opcode::Lfsx => Some(DataType::Float),
Opcode::Lfd | Opcode::Lfdu | Opcode::Lfdux | Opcode::Lfdx => Some(DataType::Double),
Opcode::Stb | Opcode::Stbu | Opcode::Stbux | Opcode::Stbx => Some(DataType::Int8),
Opcode::Sth | Opcode::Sthu | Opcode::Sthux | Opcode::Sthx => Some(DataType::Int16),
Opcode::Stw | Opcode::Stwu | Opcode::Stwux | Opcode::Stwx => Some(DataType::Int32),
Opcode::Stfs | Opcode::Stfsu | Opcode::Stfsux | Opcode::Stfsx => Some(DataType::Float),
Opcode::Stfd | Opcode::Stfdu | Opcode::Stfdux | Opcode::Stfdx => Some(DataType::Double),
_ => None,
let op = Opcode::from(instruction.op as u8);
if let Some(ty) = guess_data_type_from_load_store_inst_op(op) {
Some(ty)
} else if op == Opcode::Addi {
// Assume that any addi instruction that references a local symbol is loading a string.
// This hack is not ideal and results in tons of false positives where it will show
// garbage strings (e.g. misinterpreting arrays, float literals, etc).
// But not all strings are in the @stringBase pool, so the condition above that checks
// the target symbol name would miss some.
Some(DataType::String)
} else {
None
}
}
@@ -381,3 +383,196 @@ fn make_symbol_ref(symbol: &Symbol) -> Result<ExtabSymbolRef> {
let demangled_name = cwdemangle::demangle(&name, &cwdemangle::DemangleOptions::default());
Ok(ExtabSymbolRef { original_index: symbol.index().0, name, demangled_name })
}
fn guess_data_type_from_load_store_inst_op(inst_op: Opcode) -> Option<DataType> {
match inst_op {
Opcode::Lbz | Opcode::Lbzu | Opcode::Lbzux | Opcode::Lbzx => Some(DataType::Int8),
Opcode::Lhz | Opcode::Lhzu | Opcode::Lhzux | Opcode::Lhzx => Some(DataType::Int16),
Opcode::Lha | Opcode::Lhau | Opcode::Lhaux | Opcode::Lhax => Some(DataType::Int16),
Opcode::Lwz | Opcode::Lwzu | Opcode::Lwzux | Opcode::Lwzx => Some(DataType::Int32),
Opcode::Lfs | Opcode::Lfsu | Opcode::Lfsux | Opcode::Lfsx => Some(DataType::Float),
Opcode::Lfd | Opcode::Lfdu | Opcode::Lfdux | Opcode::Lfdx => Some(DataType::Double),
Opcode::Stb | Opcode::Stbu | Opcode::Stbux | Opcode::Stbx => Some(DataType::Int8),
Opcode::Sth | Opcode::Sthu | Opcode::Sthux | Opcode::Sthx => Some(DataType::Int16),
Opcode::Stw | Opcode::Stwu | Opcode::Stwux | Opcode::Stwx => Some(DataType::Int32),
Opcode::Stfs | Opcode::Stfsu | Opcode::Stfsux | Opcode::Stfsx => Some(DataType::Float),
Opcode::Stfd | Opcode::Stfdu | Opcode::Stfdux | Opcode::Stfdx => Some(DataType::Double),
_ => None,
}
}
// Given an instruction, determine if it could accessing data at the address in a register.
// If so, return the offset added to the register's address, the register containing that address,
// and (optionally) which destination register the address is being copied into.
fn get_offset_and_addr_gpr_for_possible_pool_reference(
opcode: Opcode,
simplified: &ParsedIns,
) -> Option<(i16, GPR, Option<GPR>)> {
let args = &simplified.args;
if guess_data_type_from_load_store_inst_op(opcode).is_some() {
match (args[1], args[2]) {
(Argument::Offset(offset), Argument::GPR(addr_src_gpr)) => {
// e.g. lwz. Immediate offset.
Some((offset.0, addr_src_gpr, None))
}
(Argument::GPR(addr_src_gpr), Argument::GPR(_offset_gpr)) => {
// e.g. lwzx. The offset is in a register and was likely calculated from an index.
// Treat the offset as being 0 in this case to show the first element of the array.
// It may be possible to show all elements by figuring out the stride of the array
// from the calculations performed on the index before it's put into offset_gpr, but
// this would be much more complicated, so it's not currently done.
Some((0, addr_src_gpr, None))
}
_ => None,
}
} else {
// If it's not a load/store instruction, there's two more possibilities we need to handle.
// 1. It could be loading a pointer to a string.
// 2. It could be moving the relocation address plus an offset into a different register to
// load from later.
// If either of these match, we also want to return the destination register that the
// address is being copied into so that we can detect any future references to that new
// register as well.
match (opcode, args[0], args[1], args[2]) {
(
Opcode::Addi,
Argument::GPR(addr_dst_gpr),
Argument::GPR(addr_src_gpr),
Argument::Simm(simm),
) => Some((simm.0, addr_src_gpr, Some(addr_dst_gpr))),
(
Opcode::Or,
Argument::GPR(addr_dst_gpr),
Argument::GPR(addr_src_gpr),
Argument::None,
) => Some((0, addr_src_gpr, Some(addr_dst_gpr))), // `mr` or `mr.`
_ => None,
}
}
}
// We create a fake relocation for an instruction, vaguely simulating what the actual relocation
// might have looked like if it wasn't pooled. This is so minimal changes are needed to display
// pooled accesses vs non-pooled accesses. We set the relocation type to R_PPC_NONE to indicate that
// there isn't really a relocation here, as copying the pool relocation's type wouldn't make sense.
// Also, if this instruction is accessing the middle of a symbol instead of the start, we add an
// addend to indicate that.
fn make_fake_pool_reloc(offset: i16, cur_addr: u32, pool_reloc: &ObjReloc) -> Option<ObjReloc> {
let offset_from_pool = pool_reloc.addend + offset as i64;
let target_address = pool_reloc.target.address.checked_add_signed(offset_from_pool)?;
let orig_section_index = pool_reloc.target.orig_section_index?;
// We also need to create a fake target symbol to go inside our fake relocation.
// This is because we don't have access to list of all symbols in this section, so we can't find
// the real symbol yet. Instead we make a placeholder that has the correct `orig_section_index`
// and `address` fields, and then later on when this information is displayed to the user, we
// can find the real symbol by searching through the object's section's symbols for one that
// contains this address.
let fake_target_symbol = ObjSymbol {
name: "".to_string(),
demangled_name: None,
address: target_address,
section_address: 0,
size: 0,
size_known: false,
kind: Default::default(),
flags: Default::default(),
orig_section_index: Some(orig_section_index),
virtual_address: None,
original_index: None,
bytes: vec![],
};
// The addend is also fake because we don't know yet if the `target_address` here is the exact
// start of the symbol or if it's in the middle of it.
let fake_addend = 0;
Some(ObjReloc {
flags: RelocationFlags::Elf { r_type: elf::R_PPC_NONE },
address: cur_addr as u64,
target: fake_target_symbol,
addend: fake_addend,
})
}
// Searches through all instructions in a function, determining which registers have the addresses
// of pooled data relocations in them, finding which instructions load data from those addresses,
// and constructing a mapping of the address of that instruction to a "fake pool relocation" that
// simulates what that instruction's relocation would look like if data hadn't been pooled.
// Limitations: This method currently only goes through the instructions in a function in linear
// order, from start to finish. It does *not* follow any branches. This means that it could have
// false positives or false negatives in determining which relocation is currently loaded in which
// register at any given point in the function, as control flow is not respected.
// There are currently no known examples of this method producing inaccurate results in reality, but
// if examples are found, it may be possible to update this method to also follow all branches so
// that it produces more accurate results.
fn generate_fake_pool_reloc_for_addr_mapping(
address: u64,
code: &[u8],
relocations: &[ObjReloc],
) -> HashMap<u32, ObjReloc> {
let mut active_pool_relocs = HashMap::new();
let mut pool_reloc_for_addr = HashMap::new();
for (cur_addr, ins) in InsIter::new(code, address as u32) {
let simplified = ins.simplified();
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
if let Some(reloc) = reloc {
// This instruction has a real relocation, so it may be a pool load we want to keep
// track of.
let args = &simplified.args;
match (ins.op, args[0], args[1], args[2]) {
(
Opcode::Addi,
Argument::GPR(addr_dst_gpr),
Argument::GPR(_addr_src_gpr),
Argument::Simm(_simm),
) => {
active_pool_relocs.insert(addr_dst_gpr.0, reloc.clone()); // `lis` + `addi`
}
(
Opcode::Ori,
Argument::GPR(addr_dst_gpr),
Argument::GPR(_addr_src_gpr),
Argument::Uimm(_uimm),
) => {
active_pool_relocs.insert(addr_dst_gpr.0, reloc.clone()); // `lis` + `ori`
}
(Opcode::B, _, _, _) => {
if simplified.mnemonic == "bl" {
// When encountering a function call, clear any active pool relocations from
// the volatile registers (r0, r3-r12), but not the nonvolatile registers.
active_pool_relocs.remove(&0);
for gpr in 3..12 {
active_pool_relocs.remove(&gpr);
}
}
}
_ => {}
}
} else if let Some((offset, addr_src_gpr, addr_dst_gpr)) =
get_offset_and_addr_gpr_for_possible_pool_reference(ins.op, &simplified)
{
// This instruction doesn't have a real relocation, so it may be a reference to one of
// the already-loaded pools.
if let Some(pool_reloc) = active_pool_relocs.get(&addr_src_gpr.0) {
if let Some(fake_pool_reloc) = make_fake_pool_reloc(offset, cur_addr, pool_reloc) {
pool_reloc_for_addr.insert(cur_addr, fake_pool_reloc);
}
if let Some(addr_dst_gpr) = addr_dst_gpr {
// If the address of the pool relocation got copied into another register, we
// need to keep track of it in that register too as future instructions may
// reference the symbol indirectly via this new register, instead of the
// register the symbol's address was originally loaded into.
// For example, the start of the function might `lis` + `addi` the start of the
// ...data pool into r25, and then later the start of a loop will `addi` r25
// with the offset within the .data section of an array variable into r21.
// Then the body of the loop will `lwzx` one of the array elements from r21.
let mut new_reloc = pool_reloc.clone();
new_reloc.addend += offset as i64;
active_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
}
}
}
}
pool_reloc_for_addr
}
+4 -3
View File
@@ -51,7 +51,7 @@ impl ObjArch for ObjArchX86 {
address: 0,
size: 0,
op: 0,
mnemonic: String::new(),
mnemonic: Cow::Borrowed("<invalid>"),
args: vec![],
reloc: None,
branch_dest: None,
@@ -76,7 +76,7 @@ impl ObjArch for ObjArchX86 {
address,
size: instruction.len() as u8,
op,
mnemonic: String::new(),
mnemonic: Cow::Borrowed("<invalid>"),
args: vec![],
reloc: reloc.cloned(),
branch_dest: None,
@@ -242,7 +242,8 @@ impl FormatterOutput for InstructionFormatterOutput {
fn write_mnemonic(&mut self, _instruction: &Instruction, text: &str) {
self.formatted.push_str(text);
self.ins.mnemonic = text.to_string();
// TODO: can iced-x86 guarantee 'static here?
self.ins.mnemonic = Cow::Owned(text.to_string());
}
fn write_number(
+2 -1
View File
@@ -1,3 +1,4 @@
#![allow(clippy::needless_lifetimes)] // Generated serde code
use crate::{
diff::{
ObjDataDiff, ObjDataDiffKind, ObjDiff, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
@@ -132,7 +133,7 @@ impl Instruction {
address: instruction.address,
size: instruction.size as u32,
opcode: instruction.op as u32,
mnemonic: instruction.mnemonic.clone(),
mnemonic: instruction.mnemonic.to_string(),
formatted: instruction.formatted.clone(),
arguments: instruction.args.iter().map(Argument::new).collect(),
relocation: instruction.reloc.as_ref().map(|reloc| Relocation::new(object, reloc)),
+2 -2
View File
@@ -1,3 +1,4 @@
#![allow(clippy::needless_lifetimes)] // Generated serde code
use std::ops::AddAssign;
use anyhow::{bail, Result};
@@ -173,8 +174,7 @@ impl Report {
continue;
}
fn is_sub_category(id: &str, parent: &str, sep: char) -> bool {
id.starts_with(parent)
&& id.get(parent.len()..).map_or(false, |s| s.starts_with(sep))
id.starts_with(parent) && id.get(parent.len()..).is_some_and(|s| s.starts_with(sep))
}
let mut sub_categories = self
.categories

Some files were not shown because too many files have changed in this diff Show More