Compare commits

..

16 Commits

Author SHA1 Message Date
Steven Casper 9b557e4c8e Limit left-panel scrollview to the file tree (#185)
* Limit left-panel scrollview to the file tree

Removes the redundant build button

* Expand ScrollArea to full side panel width

* Use auto_shrink(false) instead of set_width

---------

Co-authored-by: Luke Street <luke@street.dev>
2025-04-01 12:35:17 -06:00
LagoLunatic b9ba5796ed PPC: Fix pooled relocation addends being added twice sometimes (#184) 2025-03-30 22:00:36 -06:00
LagoLunatic e101610416 ARM: Fix subtract with overflow error when no mapping symbol at address 0 (#183) 2025-03-30 22:00:06 -06:00
LagoLunatic 196c003a92 Reimplement colorized data relocation hover diffs (#182)
* Reimplement colorized data relocation hover diffs

* Fix objdiff-wasm build

Data diffing doesn't seem to be fully implemented in objdiff-wasm yet, so just putting placeholders in so it compiles.

* Reloc hover: Add separators, override special color too
2025-03-28 21:48:14 -06:00
Luke Street 7b00a9e9f2 wasm: Cache objects via data hash (XXH3) 2025-03-21 08:27:19 -06:00
Luke Street 311de887ec Update test snapshot 2025-03-19 19:00:57 -06:00
Luke Street 485b259c32 Apply clippy suggestion 2025-03-19 19:00:01 -06:00
Luke Street d8fdfaa2c0 Fix no_std build 2025-03-19 18:57:41 -06:00
Luke Street 2612cda1fb objdiff-cli report: Skip unknown sections
Regression in v3.0.0-alpha.1

Fixes #171
2025-03-19 18:54:34 -06:00
Luke Street bc46e17824 x86_64: Fix relocation placement in instruction 2025-03-19 18:54:34 -06:00
Luke Street e735adbd3d x86: Support inline data for jumptables 2025-03-19 18:54:34 -06:00
Luke Street 6768df9d80 Version v3.0.0-beta.4 2025-03-18 21:36:37 -06:00
Luke Street eaba2391e0 arm: Fix thumb & data decoding, add tests 2025-03-18 21:26:28 -06:00
Luke Street bd8f5ede23 Add pre-commit hooks 2025-03-18 21:26:28 -06:00
Luke Street acf46c6b54 Fix no_std build 2025-03-14 09:49:31 -06:00
Luke Street 9358d8ec60 mips: Add C++ symbol demangling (CW & modern GCC) 2025-03-14 09:44:15 -06:00
40 changed files with 6854 additions and 142 deletions
+2
View File
@@ -0,0 +1,2 @@
[*.md]
trim_trailing_whitespace = false
+8 -8
View File
@@ -4,8 +4,8 @@ on:
pull_request:
push:
paths-ignore:
- '*.md'
- 'LICENSE*'
- "*.md"
- "LICENSE*"
workflow_dispatch:
env:
@@ -32,9 +32,9 @@ jobs:
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo check
run: cargo check --features all
run: cargo check --all-targets --all-features
- name: Cargo clippy
run: cargo clippy --features all
run: cargo clippy --all-targets --all-features
fmt:
name: Format
@@ -72,7 +72,7 @@ jobs:
name: Test
strategy:
matrix:
platform: [ ubuntu-latest, windows-latest, macos-latest ]
platform: [ubuntu-latest, windows-latest, macos-latest]
fail-fast: false
runs-on: ${{ matrix.platform }}
steps:
@@ -159,7 +159,7 @@ jobs:
key: ${{ matrix.target }}
- name: Cargo build
run: >
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
@@ -226,7 +226,7 @@ jobs:
key: ${{ matrix.target }}
- name: Cargo build
run: >
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
@@ -258,7 +258,7 @@ jobs:
name: Release
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [ build-cli, build-gui ]
needs: [build-cli, build-gui]
permissions:
contents: write
steps:
+36
View File
@@ -0,0 +1,36 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer
- id: fix-byte-order-marker
- id: check-yaml
- id: check-added-large-files
- repo: local
hooks:
- id: cargo-fmt
name: cargo fmt
description: Run cargo fmt on all project files.
language: system
entry: cargo
args: ["+nightly", "fmt", "--all"]
pass_filenames: false
- id: cargo clippy
name: cargo clippy
description: Run cargo clippy on all project files.
language: system
entry: cargo
args: ["+nightly", "clippy", "--all-targets", "--all-features"]
pass_filenames: false
- id: cargo-deny
name: cargo deny
description: Run cargo deny on all project files.
language: system
entry: cargo
args: ["deny", "check"]
pass_filenames: false
always_run: true
Generated
+11 -4
View File
@@ -3280,7 +3280,7 @@ dependencies = [
[[package]]
name = "objdiff-cli"
version = "3.0.0-beta.3"
version = "3.0.0-beta.5"
dependencies = [
"anyhow",
"argp",
@@ -3303,7 +3303,7 @@ dependencies = [
[[package]]
name = "objdiff-core"
version = "3.0.0-beta.3"
version = "3.0.0-beta.5"
dependencies = [
"anyhow",
"arm-attr",
@@ -3356,7 +3356,7 @@ dependencies = [
[[package]]
name = "objdiff-gui"
version = "3.0.0-beta.3"
version = "3.0.0-beta.5"
dependencies = [
"anyhow",
"cfg-if",
@@ -3392,7 +3392,7 @@ dependencies = [
[[package]]
name = "objdiff-wasm"
version = "3.0.0-beta.3"
version = "3.0.0-beta.5"
dependencies = [
"log",
"objdiff-core",
@@ -3400,6 +3400,7 @@ dependencies = [
"talc",
"wit-bindgen",
"wit-deps",
"xxhash-rust",
]
[[package]]
@@ -6547,6 +6548,12 @@ version = "0.8.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4"
[[package]]
name = "xxhash-rust"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3"
[[package]]
name = "yaxpeax-arch"
version = "0.3.2"
+1 -1
View File
@@ -14,7 +14,7 @@ strip = "debuginfo"
codegen-units = 1
[workspace.package]
version = "3.0.0-beta.3"
version = "3.0.0-beta.5"
authors = ["Luke Street <luke@street.dev>"]
edition = "2024"
license = "MIT OR Apache-2.0"
+19 -4
View File
@@ -143,15 +143,15 @@ If not specified, objdiff will use the default patterns listed above.
`units` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
>
>
> `target_path`: Path to the "target" or "expected" object from the project root.
> This object is the **intended result** of the match.
>
>
> `base_path`: Path to the "base" or "actual" object from the project root.
> This object is built from the **current source code**.
>
>
> `metadata.auto_generated` _(optional)_: Hides the object from the object list, but still includes it in reports.
>
>
> `metadata.complete` _(optional)_: Marks the object as "complete" (or "linked") in the object list.
> This is useful for marking objects that are fully decompiled. A value of `false` will mark the object as "incomplete".
@@ -173,6 +173,21 @@ $ cargo install --locked --git https://github.com/encounter/objdiff.git objdiff-
The binaries will be installed to `~/.cargo/bin` as `objdiff` and `objdiff-cli`.
## Installing `pre-commit`
When contributing, it's recommended to install `pre-commit` to automatically run the linter and formatter before a commit.
[`uv`](https://github.com/astral-sh/uv#installation) is recommended to manage Python version and tools.
Rust nightly is required for `cargo +nightly fmt` and `cargo +nightly clippy`.
```shell
$ cargo install --locked cargo-deny
$ rustup toolchain install nightly
$ uv tool install pre-commit
$ pre-commit install
```
## License
Licensed under either of
+3
View File
@@ -202,6 +202,9 @@ fn report_object(
for ((section_idx, section), section_diff) in
obj.sections.iter().enumerate().zip(&obj_diff.sections)
{
if section.kind == SectionKind::Unknown {
continue;
}
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
// Support cases where we don't have a target object,
// assume complete means 100% match
+2
View File
@@ -81,6 +81,8 @@ std = [
]
mips = [
"any-arch",
"dep:cpp_demangle",
"dep:cwdemangle",
"dep:rabbitizer",
]
ppc = [
+1 -1
View File
@@ -275,4 +275,4 @@
]
}
]
}
}
+130 -48
View File
@@ -14,7 +14,7 @@ use crate::{
arch::Arch,
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, display::InstructionPart},
obj::{
InstructionRef, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
ScannedInstruction, SymbolFlag, SymbolFlagSet, SymbolKind,
},
};
@@ -84,18 +84,11 @@ impl ArchArm {
.filter_map(|s| DisasmMode::from_symbol(&s))
.collect();
mapping_symbols.sort_unstable_by_key(|x| x.address);
(s.index().0, mapping_symbols)
(s.index().0 - 1, mapping_symbols)
})
.collect()
}
fn endian(&self) -> unarm::Endian {
match self.endianness {
object::Endianness::Little => unarm::Endian::Little,
object::Endianness::Big => unarm::Endian::Big,
}
}
fn parse_flags(&self, diff_config: &DiffObjConfig) -> unarm::ParseFlags {
unarm::ParseFlags {
ual: diff_config.arm_unified_syntax,
@@ -130,6 +123,31 @@ impl ArchArm {
code: &[u8],
diff_config: &DiffObjConfig,
) -> Result<(unarm::Ins, unarm::ParsedIns)> {
if ins_ref.opcode == thumb::Opcode::BlH as u16 && ins_ref.size == 4 {
// Special case: combined thumb BL instruction
let parse_flags = self.parse_flags(diff_config);
let first_ins = thumb::Ins {
code: match self.endianness {
object::Endianness::Little => u16::from_le_bytes([code[0], code[1]]),
object::Endianness::Big => u16::from_be_bytes([code[0], code[1]]),
} as u32,
op: thumb::Opcode::BlH,
};
let second_ins = thumb::Ins::new(
match self.endianness {
object::Endianness::Little => u16::from_le_bytes([code[2], code[3]]),
object::Endianness::Big => u16::from_be_bytes([code[2], code[3]]),
} as u32,
&parse_flags,
);
let first_parsed = first_ins.parse(&parse_flags);
let second_parsed = second_ins.parse(&parse_flags);
return Ok((
unarm::Ins::Thumb(first_ins),
first_parsed.combine_thumb_bl(&second_parsed),
));
}
let code = match (self.endianness, ins_ref.size) {
(object::Endianness::Little, 2) => u16::from_le_bytes([code[0], code[1]]) as u32,
(object::Endianness::Little, 4) => {
@@ -153,11 +171,7 @@ impl ArchArm {
} else {
let ins = thumb::Ins { code, op: thumb::Opcode::from(ins_ref.opcode as u8) };
let parsed = ins.parse(&self.parse_flags(diff_config));
if ins.is_half_bl() {
todo!("Combine thumb BL instructions");
} else {
(unarm::Ins::Thumb(ins), parsed)
}
(unarm::Ins::Thumb(ins), parsed)
};
Ok((ins, parsed_ins))
}
@@ -169,6 +183,7 @@ impl Arch for ArchArm {
address: u64,
code: &[u8],
section_index: usize,
_relocations: &[Relocation],
diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
let start_addr = address as u32;
@@ -184,61 +199,128 @@ impl Arch for ArchArm {
.unwrap_or(&fallback_mappings);
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;
.unwrap_or_else(|idx| idx.saturating_sub(1));
let mut mode = mapping_symbols[first_mapping_idx].mapping;
let mut mappings_iter =
mapping_symbols.iter().skip(first_mapping_idx + 1).take_while(|x| x.address < end_addr);
let mut mappings_iter = mapping_symbols
.iter()
.copied()
.skip(first_mapping_idx + 1)
.take_while(|x| x.address < end_addr);
let mut next_mapping = mappings_iter.next();
let ins_count = code.len() / first_mapping.instruction_size(start_addr);
let ins_count = code.len() / mode.instruction_size(start_addr);
let mut ops = Vec::<ScannedInstruction>::with_capacity(ins_count);
let endian = self.endian();
let parse_flags = self.parse_flags(diff_config);
let mut parser = unarm::Parser::new(first_mapping, start_addr, endian, parse_flags, code);
while let Some((address, ins, _parsed_ins)) = parser.next() {
let size = parser.mode.instruction_size(address);
if let Some(next) = next_mapping {
let next_address = parser.address;
if next_address >= next.address {
// Change mapping
parser.mode = next.mapping;
next_mapping = mappings_iter.next();
}
let mut address = start_addr;
while address < end_addr {
while let Some(next) = next_mapping.take_if(|x| address >= x.address) {
// Change mapping
mode = next.mapping;
next_mapping = mappings_iter.next();
}
let (opcode, branch_dest) = match ins {
unarm::Ins::Arm(x) => {
let opcode = x.op as u16 | (1 << 15);
let branch_dest = match x.op {
let mut ins_size = mode.instruction_size(address);
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,
},
branch_dest: None,
});
break;
}
let code = match (self.endianness, ins_size) {
(object::Endianness::Little, 2) => u16::from_le_bytes([data[0], data[1]]) as u32,
(object::Endianness::Little, 4) => {
u32::from_le_bytes([data[0], data[1], data[2], data[3]])
}
(object::Endianness::Big, 2) => u16::from_be_bytes([data[0], data[1]]) as u32,
(object::Endianness::Big, 4) => {
u32::from_be_bytes([data[0], data[1], data[2], data[3]])
}
_ => {
// Invalid instruction size
ops.push(ScannedInstruction {
ins_ref: InstructionRef {
address: address as u64,
size: ins_size as u8,
opcode: u16::MAX,
},
branch_dest: None,
});
address += ins_size as u32;
continue;
}
};
let (opcode, branch_dest) = match mode {
unarm::ParseMode::Arm => {
let ins = arm::Ins::new(code, &parse_flags);
let opcode = ins.op as u16 | (1 << 15);
let branch_dest = match ins.op {
arm::Opcode::B | arm::Opcode::Bl => {
address.checked_add_signed(x.field_branch_offset())
address.checked_add_signed(ins.field_branch_offset())
}
arm::Opcode::BlxI => address.checked_add_signed(x.field_blx_offset()),
arm::Opcode::BlxI => address.checked_add_signed(ins.field_blx_offset()),
_ => None,
};
(opcode, branch_dest)
}
unarm::Ins::Thumb(x) => {
let opcode = x.op as u16;
let branch_dest = match x.op {
unarm::ParseMode::Thumb => {
let ins = thumb::Ins::new(code, &parse_flags);
let opcode = ins.op as u16;
let branch_dest = match ins.op {
thumb::Opcode::B | thumb::Opcode::Bl => {
address.checked_add_signed(x.field_branch_offset_8())
address.checked_add_signed(ins.field_branch_offset_8())
}
thumb::Opcode::BlH if data.len() >= 4 => {
// Combine BL instructions
let second_ins = thumb::Ins::new(
match self.endianness {
object::Endianness::Little => {
u16::from_le_bytes([data[2], data[3]]) as u32
}
object::Endianness::Big => {
u16::from_be_bytes([data[2], data[3]]) as u32
}
},
&parse_flags,
);
if let Some(low) = match second_ins.op {
thumb::Opcode::Bl => Some(second_ins.field_low_branch_offset_11()),
thumb::Opcode::BlxI => Some(second_ins.field_low_blx_offset_11()),
_ => None,
} {
ins_size = 4;
address.checked_add_signed(
(ins.field_high_branch_offset_11() + (low as i32)) << 9 >> 9,
)
} else {
None
}
}
thumb::Opcode::BLong => {
address.checked_add_signed(x.field_branch_offset_11())
address.checked_add_signed(ins.field_branch_offset_11())
}
_ => None,
};
(opcode, branch_dest)
}
unarm::Ins::Data => (u16::MAX, None),
unarm::ParseMode::Data => (u16::MAX, None),
};
ops.push(ScannedInstruction {
ins_ref: InstructionRef { address: address as u64, size: size as u8, opcode },
ins_ref: 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;
}
Ok(ops)
@@ -391,7 +473,7 @@ fn push_args(
let reloc_arg = find_reloc_arg(parsed_ins, relocation);
let mut writeback = false;
let mut deref = false;
for (i, arg) in parsed_ins.args_iter().enumerate() {
for (i, &arg) in parsed_ins.args_iter().enumerate() {
// Emit punctuation before separator
if deref {
match arg {
@@ -463,19 +545,19 @@ fn push_args(
| args::Argument::CoOpcode(value)
| args::Argument::SatImm(value) => {
arg_cb(InstructionPart::basic("#"))?;
arg_cb(InstructionPart::unsigned(*value))?;
arg_cb(InstructionPart::unsigned(value))?;
}
args::Argument::SImm(value)
| args::Argument::OffsetImm(args::OffsetImm { post_indexed: _, value }) => {
arg_cb(InstructionPart::basic("#"))?;
arg_cb(InstructionPart::signed(*value))?;
arg_cb(InstructionPart::signed(value))?;
}
args::Argument::BranchDest(value) => {
arg_cb(InstructionPart::branch_dest(cur_addr.wrapping_add_signed(*value)))?;
arg_cb(InstructionPart::branch_dest(cur_addr.wrapping_add_signed(value)))?;
}
args::Argument::CoOption(value) => {
arg_cb(InstructionPart::basic("{"))?;
arg_cb(InstructionPart::unsigned(*value))?;
arg_cb(InstructionPart::unsigned(value))?;
arg_cb(InstructionPart::basic("}"))?;
}
args::Argument::CoprocNum(value) => {
+2 -1
View File
@@ -17,7 +17,7 @@ use crate::{
arch::Arch,
diff::{DiffObjConfig, display::InstructionPart},
obj::{
InstructionRef, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
ScannedInstruction,
},
};
@@ -35,6 +35,7 @@ impl Arch for ArchArm64 {
address: u64,
code: &[u8],
_section_index: usize,
_relocations: &[Relocation],
_diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
let start_address = address;
+9 -1
View File
@@ -1,6 +1,6 @@
use alloc::{
collections::{BTreeMap, BTreeSet},
string::ToString,
string::{String, ToString},
vec::Vec,
};
use core::ops::Range;
@@ -194,6 +194,7 @@ impl Arch for ArchMips {
address: u64,
code: &[u8],
_section_index: usize,
_relocations: &[Relocation],
diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
let instruction_flags = self.instruction_flags(diff_config);
@@ -286,6 +287,13 @@ impl Arch for ArchMips {
})
}
fn demangle(&self, name: &str) -> Option<String> {
cpp_demangle::Symbol::new(name)
.ok()
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
.or_else(|| cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default()))
}
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
match flags {
RelocationFlags::Elf(r_type) => match r_type {
+2
View File
@@ -170,6 +170,7 @@ pub trait Arch: Send + Sync + Debug {
address: u64,
code: &[u8],
section_index: usize,
relocations: &[Relocation],
diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>>;
@@ -301,6 +302,7 @@ impl Arch for ArchDummy {
_address: u64,
_code: &[u8],
_section_index: usize,
_relocations: &[Relocation],
_diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
Ok(Vec::new())
+3 -1
View File
@@ -87,6 +87,7 @@ impl Arch for ArchPpc {
address: u64,
code: &[u8],
_section_index: usize,
_relocations: &[Relocation],
_diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
ensure!(code.len() & 3 == 0, "Code length must be a multiple of 4");
@@ -512,6 +513,7 @@ fn guess_data_type_from_load_store_inst_op(inst_op: ppc750cl::Opcode) -> Option<
}
}
#[derive(Debug)]
struct PoolReference {
addr_src_gpr: ppc750cl::GPR,
addr_offset: i16,
@@ -644,7 +646,7 @@ fn make_fake_pool_reloc(
// example, dCcD_Cyl in The Wind Waker). So just showing that vtable symbol plus an addend
// to represent the offset into it works fine in this case.
target_symbol = pool_reloc.relocation.target_symbol;
addend = pool_reloc.relocation.addend + offset_from_pool;
addend = offset_from_pool;
}
Some(Relocation {
flags: RelocationFlags::Elf(elf::R_PPC_NONE),
+127 -6
View File
@@ -1,6 +1,7 @@
use alloc::{boxed::Box, string::String, vec::Vec};
use alloc::{boxed::Box, format, string::String, vec::Vec};
use core::cmp::Ordering;
use anyhow::{Result, anyhow, bail};
use anyhow::{Context, Result, anyhow, bail};
use iced_x86::{
Decoder, DecoderOptions, DecoratorKind, FormatterOutput, FormatterTextKind, GasFormatter,
Instruction, IntelFormatter, MasmFormatter, NasmFormatter, NumberKind, OpKind, Register,
@@ -10,7 +11,9 @@ use object::{Endian as _, Object as _, ObjectSection as _, pe};
use crate::{
arch::Arch,
diff::{DiffObjConfig, X86Formatter, display::InstructionPart},
obj::{InstructionRef, RelocationFlags, ResolvedInstructionRef, ScannedInstruction},
obj::{
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ScannedInstruction,
},
};
#[derive(Debug)]
@@ -80,18 +83,50 @@ impl ArchX86 {
}
}
const DATA_OPCODE: u16 = u16::MAX - 1;
impl Arch for ArchX86 {
fn scan_instructions(
&self,
address: u64,
code: &[u8],
_section_index: usize,
relocations: &[Relocation],
_diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
let mut out = Vec::with_capacity(code.len() / 2);
let mut decoder = self.decoder(code, address);
let mut instruction = Instruction::default();
while decoder.can_decode() {
let mut reloc_iter = relocations.iter().peekable();
'outer: while decoder.can_decode() {
let address = decoder.ip();
while let Some(reloc) = reloc_iter.peek() {
match reloc.address.cmp(&address) {
Ordering::Less => {
reloc_iter.next();
}
Ordering::Equal => {
// If the instruction starts at a relocation, it's inline data
let size = self.reloc_size(reloc.flags).with_context(|| {
format!("Unsupported inline x86 relocation {:?}", reloc.flags)
})?;
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,
},
branch_dest: None,
});
reloc_iter.next();
continue 'outer;
}
}
Ordering::Greater => break,
}
}
decoder.decode_out(&mut instruction);
let branch_dest = match instruction.op0_kind() {
OpKind::NearBranch16 => Some(instruction.near_branch16() as u64),
@@ -101,7 +136,7 @@ impl Arch for ArchX86 {
};
out.push(ScannedInstruction {
ins_ref: InstructionRef {
address: instruction.ip(),
address,
size: instruction.len() as u8,
opcode: instruction.mnemonic() as u16,
},
@@ -117,6 +152,21 @@ impl Arch for ArchX86 {
diff_config: &DiffObjConfig,
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
if resolved.ins_ref.opcode == DATA_OPCODE {
let (mnemonic, imm) = match resolved.ins_ref.size {
2 => (".word", self.endianness.read_u16_bytes(resolved.code.try_into()?) as u64),
4 => (".dword", self.endianness.read_u32_bytes(resolved.code.try_into()?) as u64),
_ => bail!("Unsupported x86 inline data size {}", resolved.ins_ref.size),
};
cb(InstructionPart::opcode(mnemonic, DATA_OPCODE))?;
if resolved.relocation.is_some() {
cb(InstructionPart::reloc())?;
} else {
cb(InstructionPart::unsigned(imm))?;
}
return Ok(());
}
let mut decoder = self.decoder(resolved.code, resolved.ins_ref.address);
let mut formatter = self.formatter(diff_config);
let mut instruction = Instruction::default();
@@ -329,6 +379,7 @@ impl FormatterOutput for InstructionFormatterOutput<'_> {
(NumberKind::Int8 | NumberKind::UInt8, 1)
| (NumberKind::Int16 | NumberKind::UInt16, 2)
| (NumberKind::Int32 | NumberKind::UInt32, 4)
| (NumberKind::Int64 | NumberKind::UInt64, 4) // x86_64
| (NumberKind::Int64 | NumberKind::UInt64, 8) => true,
_ => false,
}
@@ -406,7 +457,7 @@ 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(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);
@@ -611,4 +662,74 @@ mod test {
InstructionPart::basic("]"),
]);
}
#[test]
fn test_process_x86_64_instruction_with_reloc_1() {
let arch = ArchX86 { arch: Architecture::X86_64, endianness: object::Endianness::Little };
let code = [0x48, 0x8b, 0x05, 0x00, 0x00, 0x00, 0x00];
let opcode = iced_x86::Mnemonic::Mov as u16;
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1234, size: 7, opcode },
code: &code,
relocation: Some(ResolvedRelocation {
relocation: &Relocation {
flags: RelocationFlags::Coff(pe::IMAGE_REL_AMD64_REL32),
address: 0x1234 + 3,
target_symbol: 0,
addend: 0,
},
symbol: &Default::default(),
}),
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
assert_eq!(parts, &[
InstructionPart::opcode("mov", opcode),
InstructionPart::opaque("rax"),
InstructionPart::basic(","),
InstructionPart::basic(" "),
InstructionPart::basic("["),
InstructionPart::reloc(),
InstructionPart::basic("]"),
]);
}
#[test]
fn test_process_x86_64_instruction_with_reloc_2() {
let arch = ArchX86 { arch: Architecture::X86_64, endianness: object::Endianness::Little };
let code = [0xe8, 0x00, 0x00, 0x00, 0x00];
let opcode = iced_x86::Mnemonic::Call as u16;
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1234, size: 5, opcode },
code: &code,
relocation: Some(ResolvedRelocation {
relocation: &Relocation {
flags: RelocationFlags::Coff(pe::IMAGE_REL_AMD64_REL32),
address: 0x1234 + 1,
target_symbol: 0,
addend: 0,
},
symbol: &Default::default(),
}),
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
assert_eq!(parts, &[InstructionPart::opcode("call", opcode), InstructionPart::reloc()]);
}
}
+9 -1
View File
@@ -32,7 +32,13 @@ pub fn no_diff_code(
symbol.address + symbol.size
)
})?;
let ops = obj.arch.scan_instructions(symbol.address, data, section_index, diff_config)?;
let ops = obj.arch.scan_instructions(
symbol.address,
data,
section_index,
&section.relocations,
diff_config,
)?;
let mut instruction_rows = Vec::<InstructionDiffRow>::new();
for i in &ops {
instruction_rows
@@ -89,12 +95,14 @@ pub fn diff_code(
left_symbol.address,
left_data,
left_section_idx,
&left_section.relocations,
diff_config,
)?;
let right_ops = right_obj.arch.scan_instructions(
right_symbol.address,
right_data,
right_section_idx,
&right_section.relocations,
diff_config,
)?;
let (mut left_rows, mut right_rows) = diff_instructions(&left_ops, &right_ops)?;
+33 -15
View File
@@ -325,10 +325,14 @@ pub enum SymbolNavigationKind {
Extab,
}
#[derive(Debug, Clone, Default, Eq, PartialEq)]
pub enum HoverItemColor {
Normal, // Gray
#[default]
Normal, // Gray
Emphasized, // White
Special, // Blue
Delete, // Red
Insert, // Green
}
pub enum HoverItem {
@@ -355,7 +359,12 @@ pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
out
}
pub fn symbol_hover(obj: &Object, symbol_index: usize, addend: i64) -> Vec<HoverItem> {
pub fn symbol_hover(
obj: &Object,
symbol_index: usize,
addend: i64,
override_color: Option<HoverItemColor>,
) -> Vec<HoverItem> {
let symbol = &obj.symbols[symbol_index];
let addend_str = match addend.cmp(&0i64) {
Ordering::Greater => format!("+{:x}", addend),
@@ -366,51 +375,51 @@ pub fn symbol_hover(obj: &Object, symbol_index: usize, addend: i64) -> Vec<Hover
out.push(HoverItem::Text {
label: "Name".into(),
value: format!("{}{}", symbol.name, addend_str),
color: HoverItemColor::Normal,
color: override_color.clone().unwrap_or_default(),
});
if let Some(demangled_name) = &symbol.demangled_name {
out.push(HoverItem::Text {
label: "Demangled".into(),
value: demangled_name.into(),
color: HoverItemColor::Normal,
color: override_color.clone().unwrap_or_default(),
});
}
if let Some(section) = symbol.section {
out.push(HoverItem::Text {
label: "Section".into(),
value: obj.sections[section].name.clone(),
color: HoverItemColor::Normal,
color: override_color.clone().unwrap_or_default(),
});
out.push(HoverItem::Text {
label: "Address".into(),
value: format!("{:x}{}", symbol.address, addend_str),
color: HoverItemColor::Normal,
color: override_color.clone().unwrap_or_default(),
});
if symbol.flags.contains(SymbolFlag::SizeInferred) {
out.push(HoverItem::Text {
label: "Size".into(),
value: format!("{:x} (inferred)", symbol.size),
color: HoverItemColor::Normal,
color: override_color.clone().unwrap_or_default(),
});
} else {
out.push(HoverItem::Text {
label: "Size".into(),
value: format!("{:x}", symbol.size),
color: HoverItemColor::Normal,
color: override_color.clone().unwrap_or_default(),
});
}
if let Some(align) = symbol.align {
out.push(HoverItem::Text {
label: "Alignment".into(),
value: align.get().to_string(),
color: HoverItemColor::Normal,
color: override_color.clone().unwrap_or_default(),
});
}
if let Some(address) = symbol.virtual_address {
out.push(HoverItem::Text {
label: "Virtual address".into(),
value: format!("{:x}", address),
color: HoverItemColor::Special,
color: override_color.clone().unwrap_or(HoverItemColor::Special),
});
}
} else {
@@ -443,22 +452,31 @@ pub fn relocation_context(
out
}
pub fn relocation_hover(obj: &Object, reloc: ResolvedRelocation) -> Vec<HoverItem> {
pub fn relocation_hover(
obj: &Object,
reloc: ResolvedRelocation,
override_color: Option<HoverItemColor>,
) -> Vec<HoverItem> {
let mut out = Vec::new();
if let Some(name) = obj.arch.reloc_name(reloc.relocation.flags) {
out.push(HoverItem::Text {
label: "Relocation".into(),
value: name.to_string(),
color: HoverItemColor::Normal,
color: override_color.clone().unwrap_or_default(),
});
} else {
out.push(HoverItem::Text {
label: "Relocation".into(),
value: format!("<{:?}>", reloc.relocation.flags),
color: HoverItemColor::Normal,
color: override_color.clone().unwrap_or_default(),
});
}
out.append(&mut symbol_hover(obj, reloc.relocation.target_symbol, reloc.relocation.addend));
out.append(&mut symbol_hover(
obj,
reloc.relocation.target_symbol,
reloc.relocation.addend,
override_color,
));
out
}
@@ -545,7 +563,7 @@ pub fn instruction_hover(
}
if let Some(reloc) = resolved.relocation {
out.push(HoverItem::Separator);
out.append(&mut relocation_hover(obj, reloc));
out.append(&mut relocation_hover(obj, reloc, None));
if let Some(ty) = obj.arch.guess_data_type(resolved) {
let literals = display_ins_data_literals(obj, resolved);
if !literals.is_empty() {
+17
View File
@@ -15,3 +15,20 @@ fn read_arm() {
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
insta::assert_snapshot!(output);
}
#[test]
#[cfg(feature = "arm")]
fn read_thumb() {
let diff_config = diff::DiffObjConfig { mips_register_prefix: true, ..Default::default() };
let obj = obj::read::parse(include_object!("data/arm/thumb.o"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj);
let symbol_idx = obj
.symbols
.iter()
.position(|s| s.name == "THUMB_BRANCH_ServerDisplay_UncategorizedMove")
.unwrap();
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
insta::assert_debug_snapshot!(diff.instruction_rows);
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
insta::assert_snapshot!(output);
}
+13
View File
@@ -55,3 +55,16 @@ fn display_section_ordering() {
diff::display::display_sections(&obj, &obj_diff, SymbolFilter::None, false, false, false);
insta::assert_debug_snapshot!(section_display);
}
#[test]
#[cfg(feature = "x86")]
fn read_x86_jumptable() {
let diff_config = diff::DiffObjConfig::default();
let obj = obj::read::parse(include_object!("data/x86/jumptable.o"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj);
let symbol_idx = obj.symbols.iter().position(|s| s.name == "?test@@YAHH@Z").unwrap();
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
insta::assert_debug_snapshot!(diff.instruction_rows);
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
insta::assert_snapshot!(output);
}
Binary file not shown.

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