Compare commits

..

3 Commits

Author SHA1 Message Date
Luke Street f7cb494a62 Version v3.1.1 2025-09-07 11:32:43 -06:00
iFarbod 7cc6ed2b43 Add support for x86 indirect tables (#251)
* Add support for x86 indirect tables

Closes #239

* Fix formatting issues in x86.rs

* Add x86 indirect table test
2025-09-07 11:28:23 -06:00
Toms 532b684682 Implement automatic symbol pairing for MSVC generated static functions (#255) 2025-09-07 11:28:01 -06:00
11 changed files with 16490 additions and 21 deletions
Generated
+4 -4
View File
@@ -3435,7 +3435,7 @@ dependencies = [
[[package]]
name = "objdiff-cli"
version = "3.1.0"
version = "3.1.1"
dependencies = [
"anyhow",
"argp",
@@ -3458,7 +3458,7 @@ dependencies = [
[[package]]
name = "objdiff-core"
version = "3.1.0"
version = "3.1.1"
dependencies = [
"anyhow",
"arm-attr",
@@ -3513,7 +3513,7 @@ dependencies = [
[[package]]
name = "objdiff-gui"
version = "3.1.0"
version = "3.1.1"
dependencies = [
"anyhow",
"argp",
@@ -3550,7 +3550,7 @@ dependencies = [
[[package]]
name = "objdiff-wasm"
version = "3.1.0"
version = "3.1.1"
dependencies = [
"log",
"objdiff-core",
+1 -1
View File
@@ -14,7 +14,7 @@ default-members = [
resolver = "3"
[workspace.package]
version = "3.1.0"
version = "3.1.1"
authors = ["Luke Street <luke@street.dev>"]
edition = "2024"
license = "MIT OR Apache-2.0"
+63
View File
@@ -124,7 +124,40 @@ impl Arch for ArchX86 {
opcode: DATA_OPCODE,
branch_dest: None,
});
reloc_iter.next();
// support .byte arrays after jump tables (they're typically known as indirect tables)
let indirect_array_address = address.wrapping_add(size as u64);
let indirect_array_pos = decoder.position();
let max_size = code.len().saturating_sub(indirect_array_pos);
let indirect_array_size = reloc_iter
.peek()
.map(|next_reloc| {
next_reloc.address.saturating_sub(indirect_array_address)
as usize
})
.unwrap_or(max_size)
.min(max_size);
if indirect_array_size > 0 {
for i in 0..indirect_array_size {
out.push(InstructionRef {
address: indirect_array_address + i as u64,
size: 1,
opcode: DATA_OPCODE,
branch_dest: None,
});
}
// move decoder to after the array (there can be multiple jump+indirect tables in one function)
let _ =
decoder.set_position(indirect_array_pos + indirect_array_size);
decoder.set_ip(indirect_array_address + indirect_array_size as u64);
}
continue 'outer;
}
}
@@ -156,6 +189,7 @@ impl Arch for ArchX86 {
) -> Result<()> {
if resolved.ins_ref.opcode == DATA_OPCODE {
let (mnemonic, imm) = match resolved.ins_ref.size {
1 => (".byte", resolved.code[0] as u64),
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),
@@ -791,4 +825,33 @@ mod test {
.unwrap();
assert_eq!(parts, &[InstructionPart::opcode("call", opcode), InstructionPart::reloc()]);
}
#[test]
fn test_display_1_byte_inline_data() {
let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
let code = [0xAB];
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef {
address: 0x1234,
size: 1,
opcode: DATA_OPCODE,
branch_dest: None,
},
code: &code,
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
assert_eq!(parts, &[
InstructionPart::opcode(".byte", DATA_OPCODE),
InstructionPart::unsigned(0xABu64),
]);
}
}
+13 -13
View File
@@ -680,16 +680,16 @@ fn symbol_section_kind(obj: &Object, symbol: &Symbol) -> SectionKind {
}
}
/// Check if a symbol is a compiler-generated literal like @1234.
fn is_symbol_compiler_generated_literal(symbol: &Symbol) -> bool {
if !symbol.name.starts_with('@') {
return false;
}
if !symbol.name[1..].chars().all(char::is_numeric) {
/// Check if a symbol is a compiler-generated like @1234 or _$E1234.
fn is_symbol_compiler_generated(symbol: &Symbol) -> bool {
if symbol.name.starts_with('@') && symbol.name[1..].chars().all(char::is_numeric) {
// Exclude @stringBase0, @GUARD@, etc.
return false;
return true;
}
true
if symbol.name.starts_with("_$E") && symbol.name[3..].chars().all(char::is_numeric) {
return true;
}
false
}
fn find_symbol(
@@ -705,8 +705,8 @@ fn find_symbol(
// Match compiler-generated symbols against each other (e.g. @251 -> @60)
// If they are in the same section and have the same value
if is_symbol_compiler_generated_literal(in_symbol)
&& matches!(section_kind, SectionKind::Data | SectionKind::Bss)
if is_symbol_compiler_generated(in_symbol)
&& matches!(section_kind, SectionKind::Code | SectionKind::Data | SectionKind::Bss)
{
let mut closest_match_symbol_idx = None;
let mut closest_match_percent = 0.0;
@@ -717,12 +717,12 @@ fn find_symbol(
if obj.sections[section_index].name != section_name {
continue;
}
if !is_symbol_compiler_generated_literal(symbol) {
if !is_symbol_compiler_generated(symbol) {
continue;
}
match section_kind {
SectionKind::Data => {
// For data, pick the first symbol with exactly matching bytes and relocations.
SectionKind::Data | SectionKind::Code => {
// For code or data, pick the first symbol with exactly matching bytes and relocations.
// If no symbols match exactly, and `fuzzy_literals` is true, pick the closest
// plausible match instead.
if let Ok((left_diff, _right_diff)) =
+18
View File
@@ -104,3 +104,21 @@ fn read_x86_local_labels() {
.unwrap();
insta::assert_debug_snapshot!(obj);
}
#[test]
#[cfg(feature = "x86")]
fn read_x86_indirect_table() {
let diff_config = diff::DiffObjConfig::default();
let obj = obj::read::parse(
include_object!("data/x86/indirect_table.obj"),
&diff_config,
diff::DiffSide::Base,
)
.unwrap();
insta::assert_debug_snapshot!(obj);
let symbol_idx = obj.symbols.iter().position(|s| s.name == "?process@@YAHHHH@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.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "objdiff-wasm",
"version": "3.1.0",
"version": "3.1.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "objdiff-wasm",
"version": "3.1.0",
"version": "3.1.1",
"license": "MIT OR Apache-2.0",
"devDependencies": {
"@biomejs/biome": "^1.9.3",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "objdiff-wasm",
"version": "3.1.0",
"version": "3.1.1",
"description": "A local diffing tool for decompilation projects.",
"author": {
"name": "Luke Street",