mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bd3ed0d5ad | |||
| e638d0b17a | |||
| f58616b6dd | |||
| e9762e24c2 | |||
| dab79d96a1 | |||
| a57e5db983 | |||
| d0afd3b83e | |||
| a367af612b | |||
| 22052ea10b | |||
| f7c3501eae | |||
| 07ef93f16a | |||
| 8e8ab6bef8 | |||
| e865f3d598 | |||
| 2b13e9886a | |||
| 1750af736a | |||
| 731b604c24 | |||
| 2d643eb071 | |||
| 0c48d711c7 | |||
| 34220a8e26 | |||
| 0c9e5526d4 | |||
| 3db0727469 | |||
| 8b5bf21f38 | |||
| b77df77000 | |||
| 7e08f9715b | |||
| 3c05852d00 | |||
| a51ff44be1 | |||
| d225cac205 |
+4
-5
@@ -1,5 +1,4 @@
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
linker = "rust-lld"
|
||||
|
||||
[target.aarch64-pc-windows-msvc]
|
||||
linker = "rust-lld"
|
||||
# statically link the C runtime so the executable does not depend on
|
||||
# that shared/dynamic library.
|
||||
[target.'cfg(all(target_env = "msvc", target_os = "windows"))']
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
|
||||
Generated
+384
-305
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -14,7 +14,7 @@ strip = "debuginfo"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.package]
|
||||
version = "3.0.0-beta.6"
|
||||
version = "3.0.0-beta.10"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -16,11 +16,12 @@ Features:
|
||||
|
||||
Supports:
|
||||
|
||||
- PowerPC 750CL (GameCube, Wii)
|
||||
- MIPS (N64, PS1, PS2, PSP)
|
||||
- x86 (COFF only at the moment)
|
||||
- ARM (GBA, DS, 3DS)
|
||||
- ARM64 (Switch, experimental)
|
||||
- ARM64 (Switch)
|
||||
- MIPS (N64, PS1, PS2, PSP)
|
||||
- PowerPC (GameCube, Wii)
|
||||
- SuperH (Saturn, Dreamcast)
|
||||
- x86, x86_64 (PC)
|
||||
|
||||
See [Usage](#usage) for more information.
|
||||
|
||||
|
||||
@@ -175,6 +175,10 @@
|
||||
"type": "boolean",
|
||||
"description": "If true, objdiff will run the build command with the context file as an argument to generate it.",
|
||||
"default": false
|
||||
},
|
||||
"preset_id": {
|
||||
"type": "number",
|
||||
"description": "The decomp.me preset ID to use for the scratch.\nCompiler and flags in the config will take precedence over the preset, but the preset is useful for organizational purposes."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -103,6 +103,7 @@ allow = [
|
||||
"0BSD",
|
||||
"OFL-1.1",
|
||||
"Ubuntu-font-1.0",
|
||||
"CDLA-Permissive-2.0",
|
||||
]
|
||||
# The confidence threshold for detecting a license from license text.
|
||||
# The higher the value, the more closely the license text must be to the
|
||||
|
||||
@@ -28,7 +28,7 @@ supports-color = "3.0"
|
||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
typed-path = "0.10"
|
||||
typed-path = "0.11"
|
||||
|
||||
[target.'cfg(target_env = "musl")'.dependencies]
|
||||
mimalloc = "0.1"
|
||||
|
||||
@@ -85,6 +85,9 @@ pub fn run(args: Args) -> Result<()> {
|
||||
fn generate(args: GenerateArgs) -> Result<()> {
|
||||
let mut diff_config = diff::DiffObjConfig {
|
||||
function_reloc_diffs: diff::FunctionRelocDiffs::None,
|
||||
combine_data_sections: true,
|
||||
combine_text_sections: true,
|
||||
ppc_calculate_pool_relocations: false,
|
||||
..Default::default()
|
||||
};
|
||||
apply_config_args(&mut diff_config, &args.config)?;
|
||||
@@ -225,6 +228,7 @@ fn report_object(
|
||||
demangled_name: None,
|
||||
virtual_address: section.virtual_address,
|
||||
}),
|
||||
address: None,
|
||||
});
|
||||
|
||||
match section.kind {
|
||||
@@ -272,6 +276,7 @@ fn report_object(
|
||||
demangled_name: symbol.demangled_name.clone(),
|
||||
virtual_address: symbol.virtual_address,
|
||||
}),
|
||||
address: symbol.address.checked_sub(section.address),
|
||||
});
|
||||
if match_percent == 100.0 {
|
||||
measures.matched_functions += 1;
|
||||
@@ -279,6 +284,16 @@ fn report_object(
|
||||
measures.total_functions += 1;
|
||||
}
|
||||
}
|
||||
sections.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
let reverse_fn_order = object.metadata.reverse_fn_order.unwrap_or(false);
|
||||
functions.sort_by(|a, b| {
|
||||
if reverse_fn_order {
|
||||
b.address.unwrap_or(0).cmp(&a.address.unwrap_or(0))
|
||||
} else {
|
||||
a.address.unwrap_or(u64::MAX).cmp(&b.address.unwrap_or(u64::MAX))
|
||||
}
|
||||
.then_with(|| a.size.cmp(&b.size))
|
||||
});
|
||||
if metadata.complete.unwrap_or(false) {
|
||||
measures.complete_code = measures.total_code;
|
||||
measures.complete_data = measures.total_data;
|
||||
|
||||
@@ -450,11 +450,11 @@ impl UiView for FunctionDiffUi {
|
||||
|
||||
fn reload(&mut self, state: &AppState) -> Result<()> {
|
||||
let left_sym =
|
||||
state.left_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
|
||||
state.left_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
|
||||
let right_sym =
|
||||
state.right_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
|
||||
state.right_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
|
||||
let prev_sym =
|
||||
state.prev_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
|
||||
state.prev_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
|
||||
self.num_rows = match (
|
||||
get_symbol(state.left_obj.as_ref(), left_sym),
|
||||
get_symbol(state.right_obj.as_ref(), right_sym),
|
||||
@@ -570,6 +570,7 @@ impl FunctionDiffUi {
|
||||
DiffTextColor::Normal => Color::Gray,
|
||||
DiffTextColor::Dim => Color::DarkGray,
|
||||
DiffTextColor::Bright => Color::White,
|
||||
DiffTextColor::DataFlow => Color::LightCyan,
|
||||
DiffTextColor::Replace => Color::Cyan,
|
||||
DiffTextColor::Delete => Color::Red,
|
||||
DiffTextColor::Insert => Color::Green,
|
||||
@@ -650,12 +651,3 @@ fn get_symbol(
|
||||
let sym = sym?;
|
||||
Some((obj, sym, &diff.symbols[sym]))
|
||||
}
|
||||
|
||||
fn find_function(obj: &Object, name: &str) -> Option<usize> {
|
||||
for (symbol_idx, symbol) in obj.symbols.iter().enumerate() {
|
||||
if symbol.name == name {
|
||||
return Some(symbol_idx);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ any-arch = [
|
||||
"dep:regex",
|
||||
"dep:similar",
|
||||
"dep:syn",
|
||||
"dep:encoding_rs"
|
||||
]
|
||||
bindings = [
|
||||
"dep:prost",
|
||||
@@ -133,7 +134,7 @@ prost = { version = "0.13", default-features = false, features = ["prost-derive"
|
||||
regex = { version = "1.11", default-features = false, features = [], optional = true }
|
||||
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
|
||||
similar = { version = "2.7", default-features = false, features = ["hashbrown"], optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" }
|
||||
typed-path = { version = "0.10", default-features = false, optional = true }
|
||||
typed-path = { version = "0.11", default-features = false, optional = true }
|
||||
|
||||
# config
|
||||
globset = { version = "0.4", default-features = false, optional = true }
|
||||
@@ -158,7 +159,7 @@ iced-x86 = { version = "1.21", default-features = false, features = ["decoder",
|
||||
msvc-demangler = { version = "0.11", optional = true }
|
||||
|
||||
# arm
|
||||
unarm = { version = "1.7", optional = true }
|
||||
unarm = { version = "1.8", optional = true }
|
||||
arm-attr = { version = "0.2", optional = true }
|
||||
|
||||
# arm64
|
||||
@@ -171,10 +172,10 @@ notify-debouncer-full = { version = "0.5.0", optional = true }
|
||||
shell-escape = { version = "0.1", optional = true }
|
||||
tempfile = { version = "3.19", optional = true }
|
||||
time = { version = "0.3", optional = true }
|
||||
encoding_rs = "0.8.35"
|
||||
encoding_rs = { version = "0.8.35", optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3", optional = true }
|
||||
winapi = { version = "0.3", optional = true, features = ["winbase"] }
|
||||
|
||||
# For Linux static binaries, use rustls
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
@@ -200,4 +201,4 @@ syn = { version = "2.0", optional = true }
|
||||
[dev-dependencies]
|
||||
# Enable all features for tests
|
||||
objdiff-core = { path = ".", features = ["all"] }
|
||||
insta = "1.42"
|
||||
insta = "1.43"
|
||||
|
||||
@@ -5,11 +5,12 @@ objdiff-core contains the core functionality of [objdiff](https://github.com/enc
|
||||
## Crate feature flags
|
||||
|
||||
- **`all`**: Enables all main features.
|
||||
- **`bindings`**: Enables serialization and deserialization of objdiff data structures.
|
||||
- **`config`**: Enables objdiff configuration file support.
|
||||
- **`dwarf`**: Enables extraction of line number information from DWARF debug sections.
|
||||
- **`mips`**: Enables the MIPS backend powered by [rabbitizer](https://github.com/Decompollaborate/rabbitizer). (Note: C library with Rust bindings)
|
||||
- **`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.
|
||||
- **`arm`**: Enables the ARM backend powered by [unarm](https://github.com/AetiasHax/unarm).
|
||||
- **`mips`**: Enables the MIPS backend powered by [rabbitizer](https://github.com/Decompollaborate/rabbitizer).
|
||||
- **`ppc`**: Enables the PowerPC backend powered by [ppc750cl](https://github.com/encounter/ppc750cl).
|
||||
- **`superh`**: Enables the SuperH backend powered by an included disassembler.
|
||||
- **`x86`**: Enables the x86 backend powered by [iced-x86](https://crates.io/crates/iced-x86).
|
||||
|
||||
@@ -25,6 +25,20 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "analyzeDataFlow",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "(Experimental) Perform data flow analysis",
|
||||
"description": "Use data flow analysis to display known information about register contents where possible"
|
||||
},
|
||||
{
|
||||
"id": "showDataFlow",
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"name": "Show data flow",
|
||||
"description": "Show data flow analysis results in place of register name where present"
|
||||
},
|
||||
{
|
||||
"id": "spaceBetweenArgs",
|
||||
"type": "boolean",
|
||||
@@ -264,7 +278,8 @@
|
||||
"id": "ppc",
|
||||
"name": "PowerPC",
|
||||
"properties": [
|
||||
"ppc.calculatePoolRelocations"
|
||||
"ppc.calculatePoolRelocations",
|
||||
"analyzeDataFlow"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
Binary file not shown.
@@ -99,6 +99,8 @@ message ReportItem {
|
||||
float fuzzy_match_percent = 3;
|
||||
// Extra metadata for this item
|
||||
optional ReportItemMetadata metadata = 4;
|
||||
// Address of the item (section-relative offset)
|
||||
optional uint64 address = 5;
|
||||
}
|
||||
|
||||
// Extra metadata for an item
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::{
|
||||
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, display::InstructionPart},
|
||||
obj::{
|
||||
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
|
||||
ScannedInstruction, Section, SectionKind, Symbol, SymbolFlag, SymbolFlagSet, SymbolKind,
|
||||
Section, SectionKind, Symbol, SymbolFlag, SymbolFlagSet, SymbolKind,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -187,14 +187,14 @@ impl Arch for ArchArm {
|
||||
self.disasm_modes = Self::get_mapping_symbols(sections, symbols);
|
||||
}
|
||||
|
||||
fn scan_instructions(
|
||||
fn scan_instructions_internal(
|
||||
&self,
|
||||
address: u64,
|
||||
code: &[u8],
|
||||
section_index: usize,
|
||||
_relocations: &[Relocation],
|
||||
diff_config: &DiffObjConfig,
|
||||
) -> Result<Vec<ScannedInstruction>> {
|
||||
) -> Result<Vec<InstructionRef>> {
|
||||
let start_addr = address as u32;
|
||||
let end_addr = start_addr + code.len() as u32;
|
||||
|
||||
@@ -219,13 +219,13 @@ impl Arch for ArchArm {
|
||||
let mut next_mapping = mappings_iter.next();
|
||||
|
||||
let ins_count = code.len() / mode.instruction_size(start_addr);
|
||||
let mut ops = Vec::<ScannedInstruction>::with_capacity(ins_count);
|
||||
let mut ops = Vec::<InstructionRef>::with_capacity(ins_count);
|
||||
|
||||
let parse_flags = self.parse_flags(diff_config);
|
||||
|
||||
let mut address = start_addr;
|
||||
while address < end_addr {
|
||||
while let Some(next) = next_mapping.take_if(|x| address >= x.address) {
|
||||
while let Some(next) = next_mapping.filter(|x| address >= x.address) {
|
||||
// Change mapping
|
||||
mode = next.mapping;
|
||||
next_mapping = mappings_iter.next();
|
||||
@@ -235,12 +235,10 @@ impl Arch for ArchArm {
|
||||
let data = &code[(address - start_addr) as usize..];
|
||||
if data.len() < ins_size {
|
||||
// Push the remainder as data
|
||||
ops.push(ScannedInstruction {
|
||||
ins_ref: InstructionRef {
|
||||
address: address as u64,
|
||||
size: data.len() as u8,
|
||||
opcode: u16::MAX,
|
||||
},
|
||||
ops.push(InstructionRef {
|
||||
address: address as u64,
|
||||
size: data.len() as u8,
|
||||
opcode: u16::MAX,
|
||||
branch_dest: None,
|
||||
});
|
||||
break;
|
||||
@@ -256,12 +254,10 @@ impl Arch for ArchArm {
|
||||
}
|
||||
_ => {
|
||||
// Invalid instruction size
|
||||
ops.push(ScannedInstruction {
|
||||
ins_ref: InstructionRef {
|
||||
address: address as u64,
|
||||
size: ins_size as u8,
|
||||
opcode: u16::MAX,
|
||||
},
|
||||
ops.push(InstructionRef {
|
||||
address: address as u64,
|
||||
size: ins_size as u8,
|
||||
opcode: u16::MAX,
|
||||
branch_dest: None,
|
||||
});
|
||||
address += ins_size as u32;
|
||||
@@ -325,8 +321,10 @@ impl Arch for ArchArm {
|
||||
unarm::ParseMode::Data => (u16::MAX, None),
|
||||
};
|
||||
|
||||
ops.push(ScannedInstruction {
|
||||
ins_ref: InstructionRef { address: address as u64, size: ins_size as u8, opcode },
|
||||
ops.push(InstructionRef {
|
||||
address: address as u64,
|
||||
size: ins_size as u8,
|
||||
opcode,
|
||||
branch_dest: branch_dest.map(|x| x as u64),
|
||||
});
|
||||
address += ins_size as u32;
|
||||
|
||||
@@ -18,7 +18,6 @@ use crate::{
|
||||
diff::{DiffObjConfig, display::InstructionPart},
|
||||
obj::{
|
||||
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
|
||||
ScannedInstruction,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -30,16 +29,16 @@ impl ArchArm64 {
|
||||
}
|
||||
|
||||
impl Arch for ArchArm64 {
|
||||
fn scan_instructions(
|
||||
fn scan_instructions_internal(
|
||||
&self,
|
||||
address: u64,
|
||||
code: &[u8],
|
||||
_section_index: usize,
|
||||
_relocations: &[Relocation],
|
||||
_diff_config: &DiffObjConfig,
|
||||
) -> Result<Vec<ScannedInstruction>> {
|
||||
) -> Result<Vec<InstructionRef>> {
|
||||
let start_address = address;
|
||||
let mut ops = Vec::<ScannedInstruction>::with_capacity(code.len() / 4);
|
||||
let mut ops = Vec::<InstructionRef>::with_capacity(code.len() / 4);
|
||||
|
||||
let mut reader = U8Reader::new(code);
|
||||
let decoder = InstDecoder::default();
|
||||
@@ -58,8 +57,10 @@ impl Arch for ArchArm64 {
|
||||
DecodeError::InvalidOpcode
|
||||
| DecodeError::InvalidOperand
|
||||
| DecodeError::IncompleteDecoder => {
|
||||
ops.push(ScannedInstruction {
|
||||
ins_ref: InstructionRef { address, size: 4, opcode: u16::MAX },
|
||||
ops.push(InstructionRef {
|
||||
address,
|
||||
size: 4,
|
||||
opcode: u16::MAX,
|
||||
branch_dest: None,
|
||||
});
|
||||
continue;
|
||||
@@ -68,9 +69,9 @@ impl Arch for ArchArm64 {
|
||||
}
|
||||
|
||||
let opcode = opcode_to_u16(ins.opcode);
|
||||
let ins_ref = InstructionRef { address, size: 4, opcode };
|
||||
let branch_dest = branch_dest(ins_ref, &code[offset as usize..offset as usize + 4]);
|
||||
ops.push(ScannedInstruction { ins_ref, branch_dest });
|
||||
let branch_dest =
|
||||
branch_dest(opcode, address, &code[offset as usize..offset as usize + 4]);
|
||||
ops.push(InstructionRef { address, size: 4, opcode, branch_dest });
|
||||
}
|
||||
|
||||
Ok(ops)
|
||||
@@ -163,7 +164,7 @@ impl Arch for ArchArm64 {
|
||||
}
|
||||
}
|
||||
|
||||
fn branch_dest(ins_ref: InstructionRef, code: &[u8]) -> Option<u64> {
|
||||
fn branch_dest(opcode: u16, address: u64, code: &[u8]) -> Option<u64> {
|
||||
const OPCODE_B: u16 = opcode_to_u16(Opcode::B);
|
||||
const OPCODE_BL: u16 = opcode_to_u16(Opcode::BL);
|
||||
const OPCODE_BCC: u16 = opcode_to_u16(Opcode::Bcc(0));
|
||||
@@ -173,21 +174,21 @@ fn branch_dest(ins_ref: InstructionRef, code: &[u8]) -> Option<u64> {
|
||||
const OPCODE_TBNZ: u16 = opcode_to_u16(Opcode::TBNZ);
|
||||
|
||||
let word = u32::from_le_bytes(code.try_into().ok()?);
|
||||
match ins_ref.opcode {
|
||||
match opcode {
|
||||
OPCODE_B | OPCODE_BL => {
|
||||
let offset = ((word & 0x03ff_ffff) << 2) as i32;
|
||||
let extended_offset = (offset << 4) >> 4;
|
||||
ins_ref.address.checked_add_signed(extended_offset as i64)
|
||||
address.checked_add_signed(extended_offset as i64)
|
||||
}
|
||||
OPCODE_BCC | OPCODE_CBZ | OPCODE_CBNZ => {
|
||||
let offset = (word as i32 & 0x00ff_ffe0) >> 3;
|
||||
let extended_offset = (offset << 11) >> 11;
|
||||
ins_ref.address.checked_add_signed(extended_offset as i64)
|
||||
address.checked_add_signed(extended_offset as i64)
|
||||
}
|
||||
OPCODE_TBZ | OPCODE_TBNZ => {
|
||||
let offset = (word as i32 & 0x0007_ffe0) >> 3;
|
||||
let extended_offset = (offset << 16) >> 16;
|
||||
ins_ref.address.checked_add_signed(extended_offset as i64)
|
||||
address.checked_add_signed(extended_offset as i64)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ use alloc::{
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use core::ops::Range;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, elf};
|
||||
@@ -19,7 +18,7 @@ use crate::{
|
||||
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory, display::InstructionPart},
|
||||
obj::{
|
||||
InstructionArg, InstructionArgValue, InstructionRef, Relocation, RelocationFlags,
|
||||
ResolvedInstructionRef, ResolvedRelocation, ScannedInstruction, SymbolFlag, SymbolFlagSet,
|
||||
ResolvedInstructionRef, ResolvedRelocation, SymbolFlag, SymbolFlagSet,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -189,16 +188,16 @@ impl ArchMips {
|
||||
}
|
||||
|
||||
impl Arch for ArchMips {
|
||||
fn scan_instructions(
|
||||
fn scan_instructions_internal(
|
||||
&self,
|
||||
address: u64,
|
||||
code: &[u8],
|
||||
_section_index: usize,
|
||||
_relocations: &[Relocation],
|
||||
diff_config: &DiffObjConfig,
|
||||
) -> Result<Vec<ScannedInstruction>> {
|
||||
) -> Result<Vec<InstructionRef>> {
|
||||
let instruction_flags = self.instruction_flags(diff_config);
|
||||
let mut ops = Vec::<ScannedInstruction>::with_capacity(code.len() / 4);
|
||||
let mut ops = Vec::<InstructionRef>::with_capacity(code.len() / 4);
|
||||
let mut cur_addr = address as u32;
|
||||
for chunk in code.chunks_exact(4) {
|
||||
let code = self.endianness.read_u32_bytes(chunk.try_into()?);
|
||||
@@ -206,10 +205,7 @@ impl Arch for ArchMips {
|
||||
rabbitizer::Instruction::new(code, Vram::new(cur_addr), instruction_flags);
|
||||
let opcode = instruction.opcode() as u16;
|
||||
let branch_dest = instruction.get_branch_vram_generic().map(|v| v.inner() as u64);
|
||||
ops.push(ScannedInstruction {
|
||||
ins_ref: InstructionRef { address: cur_addr as u64, size: 4, opcode },
|
||||
branch_dest,
|
||||
});
|
||||
ops.push(InstructionRef { address: cur_addr as u64, size: 4, opcode, branch_dest });
|
||||
cur_addr += 4;
|
||||
}
|
||||
Ok(ops)
|
||||
@@ -225,16 +221,7 @@ impl Arch for ArchMips {
|
||||
let display_flags = self.instruction_display_flags(diff_config);
|
||||
let opcode = instruction.opcode();
|
||||
cb(InstructionPart::opcode(opcode.name(), opcode as u16))?;
|
||||
let start_address = resolved.symbol.address;
|
||||
let function_range = start_address..start_address + resolved.symbol.size;
|
||||
push_args(
|
||||
&instruction,
|
||||
resolved.relocation,
|
||||
function_range,
|
||||
resolved.section_index,
|
||||
&display_flags,
|
||||
cb,
|
||||
)?;
|
||||
push_args(&instruction, resolved.relocation, &display_flags, cb)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -338,8 +325,6 @@ impl Arch for ArchMips {
|
||||
fn push_args(
|
||||
instruction: &rabbitizer::Instruction,
|
||||
relocation: Option<ResolvedRelocation>,
|
||||
function_range: Range<u64>,
|
||||
section_index: usize,
|
||||
display_flags: &rabbitizer::InstructionDisplayFlags,
|
||||
mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
|
||||
) -> Result<()> {
|
||||
@@ -362,23 +347,7 @@ fn push_args(
|
||||
}
|
||||
ValuedOperand::core_label(..) | ValuedOperand::core_branch_target_label(..) => {
|
||||
if let Some(resolved) = relocation {
|
||||
// If the relocation target is within the current function, we can
|
||||
// convert it into a relative branch target. Note that we check
|
||||
// target_address > start_address instead of >= so that recursive
|
||||
// tail calls are not considered branch targets.
|
||||
let target_address =
|
||||
resolved.symbol.address.checked_add_signed(resolved.relocation.addend);
|
||||
if resolved.symbol.section == Some(section_index)
|
||||
&& target_address.is_some_and(|addr| {
|
||||
addr > function_range.start && addr < function_range.end
|
||||
})
|
||||
{
|
||||
// TODO move this logic up a level
|
||||
let target_address = target_address.unwrap();
|
||||
arg_cb(InstructionPart::branch_dest(target_address))?;
|
||||
} else {
|
||||
push_reloc(resolved.relocation, &mut arg_cb)?;
|
||||
}
|
||||
push_reloc(resolved.relocation, &mut arg_cb)?;
|
||||
} else if let Some(branch_dest) = instruction
|
||||
.get_branch_offset_generic()
|
||||
.map(|o| (instruction.vram() + o).inner() as u64)
|
||||
|
||||
+120
-22
@@ -1,5 +1,8 @@
|
||||
use alloc::{borrow::Cow, boxed::Box, format, string::String, vec::Vec};
|
||||
use core::{ffi::CStr, fmt, fmt::Debug};
|
||||
use core::{
|
||||
ffi::CStr,
|
||||
fmt::{self, Debug},
|
||||
};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use encoding_rs::SHIFT_JIS;
|
||||
@@ -11,8 +14,9 @@ use crate::{
|
||||
display::{ContextItem, HoverItem, InstructionPart},
|
||||
},
|
||||
obj::{
|
||||
InstructionArg, Object, ParsedInstruction, Relocation, RelocationFlags,
|
||||
ResolvedInstructionRef, ScannedInstruction, Section, Symbol, SymbolFlagSet, SymbolKind,
|
||||
FlowAnalysisResult, InstructionArg, InstructionRef, Object, ParsedInstruction, Relocation,
|
||||
RelocationFlags, ResolvedInstructionRef, ResolvedSymbol, Section, Symbol, SymbolFlagSet,
|
||||
SymbolKind,
|
||||
},
|
||||
util::ReallySigned,
|
||||
};
|
||||
@@ -31,6 +35,7 @@ pub mod superh;
|
||||
pub mod x86;
|
||||
|
||||
/// Represents the type of data associated with an instruction
|
||||
#[derive(PartialEq)]
|
||||
pub enum DataType {
|
||||
Int8,
|
||||
Int16,
|
||||
@@ -182,46 +187,108 @@ impl DataType {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Arch: Send + Sync + Debug {
|
||||
// Finishes arch-specific initialization that must be done after sections have been combined.
|
||||
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol]) {}
|
||||
|
||||
impl dyn Arch {
|
||||
/// Generate a list of instructions references (offset, size, opcode) from the given code.
|
||||
///
|
||||
/// The opcode IDs are used to generate the initial diff. Implementations should do as little
|
||||
/// parsing as possible here: just enough to identify the base instruction opcode, size, and
|
||||
/// possible branch destination (for visual representation). As needed, instructions are parsed
|
||||
/// via `process_instruction` to compare their arguments.
|
||||
fn scan_instructions(
|
||||
/// See [`scan_instructions_internal`] for more details.
|
||||
pub fn scan_instructions(
|
||||
&self,
|
||||
address: u64,
|
||||
code: &[u8],
|
||||
section_index: usize,
|
||||
relocations: &[Relocation],
|
||||
resolved: ResolvedSymbol,
|
||||
diff_config: &DiffObjConfig,
|
||||
) -> Result<Vec<ScannedInstruction>>;
|
||||
) -> Result<Vec<InstructionRef>> {
|
||||
let mut result = self.scan_instructions_internal(
|
||||
resolved.symbol.address,
|
||||
resolved.data,
|
||||
resolved.section_index,
|
||||
&resolved.section.relocations,
|
||||
diff_config,
|
||||
)?;
|
||||
|
||||
let function_start = resolved.symbol.address;
|
||||
let function_end = function_start + resolved.symbol.size;
|
||||
|
||||
// Remove any branch destinations that are outside the function range
|
||||
for ins in result.iter_mut() {
|
||||
if let Some(branch_dest) = ins.branch_dest {
|
||||
if branch_dest < function_start || branch_dest >= function_end {
|
||||
ins.branch_dest = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve relocation targets within the same function to branch destinations
|
||||
let mut ins_iter = result.iter_mut().peekable();
|
||||
'outer: for reloc in resolved
|
||||
.section
|
||||
.relocations
|
||||
.iter()
|
||||
.skip_while(|r| r.address < function_start)
|
||||
.take_while(|r| r.address < function_end)
|
||||
{
|
||||
let ins = loop {
|
||||
let Some(ins) = ins_iter.peek_mut() else {
|
||||
break 'outer;
|
||||
};
|
||||
if reloc.address < ins.address {
|
||||
continue 'outer;
|
||||
}
|
||||
let ins = ins_iter.next().unwrap();
|
||||
if reloc.address >= ins.address && reloc.address < ins.address + ins.size as u64 {
|
||||
break ins;
|
||||
}
|
||||
};
|
||||
// Clear existing branch destination for instructions with relocations
|
||||
ins.branch_dest = None;
|
||||
let Some(target) = resolved.obj.symbols.get(reloc.target_symbol) else {
|
||||
continue;
|
||||
};
|
||||
if target.section != Some(resolved.section_index) {
|
||||
continue;
|
||||
}
|
||||
let Some(target_address) = target.address.checked_add_signed(reloc.addend) else {
|
||||
continue;
|
||||
};
|
||||
// If the target address is within the function range, set it as a branch destination
|
||||
if target_address >= function_start && target_address < function_end {
|
||||
ins.branch_dest = Some(target_address);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Parse an instruction to gather its mnemonic and arguments for more detailed comparison.
|
||||
///
|
||||
/// This is called only when we need to compare the arguments of an instruction.
|
||||
fn process_instruction(
|
||||
pub fn process_instruction(
|
||||
&self,
|
||||
resolved: ResolvedInstructionRef,
|
||||
diff_config: &DiffObjConfig,
|
||||
) -> Result<ParsedInstruction> {
|
||||
let mut mnemonic = None;
|
||||
let mut args = Vec::with_capacity(8);
|
||||
let mut relocation_emitted = false;
|
||||
self.display_instruction(resolved, diff_config, &mut |part| {
|
||||
match part {
|
||||
InstructionPart::Opcode(m, _) => mnemonic = Some(Cow::Owned(m.into_owned())),
|
||||
InstructionPart::Arg(arg) => args.push(arg.into_static()),
|
||||
InstructionPart::Arg(arg) => {
|
||||
if arg == InstructionArg::Reloc {
|
||||
relocation_emitted = true;
|
||||
// If the relocation was resolved to a branch destination, emit that instead.
|
||||
if let Some(dest) = resolved.ins_ref.branch_dest {
|
||||
args.push(InstructionArg::BranchDest(dest));
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
args.push(arg.into_static());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
// If the instruction has a relocation, but we didn't format it in the display, add it to
|
||||
// the end of the arguments list.
|
||||
if resolved.relocation.is_some() && !args.contains(&InstructionArg::Reloc) {
|
||||
if resolved.relocation.is_some() && !relocation_emitted {
|
||||
args.push(InstructionArg::Reloc);
|
||||
}
|
||||
Ok(ParsedInstruction {
|
||||
@@ -230,6 +297,26 @@ pub trait Arch: Send + Sync + Debug {
|
||||
args,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Arch: Send + Sync + Debug {
|
||||
/// Finishes arch-specific initialization that must be done after sections have been combined.
|
||||
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol]) {}
|
||||
|
||||
/// Generate a list of instructions references (offset, size, opcode) from the given code.
|
||||
///
|
||||
/// The opcode IDs are used to generate the initial diff. Implementations should do as little
|
||||
/// parsing as possible here: just enough to identify the base instruction opcode, size, and
|
||||
/// possible branch destination (for visual representation). As needed, instructions are parsed
|
||||
/// via `process_instruction` to compare their arguments.
|
||||
fn scan_instructions_internal(
|
||||
&self,
|
||||
address: u64,
|
||||
code: &[u8],
|
||||
section_index: usize,
|
||||
relocations: &[Relocation],
|
||||
diff_config: &DiffObjConfig,
|
||||
) -> Result<Vec<InstructionRef>>;
|
||||
|
||||
/// Format an instruction for display.
|
||||
///
|
||||
@@ -253,6 +340,17 @@ pub trait Arch: Send + Sync + Debug {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
// Perform detailed data flow analysis
|
||||
fn data_flow_analysis(
|
||||
&self,
|
||||
_obj: &Object,
|
||||
_symbol: &Symbol,
|
||||
_code: &[u8],
|
||||
_relocations: &[Relocation],
|
||||
) -> Option<Box<dyn FlowAnalysisResult>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn implcit_addend(
|
||||
&self,
|
||||
file: &object::File<'_>,
|
||||
@@ -332,14 +430,14 @@ impl ArchDummy {
|
||||
}
|
||||
|
||||
impl Arch for ArchDummy {
|
||||
fn scan_instructions(
|
||||
fn scan_instructions_internal(
|
||||
&self,
|
||||
_address: u64,
|
||||
_code: &[u8],
|
||||
_section_index: usize,
|
||||
_relocations: &[Relocation],
|
||||
_diff_config: &DiffObjConfig,
|
||||
) -> Result<Vec<ScannedInstruction>> {
|
||||
) -> Result<Vec<InstructionRef>> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
string::{String, ToString},
|
||||
vec,
|
||||
@@ -18,11 +19,13 @@ use crate::{
|
||||
display::{ContextItem, HoverItem, HoverItemColor, InstructionPart, SymbolNavigationKind},
|
||||
},
|
||||
obj::{
|
||||
InstructionRef, Object, Relocation, RelocationFlags, ResolvedInstructionRef,
|
||||
ResolvedRelocation, ScannedInstruction, Symbol, SymbolFlag, SymbolFlagSet,
|
||||
FlowAnalysisResult, InstructionRef, Object, Relocation, RelocationFlags,
|
||||
ResolvedInstructionRef, ResolvedRelocation, Symbol, SymbolFlag, SymbolFlagSet,
|
||||
},
|
||||
};
|
||||
|
||||
mod flow_analysis;
|
||||
|
||||
// Relative relocation, can be Simm, Offset or BranchDest
|
||||
fn is_relative_arg(arg: &ppc750cl::Argument) -> bool {
|
||||
matches!(
|
||||
@@ -82,24 +85,22 @@ impl ArchPpc {
|
||||
}
|
||||
|
||||
impl Arch for ArchPpc {
|
||||
fn scan_instructions(
|
||||
fn scan_instructions_internal(
|
||||
&self,
|
||||
address: u64,
|
||||
code: &[u8],
|
||||
_section_index: usize,
|
||||
_relocations: &[Relocation],
|
||||
_diff_config: &DiffObjConfig,
|
||||
) -> Result<Vec<ScannedInstruction>> {
|
||||
) -> Result<Vec<InstructionRef>> {
|
||||
ensure!(code.len() & 3 == 0, "Code length must be a multiple of 4");
|
||||
let ins_count = code.len() / 4;
|
||||
let mut insts = Vec::<ScannedInstruction>::with_capacity(ins_count);
|
||||
let mut insts = Vec::<InstructionRef>::with_capacity(ins_count);
|
||||
for (cur_addr, ins) in ppc750cl::InsIter::new(code, address as u32) {
|
||||
insts.push(ScannedInstruction {
|
||||
ins_ref: InstructionRef {
|
||||
address: cur_addr as u64,
|
||||
size: 4,
|
||||
opcode: u8::from(ins.op) as u16,
|
||||
},
|
||||
insts.push(InstructionRef {
|
||||
address: cur_addr as u64,
|
||||
size: 4,
|
||||
opcode: u8::from(ins.op) as u16,
|
||||
branch_dest: ins.branch_dest(cur_addr).map(u64::from),
|
||||
});
|
||||
}
|
||||
@@ -159,6 +160,7 @@ impl Arch for ArchPpc {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Could be replaced by data_flow_analysis once that feature stabilizes
|
||||
fn generate_pooled_relocations(
|
||||
&self,
|
||||
address: u64,
|
||||
@@ -169,6 +171,16 @@ impl Arch for ArchPpc {
|
||||
generate_fake_pool_relocations_for_function(address, code, relocations, symbols)
|
||||
}
|
||||
|
||||
fn data_flow_analysis(
|
||||
&self,
|
||||
obj: &Object,
|
||||
symbol: &Symbol,
|
||||
code: &[u8],
|
||||
relocations: &[Relocation],
|
||||
) -> Option<Box<dyn FlowAnalysisResult>> {
|
||||
Some(flow_analysis::ppc_data_flow_analysis(obj, symbol, code, relocations))
|
||||
}
|
||||
|
||||
fn implcit_addend(
|
||||
&self,
|
||||
_file: &object::File<'_>,
|
||||
@@ -227,7 +239,7 @@ impl Arch for ArchPpc {
|
||||
return Some(DataType::String);
|
||||
}
|
||||
let opcode = ppc750cl::Opcode::from(resolved.ins_ref.opcode as u8);
|
||||
if let Some(ty) = guess_data_type_from_load_store_inst_op(opcode) {
|
||||
if let Some(ty) = flow_analysis::guess_data_type_from_load_store_inst_op(opcode) {
|
||||
// Numeric type.
|
||||
return Some(ty);
|
||||
}
|
||||
@@ -503,25 +515,6 @@ fn make_symbol_ref(symbol: &object::Symbol) -> Result<ExtabSymbolRef> {
|
||||
Ok(ExtabSymbolRef { original_index: symbol.index().0 - 1, name, demangled_name })
|
||||
}
|
||||
|
||||
fn guess_data_type_from_load_store_inst_op(inst_op: ppc750cl::Opcode) -> Option<DataType> {
|
||||
use ppc750cl::Opcode;
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PoolReference {
|
||||
addr_src_gpr: ppc750cl::GPR,
|
||||
@@ -538,7 +531,7 @@ fn get_pool_reference_for_inst(
|
||||
) -> Option<PoolReference> {
|
||||
use ppc750cl::{Argument, Opcode};
|
||||
let args = &simplified.args;
|
||||
if guess_data_type_from_load_store_inst_op(opcode).is_some() {
|
||||
if flow_analysis::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.
|
||||
@@ -670,7 +663,7 @@ fn make_fake_pool_reloc(
|
||||
// and returns a Vec of "fake pool relocations" that simulate what a relocation for that instruction
|
||||
// would look like if data hadn't been pooled.
|
||||
// This method tries to follow the function's proper control flow. It keeps track of a queue of
|
||||
// states it hasn't traversed yet, where each state holds an instruction address and a HashMap of
|
||||
// states it hasn't traversed yet, where each state holds an instruction address and a map of
|
||||
// which registers hold which pool relocations at that point.
|
||||
// When a conditional or unconditional branch is encountered, the destination of the branch is added
|
||||
// to the queue. Conditional branches will traverse both the path where the branch is taken and the
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user