Compare commits

...

15 Commits

Author SHA1 Message Date
Ryan Burns
f58616b6dd
Use symbol name when comparing against an externed reloc (#214)
* Use symbol name when comparing against an externed reloc

For partial matching files, often a symbol is externed even though it
should exist in the target object. We can still compare the symbol name,
instead of always returning a mismatch.

* Combine cases, and apply change reloc_eq in code.rs
2025-05-30 15:00:02 -06:00
Alex Page
e9762e24c2
Add support for x86 ELF object files (#213) 2025-05-30 13:19:06 -06:00
dab79d96a1 Version v3.0.0-beta.9 2025-05-27 21:32:57 -06:00
a57e5db983 WASM API updates, support symbol mapping 2025-05-27 21:31:29 -06:00
LagoLunatic
d0afd3b83e
Fix scroll hotkeys not working in data diff view (#208) 2025-05-27 09:27:00 -06:00
Anghelo Carvajal
a367af612b
Make encoding_rs an optional dependency (#205) 2025-05-17 23:14:15 -06:00
LagoLunatic
22052ea10b
Data diff view: Show bytes with relocations as ?? instead of 00 (#204)
* Data diff view: Show bytes with relocations as `xx`

* xx -> ??
2025-05-14 21:12:59 -06:00
f7c3501eae Version v3.0.0-beta.8 2025-05-13 23:15:46 -06:00
07ef93f16a Ignore extern symbols with symbol name lookups
When searching for a symbol by name, only look at
symbols that are defined within the object,
ignoring extern symbols (symbols without section).

Fixes #180
Fixes #181
2025-05-13 22:51:26 -06:00
8e8ab6bef8 Skip label symbols when inferring symbol sizes
COFF objects in particular don't contain the size of
symbols. We infer the size of these symbols by
extending them to the next symbol. If a tool emits
symbols for branch targets, this causes the inferred
size to be too small.

This checks if a symbol starts with a certain prefix
(right now, just .L or LAB_), and skips over it
during symbol size inference.

Resolves #174
2025-05-13 22:36:02 -06:00
e865f3d598 Fix symbol mapping mismatched match %
We have specific diff logic that relies on knowing
which object is the target object, and which is the
base. generate_mapping_symbols was designed in such
a way that it would reverse the target/base, leading
to a match percent shown that's different when it
gets applied.

Fixes #200
2025-05-13 21:57:16 -06:00
2b13e9886a Fix hidden symbol regression
The flagset .contains check doesn't work like this.

Fixes #199
2025-05-13 21:37:29 -06:00
1750af736a Try target-feature=+crt-static 2025-05-13 21:28:57 -06:00
LagoLunatic
731b604c24
Fix highlighting of signed vs unsigned arguments (#202)
* Fix signed and unsigned arguments not being considered equal when highlighting

* Remove unused Eq derive
2025-05-13 14:03:00 -06:00
2d643eb071 Add scratch.preset_id to config.schema.json 2025-05-09 12:51:18 -06:00
24 changed files with 539 additions and 233 deletions

View File

@ -1,5 +1,4 @@
[target.x86_64-pc-windows-msvc] # statically link the C runtime so the executable does not depend on
linker = "rust-lld" # that shared/dynamic library.
[target.'cfg(all(target_env = "msvc", target_os = "windows"))']
[target.aarch64-pc-windows-msvc] rustflags = ["-C", "target-feature=+crt-static"]
linker = "rust-lld"

8
Cargo.lock generated
View File

@ -3373,7 +3373,7 @@ dependencies = [
[[package]] [[package]]
name = "objdiff-cli" name = "objdiff-cli"
version = "3.0.0-beta.7" version = "3.0.0-beta.9"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"argp", "argp",
@ -3396,7 +3396,7 @@ dependencies = [
[[package]] [[package]]
name = "objdiff-core" name = "objdiff-core"
version = "3.0.0-beta.7" version = "3.0.0-beta.9"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arm-attr", "arm-attr",
@ -3450,7 +3450,7 @@ dependencies = [
[[package]] [[package]]
name = "objdiff-gui" name = "objdiff-gui"
version = "3.0.0-beta.7" version = "3.0.0-beta.9"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cfg-if", "cfg-if",
@ -3486,7 +3486,7 @@ dependencies = [
[[package]] [[package]]
name = "objdiff-wasm" name = "objdiff-wasm"
version = "3.0.0-beta.7" version = "3.0.0-beta.9"
dependencies = [ dependencies = [
"log", "log",
"objdiff-core", "objdiff-core",

View File

@ -14,7 +14,7 @@ strip = "debuginfo"
codegen-units = 1 codegen-units = 1
[workspace.package] [workspace.package]
version = "3.0.0-beta.7" version = "3.0.0-beta.9"
authors = ["Luke Street <luke@street.dev>"] authors = ["Luke Street <luke@street.dev>"]
edition = "2024" edition = "2024"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"

View File

@ -175,6 +175,10 @@
"type": "boolean", "type": "boolean",
"description": "If true, objdiff will run the build command with the context file as an argument to generate it.", "description": "If true, objdiff will run the build command with the context file as an argument to generate it.",
"default": false "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": [ "required": [

View File

@ -245,12 +245,14 @@ fn report_object(
for (symbol, symbol_diff) in obj.symbols.iter().zip(&obj_diff.symbols) { for (symbol, symbol_diff) in obj.symbols.iter().zip(&obj_diff.symbols) {
if symbol.section != Some(section_idx) if symbol.section != Some(section_idx)
|| symbol.size == 0 || symbol.size == 0
|| symbol.flags.contains(SymbolFlag::Hidden | SymbolFlag::Ignored) || symbol.flags.contains(SymbolFlag::Hidden)
|| symbol.flags.contains(SymbolFlag::Ignored)
{ {
continue; continue;
} }
if let Some(existing_functions) = &mut existing_functions { if let Some(existing_functions) = &mut existing_functions {
if symbol.flags.contains(SymbolFlag::Global | SymbolFlag::Weak) if (symbol.flags.contains(SymbolFlag::Global)
|| symbol.flags.contains(SymbolFlag::Weak))
&& !existing_functions.insert(symbol.name.clone()) && !existing_functions.insert(symbol.name.clone())
{ {
continue; continue;

View File

@ -450,11 +450,11 @@ impl UiView for FunctionDiffUi {
fn reload(&mut self, state: &AppState) -> Result<()> { fn reload(&mut self, state: &AppState) -> Result<()> {
let left_sym = 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 = 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 = 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 ( self.num_rows = match (
get_symbol(state.left_obj.as_ref(), left_sym), get_symbol(state.left_obj.as_ref(), left_sym),
get_symbol(state.right_obj.as_ref(), right_sym), get_symbol(state.right_obj.as_ref(), right_sym),
@ -650,12 +650,3 @@ fn get_symbol(
let sym = sym?; let sym = sym?;
Some((obj, sym, &diff.symbols[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
}

View File

@ -41,6 +41,7 @@ any-arch = [
"dep:regex", "dep:regex",
"dep:similar", "dep:similar",
"dep:syn", "dep:syn",
"dep:encoding_rs"
] ]
bindings = [ bindings = [
"dep:prost", "dep:prost",
@ -171,7 +172,7 @@ notify-debouncer-full = { version = "0.5.0", optional = true }
shell-escape = { version = "0.1", optional = true } shell-escape = { version = "0.1", optional = true }
tempfile = { version = "3.19", optional = true } tempfile = { version = "3.19", optional = true }
time = { version = "0.3", 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] [target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", optional = true } winapi = { version = "0.3", optional = true }

View File

@ -225,7 +225,7 @@ impl Arch for ArchArm {
let mut address = start_addr; let mut address = start_addr;
while address < end_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 // Change mapping
mode = next.mapping; mode = next.mapping;
next_mapping = mappings_iter.next(); next_mapping = mappings_iter.next();

View File

@ -6,7 +6,7 @@ use iced_x86::{
Decoder, DecoderOptions, DecoratorKind, FormatterOutput, FormatterTextKind, GasFormatter, Decoder, DecoderOptions, DecoratorKind, FormatterOutput, FormatterTextKind, GasFormatter,
Instruction, IntelFormatter, MasmFormatter, NasmFormatter, NumberKind, OpKind, Register, Instruction, IntelFormatter, MasmFormatter, NasmFormatter, NumberKind, OpKind, Register,
}; };
use object::{Endian as _, Object as _, ObjectSection as _, pe}; use object::{Endian as _, Object as _, ObjectSection as _, elf, pe};
use crate::{ use crate::{
arch::Arch, arch::Arch,
@ -67,16 +67,24 @@ impl ArchX86 {
pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32 => Some(4), pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32 => Some(4),
_ => None, _ => None,
}, },
RelocationFlags::Elf(typ) => match typ {
elf::R_386_32 | elf::R_386_PC32 => Some(4),
elf::R_386_16 => Some(2),
_ => None, _ => None,
}, },
},
Architecture::X86_64 => match flags { Architecture::X86_64 => match flags {
RelocationFlags::Coff(typ) => match typ { RelocationFlags::Coff(typ) => match typ {
pe::IMAGE_REL_AMD64_ADDR32NB | pe::IMAGE_REL_AMD64_REL32 => Some(4), pe::IMAGE_REL_AMD64_ADDR32NB | pe::IMAGE_REL_AMD64_REL32 => Some(4),
pe::IMAGE_REL_AMD64_ADDR64 => Some(8), pe::IMAGE_REL_AMD64_ADDR64 => Some(8),
_ => None, _ => None,
}, },
RelocationFlags::Elf(typ) => match typ {
elf::R_X86_64_PC32 => Some(4),
elf::R_X86_64_64 => Some(8),
_ => None, _ => None,
}, },
},
} }
} }
} }
@ -227,7 +235,8 @@ impl Arch for ArchX86 {
) -> Result<i64> { ) -> Result<i64> {
match self.arch { match self.arch {
Architecture::X86 => match flags { Architecture::X86 => match flags {
RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32) => { RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32)
| RelocationFlags::Elf(elf::R_386_32 | elf::R_386_PC32) => {
let data = let data =
section.data()?[address as usize..address as usize + 4].try_into()?; section.data()?[address as usize..address as usize + 4].try_into()?;
Ok(self.endianness.read_i32_bytes(data) as i64) Ok(self.endianness.read_i32_bytes(data) as i64)
@ -235,12 +244,14 @@ impl Arch for ArchX86 {
flags => bail!("Unsupported x86 implicit relocation {flags:?}"), flags => bail!("Unsupported x86 implicit relocation {flags:?}"),
}, },
Architecture::X86_64 => match flags { Architecture::X86_64 => match flags {
RelocationFlags::Coff(pe::IMAGE_REL_AMD64_ADDR32NB | pe::IMAGE_REL_AMD64_REL32) => { RelocationFlags::Coff(pe::IMAGE_REL_AMD64_ADDR32NB | pe::IMAGE_REL_AMD64_REL32)
| RelocationFlags::Elf(elf::R_X86_64_32 | elf::R_X86_64_PC32) => {
let data = let data =
section.data()?[address as usize..address as usize + 4].try_into()?; section.data()?[address as usize..address as usize + 4].try_into()?;
Ok(self.endianness.read_i32_bytes(data) as i64) Ok(self.endianness.read_i32_bytes(data) as i64)
} }
RelocationFlags::Coff(pe::IMAGE_REL_AMD64_ADDR64) => { RelocationFlags::Coff(pe::IMAGE_REL_AMD64_ADDR64)
| RelocationFlags::Elf(elf::R_X86_64_64) => {
let data = let data =
section.data()?[address as usize..address as usize + 8].try_into()?; section.data()?[address as usize..address as usize + 8].try_into()?;
Ok(self.endianness.read_i64_bytes(data)) Ok(self.endianness.read_i64_bytes(data))

View File

@ -325,12 +325,11 @@ fn reloc_eq(
|| display_ins_data_literals(left_obj, left_ins) || display_ins_data_literals(left_obj, left_ins)
== display_ins_data_literals(right_obj, right_ins)) == display_ins_data_literals(right_obj, right_ins))
} }
(Some(_), None) => false,
(None, Some(_)) => { (None, Some(_)) => {
// Match if possibly stripped weak symbol // Match if possibly stripped weak symbol
symbol_name_addend_matches && right_reloc.symbol.flags.contains(SymbolFlag::Weak) symbol_name_addend_matches && right_reloc.symbol.flags.contains(SymbolFlag::Weak)
} }
(None, None) => symbol_name_addend_matches, (Some(_), None) | (None, None) => symbol_name_addend_matches,
} }
} }

View File

@ -53,12 +53,11 @@ fn reloc_eq(
section_name_eq(left_obj, right_obj, sl, sr) section_name_eq(left_obj, right_obj, sl, sr)
&& (symbol_name_addend_matches || address_eq(left, right)) && (symbol_name_addend_matches || address_eq(left, right))
} }
(Some(_), None) => false,
(None, Some(_)) => { (None, Some(_)) => {
// Match if possibly stripped weak symbol // Match if possibly stripped weak symbol
symbol_name_addend_matches && right.symbol.flags.contains(SymbolFlag::Weak) symbol_name_addend_matches && right.symbol.flags.contains(SymbolFlag::Weak)
} }
(None, None) => symbol_name_addend_matches, (Some(_), None) | (None, None) => symbol_name_addend_matches,
} }
} }

View File

@ -77,7 +77,7 @@ impl<'a> DiffTextSegment<'a> {
const EOL_SEGMENT: DiffTextSegment<'static> = const EOL_SEGMENT: DiffTextSegment<'static> =
DiffTextSegment { text: DiffText::Eol, color: DiffTextColor::Normal, pad_to: 0 }; DiffTextSegment { text: DiffText::Eol, color: DiffTextColor::Normal, pad_to: 0 };
#[derive(Debug, Default, Clone, PartialEq, Eq)] #[derive(Debug, Default, Clone)]
pub enum HighlightKind { pub enum HighlightKind {
#[default] #[default]
None, None,
@ -288,6 +288,18 @@ pub fn display_row(
Ok(()) Ok(())
} }
impl PartialEq<HighlightKind> for HighlightKind {
fn eq(&self, other: &HighlightKind) -> bool {
match (self, other) {
(HighlightKind::Opcode(a), HighlightKind::Opcode(b)) => a == b,
(HighlightKind::Argument(a), HighlightKind::Argument(b)) => a.loose_eq(b),
(HighlightKind::Symbol(a), HighlightKind::Symbol(b)) => a == b,
(HighlightKind::Address(a), HighlightKind::Address(b)) => a == b,
_ => false,
}
}
}
impl PartialEq<DiffText<'_>> for HighlightKind { impl PartialEq<DiffText<'_>> for HighlightKind {
fn eq(&self, other: &DiffText) -> bool { fn eq(&self, other: &DiffText) -> bool {
match (self, other) { match (self, other) {
@ -604,7 +616,9 @@ fn symbol_matches_filter(
return false; return false;
} }
if !show_hidden_symbols if !show_hidden_symbols
&& (symbol.size == 0 || symbol.flags.contains(SymbolFlag::Hidden | SymbolFlag::Ignored)) && (symbol.size == 0
|| symbol.flags.contains(SymbolFlag::Hidden)
|| symbol.flags.contains(SymbolFlag::Ignored))
{ {
return false; return false;
} }

View File

@ -341,11 +341,25 @@ pub fn diff_objs(
if let (Some((right_obj, right_out)), Some((left_obj, left_out))) = if let (Some((right_obj, right_out)), Some((left_obj, left_out))) =
(right.as_mut(), left.as_mut()) (right.as_mut(), left.as_mut())
{ {
if let Some(right_name) = &mapping_config.selecting_left { if let Some(right_name) = mapping_config.selecting_left.as_deref() {
generate_mapping_symbols(right_obj, right_name, left_obj, left_out, diff_config)?; generate_mapping_symbols(
left_obj,
left_out,
right_obj,
right_out,
MappingSymbol::Right(right_name),
diff_config,
)?;
} }
if let Some(left_name) = &mapping_config.selecting_right { if let Some(left_name) = mapping_config.selecting_right.as_deref() {
generate_mapping_symbols(left_obj, left_name, right_obj, right_out, diff_config)?; generate_mapping_symbols(
left_obj,
left_out,
right_obj,
right_out,
MappingSymbol::Left(left_name),
diff_config,
)?;
} }
} }
@ -356,17 +370,28 @@ pub fn diff_objs(
}) })
} }
#[derive(Clone, Copy)]
enum MappingSymbol<'a> {
Left(&'a str),
Right(&'a str),
}
/// When we're selecting a symbol to use as a comparison, we'll create comparisons for all /// When we're selecting a symbol to use as a comparison, we'll create comparisons for all
/// symbols in the other object that match the selected symbol's section and kind. This allows /// symbols in the other object that match the selected symbol's section and kind. This allows
/// us to display match percentages for all symbols in the other object that could be selected. /// us to display match percentages for all symbols in the other object that could be selected.
fn generate_mapping_symbols( fn generate_mapping_symbols(
base_obj: &Object, left_obj: &Object,
base_name: &str, left_out: &mut ObjectDiff,
target_obj: &Object, right_obj: &Object,
target_out: &mut ObjectDiff, right_out: &mut ObjectDiff,
mapping_symbol: MappingSymbol,
config: &DiffObjConfig, config: &DiffObjConfig,
) -> Result<()> { ) -> Result<()> {
let Some(base_symbol_ref) = symbol_ref_by_name(base_obj, base_name) else { let (base_obj, base_name, target_obj) = match mapping_symbol {
MappingSymbol::Left(name) => (left_obj, name, right_obj),
MappingSymbol::Right(name) => (right_obj, name, left_obj),
};
let Some(base_symbol_ref) = base_obj.symbol_by_name(base_name) else {
return Ok(()); return Ok(());
}; };
let base_section_kind = symbol_section_kind(base_obj, &base_obj.symbols[base_symbol_ref]); let base_section_kind = symbol_section_kind(base_obj, &base_obj.symbols[base_symbol_ref]);
@ -377,32 +402,30 @@ fn generate_mapping_symbols(
{ {
continue; continue;
} }
match base_section_kind { let (left_symbol_idx, right_symbol_idx) = match mapping_symbol {
MappingSymbol::Left(_) => (base_symbol_ref, target_symbol_index),
MappingSymbol::Right(_) => (target_symbol_index, base_symbol_ref),
};
let (left_diff, right_diff) = match base_section_kind {
SectionKind::Code => { SectionKind::Code => {
let (left_diff, _right_diff) = diff_code(left_obj, right_obj, left_symbol_idx, right_symbol_idx, config)
diff_code(target_obj, base_obj, target_symbol_index, base_symbol_ref, config)?;
target_out.mapping_symbols.push(MappingSymbolDiff {
symbol_index: target_symbol_index,
symbol_diff: left_diff,
});
} }
SectionKind::Data => { SectionKind::Data => {
let (left_diff, _right_diff) = diff_data_symbol(left_obj, right_obj, left_symbol_idx, right_symbol_idx)
diff_data_symbol(target_obj, base_obj, target_symbol_index, base_symbol_ref)?;
target_out.mapping_symbols.push(MappingSymbolDiff {
symbol_index: target_symbol_index,
symbol_diff: left_diff,
});
} }
SectionKind::Bss | SectionKind::Common => { SectionKind::Bss | SectionKind::Common => {
let (left_diff, _right_diff) = diff_bss_symbol(left_obj, right_obj, left_symbol_idx, right_symbol_idx)
diff_bss_symbol(target_obj, base_obj, target_symbol_index, base_symbol_ref)?;
target_out.mapping_symbols.push(MappingSymbolDiff {
symbol_index: target_symbol_index,
symbol_diff: left_diff,
});
} }
SectionKind::Unknown => {} SectionKind::Unknown => continue,
}?;
match mapping_symbol {
MappingSymbol::Left(_) => right_out.mapping_symbols.push(MappingSymbolDiff {
symbol_index: right_symbol_idx,
symbol_diff: right_diff,
}),
MappingSymbol::Right(_) => left_out
.mapping_symbols
.push(MappingSymbolDiff { symbol_index: left_symbol_idx, symbol_diff: left_diff }),
} }
} }
Ok(()) Ok(())
@ -434,10 +457,6 @@ pub struct MappingConfig {
pub selecting_right: Option<String>, pub selecting_right: Option<String>,
} }
fn symbol_ref_by_name(obj: &Object, name: &str) -> Option<usize> {
obj.symbols.iter().position(|s| s.name == name)
}
fn apply_symbol_mappings( fn apply_symbol_mappings(
left: &Object, left: &Object,
right: &Object, right: &Object,
@ -449,25 +468,25 @@ fn apply_symbol_mappings(
// If we're selecting a symbol to use as a comparison, mark it as used // If we're selecting a symbol to use as a comparison, mark it as used
// This ensures that we don't match it to another symbol at any point // This ensures that we don't match it to another symbol at any point
if let Some(left_name) = &mapping_config.selecting_left { if let Some(left_name) = &mapping_config.selecting_left {
if let Some(left_symbol) = symbol_ref_by_name(left, left_name) { if let Some(left_symbol) = left.symbol_by_name(left_name) {
left_used.insert(left_symbol); left_used.insert(left_symbol);
} }
} }
if let Some(right_name) = &mapping_config.selecting_right { if let Some(right_name) = &mapping_config.selecting_right {
if let Some(right_symbol) = symbol_ref_by_name(right, right_name) { if let Some(right_symbol) = right.symbol_by_name(right_name) {
right_used.insert(right_symbol); right_used.insert(right_symbol);
} }
} }
// Apply manual symbol mappings // Apply manual symbol mappings
for (left_name, right_name) in &mapping_config.mappings { for (left_name, right_name) in &mapping_config.mappings {
let Some(left_symbol_index) = symbol_ref_by_name(left, left_name) else { let Some(left_symbol_index) = left.symbol_by_name(left_name) else {
continue; continue;
}; };
if left_used.contains(&left_symbol_index) { if left_used.contains(&left_symbol_index) {
continue; continue;
} }
let Some(right_symbol_index) = symbol_ref_by_name(right, right_name) else { let Some(right_symbol_index) = right.symbol_by_name(right_name) else {
continue; continue;
}; };
if right_used.contains(&right_symbol_index) { if right_used.contains(&right_symbol_index) {

View File

@ -118,7 +118,7 @@ impl Section {
Err(i) => self Err(i) => self
.relocations .relocations
.get(i) .get(i)
.take_if(|r| r.address < ins_ref.address + ins_ref.size as u64), .filter(|r| r.address < ins_ref.address + ins_ref.size as u64),
} }
.and_then(|relocation| { .and_then(|relocation| {
let symbol = obj.symbols.get(relocation.target_symbol)?; let symbol = obj.symbols.get(relocation.target_symbol)?;
@ -308,6 +308,10 @@ impl Object {
let offset = symbol.address.checked_sub(section.address)?; let offset = symbol.address.checked_sub(section.address)?;
section.data.get(offset as usize..offset as usize + symbol.size as usize) section.data.get(offset as usize..offset as usize + symbol.size as usize)
} }
pub fn symbol_by_name(&self, name: &str) -> Option<usize> {
self.symbols.iter().position(|symbol| symbol.section.is_some() && symbol.name == name)
}
} }
#[derive(Debug, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Clone, Eq, PartialEq, Hash)]

View File

@ -121,6 +121,15 @@ fn map_symbols(
Ok((symbols, symbol_indices)) Ok((symbols, symbol_indices))
} }
/// When inferring a symbol's size, we ignore symbols that start with specific prefixes. They are
/// usually emitted as branch targets and do not represent the start of a function or object.
fn is_local_label(symbol: &Symbol) -> bool {
const LABEL_PREFIXES: &[&str] = &[".L", "LAB_"];
symbol.size == 0
&& symbol.flags.contains(SymbolFlag::Local)
&& LABEL_PREFIXES.iter().any(|p| symbol.name.starts_with(p))
}
fn infer_symbol_sizes(symbols: &mut [Symbol], sections: &[Section]) { fn infer_symbol_sizes(symbols: &mut [Symbol], sections: &[Section]) {
// Create a sorted list of symbol indices by section // Create a sorted list of symbol indices by section
let mut symbols_with_section = Vec::<usize>::with_capacity(symbols.len()); let mut symbols_with_section = Vec::<usize>::with_capacity(symbols.len());
@ -167,10 +176,7 @@ fn infer_symbol_sizes(symbols: &mut [Symbol], sections: &[Section]) {
if last_end.0 == section_idx && last_end.1 > symbol.address { if last_end.0 == section_idx && last_end.1 > symbol.address {
continue; continue;
} }
let next_symbol = match symbol.kind { let next_symbol = loop {
// For function/object symbols, find the next function/object symbol (in other words:
// skip over labels)
SymbolKind::Function | SymbolKind::Object => loop {
if iter_idx >= symbols_with_section.len() { if iter_idx >= symbols_with_section.len() {
break None; break None;
} }
@ -178,16 +184,20 @@ fn infer_symbol_sizes(symbols: &mut [Symbol], sections: &[Section]) {
if next_symbol.section != Some(section_idx) { if next_symbol.section != Some(section_idx) {
break None; break None;
} }
if let SymbolKind::Function | SymbolKind::Object = next_symbol.kind { if match symbol.kind {
SymbolKind::Function | SymbolKind::Object => {
// For function/object symbols, find the next function/object
matches!(next_symbol.kind, SymbolKind::Function | SymbolKind::Object)
}
SymbolKind::Unknown | SymbolKind::Section => {
// For labels (or anything else), stop at any symbol
true
}
} && !is_local_label(next_symbol)
{
break Some(next_symbol); break Some(next_symbol);
} }
iter_idx += 1; iter_idx += 1;
},
// For labels (or anything else), simply use the next symbol's address
SymbolKind::Unknown | SymbolKind::Section => symbols_with_section
.get(iter_idx)
.map(|&i| &symbols[i])
.take_if(|s| s.section == Some(section_idx)),
}; };
let next_address = next_symbol.map(|s| s.address).unwrap_or_else(|| { let next_address = next_symbol.map(|s| s.address).unwrap_or_else(|| {
let section = &sections[section_idx]; let section = &sections[section_idx];
@ -341,7 +351,7 @@ fn map_section_relocations(
let idx = if let Some(section_symbol) = obj_file let idx = if let Some(section_symbol) = obj_file
.symbol_by_index(idx) .symbol_by_index(idx)
.ok() .ok()
.take_if(|s| s.kind() == object::SymbolKind::Section) .filter(|s| s.kind() == object::SymbolKind::Section)
{ {
let section_index = let section_index =
section_symbol.section_index().context("Section symbol without section")?; section_symbol.section_index().context("Section symbol without section")?;

View File

@ -68,3 +68,12 @@ fn read_x86_jumptable() {
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config); let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
insta::assert_snapshot!(output); insta::assert_snapshot!(output);
} }
// Inferred size of functions should ignore symbols with specific prefixes
#[test]
#[cfg(feature = "x86")]
fn read_x86_local_labels() {
let diff_config = diff::DiffObjConfig::default();
let obj = obj::read::parse(include_object!("data/x86/local_labels.obj"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj);
}

Binary file not shown.

View File

@ -0,0 +1,163 @@
---
source: objdiff-core/tests/arch_x86.rs
expression: obj
---
Object {
arch: ArchX86 {
arch: X86,
endianness: Little,
},
endianness: Little,
symbols: [
Symbol {
name: "42b830_convertToUppercaseShiftJIS.obj",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.text]",
demangled_name: None,
address: 0,
size: 0,
kind: Section,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "LAB_0042b850",
demangled_name: None,
address: 32,
size: 0,
kind: Object,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "LAB_0042b883",
demangled_name: None,
address: 83,
size: 0,
kind: Object,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "LAB_0042b87c",
demangled_name: None,
address: 76,
size: 0,
kind: Object,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "LAB_0042b884",
demangled_name: None,
address: 84,
size: 0,
kind: Object,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "LAB_0042b889",
demangled_name: None,
address: 89,
size: 0,
kind: Object,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "LAB_0042b845",
demangled_name: None,
address: 21,
size: 0,
kind: Object,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "LAB_0042b869",
demangled_name: None,
address: 57,
size: 0,
kind: Object,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "ConvertToUppercaseShiftJIS",
demangled_name: None,
address: 0,
size: 92,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global | SizeInferred),
align: None,
virtual_address: None,
},
],
sections: [
Section {
id: ".text-0",
name: ".text",
address: 0,
size: 92,
kind: Code,
data: SectionData(
92,
),
flags: FlagSet(),
align: Some(
16,
),
relocations: [],
line_info: {},
virtual_address: None,
},
],
split_meta: None,
path: None,
timestamp: None,
}

View File

@ -147,14 +147,20 @@ pub(crate) fn data_row_ui(
cur_addr += diff.len; cur_addr += diff.len;
} else { } else {
for byte in &diff.data { for byte in &diff.data {
let mut byte_text = format!("{byte:02x} ");
let mut byte_color = base_color; let mut byte_color = base_color;
if let Some(reloc_diff) = reloc_diffs.iter().find(|reloc_diff| { if let Some(reloc_diff) = reloc_diffs
reloc_diff.kind != DataDiffKind::None .iter()
&& reloc_diff.range.contains(&cur_addr_actual) .find(|reloc_diff| reloc_diff.range.contains(&cur_addr_actual))
}) { {
if *byte == 0 {
// Display 00 data bytes with a relocation as ?? instead.
byte_text = "?? ".to_string();
}
if reloc_diff.kind != DataDiffKind::None {
byte_color = get_color_for_diff_kind(reloc_diff.kind, appearance); byte_color = get_color_for_diff_kind(reloc_diff.kind, appearance);
} }
let byte_text = format!("{byte:02x} "); }
write_text(byte_text.as_str(), byte_color, &mut job, appearance.code_font.clone()); write_text(byte_text.as_str(), byte_color, &mut job, appearance.code_font.clone());
cur_addr += 1; cur_addr += 1;
cur_addr_actual += 1; cur_addr_actual += 1;

View File

@ -49,7 +49,9 @@ impl<'a> DiffColumnContext<'a> {
let selected_symbol = match view { let selected_symbol = match view {
View::SymbolDiff => None, View::SymbolDiff => None,
View::FunctionDiff | View::ExtabDiff => match (obj, selected_symbol) { View::FunctionDiff | View::ExtabDiff => match (obj, selected_symbol) {
(Some(obj), Some(s)) => find_symbol(&obj.0, s).map(SelectedSymbol::Symbol), (Some(obj), Some(s)) => {
obj.0.symbol_by_name(&s.symbol_name).map(SelectedSymbol::Symbol)
}
_ => None, _ => None,
}, },
View::DataDiff => match (obj, selected_symbol) { View::DataDiff => match (obj, selected_symbol) {
@ -497,6 +499,7 @@ pub fn diff_view_ui(
(state.current_view, left_ctx.obj, right_ctx.obj, left_ctx.section, right_ctx.section) (state.current_view, left_ctx.obj, right_ctx.obj, left_ctx.section, right_ctx.section)
{ {
// Joint diff view // Joint diff view
hotkeys::check_scroll_hotkeys(ui, true);
let left_total_bytes = let left_total_bytes =
left_section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len); left_section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
let right_total_bytes = let right_total_bytes =
@ -779,10 +782,6 @@ fn missing_obj_ui(ui: &mut Ui, appearance: &Appearance) {
}); });
} }
fn find_symbol(obj: &Object, selected_symbol: &SymbolRefByName) -> Option<usize> {
obj.symbols.iter().position(|symbol| symbol.name == selected_symbol.symbol_name)
}
fn find_section(obj: &Object, section_name: &str) -> Option<usize> { fn find_section(obj: &Object, section_name: &str) -> Option<usize> {
obj.sections.iter().position(|section| section.name == section_name) obj.sections.iter().position(|section| section.name == section_name)
} }

View File

@ -1,12 +1,12 @@
{ {
"name": "objdiff-wasm", "name": "objdiff-wasm",
"version": "3.0.0-beta.7", "version": "3.0.0-beta.9",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "objdiff-wasm", "name": "objdiff-wasm",
"version": "3.0.0-beta.7", "version": "3.0.0-beta.9",
"license": "MIT OR Apache-2.0", "license": "MIT OR Apache-2.0",
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.9.3", "@biomejs/biome": "^1.9.3",

View File

@ -1,6 +1,6 @@
{ {
"name": "objdiff-wasm", "name": "objdiff-wasm",
"version": "3.0.0-beta.7", "version": "3.0.0-beta.9",
"description": "A local diffing tool for decompilation projects.", "description": "A local diffing tool for decompilation projects.",
"author": { "author": {
"name": "Luke Street", "name": "Luke Street",

View File

@ -24,14 +24,14 @@ wit_bindgen::generate!({
use exports::objdiff::core::{ use exports::objdiff::core::{
diff::{ diff::{
DiffConfigBorrow, DiffResult, Guest as GuestDiff, GuestDiffConfig, GuestObject, DiffConfigBorrow, DiffResult, Guest as GuestDiff, GuestDiffConfig, GuestObject,
GuestObjectDiff, Object, ObjectBorrow, ObjectDiff, ObjectDiffBorrow, GuestObjectDiff, MappingConfig, Object, ObjectBorrow, ObjectDiff, ObjectDiffBorrow,
SymbolFlags, SymbolInfo, SymbolKind, SymbolRef,
}, },
display::{ display::{
ContextItem, ContextItemCopy, ContextItemNavigate, DiffText, DiffTextColor, DiffTextOpcode, ContextItem, ContextItemCopy, ContextItemNavigate, DiffText, DiffTextColor, DiffTextOpcode,
DiffTextSegment, DiffTextSymbol, DisplayConfig, Guest as GuestDisplay, HoverItem, DiffTextSegment, DiffTextSymbol, DisplayConfig, Guest as GuestDisplay, HoverItem,
HoverItemColor, HoverItemText, InstructionDiffKind, InstructionDiffRow, SectionDisplay, HoverItemColor, HoverItemText, InstructionDiffKind, InstructionDiffRow, SectionDisplay,
SectionDisplaySymbol, SymbolDisplay, SymbolFilter, SymbolFlags, SymbolKind, SymbolDisplay, SymbolFilter, SymbolNavigationKind,
SymbolNavigationKind, SymbolRef,
}, },
}; };
@ -59,15 +59,17 @@ impl GuestDiff for Component {
left: Option<ObjectBorrow>, left: Option<ObjectBorrow>,
right: Option<ObjectBorrow>, right: Option<ObjectBorrow>,
diff_config: DiffConfigBorrow, diff_config: DiffConfigBorrow,
mapping_config: MappingConfig,
) -> Result<DiffResult, String> { ) -> Result<DiffResult, String> {
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow(); let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
let mapping_config = diff::MappingConfig::from(mapping_config);
log::debug!("Running diff with config: {:?}", diff_config); log::debug!("Running diff with config: {:?}", diff_config);
let result = diff::diff_objs( let result = diff::diff_objs(
left.as_ref().map(|o| o.get::<ResourceObject>().0.as_ref()), left.as_ref().map(|o| o.get::<ResourceObject>().0.as_ref()),
right.as_ref().map(|o| o.get::<ResourceObject>().0.as_ref()), right.as_ref().map(|o| o.get::<ResourceObject>().0.as_ref()),
None, None,
&diff_config, &diff_config,
&diff::MappingConfig::default(), &mapping_config,
) )
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
Ok(DiffResult { Ok(DiffResult {
@ -134,48 +136,47 @@ impl GuestDisplay for Component {
name: d.name, name: d.name,
size: d.size, size: d.size,
match_percent: d.match_percent, match_percent: d.match_percent,
symbols: d symbols: d.symbols.into_iter().map(to_symbol_ref).collect(),
.symbols
.into_iter()
.map(|s| SectionDisplaySymbol {
symbol: s.symbol as SymbolRef,
is_mapping_symbol: s.is_mapping_symbol,
})
.collect(),
}) })
.collect() .collect()
} }
fn display_symbol( fn display_symbol(diff: ObjectDiffBorrow, symbol_ref: SymbolRef) -> SymbolDisplay {
diff: ObjectDiffBorrow,
symbol_display: SectionDisplaySymbol,
) -> SymbolDisplay {
let obj_diff = diff.get::<ResourceObjectDiff>(); let obj_diff = diff.get::<ResourceObjectDiff>();
let obj = obj_diff.0.as_ref(); let obj = obj_diff.0.as_ref();
let obj_diff = &obj_diff.1; let obj_diff = &obj_diff.1;
let symbol_idx = symbol_display.symbol as usize; let symbol_display = from_symbol_ref(symbol_ref);
let Some(symbol) = obj.symbols.get(symbol_idx) else { let Some(symbol) = obj.symbols.get(symbol_display.symbol) else {
return SymbolDisplay { name: "<unknown>".to_string(), ..Default::default() }; return SymbolDisplay {
info: SymbolInfo { name: "<unknown>".to_string(), ..Default::default() },
..Default::default()
};
}; };
let symbol_diff = if symbol_display.is_mapping_symbol { let symbol_diff = if symbol_display.is_mapping_symbol {
obj_diff obj_diff
.mapping_symbols .mapping_symbols
.iter() .iter()
.find(|s| s.symbol_index == symbol_idx) .find(|s| s.symbol_index == symbol_display.symbol)
.map(|s| &s.symbol_diff) .map(|s| &s.symbol_diff)
} else { } else {
obj_diff.symbols.get(symbol_idx) obj_diff.symbols.get(symbol_display.symbol)
}; };
SymbolDisplay { SymbolDisplay {
info: SymbolInfo {
id: to_symbol_ref(symbol_display),
name: symbol.name.clone(), name: symbol.name.clone(),
demangled_name: symbol.demangled_name.clone(), demangled_name: symbol.demangled_name.clone(),
address: symbol.address, address: symbol.address,
size: symbol.size, size: symbol.size,
kind: SymbolKind::from(symbol.kind), kind: SymbolKind::from(symbol.kind),
section: symbol.section.map(|s| s as u32), section: symbol.section.map(|s| s as u32),
section_name: symbol
.section
.and_then(|s| obj.sections.get(s).map(|sec| sec.name.clone())),
flags: SymbolFlags::from(symbol.flags), flags: SymbolFlags::from(symbol.flags),
align: symbol.align.map(|a| a.get()), align: symbol.align.map(|a| a.get()),
virtual_address: symbol.virtual_address, virtual_address: symbol.virtual_address,
},
target_symbol: symbol_diff.and_then(|sd| sd.target_symbol.map(|s| s as u32)), target_symbol: symbol_diff.and_then(|sd| sd.target_symbol.map(|s| s as u32)),
match_percent: symbol_diff.and_then(|sd| sd.match_percent), match_percent: symbol_diff.and_then(|sd| sd.match_percent),
diff_score: symbol_diff.and_then(|sd| sd.diff_score), diff_score: symbol_diff.and_then(|sd| sd.diff_score),
@ -185,22 +186,22 @@ impl GuestDisplay for Component {
fn display_instruction_row( fn display_instruction_row(
diff: ObjectDiffBorrow, diff: ObjectDiffBorrow,
symbol_display: SectionDisplaySymbol, symbol_ref: SymbolRef,
row_index: u32, row_index: u32,
diff_config: DiffConfigBorrow, diff_config: DiffConfigBorrow,
) -> InstructionDiffRow { ) -> InstructionDiffRow {
let obj_diff = diff.get::<ResourceObjectDiff>(); let obj_diff = diff.get::<ResourceObjectDiff>();
let obj = obj_diff.0.as_ref(); let obj = obj_diff.0.as_ref();
let obj_diff = &obj_diff.1; let obj_diff = &obj_diff.1;
let symbol_idx = symbol_display.symbol as usize; let symbol_display = from_symbol_ref(symbol_ref);
let symbol_diff = if symbol_display.is_mapping_symbol { let symbol_diff = if symbol_display.is_mapping_symbol {
obj_diff obj_diff
.mapping_symbols .mapping_symbols
.iter() .iter()
.find(|s| s.symbol_index == symbol_idx) .find(|s| s.symbol_index == symbol_display.symbol)
.map(|s| &s.symbol_diff) .map(|s| &s.symbol_diff)
} else { } else {
obj_diff.symbols.get(symbol_idx) obj_diff.symbols.get(symbol_display.symbol)
}; };
let Some(row) = symbol_diff.and_then(|sd| sd.instruction_rows.get(row_index as usize)) let Some(row) = symbol_diff.and_then(|sd| sd.instruction_rows.get(row_index as usize))
else { else {
@ -208,7 +209,7 @@ impl GuestDisplay for Component {
}; };
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow(); let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
let mut segments = Vec::with_capacity(16); let mut segments = Vec::with_capacity(16);
diff::display::display_row(obj, symbol_idx, row, &diff_config, |segment| { diff::display::display_row(obj, symbol_display.symbol, row, &diff_config, |segment| {
segments.push(DiffTextSegment::from(segment)); segments.push(DiffTextSegment::from(segment));
Ok(()) Ok(())
}) })
@ -216,26 +217,22 @@ impl GuestDisplay for Component {
InstructionDiffRow { segments, diff_kind: InstructionDiffKind::from(row.kind) } InstructionDiffRow { segments, diff_kind: InstructionDiffKind::from(row.kind) }
} }
fn symbol_context( fn symbol_context(diff: ObjectDiffBorrow, symbol_ref: SymbolRef) -> Vec<ContextItem> {
diff: ObjectDiffBorrow,
symbol_display: SectionDisplaySymbol,
) -> Vec<ContextItem> {
let obj_diff = diff.get::<ResourceObjectDiff>(); let obj_diff = diff.get::<ResourceObjectDiff>();
let obj = obj_diff.0.as_ref(); let obj = obj_diff.0.as_ref();
let symbol_display = from_symbol_ref(symbol_ref);
diff::display::symbol_context(obj, symbol_display.symbol as usize) diff::display::symbol_context(obj, symbol_display.symbol as usize)
.into_iter() .into_iter()
.map(|item| ContextItem::from(item)) .map(|item| ContextItem::from(item))
.collect() .collect()
} }
fn symbol_hover( fn symbol_hover(diff: ObjectDiffBorrow, symbol_ref: SymbolRef) -> Vec<HoverItem> {
diff: ObjectDiffBorrow,
symbol_display: SectionDisplaySymbol,
) -> Vec<HoverItem> {
let obj_diff = diff.get::<ResourceObjectDiff>(); let obj_diff = diff.get::<ResourceObjectDiff>();
let obj = obj_diff.0.as_ref(); let obj = obj_diff.0.as_ref();
let addend = 0; // TODO let addend = 0; // TODO
let override_color = None; // TODO: colorize replaced/deleted/inserted relocations let override_color = None; // TODO: colorize replaced/deleted/inserted relocations
let symbol_display = from_symbol_ref(symbol_ref);
diff::display::symbol_hover(obj, symbol_display.symbol as usize, addend, override_color) diff::display::symbol_hover(obj, symbol_display.symbol as usize, addend, override_color)
.into_iter() .into_iter()
.map(|item| HoverItem::from(item)) .map(|item| HoverItem::from(item))
@ -244,22 +241,22 @@ impl GuestDisplay for Component {
fn instruction_context( fn instruction_context(
diff: ObjectDiffBorrow, diff: ObjectDiffBorrow,
symbol_display: SectionDisplaySymbol, symbol_ref: SymbolRef,
row_index: u32, row_index: u32,
diff_config: DiffConfigBorrow, diff_config: DiffConfigBorrow,
) -> Vec<ContextItem> { ) -> Vec<ContextItem> {
let obj_diff = diff.get::<ResourceObjectDiff>(); let obj_diff = diff.get::<ResourceObjectDiff>();
let obj = obj_diff.0.as_ref(); let obj = obj_diff.0.as_ref();
let obj_diff = &obj_diff.1; let obj_diff = &obj_diff.1;
let symbol_idx = symbol_display.symbol as usize; let symbol_display = from_symbol_ref(symbol_ref);
let symbol_diff = if symbol_display.is_mapping_symbol { let symbol_diff = if symbol_display.is_mapping_symbol {
obj_diff obj_diff
.mapping_symbols .mapping_symbols
.iter() .iter()
.find(|s| s.symbol_index == symbol_idx) .find(|s| s.symbol_index == symbol_display.symbol)
.map(|s| &s.symbol_diff) .map(|s| &s.symbol_diff)
} else { } else {
obj_diff.symbols.get(symbol_idx) obj_diff.symbols.get(symbol_display.symbol)
}; };
let Some(ins_ref) = symbol_diff let Some(ins_ref) = symbol_diff
.and_then(|sd| sd.instruction_rows.get(row_index as usize)) .and_then(|sd| sd.instruction_rows.get(row_index as usize))
@ -268,7 +265,7 @@ impl GuestDisplay for Component {
return Vec::new(); return Vec::new();
}; };
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow(); let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
let Some(resolved) = obj.resolve_instruction_ref(symbol_idx, ins_ref) else { let Some(resolved) = obj.resolve_instruction_ref(symbol_display.symbol, ins_ref) else {
return vec![ContextItem::Copy(ContextItemCopy { return vec![ContextItem::Copy(ContextItemCopy {
value: "Failed to resolve instruction".to_string(), value: "Failed to resolve instruction".to_string(),
label: Some("error".to_string()), label: Some("error".to_string()),
@ -291,22 +288,22 @@ impl GuestDisplay for Component {
fn instruction_hover( fn instruction_hover(
diff: ObjectDiffBorrow, diff: ObjectDiffBorrow,
symbol_display: SectionDisplaySymbol, symbol_ref: SymbolRef,
row_index: u32, row_index: u32,
diff_config: DiffConfigBorrow, diff_config: DiffConfigBorrow,
) -> Vec<HoverItem> { ) -> Vec<HoverItem> {
let obj_diff = diff.get::<ResourceObjectDiff>(); let obj_diff = diff.get::<ResourceObjectDiff>();
let obj = obj_diff.0.as_ref(); let obj = obj_diff.0.as_ref();
let obj_diff = &obj_diff.1; let obj_diff = &obj_diff.1;
let symbol_idx = symbol_display.symbol as usize; let symbol_display = from_symbol_ref(symbol_ref);
let symbol_diff = if symbol_display.is_mapping_symbol { let symbol_diff = if symbol_display.is_mapping_symbol {
obj_diff obj_diff
.mapping_symbols .mapping_symbols
.iter() .iter()
.find(|s| s.symbol_index == symbol_idx) .find(|s| s.symbol_index == symbol_display.symbol)
.map(|s| &s.symbol_diff) .map(|s| &s.symbol_diff)
} else { } else {
obj_diff.symbols.get(symbol_idx) obj_diff.symbols.get(symbol_display.symbol)
}; };
let Some(ins_ref) = symbol_diff let Some(ins_ref) = symbol_diff
.and_then(|sd| sd.instruction_rows.get(row_index as usize)) .and_then(|sd| sd.instruction_rows.get(row_index as usize))
@ -315,7 +312,7 @@ impl GuestDisplay for Component {
return Vec::new(); return Vec::new();
}; };
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow(); let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
let Some(resolved) = obj.resolve_instruction_ref(symbol_idx, ins_ref) else { let Some(resolved) = obj.resolve_instruction_ref(symbol_display.symbol, ins_ref) else {
return vec![HoverItem::Text(HoverItemText { return vec![HoverItem::Text(HoverItemText {
label: "Error".to_string(), label: "Error".to_string(),
value: "Failed to resolve instruction".to_string(), value: "Failed to resolve instruction".to_string(),
@ -497,11 +494,9 @@ impl GuestObject for ResourceObject {
} }
impl GuestObjectDiff for ResourceObjectDiff { impl GuestObjectDiff for ResourceObjectDiff {
fn find_symbol(&self, name: String, section_name: Option<String>) -> Option<SymbolRef> { fn find_symbol(&self, name: String, section_name: Option<String>) -> Option<SymbolInfo> {
let obj = self.0.as_ref(); let obj = self.0.as_ref();
obj.symbols let symbol_idx = obj.symbols.iter().position(|s| {
.iter()
.position(|s| {
s.name == name s.name == name
&& match section_name.as_deref() { && match section_name.as_deref() {
Some(section_name) => { Some(section_name) => {
@ -509,8 +504,46 @@ impl GuestObjectDiff for ResourceObjectDiff {
} }
None => true, None => true,
} }
})?;
let symbol = obj.symbols.get(symbol_idx)?;
Some(SymbolInfo {
id: symbol_idx as SymbolRef,
name: symbol.name.clone(),
demangled_name: symbol.demangled_name.clone(),
address: symbol.address,
size: symbol.size,
kind: SymbolKind::from(symbol.kind),
section: symbol.section.map(|s| s as u32),
section_name: symbol
.section
.and_then(|s| obj.sections.get(s).map(|sec| sec.name.clone())),
flags: SymbolFlags::from(symbol.flags),
align: symbol.align.map(|a| a.get()),
virtual_address: symbol.virtual_address,
})
}
fn get_symbol(&self, symbol_ref: SymbolRef) -> Option<SymbolInfo> {
let obj = self.0.as_ref();
let symbol_display = from_symbol_ref(symbol_ref);
let Some(symbol) = obj.symbols.get(symbol_display.symbol) else {
return None;
};
Some(SymbolInfo {
id: to_symbol_ref(symbol_display),
name: symbol.name.clone(),
demangled_name: symbol.demangled_name.clone(),
address: symbol.address,
size: symbol.size,
kind: SymbolKind::from(symbol.kind),
section: symbol.section.map(|s| s as u32),
section_name: symbol
.section
.and_then(|s| obj.sections.get(s).map(|sec| sec.name.clone())),
flags: SymbolFlags::from(symbol.flags),
align: symbol.align.map(|a| a.get()),
virtual_address: symbol.virtual_address,
}) })
.map(|i| i as SymbolRef)
} }
} }
@ -580,18 +613,28 @@ impl Default for SymbolFlags {
fn default() -> Self { Self::empty() } fn default() -> Self { Self::empty() }
} }
impl Default for SymbolDisplay { impl Default for SymbolInfo {
fn default() -> Self { fn default() -> Self {
Self { Self {
id: u32::MAX,
name: Default::default(), name: Default::default(),
demangled_name: Default::default(), demangled_name: Default::default(),
address: Default::default(), address: Default::default(),
size: Default::default(), size: Default::default(),
kind: Default::default(), kind: Default::default(),
section: Default::default(), section: Default::default(),
section_name: Default::default(),
flags: Default::default(), flags: Default::default(),
align: Default::default(), align: Default::default(),
virtual_address: Default::default(), virtual_address: Default::default(),
}
}
}
impl Default for SymbolDisplay {
fn default() -> Self {
Self {
info: Default::default(),
target_symbol: Default::default(), target_symbol: Default::default(),
match_percent: Default::default(), match_percent: Default::default(),
diff_score: Default::default(), diff_score: Default::default(),
@ -600,4 +643,30 @@ impl Default for SymbolDisplay {
} }
} }
impl From<MappingConfig> for diff::MappingConfig {
fn from(config: MappingConfig) -> Self {
Self {
mappings: config.mappings.into_iter().collect(),
selecting_left: config.selecting_left,
selecting_right: config.selecting_right,
}
}
}
fn from_symbol_ref(symbol_ref: SymbolRef) -> diff::display::SectionDisplaySymbol {
diff::display::SectionDisplaySymbol {
symbol: (symbol_ref & !(1 << 31)) as usize,
is_mapping_symbol: (symbol_ref & (1 << 31)) != 0,
}
}
fn to_symbol_ref(display_symbol: diff::display::SectionDisplaySymbol) -> SymbolRef {
if display_symbol.is_mapping_symbol {
// Use the highest bit to indicate a mapping symbol
display_symbol.symbol as u32 | (1 << 31)
} else {
display_symbol.symbol as u32
}
}
export!(Component); export!(Component);

View File

@ -24,58 +24,8 @@ interface diff {
hash: func() -> u64; hash: func() -> u64;
} }
resource object-diff {
find-symbol: func(
name: string,
section-name: option<string>
) -> option<u32>;
}
record diff-result {
left: option<object-diff>,
right: option<object-diff>,
}
run-diff: func(
left: option<borrow<object>>,
right: option<borrow<object>>,
config: borrow<diff-config>,
) -> result<diff-result, string>;
}
interface display {
use diff.{
object,
object-diff,
diff-config
};
type symbol-ref = u32; type symbol-ref = u32;
record display-config {
show-hidden-symbols: bool,
show-mapped-symbols: bool,
reverse-fn-order: bool,
}
record symbol-filter {
regex: option<string>,
mapping: option<symbol-ref>,
}
record section-display-symbol {
symbol: symbol-ref,
is-mapping-symbol: bool,
}
record section-display {
id: string,
name: string,
size: u64,
match-percent: option<f32>,
symbols: list<section-display-symbol>,
}
enum symbol-kind { enum symbol-kind {
unknown, unknown,
function, function,
@ -94,17 +44,74 @@ interface display {
ignored, ignored,
} }
record symbol-display { record symbol-info {
id: symbol-ref,
name: string, name: string,
demangled-name: option<string>, demangled-name: option<string>,
address: u64, address: u64,
size: u64, size: u64,
kind: symbol-kind, kind: symbol-kind,
section: option<u32>, section: option<u32>,
section-name: option<string>,
%flags: symbol-flags, %flags: symbol-flags,
align: option<u32>, align: option<u32>,
virtual-address: option<u64>, virtual-address: option<u64>,
}
resource object-diff {
find-symbol: func(
name: string,
section-name: option<string>
) -> option<symbol-info>;
get-symbol: func(
id: u32
) -> option<symbol-info>;
}
record diff-result {
left: option<object-diff>,
right: option<object-diff>,
}
run-diff: func(
left: option<borrow<object>>,
right: option<borrow<object>>,
config: borrow<diff-config>,
mapping: mapping-config,
) -> result<diff-result, string>;
}
interface display {
use diff.{
object,
object-diff,
diff-config,
symbol-info,
symbol-ref
};
record display-config {
show-hidden-symbols: bool,
show-mapped-symbols: bool,
reverse-fn-order: bool,
}
record symbol-filter {
regex: option<string>,
mapping: option<symbol-ref>,
}
record section-display {
id: string,
name: string,
size: u64,
match-percent: option<f32>,
symbols: list<symbol-ref>,
}
record symbol-display {
info: symbol-info,
target-symbol: option<symbol-ref>, target-symbol: option<symbol-ref>,
match-percent: option<f32>, match-percent: option<f32>,
diff-score: option<tuple<u64, u64>>, diff-score: option<tuple<u64, u64>>,
@ -232,36 +239,36 @@ interface display {
display-symbol: func( display-symbol: func(
diff: borrow<object-diff>, diff: borrow<object-diff>,
symbol: section-display-symbol, symbol: symbol-ref,
) -> symbol-display; ) -> symbol-display;
display-instruction-row: func( display-instruction-row: func(
diff: borrow<object-diff>, diff: borrow<object-diff>,
symbol: section-display-symbol, symbol: symbol-ref,
row-index: u32, row-index: u32,
config: borrow<diff-config>, config: borrow<diff-config>,
) -> instruction-diff-row; ) -> instruction-diff-row;
symbol-context: func( symbol-context: func(
diff: borrow<object-diff>, diff: borrow<object-diff>,
symbol: section-display-symbol, symbol: symbol-ref,
) -> list<context-item>; ) -> list<context-item>;
symbol-hover: func( symbol-hover: func(
diff: borrow<object-diff>, diff: borrow<object-diff>,
symbol: section-display-symbol, symbol: symbol-ref,
) -> list<hover-item>; ) -> list<hover-item>;
instruction-context: func( instruction-context: func(
diff: borrow<object-diff>, diff: borrow<object-diff>,
symbol: section-display-symbol, symbol: symbol-ref,
row-index: u32, row-index: u32,
config: borrow<diff-config>, config: borrow<diff-config>,
) -> list<context-item>; ) -> list<context-item>;
instruction-hover: func( instruction-hover: func(
diff: borrow<object-diff>, diff: borrow<object-diff>,
symbol: section-display-symbol, symbol: symbol-ref,
row-index: u32, row-index: u32,
config: borrow<diff-config>, config: borrow<diff-config>,
) -> list<hover-item>; ) -> list<hover-item>;