mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c65e87c382 | |||
| 1756b9f6c5 | |||
| 303f2938a2 | |||
| 526e031251 | |||
| 10b2a9c129 | |||
| abe68ef2f2 | |||
| 304df96411 | |||
| 7aa878b48e | |||
| a119d9a6dd | |||
| ebf653816a | |||
| 424434edd6 | |||
| 7f14b684bf | |||
| c5da7f7dd5 | |||
| 2fd655850a | |||
| 79bd7317c1 | |||
| 21f8f2407c |
+1
-1
@@ -18,4 +18,4 @@ android.keystore
|
||||
*.frag
|
||||
*.vert
|
||||
*.metal
|
||||
.vscode/launch.json
|
||||
.vscode/
|
||||
|
||||
Generated
+819
-445
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -13,7 +13,7 @@ strip = "debuginfo"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.package]
|
||||
version = "2.3.4"
|
||||
version = "2.5.0"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
+170
-729
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
@@ -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 }
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user