Compare commits

..

10 Commits

Author SHA1 Message Date
Luke Street 0c48d711c7 Improve local branch relocation handling
Reworks the local-branch handling logic to be more
unified: scan_instructions does all the work up front,
and process_instruction / display_instruction can
simply use the calculated branch destination instead
of performing their own is-relocation-target-
function-local checks.

(Hopefully) Fixes #192
2025-05-07 22:53:10 -06:00
Luke Street 34220a8e26 Add address to ReportItem, stabilize sections/functions ordering 2025-05-07 17:36:49 -06:00
Luke Street 0c9e5526d4 Combine sections when generating report 2025-05-07 16:47:20 -06:00
Luke Street 3db0727469 Omit match % for right sections, improve multi-section diffing
Fixes #120
2025-05-07 16:47:20 -06:00
Luke Street 8b5bf21f38 Mark combined sections as SectionKind::Unknown 2025-05-07 16:45:00 -06:00
Luke Street b77df77000 Minor cleanup, remove Section::symbol_data 2025-05-07 16:43:34 -06:00
Luke Street 7e08f9715b Update dependencies 2025-05-07 16:42:02 -06:00
Luke Street 3c05852d00 Document SuperH support 2025-05-06 23:25:29 -06:00
Luke Street a51ff44be1 Fix superh wasm (no_std) build 2025-05-06 23:21:07 -06:00
Luke Street d225cac205 Add superh feature to wasm build 2025-05-06 23:14:38 -06:00
43 changed files with 1784 additions and 668 deletions
Generated
+384 -305
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -14,7 +14,7 @@ strip = "debuginfo"
codegen-units = 1
[workspace.package]
version = "3.0.0-beta.6"
version = "3.0.0-beta.7"
authors = ["Luke Street <luke@street.dev>"]
edition = "2024"
license = "MIT OR Apache-2.0"
+5 -4
View File
@@ -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 (COFF only)
See [Usage](#usage) for more information.
+1
View File
@@ -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
+1 -1
View File
@@ -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"
+17 -4
View File
@@ -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 {
@@ -241,14 +245,12 @@ fn report_object(
for (symbol, symbol_diff) in obj.symbols.iter().zip(&obj_diff.symbols) {
if symbol.section != Some(section_idx)
|| symbol.size == 0
|| symbol.flags.contains(SymbolFlag::Hidden)
|| symbol.flags.contains(SymbolFlag::Ignored)
|| symbol.flags.contains(SymbolFlag::Hidden | SymbolFlag::Ignored)
{
continue;
}
if let Some(existing_functions) = &mut existing_functions {
if (symbol.flags.contains(SymbolFlag::Global)
|| symbol.flags.contains(SymbolFlag::Weak))
if symbol.flags.contains(SymbolFlag::Global | SymbolFlag::Weak)
&& !existing_functions.insert(symbol.name.clone())
{
continue;
@@ -272,6 +274,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 +282,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;
+3 -3
View File
@@ -133,7 +133,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 +158,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
@@ -200,4 +200,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"
+6 -5
View File
@@ -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).
Binary file not shown.
+2
View File
@@ -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
+16 -18
View File
@@ -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,7 +219,7 @@ 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);
@@ -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;
+15 -14
View File
@@ -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,
}
+7 -38
View File
@@ -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)
+103 -21
View File
@@ -11,8 +11,8 @@ use crate::{
display::{ContextItem, HoverItem, InstructionPart},
},
obj::{
InstructionArg, Object, ParsedInstruction, Relocation, RelocationFlags,
ResolvedInstructionRef, ScannedInstruction, Section, Symbol, SymbolFlagSet, SymbolKind,
InstructionArg, InstructionRef, Object, ParsedInstruction, Relocation, RelocationFlags,
ResolvedInstructionRef, ResolvedSymbol, Section, Symbol, SymbolFlagSet, SymbolKind,
},
util::ReallySigned,
};
@@ -182,46 +182,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 +292,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.
///
@@ -332,14 +414,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())
}
+8 -10
View File
@@ -19,7 +19,7 @@ use crate::{
},
obj::{
InstructionRef, Object, Relocation, RelocationFlags, ResolvedInstructionRef,
ResolvedRelocation, ScannedInstruction, Symbol, SymbolFlag, SymbolFlagSet,
ResolvedRelocation, Symbol, SymbolFlag, SymbolFlagSet,
},
};
@@ -82,24 +82,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),
});
}
+2
View File
@@ -1,3 +1,5 @@
use alloc::{format, vec::Vec};
use crate::{diff::display::InstructionPart, obj::ResolvedInstructionRef};
static REG_NAMES: [&str; 16] = [
+62 -25
View File
@@ -1,5 +1,4 @@
use alloc::{string::String, vec::Vec};
use std::collections::HashMap;
use alloc::{collections::BTreeMap, format, string::String, vec, vec::Vec};
use anyhow::{Result, bail};
use object::elf;
@@ -7,9 +6,7 @@ use object::elf;
use crate::{
arch::{Arch, superh::disasm::sh2_disasm},
diff::{DiffObjConfig, display::InstructionPart},
obj::{
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ScannedInstruction,
},
obj::{InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef},
};
pub mod disasm;
@@ -27,15 +24,15 @@ struct DataInfo {
}
impl Arch for ArchSuperH {
fn scan_instructions(
fn scan_instructions_internal(
&self,
address: u64,
code: &[u8],
_section_index: usize,
_relocations: &[Relocation],
_diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
let mut ops = Vec::<ScannedInstruction>::with_capacity(code.len() / 2);
) -> Result<Vec<InstructionRef>> {
let mut ops = Vec::<InstructionRef>::with_capacity(code.len() / 2);
let mut offset = address;
for chunk in code.chunks_exact(2) {
@@ -56,9 +53,7 @@ impl Arch for ArchSuperH {
Some(InstructionPart::Opcode(_, val)) => *val,
_ => 0,
};
let ins_ref: InstructionRef =
InstructionRef { address: offset, size: 2, opcode: opcode_enum };
ops.push(ScannedInstruction { ins_ref, branch_dest });
ops.push(InstructionRef { address: offset, size: 2, opcode: opcode_enum, branch_dest });
offset += 2;
}
@@ -77,10 +72,12 @@ impl Arch for ArchSuperH {
sh2_disasm(0, opcode, true, &mut parts, &resolved, &mut branch_dest);
if let Some(symbol_data) = resolved.section.symbol_data(resolved.symbol) {
if let Some(symbol_data) =
resolved.section.data_range(resolved.symbol.address, resolved.symbol.size as usize)
{
// scan for data
// map of instruction offsets to data target offsets
let mut data_offsets: HashMap<u64, DataInfo> = HashMap::<u64, DataInfo>::new();
let mut data_offsets = BTreeMap::<u64, DataInfo>::new();
let mut pos: u64 = 0;
for chunk in symbol_data.chunks_exact(2) {
@@ -255,7 +252,7 @@ mod test {
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1000, size: 2, opcode },
ins_ref: InstructionRef { address: 0x1000, size: 2, opcode, branch_dest: None },
code: &code,
..Default::default()
},
@@ -333,7 +330,7 @@ mod test {
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1000, size: 2, opcode },
ins_ref: InstructionRef { address: 0x1000, size: 2, opcode, branch_dest: None },
code: &code,
..Default::default()
},
@@ -416,7 +413,7 @@ mod test {
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1000, size: 2, opcode },
ins_ref: InstructionRef { address: 0x1000, size: 2, opcode, branch_dest: None },
code: &code,
..Default::default()
},
@@ -453,7 +450,7 @@ mod test {
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1000, size: 2, opcode },
ins_ref: InstructionRef { address: 0x1000, size: 2, opcode, branch_dest: None },
code: &code,
..Default::default()
},
@@ -502,7 +499,12 @@ mod test {
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: addr as u64, size: 2, opcode },
ins_ref: InstructionRef {
address: addr as u64,
size: 2,
opcode,
branch_dest: None,
},
code: &code,
..Default::default()
},
@@ -538,7 +540,12 @@ mod test {
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: addr as u64, size: 2, opcode },
ins_ref: InstructionRef {
address: addr as u64,
size: 2,
opcode,
branch_dest: None,
},
code: &code,
..Default::default()
},
@@ -577,7 +584,12 @@ mod test {
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: addr as u64, size: 2, opcode },
ins_ref: InstructionRef {
address: addr as u64,
size: 2,
opcode,
branch_dest: None,
},
code: &code,
..Default::default()
},
@@ -616,7 +628,12 @@ mod test {
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: addr as u64, size: 2, opcode },
ins_ref: InstructionRef {
address: addr as u64,
size: 2,
opcode,
branch_dest: None,
},
code: &code,
..Default::default()
},
@@ -648,7 +665,12 @@ mod test {
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: addr as u64, size: 2, opcode },
ins_ref: InstructionRef {
address: addr as u64,
size: 2,
opcode,
branch_dest: None,
},
code: &code,
..Default::default()
},
@@ -677,7 +699,12 @@ mod test {
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: addr as u64, size: 2, opcode },
ins_ref: InstructionRef {
address: addr as u64,
size: 2,
opcode,
branch_dest: None,
},
code: &code,
..Default::default()
},
@@ -709,7 +736,12 @@ mod test {
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: addr as u64, size: 2, opcode },
ins_ref: InstructionRef {
address: addr as u64,
size: 2,
opcode,
branch_dest: None,
},
code: &opcode.to_be_bytes(),
symbol: &Symbol {
address: 0x0606F378, // func base address
@@ -754,7 +786,12 @@ mod test {
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: addr as u64, size: 2, opcode },
ins_ref: InstructionRef {
address: addr as u64,
size: 2,
opcode,
branch_dest: None,
},
code: &opcode.to_be_bytes(),
symbol: &Symbol {
address: 0x0606F378, // func base address
+26 -31
View File
@@ -11,9 +11,7 @@ use object::{Endian as _, Object as _, ObjectSection as _, pe};
use crate::{
arch::Arch,
diff::{DiffObjConfig, X86Formatter, display::InstructionPart},
obj::{
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ScannedInstruction,
},
obj::{InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef},
};
#[derive(Debug)]
@@ -86,14 +84,14 @@ impl ArchX86 {
const DATA_OPCODE: u16 = u16::MAX - 1;
impl Arch for ArchX86 {
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 mut out = Vec::with_capacity(code.len() / 2);
let mut decoder = self.decoder(code, address);
let mut instruction = Instruction::default();
@@ -112,12 +110,10 @@ impl Arch for ArchX86 {
})?;
if decoder.set_position(decoder.position() + size).is_ok() {
decoder.set_ip(address + size as u64);
out.push(ScannedInstruction {
ins_ref: InstructionRef {
address,
size: size as u8,
opcode: DATA_OPCODE,
},
out.push(InstructionRef {
address,
size: size as u8,
opcode: DATA_OPCODE,
branch_dest: None,
});
reloc_iter.next();
@@ -134,12 +130,10 @@ impl Arch for ArchX86 {
OpKind::NearBranch64 => Some(instruction.near_branch64()),
_ => None,
};
out.push(ScannedInstruction {
ins_ref: InstructionRef {
address,
size: instruction.len() as u8,
opcode: instruction.mnemonic() as u16,
},
out.push(InstructionRef {
address,
size: instruction.len() as u8,
opcode: instruction.mnemonic() as u16,
branch_dest,
});
}
@@ -457,15 +451,16 @@ mod test {
0xc7, 0x85, 0x68, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x04, 0x85, 0x00,
0x00, 0x00, 0x00,
];
let scanned = arch.scan_instructions(0, &code, 0, &[], &DiffObjConfig::default()).unwrap();
let scanned =
arch.scan_instructions_internal(0, &code, 0, &[], &DiffObjConfig::default()).unwrap();
assert_eq!(scanned.len(), 2);
assert_eq!(scanned[0].ins_ref.address, 0);
assert_eq!(scanned[0].ins_ref.size, 10);
assert_eq!(scanned[0].ins_ref.opcode, iced_x86::Mnemonic::Mov as u16);
assert_eq!(scanned[0].address, 0);
assert_eq!(scanned[0].size, 10);
assert_eq!(scanned[0].opcode, iced_x86::Mnemonic::Mov as u16);
assert_eq!(scanned[0].branch_dest, None);
assert_eq!(scanned[1].ins_ref.address, 10);
assert_eq!(scanned[1].ins_ref.size, 7);
assert_eq!(scanned[1].ins_ref.opcode, iced_x86::Mnemonic::Mov as u16);
assert_eq!(scanned[1].address, 10);
assert_eq!(scanned[1].size, 7);
assert_eq!(scanned[1].opcode, iced_x86::Mnemonic::Mov as u16);
assert_eq!(scanned[1].branch_dest, None);
}
@@ -477,7 +472,7 @@ mod test {
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1234, size: 10, opcode },
ins_ref: InstructionRef { address: 0x1234, size: 10, opcode, branch_dest: None },
code: &code,
..Default::default()
},
@@ -513,7 +508,7 @@ mod test {
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1234, size: 10, opcode },
ins_ref: InstructionRef { address: 0x1234, size: 10, opcode, branch_dest: None },
code: &code,
relocation: Some(ResolvedRelocation {
relocation: &Relocation {
@@ -558,7 +553,7 @@ mod test {
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1234, size: 7, opcode },
ins_ref: InstructionRef { address: 0x1234, size: 7, opcode, branch_dest: None },
code: &code,
relocation: Some(ResolvedRelocation {
relocation: &Relocation {
@@ -601,7 +596,7 @@ mod test {
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1234, size: 5, opcode },
ins_ref: InstructionRef { address: 0x1234, size: 5, opcode, branch_dest: None },
code: &code,
relocation: Some(ResolvedRelocation {
relocation: &Relocation {
@@ -632,7 +627,7 @@ mod test {
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1234, size: 6, opcode },
ins_ref: InstructionRef { address: 0x1234, size: 6, opcode, branch_dest: None },
code: &code,
relocation: Some(ResolvedRelocation {
relocation: &Relocation {
@@ -671,7 +666,7 @@ mod test {
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1234, size: 7, opcode },
ins_ref: InstructionRef { address: 0x1234, size: 7, opcode, branch_dest: None },
code: &code,
relocation: Some(ResolvedRelocation {
relocation: &Relocation {
@@ -710,7 +705,7 @@ mod test {
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1234, size: 5, opcode },
ins_ref: InstructionRef { address: 0x1234, size: 5, opcode, branch_dest: None },
code: &code,
relocation: Some(ResolvedRelocation {
relocation: &Relocation {
+1
View File
@@ -434,6 +434,7 @@ impl From<LegacyReportItem> for ReportItem {
demangled_name: value.demangled_name,
virtual_address: value.address,
}),
address: None,
}
}
}
+43 -52
View File
@@ -14,15 +14,15 @@ use super::{
};
use crate::obj::{
InstructionArg, InstructionArgValue, InstructionRef, Object, ResolvedInstructionRef,
ResolvedRelocation, ScannedInstruction, SymbolFlag, SymbolKind,
ResolvedRelocation, ResolvedSymbol, SymbolFlag, SymbolKind,
};
pub fn no_diff_code(
obj: &Object,
symbol_idx: usize,
symbol_index: usize,
diff_config: &DiffObjConfig,
) -> Result<SymbolDiff> {
let symbol = &obj.symbols[symbol_idx];
let symbol = &obj.symbols[symbol_index];
let section_index = symbol.section.ok_or_else(|| anyhow!("Missing section for symbol"))?;
let section = &obj.sections[section_index];
let data = section.data_range(symbol.address, symbol.size as usize).ok_or_else(|| {
@@ -33,18 +33,14 @@ pub fn no_diff_code(
)
})?;
let ops = obj.arch.scan_instructions(
symbol.address,
data,
section_index,
&section.relocations,
ResolvedSymbol { obj, symbol_index, symbol, section_index, section, data },
diff_config,
)?;
let mut instruction_rows = Vec::<InstructionDiffRow>::new();
for i in &ops {
instruction_rows
.push(InstructionDiffRow { ins_ref: Some(i.ins_ref), ..Default::default() });
instruction_rows.push(InstructionDiffRow { ins_ref: Some(*i), ..Default::default() });
}
resolve_branches(obj, section_index, &ops, &mut instruction_rows);
resolve_branches(&ops, &mut instruction_rows);
Ok(SymbolDiff { target_symbol: None, match_percent: None, diff_score: None, instruction_rows })
}
@@ -92,22 +88,30 @@ pub fn diff_code(
let left_section_idx = left_symbol.section.unwrap();
let right_section_idx = right_symbol.section.unwrap();
let left_ops = left_obj.arch.scan_instructions(
left_symbol.address,
left_data,
left_section_idx,
&left_section.relocations,
ResolvedSymbol {
obj: left_obj,
symbol_index: left_symbol_idx,
symbol: left_symbol,
section_index: left_section_idx,
section: left_section,
data: left_data,
},
diff_config,
)?;
let right_ops = right_obj.arch.scan_instructions(
right_symbol.address,
right_data,
right_section_idx,
&right_section.relocations,
ResolvedSymbol {
obj: right_obj,
symbol_index: right_symbol_idx,
symbol: right_symbol,
section_index: right_section_idx,
section: right_section,
data: right_data,
},
diff_config,
)?;
let (mut left_rows, mut right_rows) = diff_instructions(&left_ops, &right_ops)?;
resolve_branches(left_obj, left_section_idx, &left_ops, &mut left_rows);
resolve_branches(right_obj, right_section_idx, &right_ops, &mut right_rows);
resolve_branches(&left_ops, &mut left_rows);
resolve_branches(&right_ops, &mut right_rows);
let mut diff_state = InstructionDiffState::default();
for (left_row, right_row) in left_rows.iter_mut().zip(right_rows.iter_mut()) {
@@ -154,21 +158,21 @@ pub fn diff_code(
}
fn diff_instructions(
left_insts: &[ScannedInstruction],
right_insts: &[ScannedInstruction],
left_insts: &[InstructionRef],
right_insts: &[InstructionRef],
) -> Result<(Vec<InstructionDiffRow>, Vec<InstructionDiffRow>)> {
let left_ops = left_insts.iter().map(|i| i.ins_ref.opcode).collect::<Vec<_>>();
let right_ops = right_insts.iter().map(|i| i.ins_ref.opcode).collect::<Vec<_>>();
let left_ops = left_insts.iter().map(|i| i.opcode).collect::<Vec<_>>();
let right_ops = right_insts.iter().map(|i| i.opcode).collect::<Vec<_>>();
let ops = similar::capture_diff_slices(similar::Algorithm::Patience, &left_ops, &right_ops);
if ops.is_empty() {
ensure!(left_insts.len() == right_insts.len());
let left_diff = left_insts
.iter()
.map(|i| InstructionDiffRow { ins_ref: Some(i.ins_ref), ..Default::default() })
.map(|i| InstructionDiffRow { ins_ref: Some(*i), ..Default::default() })
.collect();
let right_diff = right_insts
.iter()
.map(|i| InstructionDiffRow { ins_ref: Some(i.ins_ref), ..Default::default() })
.map(|i| InstructionDiffRow { ins_ref: Some(*i), ..Default::default() })
.collect();
return Ok((left_diff, right_diff));
}
@@ -187,14 +191,17 @@ fn diff_instructions(
for op in ops {
let (_tag, left_range, right_range) = op.as_tag_tuple();
let len = left_range.len().max(right_range.len());
left_diff.extend(left_range.clone().map(|i| InstructionDiffRow {
ins_ref: Some(left_insts[i].ins_ref),
..Default::default()
}));
right_diff.extend(right_range.clone().map(|i| InstructionDiffRow {
ins_ref: Some(right_insts[i].ins_ref),
..Default::default()
}));
left_diff.extend(
left_range
.clone()
.map(|i| InstructionDiffRow { ins_ref: Some(left_insts[i]), ..Default::default() }),
);
right_diff.extend(
right_range.clone().map(|i| InstructionDiffRow {
ins_ref: Some(right_insts[i]),
..Default::default()
}),
);
if left_range.len() < len {
left_diff.extend((left_range.len()..len).map(|_| InstructionDiffRow::default()));
}
@@ -215,13 +222,7 @@ fn arg_to_string(arg: &InstructionArg, reloc: Option<ResolvedRelocation>) -> Str
}
}
fn resolve_branches(
obj: &Object,
section_index: usize,
ops: &[ScannedInstruction],
rows: &mut [InstructionDiffRow],
) {
let section = &obj.sections[section_index];
fn resolve_branches(ops: &[InstructionRef], rows: &mut [InstructionDiffRow]) {
let mut branch_idx = 0u32;
// Map addresses to indices
let mut addr_map = BTreeMap::<u64, u32>::new();
@@ -235,17 +236,7 @@ fn resolve_branches(
for ((i, ins_diff), ins) in
rows.iter_mut().enumerate().filter(|(_, row)| row.ins_ref.is_some()).zip(ops)
{
let branch_dest = if let Some(resolved) = section.relocation_at(obj, ins.ins_ref) {
if resolved.symbol.section == Some(section_index) {
// If the relocation target is in the same section, use it as the branch destination
resolved.symbol.address.checked_add_signed(resolved.relocation.addend)
} else {
None
}
} else {
ins.branch_dest
};
if let Some(ins_idx) = branch_dest.and_then(|a| addr_map.get(&a).copied()) {
if let Some(ins_idx) = ins.branch_dest.and_then(|a| addr_map.get(&a).copied()) {
match branches.entry(ins_idx) {
btree_map::Entry::Vacant(e) => {
ins_diff.branch_to = Some(InstructionBranchTo { ins_idx, branch_idx });

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