WIP objdiff 3.0 refactor

This commit is contained in:
Luke Street 2025-02-20 17:48:00 -07:00
parent 6d3c63ccd8
commit f3c157ff06
79 changed files with 14886 additions and 6820 deletions

View File

@ -71,7 +71,6 @@ jobs:
test: test:
name: Test name: Test
if: 'false' # No tests yet
strategy: strategy:
matrix: matrix:
platform: [ ubuntu-latest, windows-latest, macos-latest ] platform: [ ubuntu-latest, windows-latest, macos-latest ]
@ -227,6 +226,25 @@ jobs:
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe ${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
if-no-files-found: error if-no-files-found: error
build-wasm:
name: Build objdiff-wasm
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
targets: wasm32-wasip2
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Install dependencies
working-directory: objdiff-wasm
run: npm install
- name: Build
working-directory: objdiff-wasm
run: npm run build
release: release:
name: Release name: Release
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')

832
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@ members = [
"objdiff-cli", "objdiff-cli",
"objdiff-core", "objdiff-core",
"objdiff-gui", "objdiff-gui",
"objdiff-wasm",
] ]
resolver = "2" resolver = "2"
@ -13,7 +14,7 @@ strip = "debuginfo"
codegen-units = 1 codegen-units = 1
[workspace.package] [workspace.package]
version = "2.7.1" version = "3.0.0-alpha.1"
authors = ["Luke Street <luke@street.dev>"] authors = ["Luke Street <luke@street.dev>"]
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"

View File

@ -18,7 +18,7 @@ argp = "0.4"
crossterm = "0.28" crossterm = "0.28"
enable-ansi-support = "0.2" enable-ansi-support = "0.2"
memmap2 = "0.9" memmap2 = "0.9"
objdiff-core = { path = "../objdiff-core", features = ["all"] } objdiff-core = { path = "../objdiff-core", features = ["ppc", "std", "config", "dwarf", "bindings", "serde"] }
prost = "0.13" prost = "0.13"
ratatui = "0.29" ratatui = "0.29"
rayon = "1.10" rayon = "1.10"

View File

@ -30,16 +30,15 @@ use objdiff_core::{
path::{check_path_buf, platform_path, platform_path_serde_option}, path::{check_path_buf, platform_path, platform_path_serde_option},
ProjectConfig, ProjectObject, ProjectObjectMetadata, ProjectConfig, ProjectObject, ProjectObjectMetadata,
}, },
diff,
diff::{ diff::{
ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig, ObjDiff, self, ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig,
ObjectDiff,
}, },
jobs::{ jobs::{
objdiff::{start_build, ObjDiffConfig}, objdiff::{start_build, ObjDiffConfig},
Job, JobQueue, JobResult, Job, JobQueue, JobResult,
}, },
obj, obj::{self, Object},
obj::ObjInfo,
}; };
use ratatui::prelude::*; use ratatui::prelude::*;
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf}; use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
@ -239,7 +238,7 @@ fn run_oneshot(
}) })
.transpose()?; .transpose()?;
let result = let result =
diff::diff_objs(&diff_config, &mapping_config, target.as_ref(), base.as_ref(), None)?; diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
let left = target.as_ref().and_then(|o| result.left.as_ref().map(|d| (o, d))); let left = target.as_ref().and_then(|o| result.left.as_ref().map(|d| (o, d)));
let right = base.as_ref().and_then(|o| result.right.as_ref().map(|d| (o, d))); let right = base.as_ref().and_then(|o| result.right.as_ref().map(|d| (o, d)));
write_output(&DiffResult::new(left, right), Some(output), output_format)?; write_output(&DiffResult::new(left, right), Some(output), output_format)?;
@ -253,9 +252,9 @@ pub struct AppState {
pub project_config: Option<ProjectConfig>, pub project_config: Option<ProjectConfig>,
pub target_path: Option<Utf8PlatformPathBuf>, pub target_path: Option<Utf8PlatformPathBuf>,
pub base_path: Option<Utf8PlatformPathBuf>, pub base_path: Option<Utf8PlatformPathBuf>,
pub left_obj: Option<(ObjInfo, ObjDiff)>, pub left_obj: Option<(Object, ObjectDiff)>,
pub right_obj: Option<(ObjInfo, ObjDiff)>, pub right_obj: Option<(Object, ObjectDiff)>,
pub prev_obj: Option<(ObjInfo, ObjDiff)>, pub prev_obj: Option<(Object, ObjectDiff)>,
pub reload_time: Option<time::OffsetDateTime>, pub reload_time: Option<time::OffsetDateTime>,
pub time_format: Vec<time::format_description::FormatItem<'static>>, pub time_format: Vec<time::format_description::FormatItem<'static>>,
pub watcher: Option<Watcher>, pub watcher: Option<Watcher>,

View File

@ -10,7 +10,7 @@ use objdiff_core::{
}, },
config::path::platform_path, config::path::platform_path,
diff, obj, diff, obj,
obj::{ObjSectionKind, ObjSymbolFlags}, obj::{SectionKind, SymbolFlag},
}; };
use prost::Message; use prost::Message;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
@ -181,7 +181,7 @@ fn report_object(
}) })
.transpose()?; .transpose()?;
let result = let result =
diff::diff_objs(&diff_config, &mapping_config, target.as_ref(), base.as_ref(), None)?; diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
let metadata = ReportUnitMetadata { let metadata = ReportUnitMetadata {
complete: object.metadata.complete, complete: object.metadata.complete,
@ -200,7 +200,9 @@ fn report_object(
let obj = target.as_ref().or(base.as_ref()).unwrap(); let obj = target.as_ref().or(base.as_ref()).unwrap();
let obj_diff = result.left.as_ref().or(result.right.as_ref()).unwrap(); let obj_diff = result.left.as_ref().or(result.right.as_ref()).unwrap();
for (section, section_diff) in obj.sections.iter().zip(&obj_diff.sections) { for ((section_idx, section), section_diff) in
obj.sections.iter().enumerate().zip(&obj_diff.sections)
{
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| { let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
// Support cases where we don't have a target object, // Support cases where we don't have a target object,
// assume complete means 100% match // assume complete means 100% match
@ -221,23 +223,26 @@ fn report_object(
}); });
match section.kind { match section.kind {
ObjSectionKind::Data | ObjSectionKind::Bss => { SectionKind::Data | SectionKind::Bss => {
measures.total_data += section.size; measures.total_data += section.size;
if section_match_percent == 100.0 { if section_match_percent == 100.0 {
measures.matched_data += section.size; measures.matched_data += section.size;
} }
continue; continue;
} }
ObjSectionKind::Code => (), _ => {}
} }
for (symbol, symbol_diff) in section.symbols.iter().zip(&section_diff.symbols) { for (symbol, symbol_diff) in obj.symbols.iter().zip(&obj_diff.symbols) {
if symbol.size == 0 || symbol.flags.0.contains(ObjSymbolFlags::Hidden) { if symbol.section != Some(section_idx)
|| symbol.size == 0
|| symbol.flags.contains(SymbolFlag::Hidden)
{
continue; continue;
} }
if let Some(existing_functions) = &mut existing_functions { if let Some(existing_functions) = &mut existing_functions {
if (symbol.flags.0.contains(ObjSymbolFlags::Global) if (symbol.flags.contains(SymbolFlag::Global)
|| symbol.flags.0.contains(ObjSymbolFlags::Weak)) || symbol.flags.contains(SymbolFlag::Weak))
&& !existing_functions.insert(symbol.name.clone()) && !existing_functions.insert(symbol.name.clone())
{ {
continue; continue;

View File

@ -1,3 +1,5 @@
#![allow(clippy::too_many_arguments)]
mod argp_version; mod argp_version;
mod cmd; mod cmd;
mod util; mod util;

View File

@ -4,10 +4,10 @@ use anyhow::{bail, Result};
use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseEventKind}; use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseEventKind};
use objdiff_core::{ use objdiff_core::{
diff::{ diff::{
display::{display_diff, DiffText, HighlightKind}, display::{display_row, DiffText, HighlightKind},
FunctionRelocDiffs, ObjDiff, ObjInsDiffKind, ObjSymbolDiff, DiffObjConfig, FunctionRelocDiffs, InstructionDiffKind, ObjectDiff, SymbolDiff,
}, },
obj::{ObjInfo, ObjSectionKind, ObjSymbol, SymbolRef}, obj::Object,
}; };
use ratatui::{ use ratatui::{
prelude::*, prelude::*,
@ -30,9 +30,9 @@ pub struct FunctionDiffUi {
pub scroll_state_y: ScrollbarState, pub scroll_state_y: ScrollbarState,
pub per_page: usize, pub per_page: usize,
pub num_rows: usize, pub num_rows: usize,
pub left_sym: Option<SymbolRef>, pub left_sym: Option<usize>,
pub right_sym: Option<SymbolRef>, pub right_sym: Option<usize>,
pub prev_sym: Option<SymbolRef>, pub prev_sym: Option<usize>,
pub open_options: bool, pub open_options: bool,
pub three_way: bool, pub three_way: bool,
} }
@ -82,8 +82,8 @@ impl UiView for FunctionDiffUi {
f.render_widget(line_l, header_chunks[0]); f.render_widget(line_l, header_chunks[0]);
let mut line_r = Line::default(); let mut line_r = Line::default();
if let Some(percent) = if let Some(percent) = get_symbol(state.right_obj.as_ref(), self.right_sym)
get_symbol(state.right_obj.as_ref(), self.right_sym).and_then(|(_, d)| d.match_percent) .and_then(|(_, _, d)| d.match_percent)
{ {
line_r.spans.push(Span::styled( line_r.spans.push(Span::styled(
format!("{:.2}% ", percent), format!("{:.2}% ", percent),
@ -108,13 +108,17 @@ impl UiView for FunctionDiffUi {
let mut left_text = None; let mut left_text = None;
let mut left_highlight = None; let mut left_highlight = None;
let mut max_width = 0; let mut max_width = 0;
if let Some((symbol, symbol_diff)) = get_symbol(state.left_obj.as_ref(), self.left_sym) { if let Some((obj, symbol_idx, symbol_diff)) =
get_symbol(state.left_obj.as_ref(), self.left_sym)
{
let mut text = Text::default(); let mut text = Text::default();
let rect = content_chunks[0].inner(Margin::new(0, 1)); let rect = content_chunks[0].inner(Margin::new(0, 1));
left_highlight = self.print_sym( left_highlight = self.print_sym(
&mut text, &mut text,
symbol, obj,
symbol_idx,
symbol_diff, symbol_diff,
&state.diff_obj_config,
rect, rect,
&self.left_highlight, &self.left_highlight,
result, result,
@ -127,13 +131,17 @@ impl UiView for FunctionDiffUi {
let mut right_text = None; let mut right_text = None;
let mut right_highlight = None; let mut right_highlight = None;
let mut margin_text = None; let mut margin_text = None;
if let Some((symbol, symbol_diff)) = get_symbol(state.right_obj.as_ref(), self.right_sym) { if let Some((obj, symbol_idx, symbol_diff)) =
get_symbol(state.right_obj.as_ref(), self.right_sym)
{
let mut text = Text::default(); let mut text = Text::default();
let rect = content_chunks[2].inner(Margin::new(0, 1)); let rect = content_chunks[2].inner(Margin::new(0, 1));
right_highlight = self.print_sym( right_highlight = self.print_sym(
&mut text, &mut text,
symbol, obj,
symbol_idx,
symbol_diff, symbol_diff,
&state.diff_obj_config,
rect, rect,
&self.right_highlight, &self.right_highlight,
result, result,
@ -152,14 +160,17 @@ impl UiView for FunctionDiffUi {
let mut prev_text = None; let mut prev_text = None;
let mut prev_margin_text = None; let mut prev_margin_text = None;
if self.three_way { if self.three_way {
if let Some((symbol, symbol_diff)) = get_symbol(state.prev_obj.as_ref(), self.prev_sym) if let Some((obj, symbol_idx, symbol_diff)) =
get_symbol(state.prev_obj.as_ref(), self.prev_sym)
{ {
let mut text = Text::default(); let mut text = Text::default();
let rect = content_chunks[4].inner(Margin::new(0, 1)); let rect = content_chunks[4].inner(Margin::new(0, 1));
self.print_sym( self.print_sym(
&mut text, &mut text,
symbol, obj,
symbol_idx,
symbol_diff, symbol_diff,
&state.diff_obj_config,
rect, rect,
&self.right_highlight, &self.right_highlight,
result, result,
@ -437,9 +448,11 @@ impl UiView for FunctionDiffUi {
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),
) { ) {
(Some((_l, ld)), Some((_r, rd))) => ld.instructions.len().max(rd.instructions.len()), (Some((_l, _ls, ld)), Some((_r, _rs, rd))) => {
(Some((_l, ld)), None) => ld.instructions.len(), ld.instruction_rows.len().max(rd.instruction_rows.len())
(None, Some((_r, rd))) => rd.instructions.len(), }
(Some((_l, _ls, ld)), None) => ld.instruction_rows.len(),
(None, Some((_r, _rs, rd))) => rd.instruction_rows.len(),
(None, None) => bail!("Symbol not found: {}", self.symbol_name), (None, None) => bail!("Symbol not found: {}", self.symbol_name),
}; };
self.left_sym = left_sym; self.left_sym = left_sym;
@ -482,52 +495,51 @@ impl FunctionDiffUi {
self.scroll_y += self.per_page / if half { 2 } else { 1 }; self.scroll_y += self.per_page / if half { 2 } else { 1 };
} }
#[allow(clippy::too_many_arguments)]
fn print_sym( fn print_sym(
&self, &self,
out: &mut Text<'static>, out: &mut Text<'static>,
symbol: &ObjSymbol, obj: &Object,
symbol_diff: &ObjSymbolDiff, symbol_index: usize,
symbol_diff: &SymbolDiff,
diff_config: &DiffObjConfig,
rect: Rect, rect: Rect,
highlight: &HighlightKind, highlight: &HighlightKind,
result: &EventResult, result: &EventResult,
only_changed: bool, only_changed: bool,
) -> Option<HighlightKind> { ) -> Option<HighlightKind> {
let base_addr = symbol.address;
let mut new_highlight = None; let mut new_highlight = None;
for (y, ins_diff) in symbol_diff for (y, ins_row) in symbol_diff
.instructions .instruction_rows
.iter() .iter()
.skip(self.scroll_y) .skip(self.scroll_y)
.take(rect.height as usize) .take(rect.height as usize)
.enumerate() .enumerate()
{ {
if only_changed && ins_diff.kind == ObjInsDiffKind::None { if only_changed && ins_row.kind == InstructionDiffKind::None {
out.lines.push(Line::default()); out.lines.push(Line::default());
continue; continue;
} }
let mut sx = rect.x; let mut sx = rect.x;
let sy = rect.y + y as u16; let sy = rect.y + y as u16;
let mut line = Line::default(); let mut line = Line::default();
display_diff(ins_diff, base_addr, |text| -> Result<()> { display_row(obj, symbol_index, ins_row, diff_config, |text, diff_idx| {
let label_text; let label_text;
let mut base_color = match ins_diff.kind { let mut base_color = match ins_row.kind {
ObjInsDiffKind::None InstructionDiffKind::None
| ObjInsDiffKind::OpMismatch | InstructionDiffKind::OpMismatch
| ObjInsDiffKind::ArgMismatch => Color::Gray, | InstructionDiffKind::ArgMismatch => Color::Gray,
ObjInsDiffKind::Replace => Color::Cyan, InstructionDiffKind::Replace => Color::Cyan,
ObjInsDiffKind::Delete => Color::Red, InstructionDiffKind::Delete => Color::Red,
ObjInsDiffKind::Insert => Color::Green, InstructionDiffKind::Insert => Color::Green,
}; };
if let Some(idx) = diff_idx.get() {
base_color = COLOR_ROTATION[idx as usize % COLOR_ROTATION.len()];
}
let mut pad_to = 0; let mut pad_to = 0;
match text { match text {
DiffText::Basic(text) => { DiffText::Basic(text) => {
label_text = text.to_string(); label_text = text.to_string();
} }
DiffText::BasicColor(s, idx) => {
label_text = s.to_string();
base_color = COLOR_ROTATION[idx % COLOR_ROTATION.len()];
}
DiffText::Line(num) => { DiffText::Line(num) => {
label_text = format!("{num} "); label_text = format!("{num} ");
base_color = Color::DarkGray; base_color = Color::DarkGray;
@ -539,41 +551,31 @@ impl FunctionDiffUi {
} }
DiffText::Opcode(mnemonic, _op) => { DiffText::Opcode(mnemonic, _op) => {
label_text = mnemonic.to_string(); label_text = mnemonic.to_string();
if ins_diff.kind == ObjInsDiffKind::OpMismatch { if ins_row.kind == InstructionDiffKind::OpMismatch {
base_color = Color::Blue; base_color = Color::Blue;
} }
pad_to = 8; pad_to = 8;
} }
DiffText::Argument(arg, diff) => { DiffText::Argument(arg) => {
label_text = arg.to_string(); label_text = arg.to_string();
if let Some(diff) = diff {
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
} }
} DiffText::BranchDest(addr) => {
DiffText::BranchDest(addr, diff) => {
label_text = format!("{addr:x}"); label_text = format!("{addr:x}");
if let Some(diff) = diff {
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
} }
} DiffText::Symbol(sym) => {
DiffText::Symbol(sym, diff) => {
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name); let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
label_text = name.clone(); label_text = name.clone();
if let Some(diff) = diff { if diff_idx.is_none() {
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
} else {
base_color = Color::White; base_color = Color::White;
} }
} }
DiffText::Addend(addend, diff) => { DiffText::Addend(addend) => {
label_text = match addend.cmp(&0i64) { label_text = match addend.cmp(&0i64) {
Ordering::Greater => format!("+{:#x}", addend), Ordering::Greater => format!("+{:#x}", addend),
Ordering::Less => format!("-{:#x}", -addend), Ordering::Less => format!("-{:#x}", -addend),
_ => "".to_string(), _ => "".to_string(),
}; };
if let Some(diff) = diff { if diff_idx.is_none() {
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
} else {
base_color = Color::White; base_color = Color::White;
} }
} }
@ -612,12 +614,13 @@ impl FunctionDiffUi {
new_highlight new_highlight
} }
fn print_margin(&self, out: &mut Text, symbol: &ObjSymbolDiff, rect: Rect) { fn print_margin(&self, out: &mut Text, symbol: &SymbolDiff, rect: Rect) {
for ins_diff in symbol.instructions.iter().skip(self.scroll_y).take(rect.height as usize) { for ins_row in symbol.instruction_rows.iter().skip(self.scroll_y).take(rect.height as usize)
if ins_diff.kind != ObjInsDiffKind::None { {
out.lines.push(Line::raw(match ins_diff.kind { if ins_row.kind != InstructionDiffKind::None {
ObjInsDiffKind::Delete => "<", out.lines.push(Line::raw(match ins_row.kind {
ObjInsDiffKind::Insert => ">", InstructionDiffKind::Delete => "<",
InstructionDiffKind::Insert => ">",
_ => "|", _ => "|",
})); }));
} else { } else {
@ -649,23 +652,18 @@ pub fn match_percent_color(match_percent: f32) -> Color {
#[inline] #[inline]
fn get_symbol( fn get_symbol(
obj: Option<&(ObjInfo, ObjDiff)>, obj: Option<&(Object, ObjectDiff)>,
sym: Option<SymbolRef>, sym: Option<usize>,
) -> Option<(&ObjSymbol, &ObjSymbolDiff)> { ) -> Option<(&Object, usize, &SymbolDiff)> {
let (obj, diff) = obj?; let (obj, diff) = obj?;
let sym = sym?; let sym = sym?;
Some((obj.section_symbol(sym).1, diff.symbol_diff(sym))) Some((obj, sym, &diff.symbols[sym]))
} }
fn find_function(obj: &ObjInfo, name: &str) -> Option<SymbolRef> { fn find_function(obj: &Object, name: &str) -> Option<usize> {
for (section_idx, section) in obj.sections.iter().enumerate() { for (symbol_idx, symbol) in obj.symbols.iter().enumerate() {
if section.kind != ObjSectionKind::Code {
continue;
}
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
if symbol.name == name { if symbol.name == name {
return Some(SymbolRef { section_idx, symbol_idx }); return Some(symbol_idx);
}
} }
} }
None None

View File

@ -12,9 +12,6 @@ A local diffing tool for decompilation projects.
""" """
documentation = "https://docs.rs/objdiff-core" documentation = "https://docs.rs/objdiff-core"
[lib]
crate-type = ["cdylib", "rlib"]
[features] [features]
default = ["std"] default = ["std"]
all = [ all = [
@ -25,11 +22,11 @@ all = [
"dwarf", "dwarf",
"serde", "serde",
# Architectures # Architectures
"arm",
"arm64",
"mips", "mips",
"ppc", "ppc",
"x86", "x86",
"arm",
"arm64",
] ]
# Implicit, used to check if any arch is enabled # Implicit, used to check if any arch is enabled
any-arch = [ any-arch = [
@ -41,6 +38,7 @@ any-arch = [
"dep:prettyplease", "dep:prettyplease",
"dep:proc-macro2", "dep:proc-macro2",
"dep:quote", "dep:quote",
"dep:regex",
"dep:similar", "dep:similar",
"dep:strum", "dep:strum",
"dep:syn", "dep:syn",
@ -113,14 +111,6 @@ arm64 = [
"dep:yaxpeax-arch", "dep:yaxpeax-arch",
"dep:yaxpeax-arm", "dep:yaxpeax-arm",
] ]
wasm = [
"any-arch",
"bindings",
"dep:log",
"dep:talc",
"dep:spin",
"dep:wit-bindgen",
]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["all"] features = ["all"]
@ -140,6 +130,8 @@ serde = { version = "1.0", default-features = false, features = ["derive"], opti
similar = { version = "2.7", default-features = false, optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" } similar = { version = "2.7", default-features = false, optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" }
strum = { version = "0.26", default-features = false, features = ["derive"], optional = true } strum = { version = "0.26", default-features = false, features = ["derive"], optional = true }
typed-path = { version = "0.10", default-features = false, optional = true } typed-path = { version = "0.10", default-features = false, optional = true }
regex = { version = "1.11", default-features = false, features = [], optional = true }
itertools = { version = "0.14", default-features = false, features = ["use_alloc"] }
# config # config
globset = { version = "0.4", default-features = false, optional = true } globset = { version = "0.4", default-features = false, optional = true }
@ -170,13 +162,6 @@ arm-attr = { version = "0.2", optional = true }
yaxpeax-arch = { version = "0.3", default-features = false, optional = true } yaxpeax-arch = { version = "0.3", default-features = false, optional = true }
yaxpeax-arm = { version = "0.3", default-features = false, optional = true } yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
# wasm
#console_error_panic_hook = { version = "0.1", optional = true }
#console_log = { version = "1.0", optional = true }
talc = { version = "4.4", optional = true }
spin = { version = "0.9", optional = true }
wit-bindgen = { version = "0.38", default-features = false, features = ["macros"], optional = true }
# build # build
notify = { version = "8.0.0", optional = true } notify = { version = "8.0.0", optional = true }
notify-debouncer-full = { version = "0.5.0", optional = true } notify-debouncer-full = { version = "0.5.0", optional = true }
@ -207,3 +192,21 @@ quote = { version = "1.0", optional = true }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" } serde_json = { version = "1.0" }
syn = { version = "2.0", optional = true } syn = { version = "2.0", optional = true }
# Enable all features for tests
[dev-dependencies]
objdiff-core = { path = ".", features = [
# Features
"bindings",
"build",
"config",
"dwarf",
"serde",
# Architectures
"mips",
"ppc",
# "x86",
"arm",
"arm64",
] }
insta = "1.42.1"

View File

@ -1,11 +1,12 @@
#[cfg(feature = "any-arch")] #[cfg(feature = "any-arch")]
mod config_gen; mod config_gen;
fn main() { fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(feature = "bindings")] #[cfg(feature = "bindings")]
compile_protos(); compile_protos();
#[cfg(feature = "any-arch")] #[cfg(feature = "any-arch")]
config_gen::generate_diff_config(); config_gen::generate_diff_config();
Ok(())
} }
#[cfg(feature = "bindings")] #[cfg(feature = "bindings")]
@ -18,7 +19,7 @@ fn compile_protos() {
.map(|m| m.modified().unwrap()) .map(|m| m.modified().unwrap())
.unwrap_or(std::time::SystemTime::UNIX_EPOCH); .unwrap_or(std::time::SystemTime::UNIX_EPOCH);
let mut run_protoc = false; let mut run_protoc = false;
let proto_files = vec![root.join("diff.proto"), root.join("report.proto")]; let proto_files = vec![root.join("report.proto")];
for proto_file in &proto_files { for proto_file in &proto_files {
println!("cargo:rerun-if-changed={}", proto_file.display()); println!("cargo:rerun-if-changed={}", proto_file.display());
let mtime = match std::fs::metadata(proto_file) { let mtime = match std::fs::metadata(proto_file) {

View File

@ -39,6 +39,13 @@
"name": "Combine data sections", "name": "Combine data sections",
"description": "Combines data sections with equal names." "description": "Combines data sections with equal names."
}, },
{
"id": "combineTextSections",
"type": "boolean",
"default": false,
"name": "Combine text sections",
"description": "Combines all text sections into one."
},
{ {
"id": "arm.archVersion", "id": "arm.archVersion",
"type": "choice", "type": "choice",
@ -213,7 +220,8 @@
"properties": [ "properties": [
"functionRelocDiffs", "functionRelocDiffs",
"spaceBetweenArgs", "spaceBetweenArgs",
"combineDataSections" "combineDataSections",
"combineTextSections"
] ]
}, },
{ {

View File

@ -87,11 +87,11 @@ message Relocation {
} }
message RelocationTarget { message RelocationTarget {
Symbol symbol = 1; uint32 symbol_index = 1;
int64 addend = 2; int64 addend = 2;
} }
message InstructionDiff { message InstructionDiffRow {
DiffKind diff_kind = 1; DiffKind diff_kind = 1;
optional Instruction instruction = 2; optional Instruction instruction = 2;
optional InstructionBranchFrom branch_from = 3; optional InstructionBranchFrom branch_from = 3;
@ -122,17 +122,12 @@ message InstructionBranchTo {
uint32 branch_index = 2; uint32 branch_index = 2;
} }
message SymbolRef {
optional uint32 section_index = 1;
uint32 symbol_index = 2;
}
message SymbolDiff { message SymbolDiff {
Symbol symbol = 1; Symbol symbol = 1;
repeated InstructionDiff instructions = 2; repeated InstructionDiffRow instruction_rows = 2;
optional float match_percent = 3; optional float match_percent = 3;
// The symbol ref in the _other_ object that this symbol was diffed against // The symbol index in the _other_ object that this symbol was diffed against
optional SymbolRef target = 5; optional uint32 target_symbol = 5;
} }
message DataDiff { message DataDiff {
@ -147,7 +142,7 @@ message SectionDiff {
SectionKind kind = 2; SectionKind kind = 2;
uint64 size = 3; uint64 size = 3;
uint64 address = 4; uint64 address = 4;
repeated SymbolDiff symbols = 5; reserved 5;
repeated DataDiff data = 6; repeated DataDiff data = 6;
optional float match_percent = 7; optional float match_percent = 7;
} }
@ -157,11 +152,11 @@ enum SectionKind {
SECTION_TEXT = 1; SECTION_TEXT = 1;
SECTION_DATA = 2; SECTION_DATA = 2;
SECTION_BSS = 3; SECTION_BSS = 3;
SECTION_COMMON = 4;
} }
message ObjectDiff { message ObjectDiff {
repeated SectionDiff sections = 1; repeated SectionDiff sections = 1;
repeated SymbolDiff symbols = 2;
} }
message DiffResult { message DiffResult {

View File

@ -3,41 +3,37 @@ use alloc::{
collections::BTreeMap, collections::BTreeMap,
format, format,
string::{String, ToString}, string::{String, ToString},
vec,
vec::Vec, vec::Vec,
}; };
use core::ops::Range;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use arm_attr::{enums::CpuArch, tag::Tag, BuildAttrs}; use arm_attr::{enums::CpuArch, tag::Tag, BuildAttrs};
use object::{ use object::{elf, Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _};
elf::{self, SHT_ARM_ATTRIBUTES}, use unarm::{args, arm, thumb};
Endian, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, SectionKind,
Symbol, SymbolKind,
};
use unarm::{
args::{Argument, OffsetImm, OffsetReg, Register},
parse::{ArmVersion, ParseMode, Parser},
DisplayOptions, ParseFlags, ParsedIns, RegNames,
};
use crate::{ use crate::{
arch::{ObjArch, ProcessCodeResult}, arch::Arch,
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig}, diff::{display::InstructionPart, ArmArchVersion, ArmR9Usage, DiffObjConfig},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection}, obj::{
InstructionArg, InstructionArgValue, InstructionRef, RelocationFlags, ResolvedRelocation,
ScannedInstruction, SymbolFlag, SymbolFlagSet, SymbolKind,
},
}; };
pub struct ObjArchArm { #[derive(Debug)]
pub struct ArchArm {
/// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address /// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
disasm_modes: BTreeMap<usize, Vec<DisasmMode>>, disasm_modes: BTreeMap<usize, Vec<DisasmMode>>,
detected_version: Option<ArmVersion>, detected_version: Option<unarm::ArmVersion>,
endianness: object::Endianness, endianness: object::Endianness,
} }
impl ObjArchArm { impl ArchArm {
pub fn new(file: &File) -> Result<Self> { pub fn new(file: &object::File) -> Result<Self> {
let endianness = file.endianness(); let endianness = file.endianness();
match file { match file {
File::Elf32(_) => { object::File::Elf32(_) => {
let disasm_modes = Self::elf_get_mapping_symbols(file); let disasm_modes = Self::elf_get_mapping_symbols(file);
let detected_version = Self::elf_detect_arm_version(file)?; let detected_version = Self::elf_detect_arm_version(file)?;
Ok(Self { disasm_modes, detected_version, endianness }) Ok(Self { disasm_modes, detected_version, endianness })
@ -46,10 +42,11 @@ impl ObjArchArm {
} }
} }
fn elf_detect_arm_version(file: &File) -> Result<Option<ArmVersion>> { fn elf_detect_arm_version(file: &object::File) -> Result<Option<unarm::ArmVersion>> {
// Check ARM attributes // Check ARM attributes
if let Some(arm_attrs) = file.sections().find(|s| { if let Some(arm_attrs) = file.sections().find(|s| {
s.kind() == SectionKind::Elf(SHT_ARM_ATTRIBUTES) && s.name() == Ok(".ARM.attributes") s.kind() == object::SectionKind::Elf(elf::SHT_ARM_ATTRIBUTES)
&& s.name() == Ok(".ARM.attributes")
}) { }) {
let attr_data = arm_attrs.uncompressed_data()?; let attr_data = arm_attrs.uncompressed_data()?;
let build_attrs = BuildAttrs::new(&attr_data, match file.endianness() { let build_attrs = BuildAttrs::new(&attr_data, match file.endianness() {
@ -70,9 +67,9 @@ impl ObjArchArm {
} }
}); });
match cpu_arch { match cpu_arch {
Some(CpuArch::V4T) => return Ok(Some(ArmVersion::V4T)), Some(CpuArch::V4T) => return Ok(Some(unarm::ArmVersion::V4T)),
Some(CpuArch::V5TE) => return Ok(Some(ArmVersion::V5Te)), Some(CpuArch::V5TE) => return Ok(Some(unarm::ArmVersion::V5Te)),
Some(CpuArch::V6K) => return Ok(Some(ArmVersion::V6K)), Some(CpuArch::V6K) => return Ok(Some(unarm::ArmVersion::V6K)),
Some(arch) => bail!("ARM arch {} not supported", arch), Some(arch) => bail!("ARM arch {} not supported", arch),
None => {} None => {}
}; };
@ -82,9 +79,9 @@ impl ObjArchArm {
Ok(None) Ok(None)
} }
fn elf_get_mapping_symbols(file: &File) -> BTreeMap<usize, Vec<DisasmMode>> { fn elf_get_mapping_symbols(file: &object::File) -> BTreeMap<usize, Vec<DisasmMode>> {
file.sections() file.sections()
.filter(|s| s.kind() == SectionKind::Text) .filter(|s| s.kind() == object::SectionKind::Text)
.map(|s| { .map(|s| {
let index = s.index(); let index = s.index();
let mut mapping_symbols: Vec<_> = file let mut mapping_symbols: Vec<_> = file
@ -97,32 +94,95 @@ impl ObjArchArm {
}) })
.collect() .collect()
} }
fn endian(&self) -> unarm::Endian {
match self.endianness {
object::Endianness::Little => unarm::Endian::Little,
object::Endianness::Big => unarm::Endian::Big,
}
} }
impl ObjArch for ObjArchArm { fn parse_flags(&self, diff_config: &DiffObjConfig) -> unarm::ParseFlags {
fn symbol_address(&self, symbol: &Symbol) -> u64 { unarm::ParseFlags {
let address = symbol.address(); ual: diff_config.arm_unified_syntax,
if symbol.kind() == SymbolKind::Text { version: match diff_config.arm_arch_version {
address & !1 ArmArchVersion::Auto => self.detected_version.unwrap_or(unarm::ArmVersion::V5Te),
ArmArchVersion::V4t => unarm::ArmVersion::V4T,
ArmArchVersion::V5te => unarm::ArmVersion::V5Te,
ArmArchVersion::V6k => unarm::ArmVersion::V6K,
},
}
}
fn display_options(&self, diff_config: &DiffObjConfig) -> unarm::DisplayOptions {
unarm::DisplayOptions {
reg_names: unarm::RegNames {
av_registers: diff_config.arm_av_registers,
r9_use: match diff_config.arm_r9_usage {
ArmR9Usage::GeneralPurpose => unarm::R9Use::GeneralPurpose,
ArmR9Usage::Sb => unarm::R9Use::Pid,
ArmR9Usage::Tr => unarm::R9Use::Tls,
},
explicit_stack_limit: diff_config.arm_sl_usage,
frame_pointer: diff_config.arm_fp_usage,
ip: diff_config.arm_ip_usage,
},
}
}
fn parse_ins_ref(
&self,
ins_ref: InstructionRef,
code: &[u8],
diff_config: &DiffObjConfig,
) -> Result<(unarm::Ins, unarm::ParsedIns)> {
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) => {
u32::from_le_bytes([code[0], code[1], code[2], code[3]])
}
(object::Endianness::Big, 2) => u16::from_be_bytes([code[0], code[1]]) as u32,
(object::Endianness::Big, 4) => {
u32::from_be_bytes([code[0], code[1], code[2], code[3]])
}
_ => bail!("Invalid instruction size {}", ins_ref.size),
};
let (ins, parsed_ins) = if ins_ref.opcode == u16::MAX {
let mut args = args::Arguments::default();
args[0] = args::Argument::UImm(code);
let mnemonic = if ins_ref.size == 4 { ".word" } else { ".hword" };
(unarm::Ins::Data, unarm::ParsedIns { mnemonic, args })
} else if ins_ref.opcode & (1 << 15) != 0 {
let ins = arm::Ins { code, op: arm::Opcode::from(ins_ref.opcode as u8) };
let parsed = ins.parse(&self.parse_flags(diff_config));
(unarm::Ins::Arm(ins), parsed)
} else { } else {
address 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)
}
};
Ok((ins, parsed_ins))
} }
} }
fn process_code( impl Arch for ArchArm {
fn scan_instructions(
&self, &self,
address: u64, address: u64,
code: &[u8], code: &[u8],
section_index: usize, section_index: usize,
relocations: &[ObjReloc], diff_config: &DiffObjConfig,
line_info: &BTreeMap<u64, u32>, ) -> Result<Vec<ScannedInstruction>> {
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let start_addr = address as u32; let start_addr = address as u32;
let end_addr = start_addr + code.len() as u32; let end_addr = start_addr + code.len() as u32;
// Mapping symbols decide what kind of data comes after it. $a for ARM code, $t for Thumb code and $d for data. // Mapping symbols decide what kind of data comes after it. $a for ARM code, $t for Thumb code and $d for data.
let fallback_mappings = [DisasmMode { address: start_addr, mapping: ParseMode::Arm }]; let fallback_mappings =
[DisasmMode { address: start_addr, mapping: unarm::ParseMode::Arm }];
let mapping_symbols = self let mapping_symbols = self
.disasm_modes .disasm_modes
.get(&section_index) .get(&section_index)
@ -138,39 +198,14 @@ impl ObjArch for ObjArchArm {
let mut next_mapping = mappings_iter.next(); let mut next_mapping = mappings_iter.next();
let ins_count = code.len() / first_mapping.instruction_size(start_addr); let ins_count = code.len() / first_mapping.instruction_size(start_addr);
let mut ops = Vec::<u16>::with_capacity(ins_count); let mut ops = Vec::<ScannedInstruction>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
let version = match config.arm_arch_version { let endian = self.endian();
ArmArchVersion::Auto => self.detected_version.unwrap_or(ArmVersion::V5Te), let parse_flags = self.parse_flags(diff_config);
ArmArchVersion::V4t => ArmVersion::V4T, let mut parser = unarm::Parser::new(first_mapping, start_addr, endian, parse_flags, code);
ArmArchVersion::V5te => ArmVersion::V5Te,
ArmArchVersion::V6k => ArmVersion::V6K,
};
let endian = match self.endianness {
object::Endianness::Little => unarm::Endian::Little,
object::Endianness::Big => unarm::Endian::Big,
};
let parse_flags = ParseFlags { ual: config.arm_unified_syntax, version }; while let Some((address, ins, _parsed_ins)) = parser.next() {
let size = parser.mode.instruction_size(address);
let mut parser = Parser::new(first_mapping, start_addr, endian, parse_flags, code);
let display_options = DisplayOptions {
reg_names: RegNames {
av_registers: config.arm_av_registers,
r9_use: match config.arm_r9_usage {
ArmR9Usage::GeneralPurpose => unarm::R9Use::GeneralPurpose,
ArmR9Usage::Sb => unarm::R9Use::Pid,
ArmR9Usage::Tr => unarm::R9Use::Tls,
},
explicit_stack_limit: config.arm_sl_usage,
frame_pointer: config.arm_fp_usage,
ip: config.arm_ip_usage,
},
};
while let Some((address, ins, parsed_ins)) = parser.next() {
if let Some(next) = next_mapping { if let Some(next) = next_mapping {
let next_address = parser.address; let next_address = parser.address;
if next_address >= next.address { if next_address >= next.address {
@ -179,82 +214,95 @@ impl ObjArch for ObjArchArm {
next_mapping = mappings_iter.next(); next_mapping = mappings_iter.next();
} }
} }
let line = line_info.range(..=address as u64).last().map(|(_, &b)| b); let (opcode, branch_dest) = match ins {
unarm::Ins::Arm(x) => {
let reloc = relocations.iter().find(|r| (r.address as u32 & !1) == address).cloned(); let opcode = x.op as u16 | (1 << 15);
let branch_dest = match x.op {
let mut reloc_arg = None; arm::Opcode::B | arm::Opcode::Bl => {
if let Some(reloc) = &reloc { address.checked_add_signed(x.field_branch_offset())
match reloc.flags {
// Calls
RelocationFlags::Elf { r_type: elf::R_ARM_THM_XPC22 }
| RelocationFlags::Elf { r_type: elf::R_ARM_THM_PC22 }
| RelocationFlags::Elf { r_type: elf::R_ARM_PC24 }
| RelocationFlags::Elf { r_type: elf::R_ARM_XPC25 }
| RelocationFlags::Elf { r_type: elf::R_ARM_CALL } => {
reloc_arg = parsed_ins
.args
.iter()
.rposition(|a| matches!(a, Argument::BranchDest(_)));
}
// Data
RelocationFlags::Elf { r_type: elf::R_ARM_ABS32 } => {
reloc_arg =
parsed_ins.args.iter().rposition(|a| matches!(a, Argument::UImm(_)));
}
_ => (),
} }
arm::Opcode::BlxI => address.checked_add_signed(x.field_blx_offset()),
_ => None,
}; };
(opcode, branch_dest)
let (args, branch_dest) = if reloc.is_some() && parser.mode == ParseMode::Data { }
(vec![ObjInsArg::Reloc], None) unarm::Ins::Thumb(x) => {
} else { let opcode = x.op as u16;
push_args(&parsed_ins, config, reloc_arg, address, display_options)? let branch_dest = match x.op {
thumb::Opcode::B | thumb::Opcode::Bl => {
address.checked_add_signed(x.field_branch_offset_8())
}
thumb::Opcode::BLong => {
address.checked_add_signed(x.field_branch_offset_11())
}
_ => None,
}; };
(opcode, branch_dest)
ops.push(ins.opcode_id()); }
insts.push(ObjIns { unarm::Ins::Data => (u16::MAX, None),
address: address as u64, };
size: (parser.address - address) as u8, ops.push(ScannedInstruction {
op: ins.opcode_id(), ins_ref: InstructionRef { address: address as u64, size: size as u8, opcode },
mnemonic: Cow::Borrowed(parsed_ins.mnemonic), branch_dest: branch_dest.map(|x| x as u64),
args,
reloc,
branch_dest,
line,
formatted: parsed_ins.display(display_options).to_string(),
orig: None,
}); });
} }
Ok(ProcessCodeResult { ops, insts }) Ok(ops)
}
fn display_instruction(
&self,
ins_ref: InstructionRef,
code: &[u8],
relocation: Option<ResolvedRelocation>,
_function_range: Range<u64>,
_section_index: usize,
diff_config: &DiffObjConfig,
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
let (ins, parsed_ins) = self.parse_ins_ref(ins_ref, code, diff_config)?;
cb(InstructionPart::Opcode(Cow::Borrowed(parsed_ins.mnemonic), ins_ref.opcode))?;
if ins == unarm::Ins::Data && relocation.is_some() {
cb(InstructionPart::Arg(InstructionArg::Reloc))?;
} else {
push_args(
&parsed_ins,
relocation,
ins_ref.address as u32,
self.display_options(diff_config),
cb,
)?;
}
Ok(())
} }
fn implcit_addend( fn implcit_addend(
&self, &self,
_file: &File<'_>, _file: &object::File<'_>,
section: &ObjSection, section: &object::Section,
address: u64, address: u64,
reloc: &Relocation, _relocation: &object::Relocation,
flags: RelocationFlags,
) -> Result<i64> { ) -> Result<i64> {
let section_data = section.data()?;
let address = address as usize; let address = address as usize;
Ok(match reloc.flags() { Ok(match flags {
// ARM calls // ARM calls
RelocationFlags::Elf { r_type: elf::R_ARM_PC24 } RelocationFlags::Elf(elf::R_ARM_PC24)
| RelocationFlags::Elf { r_type: elf::R_ARM_XPC25 } | RelocationFlags::Elf(elf::R_ARM_XPC25)
| RelocationFlags::Elf { r_type: elf::R_ARM_CALL } => { | RelocationFlags::Elf(elf::R_ARM_CALL) => {
let data = section.data[address..address + 4].try_into()?; let data = section_data[address..address + 4].try_into()?;
let addend = self.endianness.read_i32_bytes(data); let addend = self.endianness.read_i32_bytes(data);
let imm24 = addend & 0xffffff; let imm24 = addend & 0xffffff;
(imm24 << 2) << 8 >> 8 (imm24 << 2) << 8 >> 8
} }
// Thumb calls // Thumb calls
RelocationFlags::Elf { r_type: elf::R_ARM_THM_PC22 } RelocationFlags::Elf(elf::R_ARM_THM_PC22)
| RelocationFlags::Elf { r_type: elf::R_ARM_THM_XPC22 } => { | RelocationFlags::Elf(elf::R_ARM_THM_XPC22) => {
let data = section.data[address..address + 2].try_into()?; let data = section_data[address..address + 2].try_into()?;
let high = self.endianness.read_i16_bytes(data) as i32; let high = self.endianness.read_i16_bytes(data) as i32;
let data = section.data[address + 2..address + 4].try_into()?; let data = section_data[address + 2..address + 4].try_into()?;
let low = self.endianness.read_i16_bytes(data) as i32; let low = self.endianness.read_i16_bytes(data) as i32;
let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff); let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff);
@ -262,8 +310,8 @@ impl ObjArch for ObjArchArm {
} }
// Data // Data
RelocationFlags::Elf { r_type: elf::R_ARM_ABS32 } => { RelocationFlags::Elf(elf::R_ARM_ABS32) => {
let data = section.data[address..address + 4].try_into()?; let data = section_data[address..address + 4].try_into()?;
self.endianness.read_i32_bytes(data) self.endianness.read_i32_bytes(data)
} }
@ -283,7 +331,7 @@ impl ObjArch for ObjArchArm {
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize { fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize {
match flags { match flags {
RelocationFlags::Elf { r_type } => match r_type { RelocationFlags::Elf(r_type) => match r_type {
elf::R_ARM_ABS32 => 4, elf::R_ARM_ABS32 => 4,
elf::R_ARM_REL32 => 4, elf::R_ARM_REL32 => 4,
elf::R_ARM_ABS16 => 2, elf::R_ARM_ABS16 => 2,
@ -293,48 +341,67 @@ impl ObjArch for ObjArchArm {
_ => 1, _ => 1,
} }
} }
fn symbol_address(&self, address: u64, kind: SymbolKind) -> u64 {
if kind == SymbolKind::Function {
address & !1
} else {
address
}
}
fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet {
let mut flags = SymbolFlagSet::default();
if DisasmMode::from_symbol(symbol).is_some() {
flags |= SymbolFlag::Hidden;
}
flags
}
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
struct DisasmMode { struct DisasmMode {
address: u32, address: u32,
mapping: ParseMode, mapping: unarm::ParseMode,
} }
impl DisasmMode { impl DisasmMode {
fn from_symbol<'a>(sym: &Symbol<'a, '_, &'a [u8]>) -> Option<Self> { fn from_symbol<'a>(sym: &object::Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
if let Ok(name) = sym.name() { sym.name()
ParseMode::from_mapping_symbol(name) .ok()
.and_then(unarm::ParseMode::from_mapping_symbol)
.map(|mapping| DisasmMode { address: sym.address() as u32, mapping }) .map(|mapping| DisasmMode { address: sym.address() as u32, mapping })
} else {
None
}
} }
} }
fn push_args( fn push_args(
parsed_ins: &ParsedIns, parsed_ins: &unarm::ParsedIns,
config: &DiffObjConfig, relocation: Option<ResolvedRelocation>,
reloc_arg: Option<usize>,
cur_addr: u32, cur_addr: u32,
display_options: DisplayOptions, display_options: unarm::DisplayOptions,
) -> Result<(Vec<ObjInsArg>, Option<u64>)> { mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
let mut args = vec![]; ) -> Result<()> {
let mut branch_dest = None; let reloc_arg = find_reloc_arg(parsed_ins, relocation);
let mut writeback = false; let mut writeback = false;
let mut deref = 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 // Emit punctuation before separator
if deref { if deref {
match arg { match arg {
Argument::OffsetImm(OffsetImm { post_indexed: true, value: _ }) args::Argument::OffsetImm(args::OffsetImm { post_indexed: true, value: _ })
| Argument::OffsetReg(OffsetReg { add: _, post_indexed: true, reg: _ }) | args::Argument::OffsetReg(args::OffsetReg {
| Argument::CoOption(_) => { add: _,
post_indexed: true,
reg: _,
})
| args::Argument::CoOption(_) => {
deref = false; deref = false;
args.push(ObjInsArg::PlainText("]".into())); arg_cb(InstructionPart::Basic("]"))?;
if writeback { if writeback {
writeback = false; writeback = false;
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into()))); arg_cb(InstructionPart::Arg(InstructionArg::Value(
InstructionArgValue::Opaque("!".into()),
)))?;
} }
} }
_ => {} _ => {}
@ -342,117 +409,179 @@ fn push_args(
} }
if i > 0 { if i > 0 {
args.push(ObjInsArg::PlainText(config.separator().into())); arg_cb(InstructionPart::Separator)?;
} }
if reloc_arg == Some(i) { if reloc_arg == Some(i) {
args.push(ObjInsArg::Reloc); arg_cb(InstructionPart::Arg(InstructionArg::Reloc))?;
} else { } else {
match arg { match arg {
Argument::None => {} args::Argument::None => {}
Argument::Reg(reg) => { args::Argument::Reg(reg) => {
if reg.deref { if reg.deref {
deref = true; deref = true;
args.push(ObjInsArg::PlainText("[".into())); arg_cb(InstructionPart::Basic("["))?;
} }
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque( arg_cb(InstructionPart::Arg(InstructionArg::Value(
InstructionArgValue::Opaque(
reg.reg.display(display_options.reg_names).to_string().into(), reg.reg.display(display_options.reg_names).to_string().into(),
))); ),
)))?;
if reg.writeback { if reg.writeback {
if reg.deref { if reg.deref {
writeback = true; writeback = true;
} else { } else {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into()))); arg_cb(InstructionPart::Arg(InstructionArg::Value(
InstructionArgValue::Opaque("!".into()),
)))?;
} }
} }
} }
Argument::RegList(reg_list) => { args::Argument::RegList(reg_list) => {
args.push(ObjInsArg::PlainText("{".into())); arg_cb(InstructionPart::Basic("{"))?;
let mut first = true; let mut first = true;
for i in 0..16 { for i in 0..16 {
if (reg_list.regs & (1 << i)) != 0 { if (reg_list.regs & (1 << i)) != 0 {
if !first { if !first {
args.push(ObjInsArg::PlainText(config.separator().into())); arg_cb(InstructionPart::Separator)?;
} }
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque( arg_cb(InstructionPart::Arg(InstructionArg::Value(
Register::parse(i) InstructionArgValue::Opaque(
args::Register::parse(i)
.display(display_options.reg_names) .display(display_options.reg_names)
.to_string() .to_string()
.into(), .into(),
))); ),
)))?;
first = false; first = false;
} }
} }
args.push(ObjInsArg::PlainText("}".into())); arg_cb(InstructionPart::Basic("}"))?;
if reg_list.user_mode { if reg_list.user_mode {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("^".to_string().into()))); arg_cb(InstructionPart::Arg(InstructionArg::Value(
InstructionArgValue::Opaque("^".into()),
)))?;
} }
} }
Argument::UImm(value) | Argument::CoOpcode(value) | Argument::SatImm(value) => { args::Argument::UImm(value)
args.push(ObjInsArg::PlainText("#".into())); | args::Argument::CoOpcode(value)
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64))); | args::Argument::SatImm(value) => {
arg_cb(InstructionPart::Basic("#"))?;
arg_cb(InstructionPart::Arg(InstructionArg::Value(
InstructionArgValue::Unsigned(*value as u64),
)))?;
} }
Argument::SImm(value) args::Argument::SImm(value)
| Argument::OffsetImm(OffsetImm { post_indexed: _, value }) => { | args::Argument::OffsetImm(args::OffsetImm { post_indexed: _, value }) => {
args.push(ObjInsArg::PlainText("#".into())); arg_cb(InstructionPart::Basic("#"))?;
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(*value as i64))); arg_cb(InstructionPart::Arg(InstructionArg::Value(
InstructionArgValue::Signed(*value as i64),
)))?;
} }
Argument::BranchDest(value) => { args::Argument::BranchDest(value) => {
let dest = cur_addr.wrapping_add_signed(*value) as u64; let dest = cur_addr.wrapping_add_signed(*value) as u64;
args.push(ObjInsArg::BranchDest(dest)); arg_cb(InstructionPart::Arg(InstructionArg::BranchDest(dest)))?;
branch_dest = Some(dest);
} }
Argument::CoOption(value) => { args::Argument::CoOption(value) => {
args.push(ObjInsArg::PlainText("{".into())); arg_cb(InstructionPart::Basic("{"))?;
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64))); arg_cb(InstructionPart::Arg(InstructionArg::Value(
args.push(ObjInsArg::PlainText("}".into())); InstructionArgValue::Unsigned(*value as u64),
)))?;
arg_cb(InstructionPart::Basic("}"))?;
} }
Argument::CoprocNum(value) => { args::Argument::CoprocNum(value) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(format!("p{}", value).into()))); arg_cb(InstructionPart::Arg(InstructionArg::Value(
InstructionArgValue::Opaque(format!("p{}", value).into()),
)))?;
} }
Argument::ShiftImm(shift) => { args::Argument::ShiftImm(shift) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into()))); arg_cb(InstructionPart::Arg(InstructionArg::Value(
args.push(ObjInsArg::PlainText(" #".into())); InstructionArgValue::Opaque(shift.op.to_string().into()),
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(shift.imm as u64))); )))?;
arg_cb(InstructionPart::Basic(" #"))?;
arg_cb(InstructionPart::Arg(InstructionArg::Value(
InstructionArgValue::Unsigned(shift.imm as u64),
)))?;
} }
Argument::ShiftReg(shift) => { args::Argument::ShiftReg(shift) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into()))); arg_cb(InstructionPart::Arg(InstructionArg::Value(
args.push(ObjInsArg::PlainText(" ".into())); InstructionArgValue::Opaque(shift.op.to_string().into()),
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque( )))?;
arg_cb(InstructionPart::Basic(" "))?;
arg_cb(InstructionPart::Arg(InstructionArg::Value(
InstructionArgValue::Opaque(
shift.reg.display(display_options.reg_names).to_string().into(), shift.reg.display(display_options.reg_names).to_string().into(),
))); ),
)))?;
} }
Argument::OffsetReg(offset) => { args::Argument::OffsetReg(offset) => {
if !offset.add { if !offset.add {
args.push(ObjInsArg::PlainText("-".into())); arg_cb(InstructionPart::Basic("-"))?;
} }
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque( arg_cb(InstructionPart::Arg(InstructionArg::Value(
InstructionArgValue::Opaque(
offset.reg.display(display_options.reg_names).to_string().into(), offset.reg.display(display_options.reg_names).to_string().into(),
))); ),
)))?;
} }
Argument::CpsrMode(mode) => { args::Argument::CpsrMode(mode) => {
args.push(ObjInsArg::PlainText("#".into())); arg_cb(InstructionPart::Basic("#"))?;
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(mode.mode as u64))); arg_cb(InstructionPart::Arg(InstructionArg::Value(
InstructionArgValue::Unsigned(mode.mode as u64),
)))?;
if mode.writeback { if mode.writeback {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into()))); arg_cb(InstructionPart::Arg(InstructionArg::Value(
InstructionArgValue::Opaque("!".into()),
)))?;
} }
} }
Argument::CoReg(_) args::Argument::CoReg(_)
| Argument::StatusReg(_) | args::Argument::StatusReg(_)
| Argument::StatusMask(_) | args::Argument::StatusMask(_)
| Argument::Shift(_) | args::Argument::Shift(_)
| Argument::CpsrFlags(_) | args::Argument::CpsrFlags(_)
| Argument::Endian(_) => args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque( | args::Argument::Endian(_) => {
arg_cb(InstructionPart::Arg(InstructionArg::Value(
InstructionArgValue::Opaque(
arg.display(display_options, None).to_string().into(), arg.display(display_options, None).to_string().into(),
))), ),
)))?;
}
} }
} }
} }
if deref { if deref {
args.push(ObjInsArg::PlainText("]".into())); arg_cb(InstructionPart::Basic("]"))?;
if writeback { if writeback {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into()))); arg_cb(InstructionPart::Arg(InstructionArg::Value(InstructionArgValue::Opaque(
"!".into(),
))))?;
} }
} }
Ok((args, branch_dest)) Ok(())
}
fn find_reloc_arg(
parsed_ins: &unarm::ParsedIns,
relocation: Option<ResolvedRelocation>,
) -> Option<usize> {
if let Some(resolved) = relocation {
match resolved.relocation.flags {
// Calls
RelocationFlags::Elf(elf::R_ARM_THM_XPC22)
| RelocationFlags::Elf(elf::R_ARM_THM_PC22)
| RelocationFlags::Elf(elf::R_ARM_PC24)
| RelocationFlags::Elf(elf::R_ARM_XPC25)
| RelocationFlags::Elf(elf::R_ARM_CALL) => {
parsed_ins.args.iter().rposition(|a| matches!(a, args::Argument::BranchDest(_)))
}
// Data
RelocationFlags::Elf(elf::R_ARM_ABS32) => {
parsed_ins.args.iter().rposition(|a| matches!(a, args::Argument::UImm(_)))
}
_ => None,
}
} else {
None
}
} }

View File

@ -1,57 +1,56 @@
use alloc::{ use alloc::{
borrow::Cow, borrow::Cow,
collections::BTreeMap,
format, format,
string::{String, ToString}, string::{String, ToString},
vec,
vec::Vec, vec::Vec,
}; };
use core::cmp::Ordering; use core::{cmp::Ordering, ops::Range};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use object::{elf, File, Relocation, RelocationFlags}; use object::elf;
use yaxpeax_arch::{Arch, Decoder, Reader, U8Reader}; use yaxpeax_arch::{Arch as YaxpeaxArch, Decoder, Reader, U8Reader};
use yaxpeax_arm::armv8::a64::{ use yaxpeax_arm::armv8::a64::{
ARMv8, DecodeError, InstDecoder, Instruction, Opcode, Operand, SIMDSizeCode, ShiftStyle, ARMv8, DecodeError, InstDecoder, Instruction, Opcode, Operand, SIMDSizeCode, ShiftStyle,
SizeCode, SizeCode,
}; };
use crate::{ use crate::{
arch::{ObjArch, ProcessCodeResult}, arch::Arch,
diff::DiffObjConfig, diff::{display::InstructionPart, DiffObjConfig},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection}, obj::{
InstructionArg, InstructionArgValue, InstructionRef, RelocationFlags, ResolvedRelocation,
ScannedInstruction,
},
}; };
pub struct ObjArchArm64 {} #[derive(Debug)]
pub struct ArchArm64 {}
impl ObjArchArm64 { impl ArchArm64 {
pub fn new(_file: &File) -> Result<Self> { Ok(Self {}) } pub fn new(_file: &object::File) -> Result<Self> { Ok(Self {}) }
} }
impl ObjArch for ObjArchArm64 { impl Arch for ArchArm64 {
fn process_code( fn scan_instructions(
&self, &self,
address: u64, address: u64,
code: &[u8], code: &[u8],
section_index: usize, _section_index: usize,
relocations: &[ObjReloc], _diff_config: &DiffObjConfig,
line_info: &BTreeMap<u64, u32>, ) -> Result<Vec<ScannedInstruction>> {
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let start_address = address; let start_address = address;
let end_address = address + code.len() as u64; let mut ops = Vec::<ScannedInstruction>::with_capacity(code.len() / 4);
let ins_count = code.len() / 4;
let mut ops = Vec::with_capacity(ins_count);
let mut insts = Vec::with_capacity(ins_count);
let mut reader = U8Reader::new(code); let mut reader = U8Reader::new(code);
let decoder = InstDecoder::default(); let decoder = InstDecoder::default();
let mut ins = Instruction::default(); let mut ins = Instruction::default();
loop { loop {
// This is ridiculous... // This is ridiculous...
let address = let offset = <U8Reader<'_> as Reader<
start_address + <U8Reader<'_> as Reader<<ARMv8 as Arch>::Address, <ARMv8 as Arch>::Word>>::total_offset(&mut reader); <ARMv8 as YaxpeaxArch>::Address,
<ARMv8 as YaxpeaxArch>::Word,
>>::total_offset(&mut reader);
let address = start_address + offset;
match decoder.decode_into(&mut ins, &mut reader) { match decoder.decode_into(&mut ins, &mut reader) {
Ok(()) => {} Ok(()) => {}
Err(e) => match e { Err(e) => match e {
@ -59,94 +58,182 @@ impl ObjArch for ObjArchArm64 {
DecodeError::InvalidOpcode DecodeError::InvalidOpcode
| DecodeError::InvalidOperand | DecodeError::InvalidOperand
| DecodeError::IncompleteDecoder => { | DecodeError::IncompleteDecoder => {
ops.push(u16::MAX); ops.push(ScannedInstruction {
insts.push(ObjIns { ins_ref: InstructionRef { address, size: 4, opcode: u16::MAX },
address,
size: 4,
op: u16::MAX,
mnemonic: Cow::Borrowed("<invalid>"),
args: vec![],
reloc: None,
branch_dest: None, branch_dest: None,
line: None,
formatted: "".to_string(),
orig: None,
}); });
continue; continue;
} }
}, },
} }
let line = line_info.range(..=address).last().map(|(_, &b)| b); let opcode = opcode_to_u16(ins.opcode);
let reloc = relocations.iter().find(|r| (r.address & !3) == address).cloned(); 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 });
}
Ok(ops)
}
fn display_instruction(
&self,
ins_ref: InstructionRef,
code: &[u8],
relocation: Option<ResolvedRelocation>,
function_range: Range<u64>,
_section_index: usize,
diff_config: &DiffObjConfig,
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
let mut reader = U8Reader::new(code);
let decoder = InstDecoder::default();
let mut ins = Instruction::default();
if decoder.decode_into(&mut ins, &mut reader).is_err() {
cb(InstructionPart::Opcode(Cow::Borrowed("<invalid>"), u16::MAX))?;
return Ok(());
}
let mut args = vec![];
let mut ctx = DisplayCtx { let mut ctx = DisplayCtx {
address, address: ins_ref.address,
section_index, section_index: 0,
start_address, start_address: function_range.start,
end_address, end_address: function_range.end,
reloc: reloc.as_ref(), reloc: relocation,
config, config: diff_config,
branch_dest: None,
};
// Simplify instruction and process args
let mnemonic = display_instruction(&mut args, &ins, &mut ctx);
// Format the instruction without simplification
let mut orig = ins.opcode.to_string();
for (i, o) in ins.operands.iter().enumerate() {
if let Operand::Nothing = o {
break;
}
if i == 0 {
orig.push(' ');
} else {
orig.push_str(", ");
}
orig.push_str(o.to_string().as_str());
}
if let Some(reloc) = &reloc {
if !args.iter().any(|a| matches!(a, ObjInsArg::Reloc)) {
args.push(ObjInsArg::PlainText(Cow::Borrowed(" <unhandled relocation>")));
log::warn!(
"Unhandled ARM64 relocation {:?}: {} @ {:#X}",
reloc.flags,
orig,
address
);
}
}; };
let op = opcode_to_u16(ins.opcode); let mut display_args = Vec::with_capacity(16);
ops.push(op); let mnemonic = display_instruction(&mut |ret| display_args.push(ret), &ins, &mut ctx);
let branch_dest = ctx.branch_dest; cb(InstructionPart::Opcode(Cow::Borrowed(mnemonic), ins_ref.opcode))?;
insts.push(ObjIns { for arg in display_args {
address, cb(arg)?;
size: 4, }
op, Ok(())
mnemonic: Cow::Borrowed(mnemonic),
args,
reloc,
branch_dest,
line,
formatted: ins.to_string(),
orig: Some(orig),
});
} }
Ok(ProcessCodeResult { ops, insts }) // fn process_code(
} // &self,
// address: u64,
// code: &[u8],
// section_index: usize,
// relocations: &[ObjReloc],
// line_info: &BTreeMap<u64, u32>,
// config: &DiffObjConfig,
// ) -> Result<ProcessCodeResult> {
// let start_address = address;
// let end_address = address + code.len() as u64;
// let ins_count = code.len() / 4;
//
// let mut ops = Vec::with_capacity(ins_count);
// let mut insts = Vec::with_capacity(ins_count);
//
// let mut reader = U8Reader::new(code);
// let decoder = InstDecoder::default();
// let mut ins = Instruction::default();
// loop {
// // This is ridiculous...
// let address = start_address
// + <U8Reader<'_> as Reader<
// <ARMv8 as YaxpeaxArch>::Address,
// <ARMv8 as YaxpeaxArch>::Word,
// >>::total_offset(&mut reader);
// match decoder.decode_into(&mut ins, &mut reader) {
// Ok(()) => {}
// Err(e) => match e {
// DecodeError::ExhaustedInput => break,
// DecodeError::InvalidOpcode
// | DecodeError::InvalidOperand
// | DecodeError::IncompleteDecoder => {
// ops.push(u16::MAX);
// insts.push(ObjIns {
// address,
// size: 4,
// op: u16::MAX,
// mnemonic: Cow::Borrowed("<invalid>"),
// args: vec![],
// reloc: None,
// branch_dest: None,
// line: None,
// formatted: "".to_string(),
// orig: None,
// });
// continue;
// }
// },
// }
//
// let line = line_info.range(..=address).last().map(|(_, &b)| b);
// let reloc = relocations.iter().find(|r| (r.address & !3) == address).cloned();
//
// let mut args = vec![];
// let mut ctx = DisplayCtx {
// address,
// section_index,
// start_address,
// end_address,
// reloc: reloc.as_ref(),
// config,
// branch_dest: None,
// };
// // Simplify instruction and process args
// let mnemonic = display_instruction(&mut args, &ins, &mut ctx);
//
// // Format the instruction without simplification
// let mut orig = ins.opcode.to_string();
// for (i, o) in ins.operands.iter().enumerate() {
// if let Operand::Nothing = o {
// break;
// }
// if i == 0 {
// orig.push(' ');
// } else {
// orig.push_str(", ");
// }
// orig.push_str(o.to_string().as_str());
// }
//
// if let Some(reloc) = &reloc {
// if !args.iter().any(|a| matches!(a, InstructionArg::Reloc)) {
// push_arg(args, InstructionArg::PlainText(Cow::Borrowed(" <unhandled relocation>")));
// log::warn!(
// "Unhandled ARM64 relocation {:?}: {} @ {:#X}",
// reloc.flags,
// orig,
// address
// );
// }
// };
//
// let op = opcode_to_u16(ins.opcode);
// ops.push(op);
// let branch_dest = ctx.branch_dest;
// insts.push(ObjIns {
// address,
// size: 4,
// op,
// mnemonic: Cow::Borrowed(mnemonic),
// args,
// reloc,
// branch_dest,
// line,
// formatted: ins.to_string(),
// orig: Some(orig),
// });
// }
//
// Ok(ProcessCodeResult { ops, insts })
// }
fn implcit_addend( fn implcit_addend(
&self, &self,
_file: &File<'_>, _file: &object::File<'_>,
_section: &ObjSection, _section: &object::Section,
address: u64, address: u64,
reloc: &Relocation, _relocation: &object::Relocation,
flags: RelocationFlags,
) -> Result<i64> { ) -> Result<i64> {
bail!("Unsupported ARM64 implicit relocation {:#x}:{:?}", address, reloc.flags()) bail!("Unsupported ARM64 implicit relocation {:#x}:{:?}", address, flags)
} }
fn demangle(&self, name: &str) -> Option<String> { fn demangle(&self, name: &str) -> Option<String> {
@ -157,25 +244,21 @@ impl ObjArch for ObjArchArm64 {
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> { fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
match flags { match flags {
RelocationFlags::Elf { r_type: elf::R_AARCH64_ADR_PREL_PG_HI21 } => { RelocationFlags::Elf(elf::R_AARCH64_ADR_PREL_PG_HI21) => {
Cow::Borrowed("R_AARCH64_ADR_PREL_PG_HI21") Cow::Borrowed("R_AARCH64_ADR_PREL_PG_HI21")
} }
RelocationFlags::Elf { r_type: elf::R_AARCH64_ADD_ABS_LO12_NC } => { RelocationFlags::Elf(elf::R_AARCH64_ADD_ABS_LO12_NC) => {
Cow::Borrowed("R_AARCH64_ADD_ABS_LO12_NC") Cow::Borrowed("R_AARCH64_ADD_ABS_LO12_NC")
} }
RelocationFlags::Elf { r_type: elf::R_AARCH64_JUMP26 } => { RelocationFlags::Elf(elf::R_AARCH64_JUMP26) => Cow::Borrowed("R_AARCH64_JUMP26"),
Cow::Borrowed("R_AARCH64_JUMP26") RelocationFlags::Elf(elf::R_AARCH64_CALL26) => Cow::Borrowed("R_AARCH64_CALL26"),
} RelocationFlags::Elf(elf::R_AARCH64_LDST32_ABS_LO12_NC) => {
RelocationFlags::Elf { r_type: elf::R_AARCH64_CALL26 } => {
Cow::Borrowed("R_AARCH64_CALL26")
}
RelocationFlags::Elf { r_type: elf::R_AARCH64_LDST32_ABS_LO12_NC } => {
Cow::Borrowed("R_AARCH64_LDST32_ABS_LO12_NC") Cow::Borrowed("R_AARCH64_LDST32_ABS_LO12_NC")
} }
RelocationFlags::Elf { r_type: elf::R_AARCH64_ADR_GOT_PAGE } => { RelocationFlags::Elf(elf::R_AARCH64_ADR_GOT_PAGE) => {
Cow::Borrowed("R_AARCH64_ADR_GOT_PAGE") Cow::Borrowed("R_AARCH64_ADR_GOT_PAGE")
} }
RelocationFlags::Elf { r_type: elf::R_AARCH64_LD64_GOT_LO12_NC } => { RelocationFlags::Elf(elf::R_AARCH64_LD64_GOT_LO12_NC) => {
Cow::Borrowed("R_AARCH64_LD64_GOT_LO12_NC") Cow::Borrowed("R_AARCH64_LD64_GOT_LO12_NC")
} }
_ => Cow::Owned(format!("<{flags:?}>")), _ => Cow::Owned(format!("<{flags:?}>")),
@ -184,7 +267,7 @@ impl ObjArch for ObjArchArm64 {
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize { fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize {
match flags { match flags {
RelocationFlags::Elf { r_type } => match r_type { RelocationFlags::Elf(r_type) => match r_type {
elf::R_AARCH64_ABS64 => 8, elf::R_AARCH64_ABS64 => 8,
elf::R_AARCH64_ABS32 => 4, elf::R_AARCH64_ABS32 => 4,
elf::R_AARCH64_ABS16 => 2, elf::R_AARCH64_ABS16 => 2,
@ -198,25 +281,51 @@ impl ObjArch for ObjArchArm64 {
} }
} }
fn branch_dest(ins_ref: InstructionRef, 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));
const OPCODE_CBZ: u16 = opcode_to_u16(Opcode::CBZ);
const OPCODE_CBNZ: u16 = opcode_to_u16(Opcode::CBNZ);
const OPCODE_TBZ: u16 = opcode_to_u16(Opcode::TBZ);
const OPCODE_TBNZ: u16 = opcode_to_u16(Opcode::TBNZ);
let word = u32::from_le_bytes(code.try_into().ok()?);
match ins_ref.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)
}
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)
}
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)
}
_ => None,
}
}
struct DisplayCtx<'a> { struct DisplayCtx<'a> {
address: u64, address: u64,
section_index: usize, section_index: usize,
start_address: u64, start_address: u64,
end_address: u64, end_address: u64,
reloc: Option<&'a ObjReloc>, reloc: Option<ResolvedRelocation<'a>>,
config: &'a DiffObjConfig, config: &'a DiffObjConfig,
branch_dest: Option<u64>,
} }
// Source: https://github.com/iximeow/yaxpeax-arm/blob/716a6e3fc621f5fe3300f3309e56943b8e1e65ad/src/armv8/a64.rs#L317 // Source: https://github.com/iximeow/yaxpeax-arm/blob/716a6e3fc621f5fe3300f3309e56943b8e1e65ad/src/armv8/a64.rs#L317
// License: 0BSD // License: 0BSD
// Reworked for more structured output. The library only gives us a Display impl, and no way to // Reworked for more structured output. The library only gives us a Display impl, and no way to
// capture any of this information, so it needs to be reimplemented here. // capture any of this information, so it needs to be reimplemented here.
fn display_instruction( fn display_instruction<Cb>(args: &mut Cb, ins: &Instruction, ctx: &mut DisplayCtx) -> &'static str
args: &mut Vec<ObjInsArg>, where Cb: FnMut(InstructionPart) {
ins: &Instruction,
ctx: &mut DisplayCtx,
) -> &'static str {
let mnemonic = match ins.opcode { let mnemonic = match ins.opcode {
Opcode::Invalid => return "<invalid>", Opcode::Invalid => return "<invalid>",
Opcode::UDF => "udf", Opcode::UDF => "udf",
@ -2025,12 +2134,14 @@ fn condition_code(cond: u8) -> &'static str {
} }
#[inline] #[inline]
fn push_register(args: &mut Vec<ObjInsArg>, size: SizeCode, reg: u16, sp: bool) { fn push_register<Cb>(args: &mut Cb, size: SizeCode, reg: u16, sp: bool)
where Cb: FnMut(InstructionPart) {
push_opaque(args, reg_name(size, reg, sp)); push_opaque(args, reg_name(size, reg, sp));
} }
#[inline] #[inline]
fn push_shift(args: &mut Vec<ObjInsArg>, style: ShiftStyle, amount: u8) { fn push_shift<Cb>(args: &mut Cb, style: ShiftStyle, amount: u8)
where Cb: FnMut(InstructionPart) {
push_opaque(args, shift_style(style)); push_opaque(args, shift_style(style));
if amount != 0 { if amount != 0 {
push_plain(args, " "); push_plain(args, " ");
@ -2039,11 +2150,13 @@ fn push_shift(args: &mut Vec<ObjInsArg>, style: ShiftStyle, amount: u8) {
} }
#[inline] #[inline]
fn push_condition_code(args: &mut Vec<ObjInsArg>, cond: u8) { fn push_condition_code<Cb>(args: &mut Cb, cond: u8)
where Cb: FnMut(InstructionPart) {
push_opaque(args, condition_code(cond)); push_opaque(args, condition_code(cond));
} }
fn push_barrier(args: &mut Vec<ObjInsArg>, option: u8) { fn push_barrier<Cb>(args: &mut Cb, option: u8)
where Cb: FnMut(InstructionPart) {
match option { match option {
0b0001 => push_opaque(args, "oshld"), 0b0001 => push_opaque(args, "oshld"),
0b0010 => push_opaque(args, "oshst"), 0b0010 => push_opaque(args, "oshst"),
@ -2062,89 +2175,104 @@ fn push_barrier(args: &mut Vec<ObjInsArg>, option: u8) {
} }
#[inline] #[inline]
fn push_opaque(args: &mut Vec<ObjInsArg>, text: &'static str) { fn push_opaque<Cb>(args: &mut Cb, text: &'static str)
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(Cow::Borrowed(text)))); where Cb: FnMut(InstructionPart) {
push_arg(args, InstructionArg::Value(InstructionArgValue::Opaque(Cow::Borrowed(text))));
} }
#[inline] #[inline]
fn push_plain(args: &mut Vec<ObjInsArg>, text: &'static str) { fn push_plain<Cb>(args: &mut Cb, text: &'static str)
args.push(ObjInsArg::PlainText(Cow::Borrowed(text))); where Cb: FnMut(InstructionPart) {
args(InstructionPart::Basic(text));
} }
#[inline] #[inline]
fn push_separator(args: &mut Vec<ObjInsArg>, config: &DiffObjConfig) { fn push_separator<Cb>(args: &mut Cb, _config: &DiffObjConfig)
args.push(ObjInsArg::PlainText(Cow::Borrowed(config.separator()))); where Cb: FnMut(InstructionPart) {
args(InstructionPart::Separator);
} }
#[inline] #[inline]
fn push_unsigned(args: &mut Vec<ObjInsArg>, v: u64) { fn push_unsigned<Cb>(args: &mut Cb, v: u64)
where Cb: FnMut(InstructionPart) {
push_plain(args, "#"); push_plain(args, "#");
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(v))); push_arg(args, InstructionArg::Value(InstructionArgValue::Unsigned(v)));
} }
#[inline] #[inline]
fn push_signed(args: &mut Vec<ObjInsArg>, v: i64) { fn push_signed<Cb>(args: &mut Cb, v: i64)
where Cb: FnMut(InstructionPart) {
push_plain(args, "#"); push_plain(args, "#");
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(v))); push_arg(args, InstructionArg::Value(InstructionArgValue::Signed(v)));
}
#[inline]
fn push_arg<Cb>(args: &mut Cb, arg: InstructionArg)
where Cb: FnMut(InstructionPart) {
args(InstructionPart::Arg(arg));
} }
/// Relocations that appear in Operand::PCOffset. /// Relocations that appear in Operand::PCOffset.
fn is_pc_offset_reloc(reloc: Option<&ObjReloc>) -> Option<&ObjReloc> { fn is_pc_offset_reloc(reloc: Option<ResolvedRelocation>) -> Option<ResolvedRelocation> {
if let Some(reloc) = reloc { if let Some(resolved) = reloc {
if let RelocationFlags::Elf { if let RelocationFlags::Elf(
r_type:
elf::R_AARCH64_ADR_PREL_PG_HI21 elf::R_AARCH64_ADR_PREL_PG_HI21
| elf::R_AARCH64_JUMP26 | elf::R_AARCH64_JUMP26
| elf::R_AARCH64_CALL26 | elf::R_AARCH64_CALL26
| elf::R_AARCH64_ADR_GOT_PAGE, | elf::R_AARCH64_ADR_GOT_PAGE,
} = reloc.flags ) = resolved.relocation.flags
{ {
return Some(reloc); return Some(resolved);
} }
} }
None None
} }
/// Relocations that appear in Operand::Immediate. /// Relocations that appear in Operand::Immediate.
fn is_imm_reloc(reloc: Option<&ObjReloc>) -> bool { fn is_imm_reloc(resolved: Option<ResolvedRelocation>) -> bool {
matches!(reloc, Some(reloc) if matches!(reloc.flags, RelocationFlags::Elf { resolved.is_some_and(|r| {
r_type: elf::R_AARCH64_ADD_ABS_LO12_NC, matches!(r.relocation.flags, RelocationFlags::Elf(elf::R_AARCH64_ADD_ABS_LO12_NC))
})) })
} }
/// Relocations that appear in Operand::RegPreIndex/RegPostIndex. /// Relocations that appear in Operand::RegPreIndex/RegPostIndex.
fn is_reg_index_reloc(reloc: Option<&ObjReloc>) -> bool { fn is_reg_index_reloc(resolved: Option<ResolvedRelocation>) -> bool {
matches!(reloc, Some(reloc) if matches!(reloc.flags, RelocationFlags::Elf { resolved.is_some_and(|r| {
r_type: elf::R_AARCH64_LDST32_ABS_LO12_NC | elf::R_AARCH64_LD64_GOT_LO12_NC, matches!(
})) r.relocation.flags,
RelocationFlags::Elf(
elf::R_AARCH64_LDST32_ABS_LO12_NC | elf::R_AARCH64_LD64_GOT_LO12_NC
)
)
})
} }
fn push_operand(args: &mut Vec<ObjInsArg>, o: &Operand, ctx: &mut DisplayCtx) { fn push_operand<Cb>(args: &mut Cb, o: &Operand, ctx: &mut DisplayCtx)
where Cb: FnMut(InstructionPart) {
match o { match o {
Operand::Nothing => unreachable!(), Operand::Nothing => unreachable!(),
Operand::PCOffset(off) => { Operand::PCOffset(off) => {
if let Some(reloc) = is_pc_offset_reloc(ctx.reloc) { if let Some(resolved) = is_pc_offset_reloc(ctx.reloc) {
let target_address = reloc.target.address.checked_add_signed(reloc.addend); let target_address =
if reloc.target.orig_section_index == Some(ctx.section_index) resolved.symbol.address.checked_add_signed(resolved.relocation.addend);
if resolved.symbol.section == Some(ctx.section_index)
&& matches!(target_address, Some(addr) if addr > ctx.start_address && addr < ctx.end_address) && matches!(target_address, Some(addr) if addr > ctx.start_address && addr < ctx.end_address)
{ {
let dest = target_address.unwrap(); let dest = target_address.unwrap();
push_plain(args, "$"); push_plain(args, "$");
args.push(ObjInsArg::BranchDest(dest)); push_arg(args, InstructionArg::BranchDest(dest));
ctx.branch_dest = Some(dest);
} else { } else {
args.push(ObjInsArg::Reloc); push_arg(args, InstructionArg::Reloc);
} }
} else { } else {
let dest = ctx.address.saturating_add_signed(*off); let dest = ctx.address.saturating_add_signed(*off);
push_plain(args, "$"); push_plain(args, "$");
args.push(ObjInsArg::BranchDest(dest)); push_arg(args, InstructionArg::BranchDest(dest));
ctx.branch_dest = Some(dest);
} }
} }
Operand::Immediate(imm) => { Operand::Immediate(imm) => {
if is_imm_reloc(ctx.reloc) { if is_imm_reloc(ctx.reloc) {
args.push(ObjInsArg::Reloc); push_arg(args, InstructionArg::Reloc);
} else { } else {
push_unsigned(args, *imm as u64); push_unsigned(args, *imm as u64);
} }
@ -2249,7 +2377,7 @@ fn push_operand(args: &mut Vec<ObjInsArg>, o: &Operand, ctx: &mut DisplayCtx) {
push_register(args, SizeCode::X, *reg, true); push_register(args, SizeCode::X, *reg, true);
if is_reg_index_reloc(ctx.reloc) { if is_reg_index_reloc(ctx.reloc) {
push_separator(args, ctx.config); push_separator(args, ctx.config);
args.push(ObjInsArg::Reloc); push_arg(args, InstructionArg::Reloc);
} else if *offset != 0 || *wback_bit { } else if *offset != 0 || *wback_bit {
push_separator(args, ctx.config); push_separator(args, ctx.config);
push_signed(args, *offset as i64); push_signed(args, *offset as i64);
@ -2265,7 +2393,7 @@ fn push_operand(args: &mut Vec<ObjInsArg>, o: &Operand, ctx: &mut DisplayCtx) {
push_plain(args, "]"); push_plain(args, "]");
push_separator(args, ctx.config); push_separator(args, ctx.config);
if is_reg_index_reloc(ctx.reloc) { if is_reg_index_reloc(ctx.reloc) {
args.push(ObjInsArg::Reloc); push_arg(args, InstructionArg::Reloc);
} else { } else {
push_signed(args, *offset as i64); push_signed(args, *offset as i64);
} }
@ -2276,10 +2404,13 @@ fn push_operand(args: &mut Vec<ObjInsArg>, o: &Operand, ctx: &mut DisplayCtx) {
push_plain(args, "]"); push_plain(args, "]");
push_separator(args, ctx.config); push_separator(args, ctx.config);
// TODO does 31 have to be handled separate? // TODO does 31 have to be handled separate?
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(Cow::Owned(format!( push_arg(
args,
InstructionArg::Value(InstructionArgValue::Opaque(Cow::Owned(format!(
"x{}", "x{}",
offset_reg offset_reg
))))); )))),
);
} }
// Fall back to original logic // Fall back to original logic
Operand::SIMDRegister(_, _) Operand::SIMDRegister(_, _)
@ -2293,13 +2424,16 @@ fn push_operand(args: &mut Vec<ObjInsArg>, o: &Operand, ctx: &mut DisplayCtx) {
| Operand::SystemReg(_) | Operand::SystemReg(_)
| Operand::ControlReg(_) | Operand::ControlReg(_)
| Operand::PstateField(_) => { | Operand::PstateField(_) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(Cow::Owned(o.to_string())))); push_arg(
args,
InstructionArg::Value(InstructionArgValue::Opaque(Cow::Owned(o.to_string()))),
);
} }
} }
} }
// Opcode is #[repr(u16)], but the tuple variants negate that, so we have to do this instead. // Opcode is #[repr(u16)], but the tuple variants negate that, so we have to do this instead.
fn opcode_to_u16(opcode: Opcode) -> u16 { const fn opcode_to_u16(opcode: Opcode) -> u16 {
match opcode { match opcode {
Opcode::Invalid => u16::MAX, Opcode::Invalid => u16::MAX,
Opcode::UDF => 0, Opcode::UDF => 0,

View File

@ -1,25 +1,27 @@
use alloc::{borrow::Cow, collections::BTreeMap, format, string::ToString, vec::Vec}; use alloc::{borrow::Cow, format, string::ToString, vec::Vec};
use core::ops::Range;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use object::{ use object::{elf, Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _};
elf, Endian, Endianness, File, FileFlags, Object, ObjectSection, ObjectSymbol, Relocation,
RelocationFlags, RelocationTarget,
};
use rabbitizer::{ use rabbitizer::{
abi::Abi, abi::Abi,
operands::{ValuedOperand, IU16}, operands::{ValuedOperand, IU16},
registers_meta::Register, registers_meta::Register,
Instruction, InstructionDisplayFlags, InstructionFlags, IsaExtension, IsaVersion, Vram, IsaExtension, IsaVersion, Vram,
}; };
use crate::{ use crate::{
arch::{ObjArch, ProcessCodeResult}, arch::Arch,
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory}, diff::{display::InstructionPart, DiffObjConfig, MipsAbi, MipsInstrCategory},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection}, obj::{
InstructionArg, InstructionArgValue, InstructionRef, Relocation, RelocationFlags,
ResolvedRelocation, ScannedInstruction,
},
}; };
pub struct ObjArchMips { #[derive(Debug)]
pub endianness: Endianness, pub struct ArchMips {
pub endianness: object::Endianness,
pub abi: Abi, pub abi: Abi,
pub isa_extension: Option<IsaExtension>, pub isa_extension: Option<IsaExtension>,
pub ri_gp_value: i32, pub ri_gp_value: i32,
@ -33,13 +35,13 @@ const EF_MIPS_MACH_5900: u32 = 0x00920000;
const R_MIPS15_S3: u32 = 119; const R_MIPS15_S3: u32 = 119;
impl ObjArchMips { impl ArchMips {
pub fn new(object: &File) -> Result<Self> { pub fn new(object: &object::File) -> Result<Self> {
let mut abi = Abi::O32; let mut abi = Abi::O32;
let mut isa_extension = None; let mut isa_extension = None;
match object.flags() { match object.flags() {
FileFlags::None => {} object::FileFlags::None => {}
FileFlags::Elf { e_flags, .. } => { object::FileFlags::Elf { e_flags, .. } => {
abi = match e_flags & EF_MIPS_ABI { abi = match e_flags & EF_MIPS_ABI {
elf::EF_MIPS_ABI_O32 | elf::EF_MIPS_ABI_O64 => Abi::O32, elf::EF_MIPS_ABI_O32 | elf::EF_MIPS_ABI_O64 => Abi::O32,
elf::EF_MIPS_ABI_EABI32 | elf::EF_MIPS_ABI_EABI64 => Abi::N32, elf::EF_MIPS_ABI_EABI32 | elf::EF_MIPS_ABI_EABI64 => Abi::N32,
@ -73,19 +75,9 @@ impl ObjArchMips {
Ok(Self { endianness: object.endianness(), abi, isa_extension, ri_gp_value }) Ok(Self { endianness: object.endianness(), abi, isa_extension, ri_gp_value })
} }
}
impl ObjArch for ObjArchMips { fn instruction_flags(&self, diff_config: &DiffObjConfig) -> rabbitizer::InstructionFlags {
fn process_code( let isa_extension = match diff_config.mips_instr_category {
&self,
address: u64,
code: &[u8],
section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let isa_extension = match config.mips_instr_category {
MipsInstrCategory::Auto => self.isa_extension, MipsInstrCategory::Auto => self.isa_extension,
MipsInstrCategory::Cpu => None, MipsInstrCategory::Cpu => None,
MipsInstrCategory::Rsp => Some(IsaExtension::RSP), MipsInstrCategory::Rsp => Some(IsaExtension::RSP),
@ -93,158 +85,105 @@ impl ObjArch for ObjArchMips {
MipsInstrCategory::R4000allegrex => Some(IsaExtension::R4000ALLEGREX), MipsInstrCategory::R4000allegrex => Some(IsaExtension::R4000ALLEGREX),
MipsInstrCategory::R5900 => Some(IsaExtension::R5900), MipsInstrCategory::R5900 => Some(IsaExtension::R5900),
}; };
let instruction_flags = match isa_extension { match isa_extension {
Some(extension) => InstructionFlags::new_extension(extension), Some(extension) => rabbitizer::InstructionFlags::new_extension(extension),
None => InstructionFlags::new_isa(IsaVersion::MIPS_III, None), None => rabbitizer::InstructionFlags::new_isa(IsaVersion::MIPS_III, None),
} }
.with_abi(match config.mips_abi { .with_abi(match diff_config.mips_abi {
MipsAbi::Auto => self.abi, MipsAbi::Auto => self.abi,
MipsAbi::O32 => Abi::O32, MipsAbi::O32 => Abi::O32,
MipsAbi::N32 => Abi::N32, MipsAbi::N32 => Abi::N32,
MipsAbi::N64 => Abi::N64, MipsAbi::N64 => Abi::N64,
}); })
let display_flags = InstructionDisplayFlags::default().with_unknown_instr_comment(false); }
fn instruction_display_flags(
&self,
_diff_config: &DiffObjConfig,
) -> rabbitizer::InstructionDisplayFlags {
rabbitizer::InstructionDisplayFlags::default().with_unknown_instr_comment(false)
}
fn parse_ins_ref(
&self,
ins_ref: InstructionRef,
code: &[u8],
diff_config: &DiffObjConfig,
) -> Result<rabbitizer::Instruction> {
Ok(rabbitizer::Instruction::new(
self.endianness.read_u32_bytes(code.try_into()?),
Vram::new(ins_ref.address as u32),
self.instruction_flags(diff_config),
))
}
}
impl Arch for ArchMips {
fn scan_instructions(
&self,
address: u64,
code: &[u8],
_section_index: usize,
diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
let instruction_flags = self.instruction_flags(diff_config);
let start_address = address; let start_address = address;
let end_address = address + code.len() as u64; let mut ops = Vec::<ScannedInstruction>::with_capacity(code.len() / 4);
let ins_count = code.len() / 4;
let mut ops = Vec::<u16>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
let mut cur_addr = start_address as u32; let mut cur_addr = start_address as u32;
for chunk in code.chunks_exact(4) { for chunk in code.chunks_exact(4) {
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
let code = self.endianness.read_u32_bytes(chunk.try_into()?); let code = self.endianness.read_u32_bytes(chunk.try_into()?);
let instruction = Instruction::new(code, Vram::new(cur_addr), instruction_flags); let vram = Vram::new(cur_addr);
let instruction = rabbitizer::Instruction::new(code, vram, instruction_flags);
let formatted = instruction.display(&display_flags, None::<&str>, 0).to_string(); let opcode = instruction.opcode() as u16;
let op = instruction.opcode() as u16; let branch_dest =
ops.push(op); instruction.get_branch_offset_generic().map(|o| (vram + o).inner() as u64);
ops.push(ScannedInstruction {
let mnemonic = instruction.opcode().name(); ins_ref: InstructionRef { address, size: 4, opcode },
let mut branch_dest = instruction.get_branch_offset_generic().map(|a| a.inner() as u64);
let operands = instruction.valued_operands_iter();
let mut args = Vec::with_capacity(6);
for (idx, op) in operands.enumerate() {
if idx > 0 {
args.push(ObjInsArg::PlainText(config.separator().into()));
}
match op {
ValuedOperand::core_immediate(imm) => {
if let Some(reloc) = reloc {
push_reloc(&mut args, reloc)?;
} else {
args.push(ObjInsArg::Arg(match imm {
IU16::Integer(s) => ObjInsArgValue::Signed(s as i64),
IU16::Unsigned(u) => ObjInsArgValue::Unsigned(u as u64),
}));
}
}
ValuedOperand::core_label(..) | ValuedOperand::core_branch_target_label(..) => {
if let Some(reloc) = reloc {
// 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 =
reloc.target.address.checked_add_signed(reloc.addend);
if reloc.target.orig_section_index == Some(section_index)
&& matches!(target_address, Some(addr) if addr > start_address && addr < end_address)
{
let target_address = target_address.unwrap();
args.push(ObjInsArg::BranchDest(target_address));
branch_dest = Some(target_address);
} else {
push_reloc(&mut args, reloc)?;
branch_dest = None;
}
} else if let Some(branch_dest) = branch_dest {
args.push(ObjInsArg::BranchDest(branch_dest));
} else {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
op.display(&instruction, &display_flags, None::<&str>)
.to_string()
.into(),
)));
}
}
ValuedOperand::core_immediate_base(imm, base) => {
if let Some(reloc) = reloc {
push_reloc(&mut args, reloc)?;
} else {
args.push(ObjInsArg::Arg(match imm {
IU16::Integer(s) => ObjInsArgValue::Signed(s as i64),
IU16::Unsigned(u) => ObjInsArgValue::Unsigned(u as u64),
}));
}
args.push(ObjInsArg::PlainText("(".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
base.either_name(instruction.flags().abi(), display_flags.named_gpr())
.into(),
)));
args.push(ObjInsArg::PlainText(")".into()));
}
// ValuedOperand::r5900_immediate15(..) => match reloc {
// Some(reloc)
// if reloc.flags == RelocationFlags::Elf { r_type: R_MIPS15_S3 } =>
// {
// push_reloc(&mut args, reloc)?;
// }
// _ => {
// args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
// op.disassemble(&instruction, None).into(),
// )));
// }
// },
_ => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
op.display(&instruction, &display_flags, None::<&str>)
.to_string()
.into(),
)));
}
}
}
let line = line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
insts.push(ObjIns {
address: cur_addr as u64,
size: 4,
op,
mnemonic: Cow::Borrowed(mnemonic),
args,
reloc: reloc.cloned(),
branch_dest, branch_dest,
line,
formatted,
orig: None,
}); });
cur_addr += 4; cur_addr += 4;
} }
Ok(ProcessCodeResult { ops, insts }) Ok(ops)
}
fn display_instruction(
&self,
ins_ref: InstructionRef,
code: &[u8],
relocation: Option<ResolvedRelocation>,
function_range: Range<u64>,
section_index: usize,
diff_config: &DiffObjConfig,
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
let instruction = self.parse_ins_ref(ins_ref, code, diff_config)?;
let display_flags = self.instruction_display_flags(diff_config);
let opcode = instruction.opcode();
cb(InstructionPart::Opcode(Cow::Borrowed(opcode.name()), opcode as u16))?;
push_args(&instruction, relocation, function_range, section_index, &display_flags, cb)?;
Ok(())
} }
fn implcit_addend( fn implcit_addend(
&self, &self,
file: &File<'_>, file: &object::File<'_>,
section: &ObjSection, section: &object::Section,
address: u64, address: u64,
reloc: &Relocation, reloc: &object::Relocation,
flags: RelocationFlags,
) -> Result<i64> { ) -> Result<i64> {
let data = section.data[address as usize..address as usize + 4].try_into()?; let data = section.data()?;
let addend = self.endianness.read_u32_bytes(data); let code = data[address as usize..address as usize + 4].try_into()?;
Ok(match reloc.flags() { let addend = self.endianness.read_u32_bytes(code);
RelocationFlags::Elf { r_type: elf::R_MIPS_32 } => addend as i64, Ok(match flags {
RelocationFlags::Elf { r_type: elf::R_MIPS_26 } => ((addend & 0x03FFFFFF) << 2) as i64, RelocationFlags::Elf(elf::R_MIPS_32) => addend as i64,
RelocationFlags::Elf { r_type: elf::R_MIPS_HI16 } => { RelocationFlags::Elf(elf::R_MIPS_26) => ((addend & 0x03FFFFFF) << 2) as i64,
((addend & 0x0000FFFF) << 16) as i32 as i64 RelocationFlags::Elf(elf::R_MIPS_HI16) => ((addend & 0x0000FFFF) << 16) as i32 as i64,
RelocationFlags::Elf(elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16) => {
(addend & 0x0000FFFF) as i16 as i64
} }
RelocationFlags::Elf { RelocationFlags::Elf(elf::R_MIPS_GPREL16 | elf::R_MIPS_LITERAL) => {
r_type: elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16, let object::RelocationTarget::Symbol(idx) = reloc.target() else {
} => (addend & 0x0000FFFF) as i16 as i64,
RelocationFlags::Elf { r_type: elf::R_MIPS_GPREL16 | elf::R_MIPS_LITERAL } => {
let RelocationTarget::Symbol(idx) = reloc.target() else {
bail!("Unsupported R_MIPS_GPREL16 relocation against a non-symbol"); bail!("Unsupported R_MIPS_GPREL16 relocation against a non-symbol");
}; };
let sym = file.symbol_by_index(idx)?; let sym = file.symbol_by_index(idx)?;
@ -257,15 +196,15 @@ impl ObjArch for ObjArchMips {
(addend & 0x0000FFFF) as i16 as i64 (addend & 0x0000FFFF) as i16 as i64
} }
} }
RelocationFlags::Elf { r_type: elf::R_MIPS_PC16 } => 0, // PC-relative relocation RelocationFlags::Elf(elf::R_MIPS_PC16) => 0, // PC-relative relocation
RelocationFlags::Elf { r_type: R_MIPS15_S3 } => ((addend & 0x001FFFC0) >> 3) as i64, RelocationFlags::Elf(R_MIPS15_S3) => ((addend & 0x001FFFC0) >> 3) as i64,
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"), flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
}) })
} }
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> { fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
match flags { match flags {
RelocationFlags::Elf { r_type } => match r_type { RelocationFlags::Elf(r_type) => match r_type {
elf::R_MIPS_32 => Cow::Borrowed("R_MIPS_32"), elf::R_MIPS_32 => Cow::Borrowed("R_MIPS_32"),
elf::R_MIPS_26 => Cow::Borrowed("R_MIPS_26"), elf::R_MIPS_26 => Cow::Borrowed("R_MIPS_26"),
elf::R_MIPS_HI16 => Cow::Borrowed("R_MIPS_HI16"), elf::R_MIPS_HI16 => Cow::Borrowed("R_MIPS_HI16"),
@ -284,7 +223,7 @@ impl ObjArch for ObjArchMips {
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize { fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize {
match flags { match flags {
RelocationFlags::Elf { r_type } => match r_type { RelocationFlags::Elf(r_type) => match r_type {
elf::R_MIPS_16 => 2, elf::R_MIPS_16 => 2,
elf::R_MIPS_32 => 4, elf::R_MIPS_32 => 4,
_ => 1, _ => 1,
@ -294,40 +233,139 @@ impl ObjArch for ObjArchMips {
} }
} }
fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> { 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<()> {
let operands = instruction.valued_operands_iter();
for (idx, op) in operands.enumerate() {
if idx > 0 {
arg_cb(InstructionPart::Separator)?;
}
match op {
ValuedOperand::core_immediate(imm) => {
if let Some(resolved) = relocation {
push_reloc(resolved.relocation, &mut arg_cb)?;
} else {
arg_cb(InstructionPart::Arg(InstructionArg::Value(match imm {
IU16::Integer(s) => InstructionArgValue::Signed(s as i64),
IU16::Unsigned(u) => InstructionArgValue::Unsigned(u as u64),
})))?;
}
}
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::Arg(InstructionArg::BranchDest(target_address)))?;
} else {
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)
{
arg_cb(InstructionPart::Arg(InstructionArg::BranchDest(branch_dest)))?;
} else {
arg_cb(InstructionPart::Arg(InstructionArg::Value(
InstructionArgValue::Opaque(
op.display(instruction, display_flags, None::<&str>)
.to_string()
.into(),
),
)))?;
}
}
ValuedOperand::core_immediate_base(imm, base) => {
if let Some(resolved) = relocation {
push_reloc(resolved.relocation, &mut arg_cb)?;
} else {
arg_cb(InstructionPart::Arg(InstructionArg::Value(match imm {
IU16::Integer(s) => InstructionArgValue::Signed(s as i64),
IU16::Unsigned(u) => InstructionArgValue::Unsigned(u as u64),
})))?;
}
arg_cb(InstructionPart::Basic("("))?;
arg_cb(InstructionPart::Arg(InstructionArg::Value(InstructionArgValue::Opaque(
base.either_name(instruction.flags().abi(), display_flags.named_gpr()).into(),
))))?;
arg_cb(InstructionPart::Basic(")"))?;
}
// ValuedOperand::r5900_immediate15(..) => match relocation {
// Some(resolved)
// if resolved.relocation.flags == RelocationFlags::Elf(R_MIPS15_S3) =>
// {
// push_reloc(&resolved.relocation, &mut arg_cb, &mut plain_cb)?;
// }
// _ => {
// arg_cb(InstructionArg::Value(InstructionArgValue::Opaque(
// op.disassemble(&instruction, None).into(),
// )))?;
// }
// },
_ => {
arg_cb(InstructionPart::Arg(InstructionArg::Value(InstructionArgValue::Opaque(
op.display(instruction, display_flags, None::<&str>).to_string().into(),
))))?;
}
}
}
Ok(())
}
fn push_reloc(
reloc: &Relocation,
mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
match reloc.flags { match reloc.flags {
RelocationFlags::Elf { r_type } => match r_type { RelocationFlags::Elf(r_type) => match r_type {
elf::R_MIPS_HI16 => { elf::R_MIPS_HI16 => {
args.push(ObjInsArg::PlainText("%hi(".into())); arg_cb(InstructionPart::Basic("%hi("))?;
args.push(ObjInsArg::Reloc); arg_cb(InstructionPart::Arg(InstructionArg::Reloc))?;
args.push(ObjInsArg::PlainText(")".into())); arg_cb(InstructionPart::Basic(")"))?;
} }
elf::R_MIPS_LO16 => { elf::R_MIPS_LO16 => {
args.push(ObjInsArg::PlainText("%lo(".into())); arg_cb(InstructionPart::Basic("%lo("))?;
args.push(ObjInsArg::Reloc); arg_cb(InstructionPart::Arg(InstructionArg::Reloc))?;
args.push(ObjInsArg::PlainText(")".into())); arg_cb(InstructionPart::Basic(")"))?;
} }
elf::R_MIPS_GOT16 => { elf::R_MIPS_GOT16 => {
args.push(ObjInsArg::PlainText("%got(".into())); arg_cb(InstructionPart::Basic("%got("))?;
args.push(ObjInsArg::Reloc); arg_cb(InstructionPart::Arg(InstructionArg::Reloc))?;
args.push(ObjInsArg::PlainText(")".into())); arg_cb(InstructionPart::Basic(")"))?;
} }
elf::R_MIPS_CALL16 => { elf::R_MIPS_CALL16 => {
args.push(ObjInsArg::PlainText("%call16(".into())); arg_cb(InstructionPart::Basic("%call16("))?;
args.push(ObjInsArg::Reloc); arg_cb(InstructionPart::Arg(InstructionArg::Reloc))?;
args.push(ObjInsArg::PlainText(")".into())); arg_cb(InstructionPart::Basic(")"))?;
} }
elf::R_MIPS_GPREL16 => { elf::R_MIPS_GPREL16 => {
args.push(ObjInsArg::PlainText("%gp_rel(".into())); arg_cb(InstructionPart::Basic("%gp_rel("))?;
args.push(ObjInsArg::Reloc); arg_cb(InstructionPart::Arg(InstructionArg::Reloc))?;
args.push(ObjInsArg::PlainText(")".into())); arg_cb(InstructionPart::Basic(")"))?;
} }
elf::R_MIPS_32 elf::R_MIPS_32
| elf::R_MIPS_26 | elf::R_MIPS_26
| elf::R_MIPS_LITERAL | elf::R_MIPS_LITERAL
| elf::R_MIPS_PC16 | elf::R_MIPS_PC16
| R_MIPS15_S3 => { | R_MIPS15_S3 => {
args.push(ObjInsArg::Reloc); arg_cb(InstructionPart::Arg(InstructionArg::Reloc))?;
} }
_ => bail!("Unsupported ELF MIPS relocation type {r_type}"), _ => bail!("Unsupported ELF MIPS relocation type {r_type}"),
}, },

View File

@ -1,13 +1,16 @@
use alloc::{borrow::Cow, boxed::Box, collections::BTreeMap, format, string::String, vec::Vec}; use alloc::{borrow::Cow, boxed::Box, format, string::String, vec, vec::Vec};
use core::ffi::CStr; use core::{ffi::CStr, fmt, fmt::Debug, ops::Range};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use byteorder::ByteOrder; use byteorder::ByteOrder;
use object::{Architecture, File, Object, ObjectSymbol, Relocation, RelocationFlags, Symbol}; use object::{File, Relocation, Section};
use crate::{ use crate::{
diff::DiffObjConfig, diff::{display::InstructionPart, DiffObjConfig},
obj::{ObjIns, ObjReloc, ObjSection}, obj::{
InstructionRef, ParsedInstruction, RelocationFlags, ResolvedRelocation, ScannedInstruction,
SymbolFlagSet, SymbolKind,
},
util::ReallySigned, util::ReallySigned,
}; };
@ -35,8 +38,8 @@ pub enum DataType {
String, String,
} }
impl std::fmt::Display for DataType { impl fmt::Display for DataType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
DataType::Int8 => write!(f, "Int8"), DataType::Int8 => write!(f, "Int8"),
DataType::Int16 => write!(f, "Int16"), DataType::Int16 => write!(f, "Int16"),
@ -154,23 +157,76 @@ impl DataType {
} }
} }
pub trait ObjArch: Send + Sync { pub trait Arch: Send + Sync + Debug {
fn process_code( /// 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(
&self, &self,
address: u64, address: u64,
code: &[u8], code: &[u8],
section_index: usize, section_index: usize,
relocations: &[ObjReloc], diff_config: &DiffObjConfig,
line_info: &BTreeMap<u64, u32>, ) -> Result<Vec<ScannedInstruction>>;
config: &DiffObjConfig,
) -> Result<ProcessCodeResult>; /// 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(
&self,
ins_ref: InstructionRef,
code: &[u8],
relocation: Option<ResolvedRelocation>,
function_range: Range<u64>,
section_index: usize,
diff_config: &DiffObjConfig,
) -> Result<ParsedInstruction> {
let mut mnemonic = None;
let mut args = Vec::with_capacity(8);
self.display_instruction(
ins_ref,
code,
relocation,
function_range,
section_index,
diff_config,
&mut |part| {
match part {
InstructionPart::Opcode(m, _) => mnemonic = Some(m),
InstructionPart::Arg(arg) => args.push(arg),
_ => {}
}
Ok(())
},
)?;
Ok(ParsedInstruction { ins_ref, mnemonic: mnemonic.unwrap_or_default(), args })
}
/// Format an instruction for display.
///
/// Implementations should call the callback for each part of the instruction: usually the
/// mnemonic and arguments, plus any separators and visual formatting.
fn display_instruction(
&self,
ins_ref: InstructionRef,
code: &[u8],
relocation: Option<ResolvedRelocation>,
function_range: Range<u64>,
section_index: usize,
diff_config: &DiffObjConfig,
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()>;
fn implcit_addend( fn implcit_addend(
&self, &self,
file: &File<'_>, file: &object::File<'_>,
section: &ObjSection, section: &object::Section,
address: u64, address: u64,
reloc: &Relocation, relocation: &object::Relocation,
flags: RelocationFlags,
) -> Result<i64>; ) -> Result<i64>;
fn demangle(&self, _name: &str) -> Option<String> { None } fn demangle(&self, _name: &str) -> Option<String> { None }
@ -179,9 +235,20 @@ pub trait ObjArch: Send + Sync {
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize; fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize;
fn symbol_address(&self, symbol: &Symbol) -> u64 { symbol.address() } fn symbol_address(&self, address: u64, _kind: SymbolKind) -> u64 { address }
fn guess_data_type(&self, _instruction: &ObjIns) -> Option<DataType> { None } fn extra_symbol_flags(&self, _symbol: &object::Symbol) -> SymbolFlagSet {
SymbolFlagSet::default()
}
fn guess_data_type(
&self,
_ins_ref: InstructionRef,
_code: &[u8],
_relocation: Option<ResolvedRelocation>,
) -> Option<DataType> {
None
}
fn display_data_labels(&self, _ty: DataType, bytes: &[u8]) -> Vec<String> { fn display_data_labels(&self, _ty: DataType, bytes: &[u8]) -> Vec<String> {
vec![format!("Bytes: {:#x?}", bytes)] vec![format!("Bytes: {:#x?}", bytes)]
@ -191,58 +258,113 @@ pub trait ObjArch: Send + Sync {
vec![format!("{:#?}", bytes)] vec![format!("{:#?}", bytes)]
} }
fn display_ins_data_labels(&self, ins: &ObjIns) -> Vec<String> { fn display_ins_data_labels(
let Some(reloc) = ins.reloc.as_ref() else { &self,
return Vec::new(); _ins_ref: InstructionRef,
}; _code: &[u8],
if reloc.addend >= 0 && reloc.target.bytes.len() > reloc.addend as usize { _relocation: Option<ResolvedRelocation>,
return self ) -> Vec<String> {
.guess_data_type(ins) // TODO
.map(|ty| { // let Some(reloc) = relocation else {
self.display_data_labels(ty, &reloc.target.bytes[reloc.addend as usize..]) // return Vec::new();
}) // };
.unwrap_or_default(); // if reloc.relocation.addend >= 0 && reloc.symbol.bytes.len() > reloc.relocation.addend as usize {
} // return self
// .guess_data_type(ins)
// .map(|ty| {
// self.display_data_labels(ty, &reloc.target.bytes[reloc.addend as usize..])
// })
// .unwrap_or_default();
// }
Vec::new() Vec::new()
} }
fn display_ins_data_literals(&self, ins: &ObjIns) -> Vec<String> { fn display_ins_data_literals(
let Some(reloc) = ins.reloc.as_ref() else { &self,
return Vec::new(); _ins_ref: InstructionRef,
}; _code: &[u8],
if reloc.addend >= 0 && reloc.target.bytes.len() > reloc.addend as usize { _relocation: Option<ResolvedRelocation>,
return self ) -> Vec<String> {
.guess_data_type(ins) // TODO
.map(|ty| { // let Some(reloc) = ins.reloc.as_ref() else {
self.display_data_literals(ty, &reloc.target.bytes[reloc.addend as usize..]) // return Vec::new();
}) // };
.unwrap_or_default(); // if reloc.addend >= 0 && reloc.target.bytes.len() > reloc.addend as usize {
} // return self
// .guess_data_type(ins)
// .map(|ty| {
// self.display_data_literals(ty, &reloc.target.bytes[reloc.addend as usize..])
// })
// .unwrap_or_default();
// }
Vec::new() Vec::new()
} }
// Downcast methods
#[cfg(feature = "ppc")]
fn ppc(&self) -> Option<&ppc::ObjArchPpc> { None }
} }
pub struct ProcessCodeResult { pub fn new_arch(object: &object::File) -> Result<Box<dyn Arch>> {
pub ops: Vec<u16>, use object::Object as _;
pub insts: Vec<ObjIns>,
}
pub fn new_arch(object: &File) -> Result<Box<dyn ObjArch>> {
Ok(match object.architecture() { Ok(match object.architecture() {
#[cfg(feature = "ppc")] #[cfg(feature = "ppc")]
Architecture::PowerPc => Box::new(ppc::ObjArchPpc::new(object)?), object::Architecture::PowerPc => Box::new(ppc::ArchPpc::new(object)?),
#[cfg(feature = "mips")] #[cfg(feature = "mips")]
Architecture::Mips => Box::new(mips::ObjArchMips::new(object)?), object::Architecture::Mips => Box::new(mips::ArchMips::new(object)?),
#[cfg(feature = "x86")] #[cfg(feature = "x86")]
Architecture::I386 | Architecture::X86_64 => Box::new(x86::ObjArchX86::new(object)?), object::Architecture::I386 | object::Architecture::X86_64 => {
Box::new(x86::ArchX86::new(object)?)
}
#[cfg(feature = "arm")] #[cfg(feature = "arm")]
Architecture::Arm => Box::new(arm::ObjArchArm::new(object)?), object::Architecture::Arm => Box::new(arm::ArchArm::new(object)?),
#[cfg(feature = "arm64")] #[cfg(feature = "arm64")]
Architecture::Aarch64 => Box::new(arm64::ObjArchArm64::new(object)?), object::Architecture::Aarch64 => Box::new(arm64::ArchArm64::new(object)?),
arch => bail!("Unsupported architecture: {arch:?}"), arch => bail!("Unsupported architecture: {arch:?}"),
}) })
} }
#[derive(Debug, Default)]
pub struct ArchDummy {}
impl ArchDummy {
pub fn new() -> Box<Self> { Box::new(Self {}) }
}
impl Arch for ArchDummy {
fn scan_instructions(
&self,
_address: u64,
_code: &[u8],
_section_index: usize,
_diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
Ok(Vec::new())
}
fn display_instruction(
&self,
_ins_ref: InstructionRef,
_code: &[u8],
_relocation: Option<ResolvedRelocation>,
_function_range: Range<u64>,
_section_index: usize,
_diff_config: &DiffObjConfig,
_cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
Ok(())
}
fn implcit_addend(
&self,
_file: &File<'_>,
_section: &Section,
_address: u64,
_relocation: &Relocation,
_flags: RelocationFlags,
) -> Result<i64> {
Ok(0)
}
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
format!("{flags:?}").into()
}
fn get_reloc_byte_size(&self, _flags: RelocationFlags) -> usize { 0 }
}

File diff suppressed because it is too large Load Diff

View File

@ -7,33 +7,93 @@ use alloc::{
vec, vec,
vec::Vec, vec::Vec,
}; };
use std::ops::Range;
use anyhow::{anyhow, bail, ensure, Result}; use anyhow::{anyhow, bail, ensure, Result};
use iced_x86::{ use iced_x86::{
Decoder, DecoderOptions, DecoratorKind, Formatter, FormatterOutput, FormatterTextKind, Decoder, DecoderOptions, DecoratorKind, FormatterOutput, FormatterTextKind, GasFormatter,
GasFormatter, Instruction, IntelFormatter, MasmFormatter, NasmFormatter, NumberKind, OpKind, Instruction, IntelFormatter, MasmFormatter, NasmFormatter, NumberKind, OpKind, PrefixKind,
PrefixKind, Register, Register,
}; };
use object::{pe, Endian, Endianness, File, Object, Relocation, RelocationFlags}; use object::{pe, Endian as _, Object as _, ObjectSection as _};
use crate::{ use crate::{
arch::{ObjArch, ProcessCodeResult}, arch::Arch,
diff::{DiffObjConfig, X86Formatter}, diff::{display::InstructionPart, DiffObjConfig, X86Formatter},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection}, obj::{
InstructionArg, InstructionArgValue, InstructionRef, ParsedInstruction, RelocationFlags,
ResolvedRelocation, ScannedInstruction,
},
}; };
pub struct ObjArchX86 { #[derive(Debug)]
pub struct ArchX86 {
bits: u32, bits: u32,
endianness: Endianness, endianness: object::Endianness,
} }
impl ObjArchX86 { impl ArchX86 {
pub fn new(object: &File) -> Result<Self> { pub fn new(object: &object::File) -> Result<Self> {
Ok(Self { bits: if object.is_64() { 64 } else { 32 }, endianness: object.endianness() }) Ok(Self { bits: if object.is_64() { 64 } else { 32 }, endianness: object.endianness() })
} }
fn formatter(&self, diff_config: &DiffObjConfig) -> Box<dyn iced_x86::Formatter> {
let mut formatter: Box<dyn iced_x86::Formatter> = match diff_config.x86_formatter {
X86Formatter::Intel => Box::new(IntelFormatter::new()),
X86Formatter::Gas => Box::new(GasFormatter::new()),
X86Formatter::Nasm => Box::new(NasmFormatter::new()),
X86Formatter::Masm => Box::new(MasmFormatter::new()),
};
formatter.options_mut().set_space_after_operand_separator(diff_config.space_between_args);
formatter
}
}
impl Arch for ArchX86 {
fn scan_instructions(
&self,
address: u64,
code: &[u8],
_section_index: usize,
_diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
let mut out = Vec::with_capacity(code.len() / 2);
let mut decoder = Decoder::with_ip(self.bits, code, address, DecoderOptions::NONE);
let mut instruction = Instruction::default();
while decoder.can_decode() {
decoder.decode_out(&mut instruction);
// TODO is this right?
let branch_dest = match instruction.op0_kind() {
OpKind::NearBranch16 => Some(instruction.near_branch16() as u64),
OpKind::NearBranch32 => Some(instruction.near_branch32() as u64),
OpKind::NearBranch64 => Some(instruction.near_branch64()),
_ => None,
};
out.push(ScannedInstruction {
ins_ref: InstructionRef {
address: instruction.ip(),
size: instruction.len() as u8,
opcode: instruction.mnemonic() as u16,
},
branch_dest,
});
}
Ok(out)
}
fn display_instruction(
&self,
ins_ref: InstructionRef,
code: &[u8],
relocation: Option<ResolvedRelocation>,
function_range: Range<u64>,
section_index: usize,
diff_config: &DiffObjConfig,
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
todo!()
} }
impl ObjArch for ObjArchX86 {
fn process_code( fn process_code(
&self, &self,
address: u64, address: u64,
@ -45,13 +105,7 @@ impl ObjArch for ObjArchX86 {
) -> Result<ProcessCodeResult> { ) -> Result<ProcessCodeResult> {
let mut result = ProcessCodeResult { ops: Vec::new(), insts: Vec::new() }; let mut result = ProcessCodeResult { ops: Vec::new(), insts: Vec::new() };
let mut decoder = Decoder::with_ip(self.bits, code, address, DecoderOptions::NONE); let mut decoder = Decoder::with_ip(self.bits, code, address, DecoderOptions::NONE);
let mut formatter: Box<dyn Formatter> = match config.x86_formatter { let mut formatter = self.formatter(config);
X86Formatter::Intel => Box::new(IntelFormatter::new()),
X86Formatter::Gas => Box::new(GasFormatter::new()),
X86Formatter::Nasm => Box::new(NasmFormatter::new()),
X86Formatter::Masm => Box::new(MasmFormatter::new()),
};
formatter.options_mut().set_space_after_operand_separator(config.space_between_args);
let mut output = InstructionFormatterOutput { let mut output = InstructionFormatterOutput {
formatted: String::new(), formatted: String::new(),
@ -101,10 +155,12 @@ impl ObjArch for ObjArchX86 {
output.ins.formatted.clone_from(&output.formatted); output.ins.formatted.clone_from(&output.formatted);
// Make sure we've put the relocation somewhere in the instruction // Make sure we've put the relocation somewhere in the instruction
if reloc.is_some() && !output.ins.args.iter().any(|a| matches!(a, ObjInsArg::Reloc)) { if reloc.is_some()
&& !output.ins.args.iter().any(|a| matches!(a, InstructionArg::Reloc))
{
let mut found = replace_arg( let mut found = replace_arg(
OpKind::Memory, OpKind::Memory,
ObjInsArg::Reloc, InstructionArg::Reloc,
&mut output.ins.args, &mut output.ins.args,
&instruction, &instruction,
&output.ins_operands, &output.ins_operands,
@ -112,7 +168,7 @@ impl ObjArch for ObjArchX86 {
if !found { if !found {
found = replace_arg( found = replace_arg(
OpKind::Immediate32, OpKind::Immediate32,
ObjInsArg::Reloc, InstructionArg::Reloc,
&mut output.ins.args, &mut output.ins.args,
&instruction, &instruction,
&output.ins_operands, &output.ins_operands,
@ -120,7 +176,9 @@ impl ObjArch for ObjArchX86 {
} }
ensure!(found, "x86: Failed to find operand for Absolute relocation"); ensure!(found, "x86: Failed to find operand for Absolute relocation");
} }
if reloc.is_some() && !output.ins.args.iter().any(|a| matches!(a, ObjInsArg::Reloc)) { if reloc.is_some()
&& !output.ins.args.iter().any(|a| matches!(a, InstructionArg::Reloc))
{
bail!("Failed to find relocation in instruction"); bail!("Failed to find relocation in instruction");
} }
@ -136,14 +194,15 @@ impl ObjArch for ObjArchX86 {
fn implcit_addend( fn implcit_addend(
&self, &self,
_file: &File<'_>, _file: &object::File<'_>,
section: &ObjSection, section: &object::Section,
address: u64, address: u64,
reloc: &Relocation, _relocation: &object::Relocation,
flags: RelocationFlags,
) -> Result<i64> { ) -> Result<i64> {
match reloc.flags() { match flags {
RelocationFlags::Coff { typ: pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32 } => { RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32) => {
let data = section.data[address as usize..address as usize + 4].try_into()?; let data = 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)
} }
flags => bail!("Unsupported x86 implicit relocation {flags:?}"), flags => bail!("Unsupported x86 implicit relocation {flags:?}"),
@ -162,7 +221,7 @@ impl ObjArch for ObjArchX86 {
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> { fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
match flags { match flags {
RelocationFlags::Coff { typ } => match typ { RelocationFlags::Coff(typ) => match typ {
pe::IMAGE_REL_I386_DIR32 => Cow::Borrowed("IMAGE_REL_I386_DIR32"), pe::IMAGE_REL_I386_DIR32 => Cow::Borrowed("IMAGE_REL_I386_DIR32"),
pe::IMAGE_REL_I386_REL32 => Cow::Borrowed("IMAGE_REL_I386_REL32"), pe::IMAGE_REL_I386_REL32 => Cow::Borrowed("IMAGE_REL_I386_REL32"),
_ => Cow::Owned(format!("<{flags:?}>")), _ => Cow::Owned(format!("<{flags:?}>")),
@ -173,7 +232,7 @@ impl ObjArch for ObjArchX86 {
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize { fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize {
match flags { match flags {
RelocationFlags::Coff { typ } => match typ { RelocationFlags::Coff(typ) => match typ {
pe::IMAGE_REL_I386_DIR16 => 2, pe::IMAGE_REL_I386_DIR16 => 2,
pe::IMAGE_REL_I386_REL16 => 2, pe::IMAGE_REL_I386_REL16 => 2,
pe::IMAGE_REL_I386_DIR32 => 4, pe::IMAGE_REL_I386_DIR32 => 4,
@ -187,8 +246,8 @@ impl ObjArch for ObjArchX86 {
fn replace_arg( fn replace_arg(
from: OpKind, from: OpKind,
to: ObjInsArg, to: InstructionArg,
args: &mut [ObjInsArg], args: &mut [InstructionArg],
instruction: &Instruction, instruction: &Instruction,
ins_operands: &[Option<u32>], ins_operands: &[Option<u32>],
) -> Result<bool> { ) -> Result<bool> {
@ -213,7 +272,7 @@ fn replace_arg(
struct InstructionFormatterOutput { struct InstructionFormatterOutput {
formatted: String, formatted: String,
ins: ObjIns, ins: ParsedInstruction,
error: Option<anyhow::Error>, error: Option<anyhow::Error>,
ins_operands: Vec<Option<u32>>, ins_operands: Vec<Option<u32>>,
} }
@ -223,11 +282,13 @@ impl InstructionFormatterOutput {
// The formatter writes the '-' operator and then gives us a negative value, // The formatter writes the '-' operator and then gives us a negative value,
// so convert it to a positive value to avoid double negatives // so convert it to a positive value to avoid double negatives
if value < 0 if value < 0
&& matches!(self.ins.args.last(), Some(ObjInsArg::Arg(ObjInsArgValue::Opaque(v))) if v == "-") && matches!(self.ins.args.last(), Some(InstructionArg::Value(InstructionArgValue::Opaque(v))) if v == "-")
{ {
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(value.wrapping_abs()))); self.ins
.args
.push(InstructionArg::Value(InstructionArgValue::Signed(value.wrapping_abs())));
} else { } else {
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(value))); self.ins.args.push(InstructionArg::Value(InstructionArgValue::Signed(value)));
} }
} }
} }
@ -242,10 +303,12 @@ impl FormatterOutput for InstructionFormatterOutput {
self.ins_operands.push(None); self.ins_operands.push(None);
match kind { match kind {
FormatterTextKind::Text | FormatterTextKind::Punctuation => { FormatterTextKind::Text | FormatterTextKind::Punctuation => {
self.ins.args.push(ObjInsArg::PlainText(text.to_string().into())); self.ins.args.push(InstructionArg::PlainText(text.to_string().into()));
} }
FormatterTextKind::Keyword | FormatterTextKind::Operator => { FormatterTextKind::Keyword | FormatterTextKind::Operator => {
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(text.to_string().into()))); self.ins.args.push(InstructionArg::Value(InstructionArgValue::Opaque(
text.to_string().into(),
)));
} }
_ => { _ => {
if self.error.is_none() { if self.error.is_none() {
@ -258,12 +321,13 @@ impl FormatterOutput for InstructionFormatterOutput {
fn write_prefix(&mut self, _instruction: &Instruction, text: &str, _prefix: PrefixKind) { fn write_prefix(&mut self, _instruction: &Instruction, text: &str, _prefix: PrefixKind) {
self.formatted.push_str(text); self.formatted.push_str(text);
self.ins_operands.push(None); self.ins_operands.push(None);
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(text.to_string().into()))); self.ins
.args
.push(InstructionArg::Value(InstructionArgValue::Opaque(text.to_string().into())));
} }
fn write_mnemonic(&mut self, _instruction: &Instruction, text: &str) { fn write_mnemonic(&mut self, _instruction: &Instruction, text: &str) {
self.formatted.push_str(text); self.formatted.push_str(text);
// TODO: can iced-x86 guarantee 'static here?
self.ins.mnemonic = Cow::Owned(text.to_string()); self.ins.mnemonic = Cow::Owned(text.to_string());
} }
@ -284,10 +348,11 @@ impl FormatterOutput for InstructionFormatterOutput {
match kind { match kind {
FormatterTextKind::LabelAddress => { FormatterTextKind::LabelAddress => {
if let Some(reloc) = self.ins.reloc.as_ref() { if let Some(reloc) = self.ins.reloc.as_ref() {
if matches!(reloc.flags, RelocationFlags::Coff { if matches!(
typ: pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32 reloc.flags,
}) { RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32)
self.ins.args.push(ObjInsArg::Reloc); ) {
self.ins.args.push(InstructionArg::Reloc);
return; return;
} else if self.error.is_none() { } else if self.error.is_none() {
self.error = Some(anyhow!( self.error = Some(anyhow!(
@ -296,16 +361,14 @@ impl FormatterOutput for InstructionFormatterOutput {
)); ));
} }
} }
self.ins.args.push(ObjInsArg::BranchDest(value)); self.ins.args.push(InstructionArg::BranchDest(value));
self.ins.branch_dest = Some(value); self.ins.branch_dest = Some(value);
return; return;
} }
FormatterTextKind::FunctionAddress => { FormatterTextKind::FunctionAddress => {
if let Some(reloc) = self.ins.reloc.as_ref() { if let Some(reloc) = self.ins.reloc.as_ref() {
if matches!(reloc.flags, RelocationFlags::Coff { if matches!(reloc.flags, RelocationFlags::Coff(pe::IMAGE_REL_I386_REL32)) {
typ: pe::IMAGE_REL_I386_REL32 self.ins.args.push(InstructionArg::Reloc);
}) {
self.ins.args.push(ObjInsArg::Reloc);
return; return;
} else if self.error.is_none() { } else if self.error.is_none() {
self.error = Some(anyhow!( self.error = Some(anyhow!(
@ -332,7 +395,7 @@ impl FormatterOutput for InstructionFormatterOutput {
self.push_signed(value as i64); self.push_signed(value as i64);
} }
NumberKind::UInt8 | NumberKind::UInt16 | NumberKind::UInt32 | NumberKind::UInt64 => { NumberKind::UInt8 | NumberKind::UInt16 | NumberKind::UInt32 | NumberKind::UInt64 => {
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(value))); self.ins.args.push(InstructionArg::Value(InstructionArgValue::Unsigned(value)));
} }
} }
} }
@ -347,7 +410,7 @@ impl FormatterOutput for InstructionFormatterOutput {
) { ) {
self.formatted.push_str(text); self.formatted.push_str(text);
self.ins_operands.push(instruction_operand); self.ins_operands.push(instruction_operand);
self.ins.args.push(ObjInsArg::PlainText(text.to_string().into())); self.ins.args.push(InstructionArg::PlainText(text.to_string().into()));
} }
fn write_register( fn write_register(
@ -360,6 +423,8 @@ impl FormatterOutput for InstructionFormatterOutput {
) { ) {
self.formatted.push_str(text); self.formatted.push_str(text);
self.ins_operands.push(instruction_operand); self.ins_operands.push(instruction_operand);
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(text.to_string().into()))); self.ins
.args
.push(InstructionArg::Value(InstructionArgValue::Opaque(text.to_string().into())));
} }
} }

View File

@ -1,18 +1,6 @@
#![allow(clippy::needless_lifetimes)] // Generated serde code #![allow(clippy::needless_lifetimes)] // Generated serde code
use alloc::string::ToString; use crate::{diff, obj};
use crate::{
diff::{
ObjDataDiff, ObjDataDiffKind, ObjDiff, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
ObjInsDiff, ObjInsDiffKind, ObjSectionDiff, ObjSymbolDiff,
},
obj,
obj::{
ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSectionKind, ObjSymbol,
ObjSymbolFlagSet, ObjSymbolFlags,
},
};
// Protobuf diff types // Protobuf diff types
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.rs")); include!(concat!(env!("OUT_DIR"), "/objdiff.diff.rs"));
@ -20,242 +8,235 @@ include!(concat!(env!("OUT_DIR"), "/objdiff.diff.rs"));
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.serde.rs")); include!(concat!(env!("OUT_DIR"), "/objdiff.diff.serde.rs"));
impl DiffResult { impl DiffResult {
pub fn new(left: Option<(&ObjInfo, &ObjDiff)>, right: Option<(&ObjInfo, &ObjDiff)>) -> Self { pub fn new(
_left: Option<(&obj::Object, &diff::ObjectDiff)>,
_right: Option<(&obj::Object, &diff::ObjectDiff)>,
) -> Self {
Self { Self {
left: left.map(|(obj, diff)| ObjectDiff::new(obj, diff)), // TODO
right: right.map(|(obj, diff)| ObjectDiff::new(obj, diff)), // left: left.map(|(obj, diff)| ObjectDiff::new(obj, diff)),
// right: right.map(|(obj, diff)| ObjectDiff::new(obj, diff)),
left: None,
right: None,
} }
} }
} }
impl ObjectDiff { // impl ObjectDiff {
pub fn new(obj: &ObjInfo, diff: &ObjDiff) -> Self { // pub fn new(obj: &obj::Object, diff: &diff::ObjectDiff) -> Self {
Self { // Self {
sections: diff // sections: diff
.sections // .sections
.iter() // .iter()
.enumerate() // .enumerate()
.map(|(i, d)| SectionDiff::new(obj, i, d)) // .map(|(i, d)| SectionDiff::new(obj, i, d))
.collect(), // .collect(),
} // }
} // }
} // }
//
impl SectionDiff { // impl SectionDiff {
pub fn new(obj: &ObjInfo, section_index: usize, section_diff: &ObjSectionDiff) -> Self { // pub fn new(obj: &obj::Object, section_index: usize, section_diff: &diff::SectionDiff) -> Self {
let section = &obj.sections[section_index]; // let section = &obj.sections[section_index];
let symbols = section_diff.symbols.iter().map(|d| SymbolDiff::new(obj, d)).collect(); // let symbols = section_diff.symbols.iter().map(|d| SymbolDiff::new(obj, d)).collect();
let data = section_diff.data_diff.iter().map(|d| DataDiff::new(obj, d)).collect(); // let data = section_diff.data_diff.iter().map(|d| DataDiff::new(obj, d)).collect();
// TODO: section_diff.reloc_diff // // TODO: section_diff.reloc_diff
Self { // Self {
name: section.name.to_string(), // name: section.name.to_string(),
kind: SectionKind::from(section.kind) as i32, // kind: SectionKind::from(section.kind) as i32,
size: section.size, // size: section.size,
address: section.address, // address: section.address,
symbols, // symbols,
data, // data,
match_percent: section_diff.match_percent, // match_percent: section_diff.match_percent,
} // }
} // }
} // }
//
impl From<ObjSectionKind> for SectionKind { // impl From<obj::SectionKind> for SectionKind {
fn from(value: ObjSectionKind) -> Self { // fn from(value: obj::SectionKind) -> Self {
match value { // match value {
ObjSectionKind::Code => SectionKind::SectionText, // obj::SectionKind::Code => SectionKind::SectionText,
ObjSectionKind::Data => SectionKind::SectionData, // obj::SectionKind::Data => SectionKind::SectionData,
ObjSectionKind::Bss => SectionKind::SectionBss, // obj::SectionKind::Bss => SectionKind::SectionBss,
// TODO common // // TODO common
} // }
} // }
} // }
//
impl From<obj::SymbolRef> for SymbolRef { // impl SymbolDiff {
fn from(value: obj::SymbolRef) -> Self { // pub fn new(object: &obj::Object, symbol_diff: &diff::SymbolDiff) -> Self {
Self { // let symbol = object.symbols[symbol_diff.symbol_index];
section_index: if value.section_idx == obj::SECTION_COMMON { // let instructions = symbol_diff
None // .instruction_rows
} else { // .iter()
Some(value.section_idx as u32) // .map(|ins_diff| InstructionDiff::new(object, ins_diff))
}, // .collect();
symbol_index: value.symbol_idx as u32, // Self {
} // symbol: Some(Symbol::new(symbol)),
} // instructions,
} // match_percent: symbol_diff.match_percent,
// target: symbol_diff.target_symbol.map(SymbolRef::from),
impl SymbolDiff { // }
pub fn new(object: &ObjInfo, symbol_diff: &ObjSymbolDiff) -> Self { // }
let (_section, symbol) = object.section_symbol(symbol_diff.symbol_ref); // }
let instructions = symbol_diff //
.instructions // impl DataDiff {
.iter() // pub fn new(_object: &obj::Object, data_diff: &diff::DataDiff) -> Self {
.map(|ins_diff| InstructionDiff::new(object, ins_diff)) // Self {
.collect(); // kind: DiffKind::from(data_diff.kind) as i32,
Self { // data: data_diff.data.clone(),
symbol: Some(Symbol::new(symbol)), // size: data_diff.len as u64,
instructions, // }
match_percent: symbol_diff.match_percent, // }
target: symbol_diff.target_symbol.map(SymbolRef::from), // }
} //
} // impl Symbol {
} // pub fn new(value: &ObjSymbol) -> Self {
// Self {
impl DataDiff { // name: value.name.to_string(),
pub fn new(_object: &ObjInfo, data_diff: &ObjDataDiff) -> Self { // demangled_name: value.demangled_name.clone(),
Self { // address: value.address,
kind: DiffKind::from(data_diff.kind) as i32, // size: value.size,
data: data_diff.data.clone(), // flags: symbol_flags(value.flags),
size: data_diff.len as u64, // }
} // }
} // }
} //
// fn symbol_flags(value: ObjSymbolFlagSet) -> u32 {
impl Symbol { // let mut flags = 0u32;
pub fn new(value: &ObjSymbol) -> Self { // if value.0.contains(ObjSymbolFlags::Global) {
Self { // flags |= SymbolFlag::SymbolGlobal as u32;
name: value.name.to_string(), // }
demangled_name: value.demangled_name.clone(), // if value.0.contains(ObjSymbolFlags::Local) {
address: value.address, // flags |= SymbolFlag::SymbolLocal as u32;
size: value.size, // }
flags: symbol_flags(value.flags), // if value.0.contains(ObjSymbolFlags::Weak) {
} // flags |= SymbolFlag::SymbolWeak as u32;
} // }
} // if value.0.contains(ObjSymbolFlags::Common) {
// flags |= SymbolFlag::SymbolCommon as u32;
fn symbol_flags(value: ObjSymbolFlagSet) -> u32 { // }
let mut flags = 0u32; // if value.0.contains(ObjSymbolFlags::Hidden) {
if value.0.contains(ObjSymbolFlags::Global) { // flags |= SymbolFlag::SymbolHidden as u32;
flags |= SymbolFlag::SymbolGlobal as u32; // }
} // flags
if value.0.contains(ObjSymbolFlags::Local) { // }
flags |= SymbolFlag::SymbolLocal as u32; //
} // impl Instruction {
if value.0.contains(ObjSymbolFlags::Weak) { // pub fn new(object: &obj::Object, instruction: &ObjIns) -> Self {
flags |= SymbolFlag::SymbolWeak as u32; // Self {
} // address: instruction.address,
if value.0.contains(ObjSymbolFlags::Common) { // size: instruction.size as u32,
flags |= SymbolFlag::SymbolCommon as u32; // opcode: instruction.op as u32,
} // mnemonic: instruction.mnemonic.to_string(),
if value.0.contains(ObjSymbolFlags::Hidden) { // formatted: instruction.formatted.clone(),
flags |= SymbolFlag::SymbolHidden as u32; // arguments: instruction.args.iter().map(Argument::new).collect(),
} // relocation: instruction.reloc.as_ref().map(|reloc| Relocation::new(object, reloc)),
flags // branch_dest: instruction.branch_dest,
} // line_number: instruction.line,
// original: instruction.orig.clone(),
impl Instruction { // }
pub fn new(object: &ObjInfo, instruction: &ObjIns) -> Self { // }
Self { // }
address: instruction.address, //
size: instruction.size as u32, // impl Argument {
opcode: instruction.op as u32, // pub fn new(value: &ObjInsArg) -> Self {
mnemonic: instruction.mnemonic.to_string(), // Self {
formatted: instruction.formatted.clone(), // value: Some(match value {
arguments: instruction.args.iter().map(Argument::new).collect(), // ObjInsArg::PlainText(s) => argument::Value::PlainText(s.to_string()),
relocation: instruction.reloc.as_ref().map(|reloc| Relocation::new(object, reloc)), // ObjInsArg::Arg(v) => argument::Value::Argument(ArgumentValue::new(v)),
branch_dest: instruction.branch_dest, // ObjInsArg::Reloc => argument::Value::Relocation(ArgumentRelocation {}),
line_number: instruction.line, // ObjInsArg::BranchDest(dest) => argument::Value::BranchDest(*dest),
original: instruction.orig.clone(), // }),
} // }
} // }
} // }
//
impl Argument { // impl ArgumentValue {
pub fn new(value: &ObjInsArg) -> Self { // pub fn new(value: &ObjInsArgValue) -> Self {
Self { // Self {
value: Some(match value { // value: Some(match value {
ObjInsArg::PlainText(s) => argument::Value::PlainText(s.to_string()), // ObjInsArgValue::Signed(v) => argument_value::Value::Signed(*v),
ObjInsArg::Arg(v) => argument::Value::Argument(ArgumentValue::new(v)), // ObjInsArgValue::Unsigned(v) => argument_value::Value::Unsigned(*v),
ObjInsArg::Reloc => argument::Value::Relocation(ArgumentRelocation {}), // ObjInsArgValue::Opaque(v) => argument_value::Value::Opaque(v.to_string()),
ObjInsArg::BranchDest(dest) => argument::Value::BranchDest(*dest), // }),
}), // }
} // }
} // }
} //
// impl Relocation {
impl ArgumentValue { // pub fn new(object: &obj::Object, reloc: &ObjReloc) -> Self {
pub fn new(value: &ObjInsArgValue) -> Self { // Self {
Self { // r#type: match reloc.flags {
value: Some(match value { // object::RelocationFlags::Elf { r_type } => r_type,
ObjInsArgValue::Signed(v) => argument_value::Value::Signed(*v), // object::RelocationFlags::MachO { r_type, .. } => r_type as u32,
ObjInsArgValue::Unsigned(v) => argument_value::Value::Unsigned(*v), // object::RelocationFlags::Coff { typ } => typ as u32,
ObjInsArgValue::Opaque(v) => argument_value::Value::Opaque(v.to_string()), // object::RelocationFlags::Xcoff { r_rtype, .. } => r_rtype as u32,
}), // _ => unreachable!(),
} // },
} // type_name: object.arch.display_reloc(reloc.flags).into_owned(),
} // target: Some(RelocationTarget {
// symbol: Some(Symbol::new(&reloc.target)),
impl Relocation { // addend: reloc.addend,
pub fn new(object: &ObjInfo, reloc: &ObjReloc) -> Self { // }),
Self { // }
r#type: match reloc.flags { // }
object::RelocationFlags::Elf { r_type } => r_type, // }
object::RelocationFlags::MachO { r_type, .. } => r_type as u32, //
object::RelocationFlags::Coff { typ } => typ as u32, // impl InstructionDiff {
object::RelocationFlags::Xcoff { r_rtype, .. } => r_rtype as u32, // pub fn new(object: &obj::Object, instruction_diff: &ObjInsDiff) -> Self {
_ => unreachable!(), // Self {
}, // instruction: instruction_diff.ins.as_ref().map(|ins| Instruction::new(object, ins)),
type_name: object.arch.display_reloc(reloc.flags).into_owned(), // diff_kind: DiffKind::from(instruction_diff.kind) as i32,
target: Some(RelocationTarget { // branch_from: instruction_diff.branch_from.as_ref().map(InstructionBranchFrom::new),
symbol: Some(Symbol::new(&reloc.target)), // branch_to: instruction_diff.branch_to.as_ref().map(InstructionBranchTo::new),
addend: reloc.addend, // arg_diff: instruction_diff.arg_diff.iter().map(ArgumentDiff::new).collect(),
}), // }
} // }
} // }
} //
// impl ArgumentDiff {
impl InstructionDiff { // pub fn new(value: &Option<ObjInsArgDiff>) -> Self {
pub fn new(object: &ObjInfo, instruction_diff: &ObjInsDiff) -> Self { // Self { diff_index: value.as_ref().map(|v| v.idx as u32) }
Self { // }
instruction: instruction_diff.ins.as_ref().map(|ins| Instruction::new(object, ins)), // }
diff_kind: DiffKind::from(instruction_diff.kind) as i32, //
branch_from: instruction_diff.branch_from.as_ref().map(InstructionBranchFrom::new), // impl From<ObjInsDiffKind> for DiffKind {
branch_to: instruction_diff.branch_to.as_ref().map(InstructionBranchTo::new), // fn from(value: ObjInsDiffKind) -> Self {
arg_diff: instruction_diff.arg_diff.iter().map(ArgumentDiff::new).collect(), // match value {
} // ObjInsDiffKind::None => DiffKind::DiffNone,
} // ObjInsDiffKind::OpMismatch => DiffKind::DiffOpMismatch,
} // ObjInsDiffKind::ArgMismatch => DiffKind::DiffArgMismatch,
// ObjInsDiffKind::Replace => DiffKind::DiffReplace,
impl ArgumentDiff { // ObjInsDiffKind::Delete => DiffKind::DiffDelete,
pub fn new(value: &Option<ObjInsArgDiff>) -> Self { // ObjInsDiffKind::Insert => DiffKind::DiffInsert,
Self { diff_index: value.as_ref().map(|v| v.idx as u32) } // }
} // }
} // }
//
impl From<ObjInsDiffKind> for DiffKind { // impl From<ObjDataDiffKind> for DiffKind {
fn from(value: ObjInsDiffKind) -> Self { // fn from(value: ObjDataDiffKind) -> Self {
match value { // match value {
ObjInsDiffKind::None => DiffKind::DiffNone, // ObjDataDiffKind::None => DiffKind::DiffNone,
ObjInsDiffKind::OpMismatch => DiffKind::DiffOpMismatch, // ObjDataDiffKind::Replace => DiffKind::DiffReplace,
ObjInsDiffKind::ArgMismatch => DiffKind::DiffArgMismatch, // ObjDataDiffKind::Delete => DiffKind::DiffDelete,
ObjInsDiffKind::Replace => DiffKind::DiffReplace, // ObjDataDiffKind::Insert => DiffKind::DiffInsert,
ObjInsDiffKind::Delete => DiffKind::DiffDelete, // }
ObjInsDiffKind::Insert => DiffKind::DiffInsert, // }
} // }
} //
} // impl InstructionBranchFrom {
// pub fn new(value: &ObjInsBranchFrom) -> Self {
impl From<ObjDataDiffKind> for DiffKind { // Self {
fn from(value: ObjDataDiffKind) -> Self { // instruction_index: value.ins_idx.iter().map(|&x| x as u32).collect(),
match value { // branch_index: value.branch_idx as u32,
ObjDataDiffKind::None => DiffKind::DiffNone, // }
ObjDataDiffKind::Replace => DiffKind::DiffReplace, // }
ObjDataDiffKind::Delete => DiffKind::DiffDelete, // }
ObjDataDiffKind::Insert => DiffKind::DiffInsert, //
} // impl InstructionBranchTo {
} // pub fn new(value: &ObjInsBranchTo) -> Self {
} // Self { instruction_index: value.ins_idx as u32, branch_index: value.branch_idx as u32 }
// }
impl InstructionBranchFrom { // }
pub fn new(value: &ObjInsBranchFrom) -> Self {
Self {
instruction_index: value.ins_idx.iter().map(|&x| x as u32).collect(),
branch_index: value.branch_idx as u32,
}
}
}
impl InstructionBranchTo {
pub fn new(value: &ObjInsBranchTo) -> Self {
Self { instruction_index: value.ins_idx as u32, branch_index: value.branch_idx as u32 }
}
}

View File

@ -1,428 +1,561 @@
use alloc::{ use alloc::{
collections::BTreeMap, collections::{btree_map, BTreeMap},
string::{String, ToString}, string::{String, ToString},
vec, vec,
vec::Vec, vec::Vec,
}; };
use anyhow::{anyhow, Result}; use anyhow::{anyhow, ensure, Result};
use similar::{capture_diff_slices, Algorithm};
use super::FunctionRelocDiffs; use super::{
use crate::{ DiffObjConfig, FunctionRelocDiffs, InstructionArgDiffIndex, InstructionBranchFrom,
arch::ProcessCodeResult, InstructionBranchTo, InstructionDiffKind, InstructionDiffRow, SymbolDiff,
diff::{ };
DiffObjConfig, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind, use crate::obj::{
ObjSymbolDiff, InstructionArg, InstructionArgValue, InstructionRef, Object, ResolvedRelocation,
}, ScannedInstruction, SymbolFlag, SymbolKind,
obj::{
ObjInfo, ObjIns, ObjInsArg, ObjReloc, ObjSection, ObjSymbol, ObjSymbolFlags, ObjSymbolKind,
SymbolRef,
},
}; };
pub fn process_code_symbol( pub fn no_diff_code(
obj: &ObjInfo, obj: &Object,
symbol_ref: SymbolRef, symbol_idx: usize,
config: &DiffObjConfig, diff_config: &DiffObjConfig,
) -> Result<ProcessCodeResult> { ) -> Result<SymbolDiff> {
let (section, symbol) = obj.section_symbol(symbol_ref); let symbol = &obj.symbols[symbol_idx];
let section = section.ok_or_else(|| anyhow!("Code symbol section not found"))?; let section_index = symbol.section.ok_or_else(|| anyhow!("Missing section for symbol"))?;
let code = &section.data let section = &obj.sections[section_index];
[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize]; let data = section.data_range(symbol.address, symbol.size as usize).ok_or_else(|| {
let mut res = obj.arch.process_code( anyhow!(
"Symbol data out of bounds: {:#x}..{:#x}",
symbol.address, symbol.address,
code, symbol.address + symbol.size
section.orig_index, )
&section.relocations, })?;
&section.line_info, let ops = obj.arch.scan_instructions(symbol.address, data, section_index, diff_config)?;
config, let mut instruction_rows = Vec::<InstructionDiffRow>::new();
)?; for i in &ops {
instruction_rows
for inst in res.insts.iter_mut() { .push(InstructionDiffRow { ins_ref: Some(i.ins_ref), ..Default::default() });
if let Some(reloc) = &mut inst.reloc {
if reloc.target.size == 0 && reloc.target.name.is_empty() {
// Fake target symbol we added as a placeholder. We need to find the real one.
if let Some(real_target) =
find_symbol_matching_fake_symbol_in_sections(&reloc.target, &obj.sections)
{
reloc.addend = (reloc.target.address - real_target.address) as i64;
reloc.target = real_target;
}
}
} }
resolve_branches(obj, section_index, &ops, &mut instruction_rows);
Ok(SymbolDiff { target_symbol: None, match_percent: None, diff_score: None, instruction_rows })
} }
Ok(res) const PENALTY_IMM_DIFF: u64 = 1;
} const PENALTY_REG_DIFF: u64 = 5;
const PENALTY_REPLACE: u64 = 60;
pub fn no_diff_code(out: &ProcessCodeResult, symbol_ref: SymbolRef) -> Result<ObjSymbolDiff> { const PENALTY_INSERT_DELETE: u64 = 100;
let mut diff = Vec::<ObjInsDiff>::new();
for i in &out.insts {
diff.push(ObjInsDiff {
ins: Some(i.clone()),
kind: ObjInsDiffKind::None,
..Default::default()
});
}
resolve_branches(&mut diff);
Ok(ObjSymbolDiff { symbol_ref, target_symbol: None, instructions: diff, match_percent: None })
}
pub fn diff_code( pub fn diff_code(
left_obj: &ObjInfo, left_obj: &Object,
right_obj: &ObjInfo, right_obj: &Object,
left_out: &ProcessCodeResult, left_symbol_idx: usize,
right_out: &ProcessCodeResult, right_symbol_idx: usize,
left_symbol_ref: SymbolRef, diff_config: &DiffObjConfig,
right_symbol_ref: SymbolRef, ) -> Result<(SymbolDiff, SymbolDiff)> {
config: &DiffObjConfig, let left_symbol = &left_obj.symbols[left_symbol_idx];
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> { let right_symbol = &right_obj.symbols[right_symbol_idx];
let mut left_diff = Vec::<ObjInsDiff>::new(); let left_section = left_symbol
let mut right_diff = Vec::<ObjInsDiff>::new(); .section
diff_instructions(&mut left_diff, &mut right_diff, left_out, right_out)?; .and_then(|i| left_obj.sections.get(i))
.ok_or_else(|| anyhow!("Missing section for symbol"))?;
let right_section = right_symbol
.section
.and_then(|i| right_obj.sections.get(i))
.ok_or_else(|| anyhow!("Missing section for symbol"))?;
let left_data = left_section
.data_range(left_symbol.address, left_symbol.size as usize)
.ok_or_else(|| {
anyhow!(
"Symbol data out of bounds: {:#x}..{:#x}",
left_symbol.address,
left_symbol.address + left_symbol.size
)
})?;
let right_data = right_section
.data_range(right_symbol.address, right_symbol.size as usize)
.ok_or_else(|| {
anyhow!(
"Symbol data out of bounds: {:#x}..{:#x}",
right_symbol.address,
right_symbol.address + right_symbol.size
)
})?;
resolve_branches(&mut left_diff); let left_section_idx = left_symbol.section.unwrap();
resolve_branches(&mut right_diff); let right_section_idx = right_symbol.section.unwrap();
let left_ops = left_obj.arch.scan_instructions(
left_symbol.address,
left_data,
left_section_idx,
diff_config,
)?;
let right_ops = left_obj.arch.scan_instructions(
right_symbol.address,
right_data,
right_section_idx,
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);
let mut diff_state = InsDiffState::default(); let mut diff_state = InstructionDiffState::default();
for (left, right) in left_diff.iter_mut().zip(right_diff.iter_mut()) { for (left_row, right_row) in left_rows.iter_mut().zip(right_rows.iter_mut()) {
let result = compare_ins(config, left_obj, right_obj, left, right, &mut diff_state)?; let result = diff_instruction(
left.kind = result.kind; left_obj,
right.kind = result.kind; right_obj,
left.arg_diff = result.left_args_diff; left_symbol_idx,
right.arg_diff = result.right_args_diff; right_symbol_idx,
left_row.ins_ref.as_ref(),
right_row.ins_ref.as_ref(),
left_row,
right_row,
diff_config,
&mut diff_state,
)?;
left_row.kind = result.kind;
right_row.kind = result.kind;
left_row.arg_diff = result.left_args_diff;
right_row.arg_diff = result.right_args_diff;
} }
let total = left_out.insts.len().max(right_out.insts.len()); let max_score = left_ops.len() as u64 * PENALTY_INSERT_DELETE;
let percent = if diff_state.diff_count >= total { let diff_score = diff_state.diff_score.min(max_score);
0.0 let match_percent = if max_score == 0 {
100.0
} else { } else {
((total - diff_state.diff_count) as f32 / total as f32) * 100.0 ((1.0 - (diff_score as f64 / max_score as f64)) * 100.0) as f32
}; };
Ok(( Ok((
ObjSymbolDiff { SymbolDiff {
symbol_ref: left_symbol_ref, target_symbol: Some(right_symbol_idx),
target_symbol: Some(right_symbol_ref), match_percent: Some(match_percent),
instructions: left_diff, diff_score: Some((diff_score, max_score)),
match_percent: Some(percent), instruction_rows: left_rows,
}, },
ObjSymbolDiff { SymbolDiff {
symbol_ref: right_symbol_ref, target_symbol: Some(left_symbol_idx),
target_symbol: Some(left_symbol_ref), match_percent: Some(match_percent),
instructions: right_diff, diff_score: Some((diff_score, max_score)),
match_percent: Some(percent), instruction_rows: right_rows,
}, },
)) ))
} }
fn diff_instructions( fn diff_instructions(
left_diff: &mut Vec<ObjInsDiff>, left_insts: &[ScannedInstruction],
right_diff: &mut Vec<ObjInsDiff>, right_insts: &[ScannedInstruction],
left_code: &ProcessCodeResult, ) -> Result<(Vec<InstructionDiffRow>, Vec<InstructionDiffRow>)> {
right_code: &ProcessCodeResult, let left_ops = left_insts.iter().map(|i| i.ins_ref.opcode).collect::<Vec<_>>();
) -> Result<()> { let right_ops = right_insts.iter().map(|i| i.ins_ref.opcode).collect::<Vec<_>>();
let ops = capture_diff_slices(Algorithm::Patience, &left_code.ops, &right_code.ops); let ops = similar::capture_diff_slices(similar::Algorithm::Patience, &left_ops, &right_ops);
if ops.is_empty() { if ops.is_empty() {
left_diff.extend( ensure!(left_insts.len() == right_insts.len());
left_code let left_diff = left_insts
.insts
.iter() .iter()
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }), .map(|i| InstructionDiffRow { ins_ref: Some(i.ins_ref), ..Default::default() })
); .collect();
right_diff.extend( let right_diff = right_insts
right_code
.insts
.iter() .iter()
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }), .map(|i| InstructionDiffRow { ins_ref: Some(i.ins_ref), ..Default::default() })
); .collect();
return Ok(()); return Ok((left_diff, right_diff));
} }
let row_count = ops
.iter()
.map(|op| match *op {
similar::DiffOp::Equal { len, .. } => len,
similar::DiffOp::Delete { old_len, .. } => old_len,
similar::DiffOp::Insert { new_len, .. } => new_len,
similar::DiffOp::Replace { old_len, new_len, .. } => old_len.max(new_len),
})
.sum();
let mut left_diff = Vec::<InstructionDiffRow>::with_capacity(row_count);
let mut right_diff = Vec::<InstructionDiffRow>::with_capacity(row_count);
for op in ops { for op in ops {
let (_tag, left_range, right_range) = op.as_tag_tuple(); let (_tag, left_range, right_range) = op.as_tag_tuple();
let len = left_range.len().max(right_range.len()); let len = left_range.len().max(right_range.len());
left_diff.extend( left_diff.extend(left_range.clone().map(|i| InstructionDiffRow {
left_code.insts[left_range.clone()] ins_ref: Some(left_insts[i].ins_ref),
.iter() ..Default::default()
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }), }));
); right_diff.extend(right_range.clone().map(|i| InstructionDiffRow {
right_diff.extend( ins_ref: Some(right_insts[i].ins_ref),
right_code.insts[right_range.clone()] ..Default::default()
.iter() }));
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
);
if left_range.len() < len { if left_range.len() < len {
left_diff.extend((left_range.len()..len).map(|_| ObjInsDiff::default())); left_diff.extend((left_range.len()..len).map(|_| InstructionDiffRow::default()));
} }
if right_range.len() < len { if right_range.len() < len {
right_diff.extend((right_range.len()..len).map(|_| ObjInsDiff::default())); right_diff.extend((right_range.len()..len).map(|_| InstructionDiffRow::default()));
}
}
Ok((left_diff, right_diff))
}
fn arg_to_string(arg: &InstructionArg, reloc: Option<&ResolvedRelocation>) -> String {
match arg {
InstructionArg::Value(arg) => arg.to_string(),
InstructionArg::Reloc => {
reloc.as_ref().map_or_else(|| "<unknown>".to_string(), |r| r.symbol.name.clone())
}
InstructionArg::BranchDest(arg) => arg.to_string(),
} }
} }
Ok(()) fn resolve_branches(
} obj: &Object,
section_index: usize,
fn resolve_branches(vec: &mut [ObjInsDiff]) { ops: &[ScannedInstruction],
let mut branch_idx = 0usize; rows: &mut [InstructionDiffRow],
) {
let section = &obj.sections[section_index];
let mut branch_idx = 0u32;
// Map addresses to indices // Map addresses to indices
let mut addr_map = BTreeMap::<u64, usize>::new(); let mut addr_map = BTreeMap::<u64, u32>::new();
for (i, ins_diff) in vec.iter().enumerate() { for (i, ins_diff) in rows.iter().enumerate() {
if let Some(ins) = &ins_diff.ins { if let Some(ins) = ins_diff.ins_ref {
addr_map.insert(ins.address, i); addr_map.insert(ins.address, i as u32);
} }
} }
// Generate branches // Generate branches
let mut branches = BTreeMap::<usize, ObjInsBranchFrom>::new(); let mut branches = BTreeMap::<u32, InstructionBranchFrom>::new();
for (i, ins_diff) in vec.iter_mut().enumerate() { for ((i, ins_diff), ins) in
if let Some(ins) = &ins_diff.ins { rows.iter_mut().enumerate().filter(|(_, row)| row.ins_ref.is_some()).zip(ops)
if let Some(ins_idx) = ins.branch_dest.and_then(|a| addr_map.get(&a)) { {
if let Some(branch) = branches.get_mut(ins_idx) { let branch_dest = if let Some(resolved) = section.relocation_at(ins.ins_ref.address, obj) {
ins_diff.branch_to = if resolved.symbol.section == Some(section_index) {
Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx: branch.branch_idx }); // If the relocation target is in the same section, use it as the branch destination
branch.ins_idx.push(i); resolved.symbol.address.checked_add_signed(resolved.relocation.addend)
} else { } else {
ins_diff.branch_to = Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx }); None
branches.insert(*ins_idx, ObjInsBranchFrom { ins_idx: vec![i], branch_idx }); }
} else {
ins.branch_dest
};
if let Some(ins_idx) = 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 });
e.insert(InstructionBranchFrom { ins_idx: vec![i as u32], branch_idx });
branch_idx += 1; branch_idx += 1;
} }
btree_map::Entry::Occupied(e) => {
let branch = e.into_mut();
ins_diff.branch_to =
Some(InstructionBranchTo { ins_idx, branch_idx: branch.branch_idx });
branch.ins_idx.push(i as u32);
}
} }
} }
} }
// Store branch from // Store branch from
for (i, branch) in branches { for (i, branch) in branches {
vec[i].branch_from = Some(branch); rows[i as usize].branch_from = Some(branch);
} }
} }
pub fn address_eq(left: &ObjReloc, right: &ObjReloc) -> bool { pub(crate) fn address_eq(left: &ResolvedRelocation, right: &ResolvedRelocation) -> bool {
if right.target.size == 0 && left.target.size != 0 { if right.symbol.size == 0 && left.symbol.size != 0 {
// The base relocation is against a pool but the target relocation isn't. // The base relocation is against a pool but the target relocation isn't.
// This can happen in rare cases where the compiler will generate a pool+addend relocation // This can happen in rare cases where the compiler will generate a pool+addend relocation
// in the base's data, but the one detected in the target is direct with no addend. // in the base's data, but the one detected in the target is direct with no addend.
// Just check that the final address is the same so these count as a match. // Just check that the final address is the same so these count as a match.
left.target.address as i64 + left.addend == right.target.address as i64 + right.addend left.symbol.address as i64 + left.relocation.addend
== right.symbol.address as i64 + right.relocation.addend
} else { } else {
// But otherwise, if the compiler isn't using a pool, we're more strict and check that the // But otherwise, if the compiler isn't using a pool, we're more strict and check that the
// target symbol address and relocation addend both match exactly. // target symbol address and relocation addend both match exactly.
left.target.address == right.target.address && left.addend == right.addend left.symbol.address == right.symbol.address
&& left.relocation.addend == right.relocation.addend
} }
} }
pub fn section_name_eq( pub(crate) fn section_name_eq(
left_obj: &ObjInfo, left_obj: &Object,
right_obj: &ObjInfo, right_obj: &Object,
left_orig_section_index: usize, left_section_index: usize,
right_orig_section_index: usize, right_section_index: usize,
) -> bool { ) -> bool {
let Some(left_section) = left_obj.sections.get(left_section_index).is_some_and(|left_section| {
left_obj.sections.iter().find(|s| s.orig_index == left_orig_section_index) right_obj
else { .sections
return false; .get(right_section_index)
}; .is_some_and(|right_section| left_section.name == right_section.name)
let Some(right_section) = })
right_obj.sections.iter().find(|s| s.orig_index == right_orig_section_index)
else {
return false;
};
left_section.name == right_section.name
} }
fn reloc_eq( fn reloc_eq(
config: &DiffObjConfig, left_obj: &Object,
left_obj: &ObjInfo, right_obj: &Object,
right_obj: &ObjInfo, left_reloc: Option<ResolvedRelocation>,
left_ins: Option<&ObjIns>, right_reloc: Option<ResolvedRelocation>,
right_ins: Option<&ObjIns>, diff_config: &DiffObjConfig,
) -> bool { ) -> bool {
let (Some(left_ins), Some(right_ins)) = (left_ins, right_ins) else { let relax_reloc_diffs = diff_config.function_reloc_diffs == FunctionRelocDiffs::None;
return false; let (left_reloc, right_reloc) = match (left_reloc, right_reloc) {
(Some(left_reloc), Some(right_reloc)) => (left_reloc, right_reloc),
// If relocations are relaxed, match if left is missing a reloc
(None, Some(_)) => return relax_reloc_diffs,
(None, None) => return true,
_ => return false,
}; };
let (Some(left), Some(right)) = (&left_ins.reloc, &right_ins.reloc) else { if left_reloc.relocation.flags != right_reloc.relocation.flags {
return false;
};
if left.flags != right.flags {
return false; return false;
} }
if config.function_reloc_diffs == FunctionRelocDiffs::None { if relax_reloc_diffs {
return true; return true;
} }
let symbol_name_addend_matches = let symbol_name_addend_matches = left_reloc.symbol.name == right_reloc.symbol.name
left.target.name == right.target.name && left.addend == right.addend; && left_reloc.relocation.addend == right_reloc.relocation.addend;
match (&left.target.orig_section_index, &right.target.orig_section_index) { match (&left_reloc.symbol.section, &right_reloc.symbol.section) {
(Some(sl), Some(sr)) => { (Some(sl), Some(sr)) => {
// Match if section and name+addend or address match // Match if section and name or address match
section_name_eq(left_obj, right_obj, *sl, *sr) section_name_eq(left_obj, right_obj, *sl, *sr)
&& (config.function_reloc_diffs == FunctionRelocDiffs::DataValue && (diff_config.function_reloc_diffs == FunctionRelocDiffs::DataValue
|| symbol_name_addend_matches || symbol_name_addend_matches
|| address_eq(left, right)) || address_eq(&left_reloc, &right_reloc))
&& (config.function_reloc_diffs == FunctionRelocDiffs::NameAddress && (
|| left.target.kind != ObjSymbolKind::Object diff_config.function_reloc_diffs == FunctionRelocDiffs::NameAddress
|| left_obj.arch.display_ins_data_labels(left_ins) || left_reloc.symbol.kind != SymbolKind::Object
== left_obj.arch.display_ins_data_labels(right_ins)) // TODO
// || left_obj.arch.display_ins_data_labels(left_ins)
// == left_obj.arch.display_ins_data_labels(right_ins))
)
} }
(Some(_), None) => false, (Some(_), None) => false,
(None, Some(_)) => { (None, Some(_)) => {
// Match if possibly stripped weak symbol // Match if possibly stripped weak symbol
symbol_name_addend_matches && right.target.flags.0.contains(ObjSymbolFlags::Weak) symbol_name_addend_matches && right_reloc.symbol.flags.contains(SymbolFlag::Weak)
} }
(None, None) => symbol_name_addend_matches, (None, None) => symbol_name_addend_matches,
} }
} }
fn arg_eq( fn arg_eq(
config: &DiffObjConfig, left_obj: &Object,
left_obj: &ObjInfo, right_obj: &Object,
right_obj: &ObjInfo, left_row: &InstructionDiffRow,
left: &ObjInsArg, right_row: &InstructionDiffRow,
right: &ObjInsArg, left_arg: &InstructionArg,
left_diff: &ObjInsDiff, right_arg: &InstructionArg,
right_diff: &ObjInsDiff, left_reloc: Option<ResolvedRelocation>,
right_reloc: Option<ResolvedRelocation>,
diff_config: &DiffObjConfig,
) -> bool { ) -> bool {
match left { match left_arg {
ObjInsArg::PlainText(l) => match right { InstructionArg::Value(l) => match right_arg {
ObjInsArg::PlainText(r) => l == r, InstructionArg::Value(r) => l.loose_eq(r),
_ => false,
},
ObjInsArg::Arg(l) => match right {
ObjInsArg::Arg(r) => l.loose_eq(r),
// If relocations are relaxed, match if left is a constant and right is a reloc // If relocations are relaxed, match if left is a constant and right is a reloc
// Useful for instances where the target object is created without relocations // Useful for instances where the target object is created without relocations
ObjInsArg::Reloc => config.function_reloc_diffs == FunctionRelocDiffs::None, InstructionArg::Reloc => diff_config.function_reloc_diffs == FunctionRelocDiffs::None,
_ => false, _ => false,
}, },
ObjInsArg::Reloc => { InstructionArg::Reloc => {
matches!(right, ObjInsArg::Reloc) matches!(right_arg, InstructionArg::Reloc)
&& reloc_eq( && reloc_eq(left_obj, right_obj, left_reloc, right_reloc, diff_config)
config, }
InstructionArg::BranchDest(_) => match right_arg {
// Compare dest instruction idx after diffing
InstructionArg::BranchDest(_) => {
left_row.branch_to.as_ref().map(|b| b.ins_idx)
== right_row.branch_to.as_ref().map(|b| b.ins_idx)
}
// If relocations are relaxed, match if left is a constant and right is a reloc
// Useful for instances where the target object is created without relocations
InstructionArg::Reloc => diff_config.function_reloc_diffs == FunctionRelocDiffs::None,
_ => false,
},
}
}
#[derive(Default)]
struct InstructionDiffState {
diff_score: u64,
left_arg_idx: u32,
right_arg_idx: u32,
left_args_idx: BTreeMap<String, u32>,
right_args_idx: BTreeMap<String, u32>,
}
#[derive(Default)]
struct InstructionDiffResult {
kind: InstructionDiffKind,
left_args_diff: Vec<InstructionArgDiffIndex>,
right_args_diff: Vec<InstructionArgDiffIndex>,
}
impl InstructionDiffResult {
#[inline]
const fn new(kind: InstructionDiffKind) -> Self {
Self { kind, left_args_diff: Vec::new(), right_args_diff: Vec::new() }
}
}
fn diff_instruction(
left_obj: &Object,
right_obj: &Object,
left_symbol_idx: usize,
right_symbol_idx: usize,
l: Option<&InstructionRef>,
r: Option<&InstructionRef>,
left_row: &InstructionDiffRow,
right_row: &InstructionDiffRow,
diff_config: &DiffObjConfig,
state: &mut InstructionDiffState,
) -> Result<InstructionDiffResult> {
let (l, r) = match (l, r) {
(Some(l), Some(r)) => (l, r),
(Some(_), None) => {
state.diff_score += PENALTY_INSERT_DELETE;
return Ok(InstructionDiffResult::new(InstructionDiffKind::Delete));
}
(None, Some(_)) => {
state.diff_score += PENALTY_INSERT_DELETE;
return Ok(InstructionDiffResult::new(InstructionDiffKind::Insert));
}
(None, None) => return Ok(InstructionDiffResult::new(InstructionDiffKind::None)),
};
// If opcodes don't match, replace
if l.opcode != r.opcode {
state.diff_score += PENALTY_REPLACE;
return Ok(InstructionDiffResult::new(InstructionDiffKind::Replace));
}
let left_symbol = &left_obj.symbols[left_symbol_idx];
let right_symbol = &right_obj.symbols[right_symbol_idx];
let left_section = left_symbol
.section
.and_then(|i| left_obj.sections.get(i))
.ok_or_else(|| anyhow!("Missing section for symbol"))?;
let right_section = right_symbol
.section
.and_then(|i| right_obj.sections.get(i))
.ok_or_else(|| anyhow!("Missing section for symbol"))?;
// Resolve relocations
let left_reloc = left_section.relocation_at(l.address, left_obj);
let right_reloc = right_section.relocation_at(r.address, right_obj);
// Compare instruction data
let left_data = left_section.data_range(l.address, l.size as usize).ok_or_else(|| {
anyhow!(
"Instruction data out of bounds: {:#x}..{:#x}",
l.address,
l.address + l.size as u64
)
})?;
let right_data = right_section.data_range(r.address, r.size as usize).ok_or_else(|| {
anyhow!(
"Instruction data out of bounds: {:#x}..{:#x}",
r.address,
r.address + r.size as u64
)
})?;
if left_data != right_data {
// If data doesn't match, process instructions and compare args
let left_ins = left_obj.arch.process_instruction(
*l,
left_data,
left_reloc,
left_symbol.address..left_symbol.address + left_symbol.size,
left_symbol.section.unwrap(),
diff_config,
)?;
let right_ins = left_obj.arch.process_instruction(
*r,
right_data,
right_reloc,
right_symbol.address..right_symbol.address + right_symbol.size,
right_symbol.section.unwrap(),
diff_config,
)?;
if left_ins.args.len() != right_ins.args.len() {
state.diff_score += PENALTY_REPLACE;
return Ok(InstructionDiffResult::new(InstructionDiffKind::Replace));
}
let mut result = InstructionDiffResult::new(InstructionDiffKind::None);
if left_ins.mnemonic != right_ins.mnemonic {
state.diff_score += PENALTY_REG_DIFF;
result.kind = InstructionDiffKind::OpMismatch;
}
for (a, b) in left_ins.args.iter().zip(right_ins.args.iter()) {
if arg_eq(
left_obj, left_obj,
right_obj, right_obj,
left_diff.ins.as_ref(), left_row,
right_diff.ins.as_ref(), right_row,
) a,
b,
left_reloc,
right_reloc,
diff_config,
) {
result.left_args_diff.push(InstructionArgDiffIndex::NONE);
result.right_args_diff.push(InstructionArgDiffIndex::NONE);
} else {
state.diff_score += if let InstructionArg::Value(
InstructionArgValue::Signed(_) | InstructionArgValue::Unsigned(_),
) = a
{
PENALTY_IMM_DIFF
} else {
PENALTY_REG_DIFF
};
if result.kind == InstructionDiffKind::None {
result.kind = InstructionDiffKind::ArgMismatch;
} }
ObjInsArg::BranchDest(_) => match right { let a_str = arg_to_string(a, left_reloc.as_ref());
// Compare dest instruction idx after diffing let a_diff = match state.left_args_idx.entry(a_str) {
ObjInsArg::BranchDest(_) => { btree_map::Entry::Vacant(e) => {
left_diff.branch_to.as_ref().map(|b| b.ins_idx) let idx = state.left_arg_idx;
== right_diff.branch_to.as_ref().map(|b| b.ins_idx) state.left_arg_idx = idx + 1;
e.insert(idx);
idx
} }
// If relocations are relaxed, match if left is a constant and right is a reloc btree_map::Entry::Occupied(e) => *e.get(),
// Useful for instances where the target object is created without relocations };
ObjInsArg::Reloc => config.function_reloc_diffs == FunctionRelocDiffs::None, let b_str = arg_to_string(b, right_reloc.as_ref());
_ => false, let b_diff = match state.right_args_idx.entry(b_str) {
}, btree_map::Entry::Vacant(e) => {
let idx = state.right_arg_idx;
state.right_arg_idx = idx + 1;
e.insert(idx);
idx
}
btree_map::Entry::Occupied(e) => *e.get(),
};
result.left_args_diff.push(InstructionArgDiffIndex::new(a_diff));
result.right_args_diff.push(InstructionArgDiffIndex::new(b_diff));
} }
} }
#[derive(Default)]
struct InsDiffState {
diff_count: usize,
left_arg_idx: usize,
right_arg_idx: usize,
left_args_idx: BTreeMap<String, usize>,
right_args_idx: BTreeMap<String, usize>,
}
#[derive(Default)]
struct InsDiffResult {
kind: ObjInsDiffKind,
left_args_diff: Vec<Option<ObjInsArgDiff>>,
right_args_diff: Vec<Option<ObjInsArgDiff>>,
}
fn compare_ins(
config: &DiffObjConfig,
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left: &ObjInsDiff,
right: &ObjInsDiff,
state: &mut InsDiffState,
) -> Result<InsDiffResult> {
let mut result = InsDiffResult::default();
if let (Some(left_ins), Some(right_ins)) = (&left.ins, &right.ins) {
// Count only non-PlainText args
let left_args_count = left_ins.iter_args().count();
let right_args_count = right_ins.iter_args().count();
if left_args_count != right_args_count || left_ins.op != right_ins.op {
// Totally different op
result.kind = ObjInsDiffKind::Replace;
state.diff_count += 1;
return Ok(result); return Ok(result);
} }
if left_ins.mnemonic != right_ins.mnemonic {
// Same op but different mnemonic, still cmp args // Compare relocations
result.kind = ObjInsDiffKind::OpMismatch; if !reloc_eq(left_obj, right_obj, left_reloc, right_reloc, diff_config) {
state.diff_count += 1; state.diff_score += PENALTY_REG_DIFF;
} return Ok(InstructionDiffResult::new(InstructionDiffKind::ArgMismatch));
for (a, b) in left_ins.iter_args().zip(right_ins.iter_args()) {
if arg_eq(config, left_obj, right_obj, a, b, left, right) {
result.left_args_diff.push(None);
result.right_args_diff.push(None);
} else {
if result.kind == ObjInsDiffKind::None {
result.kind = ObjInsDiffKind::ArgMismatch;
state.diff_count += 1;
}
let a_str = match a {
ObjInsArg::PlainText(arg) => arg.to_string(),
ObjInsArg::Arg(arg) => arg.to_string(),
ObjInsArg::Reloc => left_ins
.reloc
.as_ref()
.map_or_else(|| "<unknown>".to_string(), |r| r.target.name.clone()),
ObjInsArg::BranchDest(arg) => arg.to_string(),
};
let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) {
ObjInsArgDiff { idx: *idx }
} else {
let idx = state.left_arg_idx;
state.left_args_idx.insert(a_str, idx);
state.left_arg_idx += 1;
ObjInsArgDiff { idx }
};
let b_str = match b {
ObjInsArg::PlainText(arg) => arg.to_string(),
ObjInsArg::Arg(arg) => arg.to_string(),
ObjInsArg::Reloc => right_ins
.reloc
.as_ref()
.map_or_else(|| "<unknown>".to_string(), |r| r.target.name.clone()),
ObjInsArg::BranchDest(arg) => arg.to_string(),
};
let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) {
ObjInsArgDiff { idx: *idx }
} else {
let idx = state.right_arg_idx;
state.right_args_idx.insert(b_str, idx);
state.right_arg_idx += 1;
ObjInsArgDiff { idx }
};
result.left_args_diff.push(Some(a_diff));
result.right_args_diff.push(Some(b_diff));
}
}
} else if left.ins.is_some() {
result.kind = ObjInsDiffKind::Delete;
state.diff_count += 1;
} else {
result.kind = ObjInsDiffKind::Insert;
state.diff_count += 1;
}
Ok(result)
} }
fn find_symbol_matching_fake_symbol_in_sections( Ok(InstructionDiffResult::new(InstructionDiffKind::None))
fake_symbol: &ObjSymbol,
sections: &[ObjSection],
) -> Option<ObjSymbol> {
let orig_section_index = fake_symbol.orig_section_index?;
let section = sections.iter().find(|s| s.orig_index == orig_section_index)?;
let real_symbol = section
.symbols
.iter()
.find(|s| s.size > 0 && (s.address..s.address + s.size).contains(&fake_symbol.address))?;
Some(real_symbol.clone())
} }
// TODO
// fn find_symbol_matching_fake_symbol_in_sections(
// fake_symbol: &ObjSymbol,
// sections: &[ObjSection],
// ) -> Option<ObjSymbol> {
// let orig_section_index = fake_symbol.orig_section_index?;
// let section = sections.iter().find(|s| s.orig_index == orig_section_index)?;
// let real_symbol = section
// .symbols
// .iter()
// .find(|s| s.size > 0 && (s.address..s.address + s.size).contains(&fake_symbol.address))?;
// Some(real_symbol.clone())
// }

View File

@ -4,119 +4,130 @@ use core::{cmp::Ordering, ops::Range};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use similar::{capture_diff_slices, get_diff_ratio, Algorithm}; use similar::{capture_diff_slices, get_diff_ratio, Algorithm};
use super::code::{address_eq, section_name_eq}; use super::{
use crate::{ code::{address_eq, section_name_eq},
diff::{ObjDataDiff, ObjDataDiffKind, ObjDataRelocDiff, ObjSectionDiff, ObjSymbolDiff}, DataDiff, DataDiffKind, DataRelocationDiff, ObjectDiff, SectionDiff, SymbolDiff,
obj::{ObjInfo, ObjReloc, ObjSection, ObjSymbolFlags, SymbolRef},
}; };
use crate::obj::{Object, Relocation, ResolvedRelocation, SymbolFlag, SymbolKind};
pub fn diff_bss_symbol( pub fn diff_bss_symbol(
left_obj: &ObjInfo, left_obj: &Object,
right_obj: &ObjInfo, right_obj: &Object,
left_symbol_ref: SymbolRef, left_symbol_ref: usize,
right_symbol_ref: SymbolRef, right_symbol_ref: usize,
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> { ) -> Result<(SymbolDiff, SymbolDiff)> {
let (_, left_symbol) = left_obj.section_symbol(left_symbol_ref); let left_symbol = &left_obj.symbols[left_symbol_ref];
let (_, right_symbol) = right_obj.section_symbol(right_symbol_ref); let right_symbol = &right_obj.symbols[right_symbol_ref];
let percent = if left_symbol.size == right_symbol.size { 100.0 } else { 50.0 }; let percent = if left_symbol.size == right_symbol.size { 100.0 } else { 50.0 };
Ok(( Ok((
ObjSymbolDiff { SymbolDiff {
symbol_ref: left_symbol_ref,
target_symbol: Some(right_symbol_ref), target_symbol: Some(right_symbol_ref),
instructions: vec![],
match_percent: Some(percent), match_percent: Some(percent),
diff_score: None,
instruction_rows: vec![],
}, },
ObjSymbolDiff { SymbolDiff {
symbol_ref: right_symbol_ref,
target_symbol: Some(left_symbol_ref), target_symbol: Some(left_symbol_ref),
instructions: vec![],
match_percent: Some(percent), match_percent: Some(percent),
diff_score: None,
instruction_rows: vec![],
}, },
)) ))
} }
pub fn no_diff_symbol(_obj: &ObjInfo, symbol_ref: SymbolRef) -> ObjSymbolDiff { fn reloc_eq(
ObjSymbolDiff { symbol_ref, target_symbol: None, instructions: vec![], match_percent: None } left_obj: &Object,
} right_obj: &Object,
left: &ResolvedRelocation,
fn reloc_eq(left_obj: &ObjInfo, right_obj: &ObjInfo, left: &ObjReloc, right: &ObjReloc) -> bool { right: &ResolvedRelocation,
if left.flags != right.flags { ) -> bool {
if left.relocation.flags != right.relocation.flags {
return false; return false;
} }
let symbol_name_addend_matches = let symbol_name_addend_matches =
left.target.name == right.target.name && left.addend == right.addend; left.symbol.name == right.symbol.name && left.relocation.addend == right.relocation.addend;
match (&left.target.orig_section_index, &right.target.orig_section_index) { match (left.symbol.section, right.symbol.section) {
(Some(sl), Some(sr)) => { (Some(sl), Some(sr)) => {
// Match if section and name+addend or address match // Match if section and name+addend or address match
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, (Some(_), None) => false,
(None, Some(_)) => { (None, Some(_)) => {
// Match if possibly stripped weak symbol // Match if possibly stripped weak symbol
symbol_name_addend_matches && right.target.flags.0.contains(ObjSymbolFlags::Weak) symbol_name_addend_matches && right.symbol.flags.contains(SymbolFlag::Weak)
} }
(None, None) => symbol_name_addend_matches, (None, None) => symbol_name_addend_matches,
} }
} }
#[inline]
fn resolve_relocation<'obj>(
obj: &'obj Object,
reloc: &'obj Relocation,
) -> ResolvedRelocation<'obj> {
let symbol = &obj.symbols[reloc.target_symbol];
ResolvedRelocation { relocation: reloc, symbol }
}
/// Compares relocations contained with a certain data range. /// Compares relocations contained with a certain data range.
/// The ObjDataDiffKind for each diff will either be `None`` (if the relocation matches), /// The DataDiffKind for each diff will either be `None`` (if the relocation matches),
/// or `Replace` (if a relocation was changed, added, or removed). /// or `Replace` (if a relocation was changed, added, or removed).
/// `Insert` and `Delete` are not used when a relocation is added or removed to avoid confusing diffs /// `Insert` and `Delete` are not used when a relocation is added or removed to avoid confusing diffs
/// where it looks like the bytes themselves were changed but actually only the relocations changed. /// where it looks like the bytes themselves were changed but actually only the relocations changed.
fn diff_data_relocs_for_range( fn diff_data_relocs_for_range<'left, 'right>(
left_obj: &ObjInfo, left_obj: &'left Object,
right_obj: &ObjInfo, right_obj: &'right Object,
left: &ObjSection, left_section_idx: usize,
right: &ObjSection, right_section_idx: usize,
left_range: Range<usize>, left_range: Range<usize>,
right_range: Range<usize>, right_range: Range<usize>,
) -> Vec<(ObjDataDiffKind, Option<ObjReloc>, Option<ObjReloc>)> { ) -> Vec<(DataDiffKind, Option<ResolvedRelocation<'left>>, Option<ResolvedRelocation<'right>>)> {
let left_section = &left_obj.sections[left_section_idx];
let right_section = &right_obj.sections[right_section_idx];
let mut diffs = Vec::new(); let mut diffs = Vec::new();
for left_reloc in left.relocations.iter() { for left_reloc in left_section.relocations.iter() {
if !left_range.contains(&(left_reloc.address as usize)) { if !left_range.contains(&(left_reloc.address as usize)) {
continue; continue;
} }
let left_offset = left_reloc.address as usize - left_range.start; let left_offset = left_reloc.address as usize - left_range.start;
let Some(right_reloc) = right.relocations.iter().find(|r| { let left_reloc = resolve_relocation(left_obj, left_reloc);
let Some(right_reloc) = right_section.relocations.iter().find(|r| {
if !right_range.contains(&(r.address as usize)) { if !right_range.contains(&(r.address as usize)) {
return false; return false;
} }
let right_offset = r.address as usize - right_range.start; let right_offset = r.address as usize - right_range.start;
right_offset == left_offset right_offset == left_offset
}) else { }) else {
diffs.push((ObjDataDiffKind::Delete, Some(left_reloc.clone()), None)); diffs.push((DataDiffKind::Delete, Some(left_reloc), None));
continue; continue;
}; };
if reloc_eq(left_obj, right_obj, left_reloc, right_reloc) { let right_reloc = resolve_relocation(right_obj, right_reloc);
diffs.push(( if reloc_eq(left_obj, right_obj, &left_reloc, &right_reloc) {
ObjDataDiffKind::None, diffs.push((DataDiffKind::None, Some(left_reloc), Some(right_reloc)));
Some(left_reloc.clone()),
Some(right_reloc.clone()),
));
} else { } else {
diffs.push(( diffs.push((
ObjDataDiffKind::Replace, DataDiffKind::Replace,
Some(left_reloc.clone()), Some(left_reloc),
Some(right_reloc.clone()), Some(right_reloc),
)); ));
} }
} }
for right_reloc in right.relocations.iter() { for right_reloc in right_section.relocations.iter() {
if !right_range.contains(&(right_reloc.address as usize)) { if !right_range.contains(&(right_reloc.address as usize)) {
continue; continue;
} }
let right_offset = right_reloc.address as usize - right_range.start; let right_offset = right_reloc.address as usize - right_range.start;
let Some(_) = left.relocations.iter().find(|r| { let right_reloc = resolve_relocation(right_obj, right_reloc);
let Some(_) = left_section.relocations.iter().find(|r| {
if !left_range.contains(&(r.address as usize)) { if !left_range.contains(&(r.address as usize)) {
return false; return false;
} }
let left_offset = r.address as usize - left_range.start; let left_offset = r.address as usize - left_range.start;
left_offset == right_offset left_offset == right_offset
}) else { }) else {
diffs.push((ObjDataDiffKind::Insert, None, Some(right_reloc.clone()))); diffs.push((DataDiffKind::Insert, None, Some(right_reloc)));
continue; continue;
}; };
// No need to check the cases for relocations being deleted or matching again. // No need to check the cases for relocations being deleted or matching again.
@ -127,81 +138,103 @@ fn diff_data_relocs_for_range(
/// Compare the data sections of two object files. /// Compare the data sections of two object files.
pub fn diff_data_section( pub fn diff_data_section(
left_obj: &ObjInfo, left_obj: &Object,
right_obj: &ObjInfo, right_obj: &Object,
left: &ObjSection, left_diff: &ObjectDiff,
right: &ObjSection, right_diff: &ObjectDiff,
left_section_diff: &ObjSectionDiff, left_section_idx: usize,
right_section_diff: &ObjSectionDiff, right_section_idx: usize,
) -> Result<(ObjSectionDiff, ObjSectionDiff)> { ) -> Result<(SectionDiff, SectionDiff)> {
let left_max = let left_section = &left_obj.sections[left_section_idx];
left.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(left.size); let right_section = &right_obj.sections[right_section_idx];
let right_max = let left_max = left_obj
right.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(right.size); .symbols
let left_data = &left.data[..left_max as usize]; .iter()
let right_data = &right.data[..right_max as usize]; .filter_map(|s| {
if s.section != Some(left_section_idx) || s.kind == SymbolKind::Section {
return None;
}
s.address.checked_sub(left_section.address).map(|a| a + s.size)
})
.max()
.unwrap_or(0)
.min(left_section.size);
let right_max = right_obj
.symbols
.iter()
.filter_map(|s| {
if s.section != Some(right_section_idx) || s.kind == SymbolKind::Section {
return None;
}
s.address.checked_sub(right_section.address).map(|a| a + s.size)
})
.max()
.unwrap_or(0)
.min(right_section.size);
let left_data = &left_section.data[..left_max as usize];
let right_data = &right_section.data[..right_max as usize];
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data); let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0; let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0;
let mut left_diff = Vec::<ObjDataDiff>::new(); let mut left_data_diff = Vec::<DataDiff>::new();
let mut right_diff = Vec::<ObjDataDiff>::new(); let mut right_data_diff = Vec::<DataDiff>::new();
for op in ops { for op in ops {
let (tag, left_range, right_range) = op.as_tag_tuple(); let (tag, left_range, right_range) = op.as_tag_tuple();
let left_len = left_range.len(); let left_len = left_range.len();
let right_len = right_range.len(); let right_len = right_range.len();
let mut len = left_len.max(right_len); let mut len = left_len.max(right_len);
let kind = match tag { let kind = match tag {
similar::DiffTag::Equal => ObjDataDiffKind::None, similar::DiffTag::Equal => DataDiffKind::None,
similar::DiffTag::Delete => ObjDataDiffKind::Delete, similar::DiffTag::Delete => DataDiffKind::Delete,
similar::DiffTag::Insert => ObjDataDiffKind::Insert, similar::DiffTag::Insert => DataDiffKind::Insert,
similar::DiffTag::Replace => { similar::DiffTag::Replace => {
// Ensure replacements are equal length // Ensure replacements are equal length
len = left_len.min(right_len); len = left_len.min(right_len);
ObjDataDiffKind::Replace DataDiffKind::Replace
} }
}; };
let left_data = &left.data[left_range]; let left_data = &left_section.data[left_range];
let right_data = &right.data[right_range]; let right_data = &right_section.data[right_range];
left_diff.push(ObjDataDiff { left_data_diff.push(DataDiff {
data: left_data[..len.min(left_data.len())].to_vec(), data: left_data[..len.min(left_data.len())].to_vec(),
kind, kind,
len, len,
..Default::default() ..Default::default()
}); });
right_diff.push(ObjDataDiff { right_data_diff.push(DataDiff {
data: right_data[..len.min(right_data.len())].to_vec(), data: right_data[..len.min(right_data.len())].to_vec(),
kind, kind,
len, len,
..Default::default() ..Default::default()
}); });
if kind == ObjDataDiffKind::Replace { if kind == DataDiffKind::Replace {
match left_len.cmp(&right_len) { match left_len.cmp(&right_len) {
Ordering::Less => { Ordering::Less => {
let len = right_len - left_len; let len = right_len - left_len;
left_diff.push(ObjDataDiff { left_data_diff.push(DataDiff {
data: vec![], data: vec![],
kind: ObjDataDiffKind::Insert, kind: DataDiffKind::Insert,
len, len,
..Default::default() ..Default::default()
}); });
right_diff.push(ObjDataDiff { right_data_diff.push(DataDiff {
data: right_data[left_len..right_len].to_vec(), data: right_data[left_len..right_len].to_vec(),
kind: ObjDataDiffKind::Insert, kind: DataDiffKind::Insert,
len, len,
..Default::default() ..Default::default()
}); });
} }
Ordering::Greater => { Ordering::Greater => {
let len = left_len - right_len; let len = left_len - right_len;
left_diff.push(ObjDataDiff { left_data_diff.push(DataDiff {
data: left_data[right_len..left_len].to_vec(), data: left_data[right_len..left_len].to_vec(),
kind: ObjDataDiffKind::Delete, kind: DataDiffKind::Delete,
len, len,
..Default::default() ..Default::default()
}); });
right_diff.push(ObjDataDiff { right_data_diff.push(DataDiff {
data: vec![], data: vec![],
kind: ObjDataDiffKind::Delete, kind: DataDiffKind::Delete,
len, len,
..Default::default() ..Default::default()
}); });
@ -216,28 +249,36 @@ pub fn diff_data_section(
for (diff_kind, left_reloc, right_reloc) in diff_data_relocs_for_range( for (diff_kind, left_reloc, right_reloc) in diff_data_relocs_for_range(
left_obj, left_obj,
right_obj, right_obj,
left, left_section_idx,
right, right_section_idx,
0..left_max as usize, 0..left_max as usize,
0..right_max as usize, 0..right_max as usize,
) { ) {
if let Some(left_reloc) = left_reloc { if let Some(left_reloc) = left_reloc {
let len = left_obj.arch.get_reloc_byte_size(left_reloc.flags); let len = left_obj.arch.get_reloc_byte_size(left_reloc.relocation.flags);
let range = left_reloc.address as usize..left_reloc.address as usize + len; let range = left_reloc.relocation.address as usize
left_reloc_diffs.push(ObjDataRelocDiff { reloc: left_reloc, kind: diff_kind, range }); ..left_reloc.relocation.address as usize + len;
left_reloc_diffs.push(DataRelocationDiff { kind: diff_kind, range });
} }
if let Some(right_reloc) = right_reloc { if let Some(right_reloc) = right_reloc {
let len = right_obj.arch.get_reloc_byte_size(right_reloc.flags); let len = right_obj.arch.get_reloc_byte_size(right_reloc.relocation.flags);
let range = right_reloc.address as usize..right_reloc.address as usize + len; let range = right_reloc.relocation.address as usize
right_reloc_diffs.push(ObjDataRelocDiff { reloc: right_reloc, kind: diff_kind, range }); ..right_reloc.relocation.address as usize + len;
right_reloc_diffs.push(DataRelocationDiff { kind: diff_kind, range });
} }
} }
let (mut left_section_diff, mut right_section_diff) = let (mut left_section_diff, mut right_section_diff) = diff_generic_section(
diff_generic_section(left, right, left_section_diff, right_section_diff)?; left_obj,
let all_left_relocs_match = left_reloc_diffs.iter().all(|d| d.kind == ObjDataDiffKind::None); right_obj,
left_section_diff.data_diff = left_diff; left_diff,
right_section_diff.data_diff = right_diff; right_diff,
left_section_idx,
right_section_idx,
)?;
let all_left_relocs_match = left_reloc_diffs.iter().all(|d| d.kind == DataDiffKind::None);
left_section_diff.data_diff = left_data_diff;
right_section_diff.data_diff = right_data_diff;
left_section_diff.reloc_diff = left_reloc_diffs; left_section_diff.reloc_diff = left_reloc_diffs;
right_section_diff.reloc_diff = right_reloc_diffs; right_section_diff.reloc_diff = right_reloc_diffs;
if all_left_relocs_match { if all_left_relocs_match {
@ -254,29 +295,58 @@ pub fn diff_data_section(
} }
pub fn diff_data_symbol( pub fn diff_data_symbol(
left_obj: &ObjInfo, left_obj: &Object,
right_obj: &ObjInfo, right_obj: &Object,
left_symbol_ref: SymbolRef, left_symbol_idx: usize,
right_symbol_ref: SymbolRef, right_symbol_idx: usize,
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> { ) -> Result<(SymbolDiff, SymbolDiff)> {
let (left_section, left_symbol) = left_obj.section_symbol(left_symbol_ref); let left_symbol = &left_obj.symbols[left_symbol_idx];
let (right_section, right_symbol) = right_obj.section_symbol(right_symbol_ref); let right_symbol = &right_obj.symbols[right_symbol_idx];
let left_section = left_section.ok_or_else(|| anyhow!("Data symbol section not found"))?; let left_section_idx =
let right_section = right_section.ok_or_else(|| anyhow!("Data symbol section not found"))?; left_symbol.section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
let right_section_idx =
right_symbol.section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
let left_range = left_symbol.section_address as usize let left_section = &left_obj.sections[left_section_idx];
..(left_symbol.section_address + left_symbol.size) as usize; let right_section = &right_obj.sections[right_section_idx];
let right_range = right_symbol.section_address as usize
..(right_symbol.section_address + right_symbol.size) as usize; let left_start = left_symbol
.address
.checked_sub(left_section.address)
.ok_or_else(|| anyhow!("Symbol address out of section bounds"))?;
let right_start = right_symbol
.address
.checked_sub(right_section.address)
.ok_or_else(|| anyhow!("Symbol address out of section bounds"))?;
let left_end = left_start + left_symbol.size;
if left_end > left_section.size {
return Err(anyhow!(
"Symbol {} size out of section bounds ({} > {})",
left_symbol.name,
left_end,
left_section.size
));
}
let right_end = right_start + right_symbol.size;
if right_end > right_section.size {
return Err(anyhow!(
"Symbol {} size out of section bounds ({} > {})",
right_symbol.name,
right_end,
right_section.size
));
}
let left_range = left_start as usize..left_end as usize;
let right_range = right_start as usize..right_end as usize;
let left_data = &left_section.data[left_range.clone()]; let left_data = &left_section.data[left_range.clone()];
let right_data = &right_section.data[right_range.clone()]; let right_data = &right_section.data[right_range.clone()];
let reloc_diffs = diff_data_relocs_for_range( let reloc_diffs = diff_data_relocs_for_range(
left_obj, left_obj,
right_obj, right_obj,
left_section, left_section_idx,
right_section, right_section_idx,
left_range, left_range,
right_range, right_range,
); );
@ -291,11 +361,15 @@ pub fn diff_data_symbol(
for (diff_kind, left_reloc, right_reloc) in reloc_diffs { for (diff_kind, left_reloc, right_reloc) in reloc_diffs {
let reloc_diff_len = match (left_reloc, right_reloc) { let reloc_diff_len = match (left_reloc, right_reloc) {
(None, None) => unreachable!(), (None, None) => unreachable!(),
(None, Some(right_reloc)) => right_obj.arch.get_reloc_byte_size(right_reloc.flags), (None, Some(right_reloc)) => {
(Some(left_reloc), _) => left_obj.arch.get_reloc_byte_size(left_reloc.flags), right_obj.arch.get_reloc_byte_size(right_reloc.relocation.flags)
}
(Some(left_reloc), _) => {
left_obj.arch.get_reloc_byte_size(left_reloc.relocation.flags)
}
}; };
total_reloc_bytes += reloc_diff_len; total_reloc_bytes += reloc_diff_len;
if diff_kind == ObjDataDiffKind::None { if diff_kind == DataDiffKind::None {
matching_reloc_bytes += reloc_diff_len; matching_reloc_bytes += reloc_diff_len;
} }
} }
@ -315,17 +389,17 @@ pub fn diff_data_symbol(
let match_percent = match_ratio * 100.0; let match_percent = match_ratio * 100.0;
Ok(( Ok((
ObjSymbolDiff { SymbolDiff {
symbol_ref: left_symbol_ref, target_symbol: Some(right_symbol_idx),
target_symbol: Some(right_symbol_ref),
instructions: vec![],
match_percent: Some(match_percent), match_percent: Some(match_percent),
diff_score: None,
instruction_rows: vec![],
}, },
ObjSymbolDiff { SymbolDiff {
symbol_ref: right_symbol_ref, target_symbol: Some(left_symbol_idx),
target_symbol: Some(left_symbol_ref),
instructions: vec![],
match_percent: Some(match_percent), match_percent: Some(match_percent),
diff_score: None,
instruction_rows: vec![],
}, },
)) ))
} }
@ -333,69 +407,89 @@ pub fn diff_data_symbol(
/// Compares a section of two object files. /// Compares a section of two object files.
/// This essentially adds up the match percentage of each symbol in the section. /// This essentially adds up the match percentage of each symbol in the section.
pub fn diff_generic_section( pub fn diff_generic_section(
left: &ObjSection, left_obj: &Object,
_right: &ObjSection, _right_obj: &Object,
left_diff: &ObjSectionDiff, left_diff: &ObjectDiff,
_right_diff: &ObjSectionDiff, _right_diff: &ObjectDiff,
) -> Result<(ObjSectionDiff, ObjSectionDiff)> { left_section_idx: usize,
let match_percent = if left_diff.symbols.iter().all(|d| d.match_percent == Some(100.0)) { _right_section_idx: usize,
) -> Result<(SectionDiff, SectionDiff)> {
let match_percent = if left_obj
.symbols
.iter()
.enumerate()
.filter(|(_, s)| s.section == Some(left_section_idx) && s.kind != SymbolKind::Section)
.map(|(i, _)| &left_diff.symbols[i])
.all(|d| d.match_percent == Some(100.0))
{
100.0 // Avoid fp precision issues 100.0 // Avoid fp precision issues
} else { } else {
left.symbols let (matched, total) = left_obj
.symbols
.iter() .iter()
.zip(left_diff.symbols.iter()) .enumerate()
.map(|(s, d)| d.match_percent.unwrap_or(0.0) * s.size as f32) .filter(|(_, s)| s.section == Some(left_section_idx) && s.kind != SymbolKind::Section)
.sum::<f32>() .map(|(i, s)| (s, &left_diff.symbols[i]))
/ left.size as f32 .fold((0.0, 0.0), |(matched, total), (s, d)| {
(matched + d.match_percent.unwrap_or(0.0) * s.size as f32, total + s.size as f32)
});
if total == 0.0 {
100.0
} else {
matched / total
}
}; };
Ok(( Ok((
ObjSectionDiff { SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
symbols: vec![], SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
data_diff: vec![],
reloc_diff: vec![],
match_percent: Some(match_percent),
},
ObjSectionDiff {
symbols: vec![],
data_diff: vec![],
reloc_diff: vec![],
match_percent: Some(match_percent),
},
)) ))
} }
/// Compare the addresses and sizes of each symbol in the BSS sections. /// Compare the addresses and sizes of each symbol in the BSS sections.
pub fn diff_bss_section( pub fn diff_bss_section(
left: &ObjSection, left_obj: &Object,
right: &ObjSection, right_obj: &Object,
left_diff: &ObjSectionDiff, left_diff: &ObjectDiff,
right_diff: &ObjSectionDiff, right_diff: &ObjectDiff,
) -> Result<(ObjSectionDiff, ObjSectionDiff)> { left_section_idx: usize,
let left_sizes = left.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>(); right_section_idx: usize,
let right_sizes = right.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>(); ) -> Result<(SectionDiff, SectionDiff)> {
let left_section = &left_obj.sections[left_section_idx];
let left_sizes = left_obj
.symbols
.iter()
.enumerate()
.filter(|(_, s)| s.section == Some(left_section_idx) && s.kind != SymbolKind::Section)
.filter_map(|(_, s)| s.address.checked_sub(left_section.address).map(|a| (a, s.size)))
.collect::<Vec<_>>();
let right_section = &right_obj.sections[right_section_idx];
let right_sizes = right_obj
.symbols
.iter()
.enumerate()
.filter(|(_, s)| s.section == Some(right_section_idx) && s.kind != SymbolKind::Section)
.filter_map(|(_, s)| s.address.checked_sub(right_section.address).map(|a| (a, s.size)))
.collect::<Vec<_>>();
let ops = capture_diff_slices(Algorithm::Patience, &left_sizes, &right_sizes); let ops = capture_diff_slices(Algorithm::Patience, &left_sizes, &right_sizes);
let mut match_percent = get_diff_ratio(&ops, left_sizes.len(), right_sizes.len()) * 100.0; let mut match_percent = get_diff_ratio(&ops, left_sizes.len(), right_sizes.len()) * 100.0;
// Use the highest match percent between two options: // Use the highest match percent between two options:
// - Left symbols matching right symbols by name // - Left symbols matching right symbols by name
// - Diff of the addresses and sizes of each symbol // - Diff of the addresses and sizes of each symbol
let (generic_diff, _) = diff_generic_section(left, right, left_diff, right_diff)?; let (generic_diff, _) = diff_generic_section(
left_obj,
right_obj,
left_diff,
right_diff,
left_section_idx,
right_section_idx,
)?;
if generic_diff.match_percent.unwrap_or(-1.0) > match_percent { if generic_diff.match_percent.unwrap_or(-1.0) > match_percent {
match_percent = generic_diff.match_percent.unwrap(); match_percent = generic_diff.match_percent.unwrap();
} }
Ok(( Ok((
ObjSectionDiff { SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
symbols: vec![], SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
data_diff: vec![],
reloc_diff: vec![],
match_percent: Some(match_percent),
},
ObjSectionDiff {
symbols: vec![],
data_diff: vec![],
reloc_diff: vec![],
match_percent: Some(match_percent),
},
)) ))
} }

View File

@ -1,16 +1,28 @@
use alloc::string::{String, ToString}; use alloc::{
borrow::Cow,
collections::BTreeSet,
format,
string::{String, ToString},
vec::Vec,
};
use core::cmp::Ordering;
use anyhow::Result;
use itertools::Itertools;
use regex::Regex;
use crate::{ use crate::{
diff::{ObjInsArgDiff, ObjInsDiff}, diff::{DiffObjConfig, InstructionArgDiffIndex, InstructionDiffRow, ObjectDiff, SymbolDiff},
obj::{ObjInsArg, ObjInsArgValue, ObjReloc, ObjSymbol}, obj::{
InstructionArg, InstructionArgValue, Object, SectionFlag, SectionKind, Symbol, SymbolFlag,
SymbolKind,
},
}; };
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum DiffText<'a> { pub enum DiffText<'a> {
/// Basic text /// Basic text
Basic(&'a str), Basic(&'a str),
/// Colored text
BasicColor(&'a str, usize),
/// Line number /// Line number
Line(u32), Line(u32),
/// Instruction address /// Instruction address
@ -18,13 +30,13 @@ pub enum DiffText<'a> {
/// Instruction mnemonic /// Instruction mnemonic
Opcode(&'a str, u16), Opcode(&'a str, u16),
/// Instruction argument /// Instruction argument
Argument(&'a ObjInsArgValue, Option<&'a ObjInsArgDiff>), Argument(&'a InstructionArgValue),
/// Branch destination /// Branch destination
BranchDest(u64, Option<&'a ObjInsArgDiff>), BranchDest(u64),
/// Symbol name /// Symbol name
Symbol(&'a ObjSymbol, Option<&'a ObjInsArgDiff>), Symbol(&'a Symbol),
/// Relocation addend /// Relocation addend
Addend(i64, Option<&'a ObjInsArgDiff>), Addend(i64),
/// Number of spaces /// Number of spaces
Spacing(usize), Spacing(usize),
/// End of line /// End of line
@ -36,83 +48,113 @@ pub enum HighlightKind {
#[default] #[default]
None, None,
Opcode(u16), Opcode(u16),
Arg(ObjInsArgValue), Argument(InstructionArgValue),
Symbol(String), Symbol(String),
Address(u64), Address(u64),
} }
pub fn display_diff<E>( pub enum InstructionPart {
ins_diff: &ObjInsDiff, Basic(&'static str),
base_addr: u64, Opcode(Cow<'static, str>, u16),
mut cb: impl FnMut(DiffText) -> Result<(), E>, Arg(InstructionArg),
) -> Result<(), E> { Separator,
let Some(ins) = &ins_diff.ins else {
cb(DiffText::Eol)?;
return Ok(());
};
if let Some(line) = ins.line {
cb(DiffText::Line(line))?;
}
cb(DiffText::Address(ins.address - base_addr))?;
if let Some(branch) = &ins_diff.branch_from {
cb(DiffText::BasicColor(" ~> ", branch.branch_idx))?;
} else {
cb(DiffText::Spacing(4))?;
}
cb(DiffText::Opcode(&ins.mnemonic, ins.op))?;
let mut arg_diff_idx = 0; // non-PlainText index
for (i, arg) in ins.args.iter().enumerate() {
if i == 0 {
cb(DiffText::Spacing(1))?;
}
let diff = ins_diff.arg_diff.get(arg_diff_idx).and_then(|o| o.as_ref());
match arg {
ObjInsArg::PlainText(s) => {
cb(DiffText::Basic(s))?;
}
ObjInsArg::Arg(v) => {
cb(DiffText::Argument(v, diff))?;
arg_diff_idx += 1;
}
ObjInsArg::Reloc => {
display_reloc_name(ins.reloc.as_ref().unwrap(), &mut cb, diff)?;
arg_diff_idx += 1;
}
ObjInsArg::BranchDest(dest) => {
if let Some(dest) = dest.checked_sub(base_addr) {
cb(DiffText::BranchDest(dest, diff))?;
} else {
cb(DiffText::Basic("<unknown>"))?;
}
arg_diff_idx += 1;
}
}
}
if let Some(branch) = &ins_diff.branch_to {
cb(DiffText::BasicColor(" ~>", branch.branch_idx))?;
}
cb(DiffText::Eol)?;
Ok(())
} }
fn display_reloc_name<E>( pub fn display_row(
reloc: &ObjReloc, obj: &Object,
mut cb: impl FnMut(DiffText) -> Result<(), E>, symbol_index: usize,
diff: Option<&ObjInsArgDiff>, ins_row: &InstructionDiffRow,
) -> Result<(), E> { diff_config: &DiffObjConfig,
cb(DiffText::Symbol(&reloc.target, diff))?; mut cb: impl FnMut(DiffText, InstructionArgDiffIndex) -> Result<()>,
cb(DiffText::Addend(reloc.addend, diff)) ) -> Result<()> {
let Some(ins_ref) = ins_row.ins_ref else {
cb(DiffText::Eol, InstructionArgDiffIndex::NONE)?;
return Ok(());
};
let symbol = &obj.symbols[symbol_index];
let Some(section_index) = symbol.section else {
cb(DiffText::Eol, InstructionArgDiffIndex::NONE)?;
return Ok(());
};
let section = &obj.sections[section_index];
let Some(data) = section.data_range(ins_ref.address, ins_ref.size as usize) else {
cb(DiffText::Eol, InstructionArgDiffIndex::NONE)?;
return Ok(());
};
if let Some(line) = section.line_info.range(..=ins_ref.address).last().map(|(_, &b)| b) {
cb(DiffText::Line(line), InstructionArgDiffIndex::NONE)?;
}
cb(
DiffText::Address(ins_ref.address.saturating_sub(symbol.address)),
InstructionArgDiffIndex::NONE,
)?;
if let Some(branch) = &ins_row.branch_from {
cb(DiffText::Basic(" ~> "), InstructionArgDiffIndex::new(branch.branch_idx))?;
} else {
cb(DiffText::Spacing(4), InstructionArgDiffIndex::NONE)?;
}
let mut arg_idx = 0;
let relocation = section.relocation_at(ins_ref.address, obj);
obj.arch.display_instruction(
ins_ref,
data,
relocation,
symbol.address..symbol.address + symbol.size,
section_index,
diff_config,
&mut |part| match part {
InstructionPart::Basic(text) => {
cb(DiffText::Basic(text), InstructionArgDiffIndex::NONE)
}
InstructionPart::Opcode(mnemonic, opcode) => {
cb(DiffText::Opcode(mnemonic.as_ref(), opcode), InstructionArgDiffIndex::NONE)
}
InstructionPart::Arg(arg) => {
let diff_index = ins_row.arg_diff.get(arg_idx).copied().unwrap_or_default();
arg_idx += 1;
match arg {
InstructionArg::Value(ref value) => cb(DiffText::Argument(value), diff_index),
InstructionArg::Reloc => {
let resolved = relocation.unwrap();
cb(DiffText::Symbol(resolved.symbol), diff_index)?;
if resolved.relocation.addend != 0 {
cb(DiffText::Addend(resolved.relocation.addend), diff_index)?;
}
Ok(())
}
InstructionArg::BranchDest(dest) => {
if let Some(addr) = dest.checked_sub(symbol.address) {
cb(DiffText::BranchDest(addr), diff_index)
} else {
cb(
DiffText::Argument(&InstructionArgValue::Opaque(Cow::Borrowed(
"<invalid>",
))),
diff_index,
)
}
}
}
}
InstructionPart::Separator => {
cb(DiffText::Basic(diff_config.separator()), InstructionArgDiffIndex::NONE)
}
},
)?;
if let Some(branch) = &ins_row.branch_to {
cb(DiffText::Basic(" ~>"), InstructionArgDiffIndex::new(branch.branch_idx))?;
}
cb(DiffText::Eol, InstructionArgDiffIndex::NONE)?;
Ok(())
} }
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) {
(HighlightKind::Opcode(a), DiffText::Opcode(_, b)) => a == b, (HighlightKind::Opcode(a), DiffText::Opcode(_, b)) => a == b,
(HighlightKind::Arg(a), DiffText::Argument(b, _)) => a.loose_eq(b), (HighlightKind::Argument(a), DiffText::Argument(b)) => a.loose_eq(b),
(HighlightKind::Symbol(a), DiffText::Symbol(b, _)) => a == &b.name, (HighlightKind::Symbol(a), DiffText::Symbol(b)) => a == &b.name,
(HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b, _)) => { (HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b)) => a == b,
a == b
}
_ => false, _ => false,
} }
} }
@ -126,10 +168,245 @@ impl From<DiffText<'_>> for HighlightKind {
fn from(value: DiffText<'_>) -> Self { fn from(value: DiffText<'_>) -> Self {
match value { match value {
DiffText::Opcode(_, op) => HighlightKind::Opcode(op), DiffText::Opcode(_, op) => HighlightKind::Opcode(op),
DiffText::Argument(arg, _) => HighlightKind::Arg(arg.clone()), DiffText::Argument(arg) => HighlightKind::Argument(arg.clone()),
DiffText::Symbol(sym, _) => HighlightKind::Symbol(sym.name.to_string()), DiffText::Symbol(sym) => HighlightKind::Symbol(sym.name.to_string()),
DiffText::Address(addr) | DiffText::BranchDest(addr, _) => HighlightKind::Address(addr), DiffText::Address(addr) | DiffText::BranchDest(addr) => HighlightKind::Address(addr),
_ => HighlightKind::None, _ => HighlightKind::None,
} }
} }
} }
pub enum ContextMenuItem {
Copy { value: String, label: Option<String> },
Navigate { label: String },
}
pub enum HoverItemColor {
Normal, // Gray
Emphasized, // White
Special, // Blue
}
pub struct HoverItem {
pub text: String,
pub color: HoverItemColor,
}
pub fn symbol_context(_obj: &Object, symbol: &Symbol) -> Vec<ContextMenuItem> {
let mut out = Vec::new();
if let Some(name) = &symbol.demangled_name {
out.push(ContextMenuItem::Copy { value: name.clone(), label: None });
}
out.push(ContextMenuItem::Copy { value: symbol.name.clone(), label: None });
if let Some(address) = symbol.virtual_address {
out.push(ContextMenuItem::Copy {
value: format!("{:#x}", address),
label: Some("virtual address".to_string()),
});
}
// if let Some(_extab) = obj.arch.ppc().and_then(|ppc| ppc.extab_for_symbol(symbol)) {
// out.push(ContextMenuItem::Navigate { label: "Decode exception table".to_string() });
// }
out
}
pub fn symbol_hover(_obj: &Object, symbol: &Symbol) -> Vec<HoverItem> {
let mut out = Vec::new();
out.push(HoverItem {
text: format!("Name: {}", symbol.name),
color: HoverItemColor::Emphasized,
});
out.push(HoverItem {
text: format!("Address: {:x}", symbol.address),
color: HoverItemColor::Emphasized,
});
if symbol.flags.contains(SymbolFlag::SizeInferred) {
out.push(HoverItem {
text: format!("Size: {:x} (inferred)", symbol.size),
color: HoverItemColor::Emphasized,
});
} else {
out.push(HoverItem {
text: format!("Size: {:x}", symbol.size),
color: HoverItemColor::Emphasized,
});
}
if let Some(address) = symbol.virtual_address {
out.push(HoverItem {
text: format!("Virtual address: {:#x}", address),
color: HoverItemColor::Special,
});
}
// if let Some(extab) = obj.arch.ppc().and_then(|ppc| ppc.extab_for_symbol(symbol)) {
// out.push(HoverItem {
// text: format!("extab symbol: {}", extab.etb_symbol.name),
// color: HoverItemColor::Special,
// });
// out.push(HoverItem {
// text: format!("extabindex symbol: {}", extab.eti_symbol.name),
// color: HoverItemColor::Special,
// });
// }
out
}
#[derive(Copy, Clone)]
pub enum SymbolFilter<'a> {
None,
Search(&'a Regex),
Mapping(usize, Option<&'a Regex>),
}
fn symbol_matches_filter(
symbol: &Symbol,
diff: &SymbolDiff,
filter: SymbolFilter<'_>,
show_hidden_symbols: bool,
) -> bool {
// Ignore absolute symbols
if symbol.section.is_none() && !symbol.flags.contains(SymbolFlag::Common) {
return false;
}
if !show_hidden_symbols && symbol.flags.contains(SymbolFlag::Hidden) {
return false;
}
match filter {
SymbolFilter::None => true,
SymbolFilter::Search(regex) => {
regex.is_match(&symbol.name)
|| symbol.demangled_name.as_deref().is_some_and(|s| regex.is_match(s))
}
SymbolFilter::Mapping(symbol_ref, regex) => {
diff.target_symbol == Some(symbol_ref)
&& regex.is_none_or(|r| {
r.is_match(&symbol.name)
|| symbol.demangled_name.as_deref().is_some_and(|s| r.is_match(s))
})
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct SectionDisplaySymbol {
pub symbol: usize,
pub is_mapping_symbol: bool,
}
#[derive(Debug, Clone)]
pub struct SectionDisplay {
pub id: String,
pub name: String,
pub size: u64,
pub match_percent: Option<f32>,
pub symbols: Vec<SectionDisplaySymbol>,
}
pub fn display_sections(
obj: &Object,
diff: &ObjectDiff,
filter: SymbolFilter<'_>,
show_hidden_symbols: bool,
show_mapped_symbols: bool,
reverse_fn_order: bool,
) -> Vec<SectionDisplay> {
let mut mapping = BTreeSet::new();
let is_mapping_symbol = if let SymbolFilter::Mapping(_, _) = filter {
for mapping_diff in &diff.mapping_symbols {
let symbol = &obj.symbols[mapping_diff.symbol_index];
if !symbol_matches_filter(
symbol,
&mapping_diff.symbol_diff,
filter,
show_hidden_symbols,
) {
continue;
}
if !show_mapped_symbols {
let symbol_diff = &diff.symbols[mapping_diff.symbol_index];
if symbol_diff.target_symbol.is_some() {
continue;
}
}
mapping.insert((symbol.section, mapping_diff.symbol_index));
}
true
} else {
for (symbol_idx, (symbol, symbol_diff)) in obj.symbols.iter().zip(&diff.symbols).enumerate()
{
if !symbol_matches_filter(symbol, symbol_diff, filter, show_hidden_symbols) {
continue;
}
mapping.insert((symbol.section, symbol_idx));
}
false
};
let num_sections = mapping.iter().map(|(section_idx, _)| *section_idx).dedup().count();
let mut sections = Vec::with_capacity(num_sections);
for (section_idx, group) in &mapping.iter().chunk_by(|(section_idx, _)| *section_idx) {
let mut symbols = group
.map(|&(_, symbol)| SectionDisplaySymbol { symbol, is_mapping_symbol })
.collect::<Vec<_>>();
if let Some(section_idx) = section_idx {
let section = &obj.sections[section_idx];
if section.kind == SectionKind::Unknown || section.flags.contains(SectionFlag::Hidden) {
// Skip unknown and hidden sections
continue;
}
let section_diff = &diff.sections[section_idx];
if section.kind == SectionKind::Code && reverse_fn_order {
symbols.sort_by(|a, b| {
let a_symbol = &obj.symbols[a.symbol];
let b_symbol = &obj.symbols[b.symbol];
symbol_sort_reverse(a_symbol, b_symbol)
});
} else {
symbols.sort_by(|a, b| {
let a_symbol = &obj.symbols[a.symbol];
let b_symbol = &obj.symbols[b.symbol];
symbol_sort(a_symbol, b_symbol)
});
}
sections.push(SectionDisplay {
id: section.id.clone(),
name: if section.flags.contains(SectionFlag::Combined) {
format!("{} [combined]", section.name)
} else {
section.name.clone()
},
size: section.size,
match_percent: section_diff.match_percent,
symbols,
});
} else {
// Don't sort, preserve order of absolute symbols
sections.push(SectionDisplay {
id: ".comm".to_string(),
name: ".comm".to_string(),
size: 0,
match_percent: None,
symbols,
});
}
}
sections.sort_by(|a, b| a.id.cmp(&b.id));
sections
}
fn section_symbol_sort(a: &Symbol, b: &Symbol) -> Ordering {
if a.kind == SymbolKind::Section {
if b.kind != SymbolKind::Section {
return Ordering::Less;
}
} else if b.kind == SymbolKind::Section {
return Ordering::Greater;
}
Ordering::Equal
}
fn symbol_sort(a: &Symbol, b: &Symbol) -> Ordering {
section_symbol_sort(a, b).then(a.address.cmp(&b.address)).then(a.size.cmp(&b.size))
}
fn symbol_sort_reverse(a: &Symbol, b: &Symbol) -> Ordering {
section_symbol_sort(a, b).then(b.address.cmp(&a.address)).then(b.size.cmp(&a.size))
}

View File

@ -4,21 +4,19 @@ use alloc::{
vec, vec,
vec::Vec, vec::Vec,
}; };
use core::ops::Range; use core::{num::NonZeroU32, ops::Range};
use anyhow::Result; use anyhow::Result;
use crate::{ use crate::{
diff::{ diff::{
code::{diff_code, no_diff_code, process_code_symbol}, code::{diff_code, no_diff_code},
data::{ data::{
diff_bss_section, diff_bss_symbol, diff_data_section, diff_data_symbol, diff_bss_section, diff_bss_symbol, diff_data_section, diff_data_symbol,
diff_generic_section, no_diff_symbol, diff_generic_section,
}, },
}, },
obj::{ obj::{InstructionRef, Object, SectionKind, Symbol, SymbolFlag},
ObjInfo, ObjIns, ObjReloc, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef, SECTION_COMMON,
},
}; };
pub mod code; pub mod code;
@ -38,47 +36,44 @@ impl DiffObjConfig {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjSectionDiff { pub struct SectionDiff {
pub symbols: Vec<ObjSymbolDiff>, // pub target_section: Option<usize>,
pub data_diff: Vec<ObjDataDiff>,
pub reloc_diff: Vec<ObjDataRelocDiff>,
pub match_percent: Option<f32>, pub match_percent: Option<f32>,
} pub data_diff: Vec<DataDiff>,
pub reloc_diff: Vec<DataRelocationDiff>,
impl ObjSectionDiff {
fn merge(&mut self, other: ObjSectionDiff) {
// symbols ignored
self.data_diff = other.data_diff;
self.reloc_diff = other.reloc_diff;
self.match_percent = other.match_percent;
}
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ObjSymbolDiff { pub struct SymbolDiff {
/// The symbol ref this object /// The symbol index in the _other_ object that this symbol was diffed against
pub symbol_ref: SymbolRef, pub target_symbol: Option<usize>,
/// The symbol ref in the _other_ object that this symbol was diffed against
pub target_symbol: Option<SymbolRef>,
pub instructions: Vec<ObjInsDiff>,
pub match_percent: Option<f32>, pub match_percent: Option<f32>,
pub diff_score: Option<(u64, u64)>,
pub instruction_rows: Vec<InstructionDiffRow>,
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ObjInsDiff { pub struct MappingSymbolDiff {
pub ins: Option<ObjIns>, pub symbol_index: usize,
pub symbol_diff: SymbolDiff,
}
#[derive(Debug, Clone, Default)]
pub struct InstructionDiffRow {
/// Instruction reference
pub ins_ref: Option<InstructionRef>,
/// Diff kind /// Diff kind
pub kind: ObjInsDiffKind, pub kind: InstructionDiffKind,
/// Branches from instruction /// Branches from instruction(s)
pub branch_from: Option<ObjInsBranchFrom>, pub branch_from: Option<InstructionBranchFrom>,
/// Branches to instruction /// Branches to instruction
pub branch_to: Option<ObjInsBranchTo>, pub branch_to: Option<InstructionBranchTo>,
/// Arg diffs (only contains non-PlainText args) /// Arg diffs
pub arg_diff: Vec<Option<ObjInsArgDiff>>, pub arg_diff: Vec<InstructionArgDiffIndex>,
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum ObjInsDiffKind { pub enum InstructionDiffKind {
#[default] #[default]
None, None,
OpMismatch, OpMismatch,
@ -89,22 +84,21 @@ pub enum ObjInsDiffKind {
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ObjDataDiff { pub struct DataDiff {
pub data: Vec<u8>, pub data: Vec<u8>,
pub kind: ObjDataDiffKind, pub kind: DataDiffKind,
pub len: usize, pub len: usize,
pub symbol: String, pub symbol: String,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjDataRelocDiff { pub struct DataRelocationDiff {
pub reloc: ObjReloc, pub kind: DataDiffKind,
pub kind: ObjDataDiffKind,
pub range: Range<usize>, pub range: Range<usize>,
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum ObjDataDiffKind { pub enum DataDiffKind {
#[default] #[default]
None, None,
Replace, Replace,
@ -112,127 +106,102 @@ pub enum ObjDataDiffKind {
Insert, Insert,
} }
#[derive(Debug, Copy, Clone)] /// Index of the argument diff for coloring.
pub struct ObjInsArgDiff { #[repr(transparent)]
/// Incrementing index for coloring #[derive(Debug, Copy, Clone, Default)]
pub idx: usize, pub struct InstructionArgDiffIndex(pub Option<NonZeroU32>);
impl InstructionArgDiffIndex {
pub const NONE: Self = Self(None);
#[inline(always)]
pub fn new(idx: u32) -> Self {
Self(Some(unsafe { NonZeroU32::new_unchecked(idx.saturating_add(1)) }))
}
#[inline(always)]
pub fn get(&self) -> Option<u32> { self.0.map(|idx| idx.get() - 1) }
#[inline(always)]
pub fn is_some(&self) -> bool { self.0.is_some() }
#[inline(always)]
pub fn is_none(&self) -> bool { self.0.is_none() }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjInsBranchFrom { pub struct InstructionBranchFrom {
/// Source instruction indices /// Source instruction indices
pub ins_idx: Vec<usize>, pub ins_idx: Vec<u32>,
/// Incrementing index for coloring /// Incrementing index for coloring
pub branch_idx: usize, pub branch_idx: u32,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjInsBranchTo { pub struct InstructionBranchTo {
/// Target instruction index /// Target instruction index
pub ins_idx: usize, pub ins_idx: u32,
/// Incrementing index for coloring /// Incrementing index for coloring
pub branch_idx: usize, pub branch_idx: u32,
} }
#[derive(Default)] #[derive(Debug, Default)]
pub struct ObjDiff { pub struct ObjectDiff {
/// A list of all symbol diffs in the object.
pub symbols: Vec<SymbolDiff>,
/// A list of all section diffs in the object. /// A list of all section diffs in the object.
pub sections: Vec<ObjSectionDiff>, pub sections: Vec<SectionDiff>,
/// Common BSS symbols don't live in a section, so they're stored separately.
pub common: Vec<ObjSymbolDiff>,
/// If `selecting_left` or `selecting_right` is set, this is the list of symbols /// If `selecting_left` or `selecting_right` is set, this is the list of symbols
/// that are being mapped to the other object. /// that are being mapped to the other object.
pub mapping_symbols: Vec<ObjSymbolDiff>, pub mapping_symbols: Vec<MappingSymbolDiff>,
} }
impl ObjDiff { impl ObjectDiff {
pub fn new_from_obj(obj: &ObjInfo) -> Self { pub fn new_from_obj(obj: &Object) -> Self {
let mut result = Self { let mut result = Self {
symbols: Vec::with_capacity(obj.symbols.len()),
sections: Vec::with_capacity(obj.sections.len()), sections: Vec::with_capacity(obj.sections.len()),
common: Vec::with_capacity(obj.common.len()),
mapping_symbols: vec![], mapping_symbols: vec![],
}; };
for (section_idx, section) in obj.sections.iter().enumerate() { for _ in obj.symbols.iter() {
let mut symbols = Vec::with_capacity(section.symbols.len()); result.symbols.push(SymbolDiff {
for (symbol_idx, _) in section.symbols.iter().enumerate() {
symbols.push(ObjSymbolDiff {
symbol_ref: SymbolRef { section_idx, symbol_idx },
target_symbol: None, target_symbol: None,
instructions: vec![],
match_percent: None, match_percent: None,
diff_score: None,
instruction_rows: vec![],
}); });
} }
result.sections.push(ObjSectionDiff { for _ in obj.sections.iter() {
symbols, result.sections.push(SectionDiff {
data_diff: vec![ObjDataDiff { // target_section: None,
data: section.data.clone(), match_percent: None,
kind: ObjDataDiffKind::None, data_diff: vec![],
len: section.data.len(),
symbol: section.name.clone(),
}],
reloc_diff: vec![], reloc_diff: vec![],
match_percent: None,
});
}
for (symbol_idx, _) in obj.common.iter().enumerate() {
result.common.push(ObjSymbolDiff {
symbol_ref: SymbolRef { section_idx: SECTION_COMMON, symbol_idx },
target_symbol: None,
instructions: vec![],
match_percent: None,
}); });
} }
result result
} }
#[inline]
pub fn section_diff(&self, section_idx: usize) -> &ObjSectionDiff {
&self.sections[section_idx]
} }
#[inline] #[derive(Debug, Default)]
pub fn section_diff_mut(&mut self, section_idx: usize) -> &mut ObjSectionDiff {
&mut self.sections[section_idx]
}
#[inline]
pub fn symbol_diff(&self, symbol_ref: SymbolRef) -> &ObjSymbolDiff {
if symbol_ref.section_idx == SECTION_COMMON {
&self.common[symbol_ref.symbol_idx]
} else {
&self.section_diff(symbol_ref.section_idx).symbols[symbol_ref.symbol_idx]
}
}
#[inline]
pub fn symbol_diff_mut(&mut self, symbol_ref: SymbolRef) -> &mut ObjSymbolDiff {
if symbol_ref.section_idx == SECTION_COMMON {
&mut self.common[symbol_ref.symbol_idx]
} else {
&mut self.section_diff_mut(symbol_ref.section_idx).symbols[symbol_ref.symbol_idx]
}
}
}
#[derive(Default)]
pub struct DiffObjsResult { pub struct DiffObjsResult {
pub left: Option<ObjDiff>, pub left: Option<ObjectDiff>,
pub right: Option<ObjDiff>, pub right: Option<ObjectDiff>,
pub prev: Option<ObjDiff>, pub prev: Option<ObjectDiff>,
} }
pub fn diff_objs( pub fn diff_objs(
left: Option<&Object>,
right: Option<&Object>,
prev: Option<&Object>,
diff_config: &DiffObjConfig, diff_config: &DiffObjConfig,
mapping_config: &MappingConfig, mapping_config: &MappingConfig,
left: Option<&ObjInfo>,
right: Option<&ObjInfo>,
prev: Option<&ObjInfo>,
) -> Result<DiffObjsResult> { ) -> Result<DiffObjsResult> {
let symbol_matches = matching_symbols(left, right, prev, mapping_config)?; let symbol_matches = matching_symbols(left, right, prev, mapping_config)?;
let section_matches = matching_sections(left, right)?; let section_matches = matching_sections(left, right)?;
let mut left = left.map(|p| (p, ObjDiff::new_from_obj(p))); let mut left = left.map(|p| (p, ObjectDiff::new_from_obj(p)));
let mut right = right.map(|p| (p, ObjDiff::new_from_obj(p))); let mut right = right.map(|p| (p, ObjectDiff::new_from_obj(p)));
let mut prev = prev.map(|p| (p, ObjDiff::new_from_obj(p))); let mut prev = prev.map(|p| (p, ObjectDiff::new_from_obj(p)));
for symbol_match in symbol_matches { for symbol_match in symbol_matches {
match symbol_match { match symbol_match {
@ -245,87 +214,76 @@ pub fn diff_objs(
let (left_obj, left_out) = left.as_mut().unwrap(); let (left_obj, left_out) = left.as_mut().unwrap();
let (right_obj, right_out) = right.as_mut().unwrap(); let (right_obj, right_out) = right.as_mut().unwrap();
match section_kind { match section_kind {
ObjSectionKind::Code => { SectionKind::Code => {
let left_code =
process_code_symbol(left_obj, left_symbol_ref, diff_config)?;
let right_code =
process_code_symbol(right_obj, right_symbol_ref, diff_config)?;
let (left_diff, right_diff) = diff_code( let (left_diff, right_diff) = diff_code(
left_obj, left_obj,
right_obj, right_obj,
&left_code,
&right_code,
left_symbol_ref, left_symbol_ref,
right_symbol_ref, right_symbol_ref,
diff_config, diff_config,
)?; )?;
*left_out.symbol_diff_mut(left_symbol_ref) = left_diff; left_out.symbols[left_symbol_ref] = left_diff;
*right_out.symbol_diff_mut(right_symbol_ref) = right_diff; right_out.symbols[right_symbol_ref] = right_diff;
if let Some(prev_symbol_ref) = prev_symbol_ref { if let Some(prev_symbol_ref) = prev_symbol_ref {
let (prev_obj, prev_out) = prev.as_mut().unwrap(); let (_prev_obj, prev_out) = prev.as_mut().unwrap();
let prev_code =
process_code_symbol(prev_obj, prev_symbol_ref, diff_config)?;
let (_, prev_diff) = diff_code( let (_, prev_diff) = diff_code(
left_obj, left_obj,
right_obj, right_obj,
&right_code,
&prev_code,
right_symbol_ref, right_symbol_ref,
prev_symbol_ref, prev_symbol_ref,
diff_config, diff_config,
)?; )?;
*prev_out.symbol_diff_mut(prev_symbol_ref) = prev_diff; prev_out.symbols[prev_symbol_ref] = prev_diff;
} }
} }
ObjSectionKind::Data => { SectionKind::Data => {
let (left_diff, right_diff) = diff_data_symbol( let (left_diff, right_diff) = diff_data_symbol(
left_obj, left_obj,
right_obj, right_obj,
left_symbol_ref, left_symbol_ref,
right_symbol_ref, right_symbol_ref,
)?; )?;
*left_out.symbol_diff_mut(left_symbol_ref) = left_diff; left_out.symbols[left_symbol_ref] = left_diff;
*right_out.symbol_diff_mut(right_symbol_ref) = right_diff; right_out.symbols[right_symbol_ref] = right_diff;
} }
ObjSectionKind::Bss => { SectionKind::Bss | SectionKind::Common => {
let (left_diff, right_diff) = diff_bss_symbol( let (left_diff, right_diff) = diff_bss_symbol(
left_obj, left_obj,
right_obj, right_obj,
left_symbol_ref, left_symbol_ref,
right_symbol_ref, right_symbol_ref,
)?; )?;
*left_out.symbol_diff_mut(left_symbol_ref) = left_diff; left_out.symbols[left_symbol_ref] = left_diff;
*right_out.symbol_diff_mut(right_symbol_ref) = right_diff; right_out.symbols[right_symbol_ref] = right_diff;
} }
SectionKind::Unknown => unreachable!(),
} }
} }
SymbolMatch { left: Some(left_symbol_ref), right: None, prev: _, section_kind } => { SymbolMatch { left: Some(left_symbol_ref), right: None, prev: _, section_kind } => {
let (left_obj, left_out) = left.as_mut().unwrap(); let (left_obj, left_out) = left.as_mut().unwrap();
match section_kind { match section_kind {
ObjSectionKind::Code => { SectionKind::Code => {
let code = process_code_symbol(left_obj, left_symbol_ref, diff_config)?; left_out.symbols[left_symbol_ref] =
*left_out.symbol_diff_mut(left_symbol_ref) = no_diff_code(left_obj, left_symbol_ref, diff_config)?;
no_diff_code(&code, left_symbol_ref)?;
} }
ObjSectionKind::Data | ObjSectionKind::Bss => { SectionKind::Data | SectionKind::Bss | SectionKind::Common => {
*left_out.symbol_diff_mut(left_symbol_ref) = // Nothing needs to be done
no_diff_symbol(left_obj, left_symbol_ref);
} }
SectionKind::Unknown => unreachable!(),
} }
} }
SymbolMatch { left: None, right: Some(right_symbol_ref), prev: _, section_kind } => { SymbolMatch { left: None, right: Some(right_symbol_ref), prev: _, section_kind } => {
let (right_obj, right_out) = right.as_mut().unwrap(); let (right_obj, right_out) = right.as_mut().unwrap();
match section_kind { match section_kind {
ObjSectionKind::Code => { SectionKind::Code => {
let code = process_code_symbol(right_obj, right_symbol_ref, diff_config)?; right_out.symbols[right_symbol_ref] =
*right_out.symbol_diff_mut(right_symbol_ref) = no_diff_code(right_obj, right_symbol_ref, diff_config)?;
no_diff_code(&code, right_symbol_ref)?;
} }
ObjSectionKind::Data | ObjSectionKind::Bss => { SectionKind::Data | SectionKind::Bss | SectionKind::Common => {
*right_out.symbol_diff_mut(right_symbol_ref) = // Nothing needs to be done
no_diff_symbol(right_obj, right_symbol_ref);
} }
SectionKind::Unknown => unreachable!(),
} }
} }
SymbolMatch { left: None, right: None, .. } => { SymbolMatch { left: None, right: None, .. } => {
@ -343,47 +301,44 @@ pub fn diff_objs(
{ {
let (left_obj, left_out) = left.as_mut().unwrap(); let (left_obj, left_out) = left.as_mut().unwrap();
let (right_obj, right_out) = right.as_mut().unwrap(); let (right_obj, right_out) = right.as_mut().unwrap();
let left_section = &left_obj.sections[left_section_idx];
let right_section = &right_obj.sections[right_section_idx];
match section_kind { match section_kind {
ObjSectionKind::Code => { SectionKind::Code => {
let left_section_diff = left_out.section_diff(left_section_idx);
let right_section_diff = right_out.section_diff(right_section_idx);
let (left_diff, right_diff) = diff_generic_section( let (left_diff, right_diff) = diff_generic_section(
left_section, left_obj,
right_section, right_obj,
left_section_diff, left_out,
right_section_diff, right_out,
left_section_idx,
right_section_idx,
)?; )?;
left_out.section_diff_mut(left_section_idx).merge(left_diff); left_out.sections[left_section_idx] = left_diff;
right_out.section_diff_mut(right_section_idx).merge(right_diff); right_out.sections[right_section_idx] = right_diff;
} }
ObjSectionKind::Data => { SectionKind::Data => {
let left_section_diff = left_out.section_diff(left_section_idx);
let right_section_diff = right_out.section_diff(right_section_idx);
let (left_diff, right_diff) = diff_data_section( let (left_diff, right_diff) = diff_data_section(
left_obj, left_obj,
right_obj, right_obj,
left_section, left_out,
right_section, right_out,
left_section_diff, left_section_idx,
right_section_diff, right_section_idx,
)?; )?;
left_out.section_diff_mut(left_section_idx).merge(left_diff); left_out.sections[left_section_idx] = left_diff;
right_out.section_diff_mut(right_section_idx).merge(right_diff); right_out.sections[right_section_idx] = right_diff;
} }
ObjSectionKind::Bss => { SectionKind::Bss | SectionKind::Common => {
let left_section_diff = left_out.section_diff(left_section_idx);
let right_section_diff = right_out.section_diff(right_section_idx);
let (left_diff, right_diff) = diff_bss_section( let (left_diff, right_diff) = diff_bss_section(
left_section, left_obj,
right_section, right_obj,
left_section_diff, left_out,
right_section_diff, right_out,
left_section_idx,
right_section_idx,
)?; )?;
left_out.section_diff_mut(left_section_idx).merge(left_diff); left_out.sections[left_section_idx] = left_diff;
right_out.section_diff_mut(right_section_idx).merge(right_diff); right_out.sections[right_section_idx] = right_diff;
} }
SectionKind::Unknown => unreachable!(),
} }
} }
} }
@ -410,54 +365,46 @@ pub fn diff_objs(
/// 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: &ObjInfo, base_obj: &Object,
base_name: &str, base_name: &str,
target_obj: &ObjInfo, target_obj: &Object,
target_out: &mut ObjDiff, target_out: &mut ObjectDiff,
config: &DiffObjConfig, config: &DiffObjConfig,
) -> Result<()> { ) -> Result<()> {
let Some(base_symbol_ref) = symbol_ref_by_name(base_obj, base_name) else { let Some(base_symbol_ref) = symbol_ref_by_name(base_obj, base_name) else {
return Ok(()); return Ok(());
}; };
let (base_section, _base_symbol) = base_obj.section_symbol(base_symbol_ref); let base_section_kind = symbol_section_kind(base_obj, &base_obj.symbols[base_symbol_ref]);
let Some(base_section) = base_section else { for (target_symbol_index, target_symbol) in target_obj.symbols.iter().enumerate() {
return Ok(()); if symbol_section_kind(target_obj, target_symbol) != base_section_kind {
}; continue;
let base_code = match base_section.kind {
ObjSectionKind::Code => Some(process_code_symbol(base_obj, base_symbol_ref, config)?),
_ => None,
};
for (target_section_index, target_section) in
target_obj.sections.iter().enumerate().filter(|(_, s)| s.kind == base_section.kind)
{
for (target_symbol_index, _target_symbol) in target_section.symbols.iter().enumerate() {
let target_symbol_ref =
SymbolRef { section_idx: target_section_index, symbol_idx: target_symbol_index };
match base_section.kind {
ObjSectionKind::Code => {
let target_code = process_code_symbol(target_obj, target_symbol_ref, config)?;
let (left_diff, _right_diff) = diff_code(
target_obj,
base_obj,
&target_code,
base_code.as_ref().unwrap(),
target_symbol_ref,
base_symbol_ref,
config,
)?;
target_out.mapping_symbols.push(left_diff);
} }
ObjSectionKind::Data => { match base_section_kind {
SectionKind::Code => {
let (left_diff, _right_diff) = let (left_diff, _right_diff) =
diff_data_symbol(target_obj, base_obj, target_symbol_ref, base_symbol_ref)?; diff_code(target_obj, base_obj, target_symbol_index, base_symbol_ref, config)?;
target_out.mapping_symbols.push(left_diff); target_out.mapping_symbols.push(MappingSymbolDiff {
symbol_index: target_symbol_index,
symbol_diff: left_diff,
});
} }
ObjSectionKind::Bss => { SectionKind::Data => {
let (left_diff, _right_diff) = let (left_diff, _right_diff) =
diff_bss_symbol(target_obj, base_obj, target_symbol_ref, base_symbol_ref)?; diff_data_symbol(target_obj, base_obj, target_symbol_index, base_symbol_ref)?;
target_out.mapping_symbols.push(left_diff); target_out.mapping_symbols.push(MappingSymbolDiff {
symbol_index: target_symbol_index,
symbol_diff: left_diff,
});
} }
SectionKind::Bss | SectionKind::Common => {
let (left_diff, _right_diff) =
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 => {}
} }
} }
Ok(()) Ok(())
@ -465,17 +412,17 @@ fn generate_mapping_symbols(
#[derive(Copy, Clone, Eq, PartialEq)] #[derive(Copy, Clone, Eq, PartialEq)]
struct SymbolMatch { struct SymbolMatch {
left: Option<SymbolRef>, left: Option<usize>,
right: Option<SymbolRef>, right: Option<usize>,
prev: Option<SymbolRef>, prev: Option<usize>,
section_kind: ObjSectionKind, section_kind: SectionKind,
} }
#[derive(Copy, Clone, Eq, PartialEq)] #[derive(Copy, Clone, Eq, PartialEq)]
struct SectionMatch { struct SectionMatch {
left: Option<usize>, left: Option<usize>,
right: Option<usize>, right: Option<usize>,
section_kind: ObjSectionKind, section_kind: SectionKind,
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
@ -489,23 +436,16 @@ pub struct MappingConfig {
pub selecting_right: Option<String>, pub selecting_right: Option<String>,
} }
fn symbol_ref_by_name(obj: &ObjInfo, name: &str) -> Option<SymbolRef> { fn symbol_ref_by_name(obj: &Object, name: &str) -> Option<usize> {
for (section_idx, section) in obj.sections.iter().enumerate() { obj.symbols.iter().position(|s| s.name == name)
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
if symbol.name == name {
return Some(SymbolRef { section_idx, symbol_idx });
}
}
}
None
} }
fn apply_symbol_mappings( fn apply_symbol_mappings(
left: &ObjInfo, left: &Object,
right: &ObjInfo, right: &Object,
mapping_config: &MappingConfig, mapping_config: &MappingConfig,
left_used: &mut BTreeSet<SymbolRef>, left_used: &mut BTreeSet<usize>,
right_used: &mut BTreeSet<SymbolRef>, right_used: &mut BTreeSet<usize>,
matches: &mut Vec<SymbolMatch>, matches: &mut Vec<SymbolMatch>,
) -> Result<()> { ) -> Result<()> {
// 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
@ -523,47 +463,57 @@ fn apply_symbol_mappings(
// 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) = symbol_ref_by_name(left, left_name) else { let Some(left_symbol_index) = symbol_ref_by_name(left, left_name) else {
continue; continue;
}; };
if left_used.contains(&left_symbol) { if left_used.contains(&left_symbol_index) {
continue; continue;
} }
let Some(right_symbol) = symbol_ref_by_name(right, right_name) else { let Some(right_symbol_index) = symbol_ref_by_name(right, right_name) else {
continue; continue;
}; };
if right_used.contains(&right_symbol) { if right_used.contains(&right_symbol_index) {
continue; continue;
} }
let left_section = &left.sections[left_symbol.section_idx]; let left_section_kind = left
let right_section = &right.sections[right_symbol.section_idx]; .symbols
if left_section.kind != right_section.kind { .get(left_symbol_index)
.and_then(|s| s.section)
.and_then(|section_index| left.sections.get(section_index))
.map_or(SectionKind::Unknown, |s| s.kind);
let right_section_kind = right
.symbols
.get(right_symbol_index)
.and_then(|s| s.section)
.and_then(|section_index| right.sections.get(section_index))
.map_or(SectionKind::Unknown, |s| s.kind);
if left_section_kind != right_section_kind {
log::warn!( log::warn!(
"Symbol section kind mismatch: {} ({:?}) vs {} ({:?})", "Symbol section kind mismatch: {} ({:?}) vs {} ({:?})",
left_name, left_name,
left_section.kind, left_section_kind,
right_name, right_name,
right_section.kind right_section_kind
); );
continue; continue;
} }
matches.push(SymbolMatch { matches.push(SymbolMatch {
left: Some(left_symbol), left: Some(left_symbol_index),
right: Some(right_symbol), right: Some(right_symbol_index),
prev: None, // TODO prev: None, // TODO
section_kind: left_section.kind, section_kind: left_section_kind,
}); });
left_used.insert(left_symbol); left_used.insert(left_symbol_index);
right_used.insert(right_symbol); right_used.insert(right_symbol_index);
} }
Ok(()) Ok(())
} }
/// Find matching symbols between each object. /// Find matching symbols between each object.
fn matching_symbols( fn matching_symbols(
left: Option<&ObjInfo>, left: Option<&Object>,
right: Option<&ObjInfo>, right: Option<&Object>,
prev: Option<&ObjInfo>, prev: Option<&Object>,
mappings: &MappingConfig, mappings: &MappingConfig,
) -> Result<Vec<SymbolMatch>> { ) -> Result<Vec<SymbolMatch>> {
let mut matches = Vec::new(); let mut matches = Vec::new();
@ -580,34 +530,19 @@ fn matching_symbols(
&mut matches, &mut matches,
)?; )?;
} }
for (section_idx, section) in left.sections.iter().enumerate() { for (symbol_idx, symbol) in left.symbols.iter().enumerate() {
for (symbol_idx, symbol) in section.symbols.iter().enumerate() { let section_kind = symbol_section_kind(left, symbol);
let symbol_ref = SymbolRef { section_idx, symbol_idx }; if section_kind == SectionKind::Unknown {
if left_used.contains(&symbol_ref) { continue;
}
if left_used.contains(&symbol_idx) {
continue; continue;
} }
let symbol_match = SymbolMatch { let symbol_match = SymbolMatch {
left: Some(symbol_ref), left: Some(symbol_idx),
right: find_symbol(right, symbol, section, Some(&right_used)), right: find_symbol(right, left, symbol, Some(&right_used)),
prev: find_symbol(prev, symbol, section, None), prev: find_symbol(prev, left, symbol, None),
section_kind: section.kind, section_kind,
};
matches.push(symbol_match);
if let Some(right) = symbol_match.right {
right_used.insert(right);
}
}
}
for (symbol_idx, symbol) in left.common.iter().enumerate() {
let symbol_ref = SymbolRef { section_idx: SECTION_COMMON, symbol_idx };
if left_used.contains(&symbol_ref) {
continue;
}
let symbol_match = SymbolMatch {
left: Some(symbol_ref),
right: find_common_symbol(right, symbol),
prev: find_common_symbol(prev, symbol),
section_kind: ObjSectionKind::Bss,
}; };
matches.push(symbol_match); matches.push(symbol_match);
if let Some(right) = symbol_match.right { if let Some(right) = symbol_match.right {
@ -616,83 +551,84 @@ fn matching_symbols(
} }
} }
if let Some(right) = right { if let Some(right) = right {
for (section_idx, section) in right.sections.iter().enumerate() { for (symbol_idx, symbol) in right.symbols.iter().enumerate() {
for (symbol_idx, symbol) in section.symbols.iter().enumerate() { let section_kind = symbol_section_kind(right, symbol);
let symbol_ref = SymbolRef { section_idx, symbol_idx }; if section_kind == SectionKind::Unknown {
if right_used.contains(&symbol_ref) { continue;
}
if right_used.contains(&symbol_idx) {
continue; continue;
} }
matches.push(SymbolMatch { matches.push(SymbolMatch {
left: None, left: None,
right: Some(symbol_ref), right: Some(symbol_idx),
prev: find_symbol(prev, symbol, section, None), prev: find_symbol(prev, right, symbol, None),
section_kind: section.kind, section_kind,
});
}
}
for (symbol_idx, symbol) in right.common.iter().enumerate() {
let symbol_ref = SymbolRef { section_idx: SECTION_COMMON, symbol_idx };
if right_used.contains(&symbol_ref) {
continue;
}
matches.push(SymbolMatch {
left: None,
right: Some(symbol_ref),
prev: find_common_symbol(prev, symbol),
section_kind: ObjSectionKind::Bss,
}); });
} }
} }
Ok(matches) Ok(matches)
} }
fn unmatched_symbols<'section, 'used>( fn unmatched_symbols<'obj, 'used>(
section: &'section ObjSection, obj: &'obj Object,
section_idx: usize, used: Option<&'used BTreeSet<usize>>,
used: Option<&'used BTreeSet<SymbolRef>>, ) -> impl Iterator<Item = (usize, &'obj Symbol)> + 'used
) -> impl Iterator<Item = (usize, &'section ObjSymbol)> + 'used
where where
'section: 'used, 'obj: 'used,
{ {
section.symbols.iter().enumerate().filter(move |&(symbol_idx, _)| { obj.symbols.iter().enumerate().filter(move |&(symbol_idx, _)| {
// Skip symbols that have already been matched // Skip symbols that have already been matched
!used.map(|u| u.contains(&SymbolRef { section_idx, symbol_idx })).unwrap_or(false) !used.is_some_and(|u| u.contains(&symbol_idx))
}) })
} }
fn symbol_section<'obj>(obj: &'obj Object, symbol: &Symbol) -> Option<(&'obj str, SectionKind)> {
if let Some(section) = symbol.section.and_then(|section_idx| obj.sections.get(section_idx)) {
Some((section.name.as_str(), section.kind))
} else if symbol.flags.contains(SymbolFlag::Common) {
Some((".comm", SectionKind::Common))
} else {
None
}
}
fn symbol_section_kind(obj: &Object, symbol: &Symbol) -> SectionKind {
match symbol.section {
Some(section_index) => obj.sections[section_index].kind,
None if symbol.flags.contains(SymbolFlag::Common) => SectionKind::Common,
None => SectionKind::Unknown,
}
}
fn find_symbol( fn find_symbol(
obj: Option<&ObjInfo>, obj: Option<&Object>,
in_symbol: &ObjSymbol, in_obj: &Object,
in_section: &ObjSection, in_symbol: &Symbol,
used: Option<&BTreeSet<SymbolRef>>, used: Option<&BTreeSet<usize>>,
) -> Option<SymbolRef> { ) -> Option<usize> {
let obj = obj?; let obj = obj?;
let (section_name, section_kind) = symbol_section(in_obj, in_symbol)?;
// Try to find an exact name match // Try to find an exact name match
for (section_idx, section) in obj.sections.iter().enumerate() { if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|(_, symbol)| {
if section.kind != in_section.kind { symbol.name == in_symbol.name && symbol_section_kind(obj, symbol) == section_kind
continue; }) {
} return Some(symbol_idx);
if let Some((symbol_idx, _)) = unmatched_symbols(section, section_idx, used)
.find(|(_, symbol)| symbol.name == in_symbol.name)
{
return Some(SymbolRef { section_idx, symbol_idx });
}
} }
// Match compiler-generated symbols against each other (e.g. @251 -> @60) // Match compiler-generated symbols against each other (e.g. @251 -> @60)
// If they are at the same address in the same section // If they are at the same address in the same section
if in_symbol.name.starts_with('@') if in_symbol.name.starts_with('@')
&& matches!(in_section.kind, ObjSectionKind::Data | ObjSectionKind::Bss) && matches!(section_kind, SectionKind::Data | SectionKind::Bss)
{ {
if let Some((section_idx, section)) = if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|(_, symbol)| {
obj.sections.iter().enumerate().find(|(_, s)| s.name == in_section.name) let Some(section_index) = symbol.section else {
{ return false;
if let Some((symbol_idx, _)) = };
unmatched_symbols(section, section_idx, used).find(|(_, symbol)| { symbol.name.starts_with('@')
symbol.address == in_symbol.address && symbol.name.starts_with('@') && symbol.address == in_symbol.address
}) && obj.sections[section_index].name == section_name
{ }) {
return Some(SymbolRef { section_idx, symbol_idx }); return Some(symbol_idx);
}
} }
} }
// Match Metrowerks symbol$1234 against symbol$2345 // Match Metrowerks symbol$1234 against symbol$2345
@ -700,41 +636,29 @@ fn find_symbol(
if !suffix.chars().all(char::is_numeric) { if !suffix.chars().all(char::is_numeric) {
return None; return None;
} }
for (section_idx, section) in obj.sections.iter().enumerate() { if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|&(_, symbol)| {
if section.kind != in_section.kind {
continue;
}
if let Some((symbol_idx, _)) =
unmatched_symbols(section, section_idx, used).find(|&(_, symbol)| {
if let Some((p, s)) = symbol.name.split_once('$') { if let Some((p, s)) = symbol.name.split_once('$') {
prefix == p && s.chars().all(char::is_numeric) prefix == p
&& s.chars().all(char::is_numeric)
&& symbol_section_kind(obj, symbol) == section_kind
} else { } else {
false false
} }
}) }) {
{ return Some(symbol_idx);
return Some(SymbolRef { section_idx, symbol_idx });
}
}
}
None
}
fn find_common_symbol(obj: Option<&ObjInfo>, in_symbol: &ObjSymbol) -> Option<SymbolRef> {
let obj = obj?;
for (symbol_idx, symbol) in obj.common.iter().enumerate() {
if symbol.name == in_symbol.name {
return Some(SymbolRef { section_idx: SECTION_COMMON, symbol_idx });
} }
} }
None None
} }
/// Find matching sections between each object. /// Find matching sections between each object.
fn matching_sections(left: Option<&ObjInfo>, right: Option<&ObjInfo>) -> Result<Vec<SectionMatch>> { fn matching_sections(left: Option<&Object>, right: Option<&Object>) -> Result<Vec<SectionMatch>> {
let mut matches = Vec::new(); let mut matches = Vec::new();
if let Some(left) = left { if let Some(left) = left {
for (section_idx, section) in left.sections.iter().enumerate() { for (section_idx, section) in left.sections.iter().enumerate() {
if section.kind == SectionKind::Unknown {
continue;
}
matches.push(SectionMatch { matches.push(SectionMatch {
left: Some(section_idx), left: Some(section_idx),
right: find_section(right, &section.name, section.kind), right: find_section(right, &section.name, section.kind),
@ -744,6 +668,9 @@ fn matching_sections(left: Option<&ObjInfo>, right: Option<&ObjInfo>) -> Result<
} }
if let Some(right) = right { if let Some(right) = right {
for (section_idx, section) in right.sections.iter().enumerate() { for (section_idx, section) in right.sections.iter().enumerate() {
if section.kind == SectionKind::Unknown {
continue;
}
if matches.iter().any(|m| m.right == Some(section_idx)) { if matches.iter().any(|m| m.right == Some(section_idx)) {
continue; continue;
} }
@ -757,14 +684,6 @@ fn matching_sections(left: Option<&ObjInfo>, right: Option<&ObjInfo>) -> Result<
Ok(matches) Ok(matches)
} }
fn find_section(obj: Option<&ObjInfo>, name: &str, section_kind: ObjSectionKind) -> Option<usize> { fn find_section(obj: Option<&Object>, name: &str, section_kind: SectionKind) -> Option<usize> {
for (section_idx, section) in obj?.sections.iter().enumerate() { obj?.sections.iter().position(|s| s.kind == section_kind && s.name == name)
if section.kind != section_kind {
continue;
}
if section.name == name {
return Some(section_idx);
}
}
None
} }

View File

@ -6,9 +6,9 @@ use typed_path::Utf8PlatformPathBuf;
use crate::{ use crate::{
build::{run_make, BuildConfig, BuildStatus}, build::{run_make, BuildConfig, BuildStatus},
diff::{diff_objs, DiffObjConfig, MappingConfig, ObjDiff}, diff::{diff_objs, DiffObjConfig, MappingConfig, ObjectDiff},
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState}, jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
obj::{read, ObjInfo}, obj::{read, Object},
}; };
pub struct ObjDiffConfig { pub struct ObjDiffConfig {
@ -24,8 +24,8 @@ pub struct ObjDiffConfig {
pub struct ObjDiffResult { pub struct ObjDiffResult {
pub first_status: BuildStatus, pub first_status: BuildStatus,
pub second_status: BuildStatus, pub second_status: BuildStatus,
pub first_obj: Option<(ObjInfo, ObjDiff)>, pub first_obj: Option<(Object, ObjectDiff)>,
pub second_obj: Option<(ObjInfo, ObjDiff)>, pub second_obj: Option<(Object, ObjectDiff)>,
pub time: OffsetDateTime, pub time: OffsetDateTime,
} }
@ -170,11 +170,11 @@ fn run_build(
update_status(context, "Performing diff".to_string(), step_idx, total, &cancel)?; update_status(context, "Performing diff".to_string(), step_idx, total, &cancel)?;
step_idx += 1; step_idx += 1;
let result = diff_objs( let result = diff_objs(
&config.diff_obj_config,
&config.mapping_config,
first_obj.as_ref(), first_obj.as_ref(),
second_obj.as_ref(), second_obj.as_ref(),
None, None,
&config.diff_obj_config,
&config.mapping_config,
)?; )?;
update_status(context, "Complete".to_string(), step_idx, total, &cancel)?; update_status(context, "Complete".to_string(), step_idx, total, &cancel)?;

View File

@ -1,3 +1,4 @@
#![allow(clippy::too_many_arguments)]
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc; extern crate alloc;
@ -17,5 +18,3 @@ pub mod jobs;
pub mod obj; pub mod obj;
#[cfg(feature = "any-arch")] #[cfg(feature = "any-arch")]
pub mod util; pub mod util;
#[cfg(feature = "wasm")]
pub mod wasm;

View File

@ -1,23 +1,30 @@
pub mod read; pub mod read;
pub mod split_meta; pub mod split_meta;
use alloc::{borrow::Cow, boxed::Box, collections::BTreeMap, string::String, vec::Vec}; use alloc::{borrow::Cow, boxed::Box, collections::BTreeMap, string::String, vec, vec::Vec};
use core::fmt; use core::{fmt, num::NonZeroU32};
use flagset::{flags, FlagSet}; use flagset::{flags, FlagSet};
use object::RelocationFlags;
use split_meta::SplitMeta;
use crate::{arch::ObjArch, util::ReallySigned}; use crate::{
arch::{Arch, ArchDummy},
obj::split_meta::SplitMeta,
util::ReallySigned,
};
#[derive(Debug, Eq, PartialEq, Copy, Clone)] #[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
pub enum ObjSectionKind { pub enum SectionKind {
#[default]
Unknown = -1,
Code, Code,
Data, Data,
Bss, Bss,
Common,
} }
flags! { flags! {
pub enum ObjSymbolFlags: u8 { #[derive(Hash)]
pub enum SymbolFlag: u8 {
Global, Global,
Local, Local,
Weak, Weak,
@ -26,105 +33,152 @@ flags! {
/// Has extra data associated with the symbol /// Has extra data associated with the symbol
/// (e.g. exception table entry) /// (e.g. exception table entry)
HasExtra, HasExtra,
/// Symbol size was missing and was inferred
SizeInferred,
} }
} }
#[derive(Debug, Copy, Clone, Default, PartialEq)]
pub struct ObjSymbolFlagSet(pub FlagSet<ObjSymbolFlags>);
#[derive(Debug, Clone)] pub type SymbolFlagSet = FlagSet<SymbolFlag>;
pub struct ObjSection {
flags! {
#[derive(Hash)]
pub enum SectionFlag: u8 {
/// Section combined from multiple input sections
Combined,
Hidden,
}
}
pub type SectionFlagSet = FlagSet<SectionFlag>;
#[derive(Debug, Clone, Default)]
pub struct Section {
/// Unique section ID
pub id: String,
pub name: String, pub name: String,
pub kind: ObjSectionKind,
pub address: u64, pub address: u64,
pub size: u64, pub size: u64,
pub data: Vec<u8>, pub kind: SectionKind,
pub orig_index: usize, pub data: SectionData,
pub symbols: Vec<ObjSymbol>, pub flags: SectionFlagSet,
pub relocations: Vec<ObjReloc>, pub relocations: Vec<Relocation>,
pub virtual_address: Option<u64>,
/// Line number info (.line or .debug_line section) /// Line number info (.line or .debug_line section)
pub line_info: BTreeMap<u64, u32>, pub line_info: BTreeMap<u64, u32>,
/// Original virtual address (from .note.split section)
pub virtual_address: Option<u64>,
}
#[derive(Clone, Default)]
#[repr(transparent)]
pub struct SectionData(pub Vec<u8>);
impl core::ops::Deref for SectionData {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl fmt::Debug for SectionData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("SectionData").field(&self.0.len()).finish()
}
}
impl Section {
pub fn data_range(&self, address: u64, size: usize) -> Option<&[u8]> {
let start = self.address;
let end = start + self.size;
if address >= start && address + size as u64 <= end {
let offset = (address - start) as usize;
Some(&self.data[offset..offset + size])
} else {
None
}
}
pub fn relocation_at<'obj>(
&'obj self,
address: u64,
obj: &'obj Object,
) -> Option<ResolvedRelocation<'obj>> {
self.relocations.binary_search_by_key(&address, |r| r.address).ok().and_then(|i| {
let relocation = self.relocations.get(i)?;
let symbol = obj.symbols.get(relocation.target_symbol)?;
Some(ResolvedRelocation { relocation, symbol })
})
}
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum ObjInsArgValue { pub enum InstructionArgValue {
Signed(i64), Signed(i64),
Unsigned(u64), Unsigned(u64),
Opaque(Cow<'static, str>), Opaque(Cow<'static, str>),
} }
impl ObjInsArgValue { impl InstructionArgValue {
pub fn loose_eq(&self, other: &ObjInsArgValue) -> bool { pub fn loose_eq(&self, other: &InstructionArgValue) -> bool {
match (self, other) { match (self, other) {
(ObjInsArgValue::Signed(a), ObjInsArgValue::Signed(b)) => a == b, (InstructionArgValue::Signed(a), InstructionArgValue::Signed(b)) => a == b,
(ObjInsArgValue::Unsigned(a), ObjInsArgValue::Unsigned(b)) => a == b, (InstructionArgValue::Unsigned(a), InstructionArgValue::Unsigned(b)) => a == b,
(ObjInsArgValue::Signed(a), ObjInsArgValue::Unsigned(b)) (InstructionArgValue::Signed(a), InstructionArgValue::Unsigned(b))
| (ObjInsArgValue::Unsigned(b), ObjInsArgValue::Signed(a)) => *a as u64 == *b, | (InstructionArgValue::Unsigned(b), InstructionArgValue::Signed(a)) => *a as u64 == *b,
(ObjInsArgValue::Opaque(a), ObjInsArgValue::Opaque(b)) => a == b, (InstructionArgValue::Opaque(a), InstructionArgValue::Opaque(b)) => a == b,
_ => false, _ => false,
} }
} }
} }
impl fmt::Display for ObjInsArgValue { impl fmt::Display for InstructionArgValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
ObjInsArgValue::Signed(v) => write!(f, "{:#x}", ReallySigned(*v)), InstructionArgValue::Signed(v) => write!(f, "{:#x}", ReallySigned(*v)),
ObjInsArgValue::Unsigned(v) => write!(f, "{:#x}", v), InstructionArgValue::Unsigned(v) => write!(f, "{:#x}", v),
ObjInsArgValue::Opaque(v) => write!(f, "{}", v), InstructionArgValue::Opaque(v) => write!(f, "{}", v),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ObjInsArg {
PlainText(Cow<'static, str>),
Arg(ObjInsArgValue),
Reloc,
BranchDest(u64),
}
impl ObjInsArg {
#[inline]
pub fn is_plain_text(&self) -> bool { matches!(self, ObjInsArg::PlainText(_)) }
pub fn loose_eq(&self, other: &ObjInsArg) -> bool {
match (self, other) {
(ObjInsArg::Arg(a), ObjInsArg::Arg(b)) => a.loose_eq(b),
(ObjInsArg::Reloc, ObjInsArg::Reloc) => true,
(ObjInsArg::BranchDest(a), ObjInsArg::BranchDest(b)) => a == b,
_ => false,
} }
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjIns { pub enum InstructionArg {
pub address: u64, Value(InstructionArgValue),
pub size: u8, Reloc,
pub op: u16, BranchDest(u64),
pub mnemonic: Cow<'static, str>,
pub args: Vec<ObjInsArg>,
pub reloc: Option<ObjReloc>,
pub branch_dest: Option<u64>,
/// Line number
pub line: Option<u32>,
/// Formatted instruction
pub formatted: String,
/// Original (unsimplified) instruction
pub orig: Option<String>,
} }
impl ObjIns { impl InstructionArg {
/// Iterate over non-PlainText arguments. pub fn loose_eq(&self, other: &InstructionArg) -> bool {
#[inline] match (self, other) {
pub fn iter_args(&self) -> impl DoubleEndedIterator<Item = &ObjInsArg> { (InstructionArg::Value(a), InstructionArg::Value(b)) => a.loose_eq(b),
self.args.iter().filter(|a| !a.is_plain_text()) (InstructionArg::Reloc, InstructionArg::Reloc) => true,
(InstructionArg::BranchDest(a), InstructionArg::BranchDest(b)) => a == b,
_ => false,
} }
} }
}
#[derive(Copy, Clone, Debug)]
pub struct InstructionRef {
pub address: u64,
pub size: u8,
pub opcode: u16,
}
#[derive(Copy, Clone, Debug)]
pub struct ScannedInstruction {
pub ins_ref: InstructionRef,
pub branch_dest: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct ParsedInstruction {
pub ins_ref: InstructionRef,
pub mnemonic: Cow<'static, str>,
pub args: Vec<InstructionArg>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
pub enum ObjSymbolKind { pub enum SymbolKind {
#[default] #[default]
Unknown, Unknown,
Function, Function,
@ -132,60 +186,65 @@ pub enum ObjSymbolKind {
Section, Section,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub struct ObjSymbol { pub struct Symbol {
pub name: String, pub name: String,
pub demangled_name: Option<String>, pub demangled_name: Option<String>,
pub address: u64, pub address: u64,
pub section_address: u64,
pub size: u64, pub size: u64,
pub size_known: bool, pub kind: SymbolKind,
pub kind: ObjSymbolKind, pub section: Option<usize>,
pub flags: ObjSymbolFlagSet, pub flags: SymbolFlagSet,
pub orig_section_index: Option<usize>, /// Alignment (from Metrowerks .comment section)
pub align: Option<NonZeroU32>,
/// Original virtual address (from .note.split section) /// Original virtual address (from .note.split section)
pub virtual_address: Option<u64>, pub virtual_address: Option<u64>,
/// Original index in object symbol table
pub original_index: Option<usize>,
pub bytes: Vec<u8>,
} }
pub struct ObjInfo { #[derive(Debug)]
pub arch: Box<dyn ObjArch>, pub struct Object {
pub path: Option<String>, pub arch: Box<dyn Arch>,
#[cfg(feature = "std")] pub symbols: Vec<Symbol>,
pub timestamp: Option<filetime::FileTime>, pub sections: Vec<Section>,
pub sections: Vec<ObjSection>,
/// Common BSS symbols
pub common: Vec<ObjSymbol>,
/// Split object metadata (.note.split section) /// Split object metadata (.note.split section)
pub split_meta: Option<SplitMeta>, pub split_meta: Option<SplitMeta>,
#[cfg(feature = "std")]
pub path: Option<std::path::PathBuf>,
#[cfg(feature = "std")]
pub timestamp: Option<filetime::FileTime>,
} }
#[derive(Debug, Clone, PartialEq)] impl Default for Object {
pub struct ObjReloc { fn default() -> Self {
Self {
arch: ArchDummy::new(),
symbols: vec![],
sections: vec![],
split_meta: None,
#[cfg(feature = "std")]
path: None,
#[cfg(feature = "std")]
timestamp: None,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Relocation {
pub flags: RelocationFlags, pub flags: RelocationFlags,
pub address: u64, pub address: u64,
pub target: ObjSymbol, pub target_symbol: usize,
pub addend: i64, pub addend: i64,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct SymbolRef { pub enum RelocationFlags {
pub section_idx: usize, Elf(u32),
pub symbol_idx: usize, Coff(u16),
} }
pub const SECTION_COMMON: usize = usize::MAX - 1; #[derive(Clone, Copy)]
pub struct ResolvedRelocation<'a> {
impl ObjInfo { pub relocation: &'a Relocation,
pub fn section_symbol(&self, symbol_ref: SymbolRef) -> (Option<&ObjSection>, &ObjSymbol) { pub symbol: &'a Symbol,
if symbol_ref.section_idx == SECTION_COMMON {
let symbol = &self.common[symbol_ref.symbol_idx];
return (None, symbol);
}
let section = &self.sections[symbol_ref.section_idx];
let symbol = &section.symbols[symbol_ref.symbol_idx];
(Some(section), symbol)
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,161 @@
---
source: objdiff-core/src/obj/read.rs
expression: "(sections, symbols)"
---
(
[
Section {
id: ".text-0",
name: ".text",
address: 0,
size: 8,
kind: Code,
data: SectionData(
8,
),
flags: FlagSet(),
relocations: [
Relocation {
flags: Elf(
0,
),
address: 0,
target_symbol: 0,
addend: 4,
},
Relocation {
flags: Elf(
0,
),
address: 2,
target_symbol: 1,
addend: 0,
},
Relocation {
flags: Elf(
0,
),
address: 4,
target_symbol: 0,
addend: 10,
},
],
line_info: {},
virtual_address: None,
},
Section {
id: ".data-0",
name: ".data",
address: 0,
size: 12,
kind: Data,
data: SectionData(
12,
),
flags: FlagSet(Combined),
relocations: [
Relocation {
flags: Elf(
0,
),
address: 0,
target_symbol: 2,
addend: 0,
},
Relocation {
flags: Elf(
0,
),
address: 4,
target_symbol: 2,
addend: 0,
},
],
line_info: {
0: 1,
8: 2,
},
virtual_address: None,
},
Section {
id: ".data-1",
name: ".data",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".data-2",
name: ".data",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
],
[
Symbol {
name: ".data",
demangled_name: None,
address: 0,
size: 0,
kind: Section,
section: Some(
1,
),
flags: FlagSet(),
align: None,
virtual_address: None,
},
Symbol {
name: "symbol",
demangled_name: None,
address: 4,
size: 4,
kind: Object,
section: Some(
1,
),
flags: FlagSet(),
align: None,
virtual_address: None,
},
Symbol {
name: "function",
demangled_name: None,
address: 0,
size: 8,
kind: Function,
section: Some(
0,
),
flags: FlagSet(),
align: None,
virtual_address: None,
},
Symbol {
name: ".data",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(),
align: None,
virtual_address: None,
},
],
)

View File

@ -1,7 +1,7 @@
use alloc::format; use alloc::format;
use core::fmt; use core::fmt;
use anyhow::Result; use anyhow::{ensure, Result};
use num_traits::PrimInt; use num_traits::PrimInt;
use object::{Endian, Object}; use object::{Endian, Object};
@ -27,17 +27,15 @@ impl<N: PrimInt> fmt::UpperHex for ReallySigned<N> {
} }
pub fn read_u32(obj_file: &object::File, reader: &mut &[u8]) -> Result<u32> { pub fn read_u32(obj_file: &object::File, reader: &mut &[u8]) -> Result<u32> {
if reader.len() < 4 { ensure!(reader.len() >= 4, "Not enough bytes to read u32");
return Err(anyhow::anyhow!("Not enough bytes to read u32"));
}
let value = u32::from_ne_bytes(reader[..4].try_into()?); let value = u32::from_ne_bytes(reader[..4].try_into()?);
*reader = &reader[4..];
Ok(obj_file.endianness().read_u32(value)) Ok(obj_file.endianness().read_u32(value))
} }
pub fn read_u16(obj_file: &object::File, reader: &mut &[u8]) -> Result<u16> { pub fn read_u16(obj_file: &object::File, reader: &mut &[u8]) -> Result<u16> {
if reader.len() < 2 { ensure!(reader.len() >= 2, "Not enough bytes to read u16");
return Err(anyhow::anyhow!("Not enough bytes to read u16"));
}
let value = u16::from_ne_bytes(reader[..2].try_into()?); let value = u16::from_ne_bytes(reader[..2].try_into()?);
*reader = &reader[2..];
Ok(obj_file.endianness().read_u16(value)) Ok(obj_file.endianness().read_u16(value))
} }

View File

@ -1,99 +0,0 @@
use alloc::{
format,
str::FromStr,
string::{String, ToString},
vec::Vec,
};
use core::cell::RefCell;
use prost::Message;
use crate::{bindings::diff::DiffResult, diff, obj};
wit_bindgen::generate!({
world: "api",
});
use exports::objdiff::core::diff::{
DiffConfigBorrow, Guest as GuestTypes, GuestDiffConfig, GuestObject, Object, ObjectBorrow,
};
struct Component;
impl Guest for Component {
fn init() -> Result<(), String> {
// console_error_panic_hook::set_once();
// #[cfg(debug_assertions)]
// console_log::init_with_level(log::Level::Debug).map_err(|e| e.to_string())?;
// #[cfg(not(debug_assertions))]
// console_log::init_with_level(log::Level::Info).map_err(|e| e.to_string())?;
Ok(())
}
fn version() -> String { env!("CARGO_PKG_VERSION").to_string() }
}
#[repr(transparent)]
struct ResourceDiffConfig(RefCell<diff::DiffObjConfig>);
impl GuestTypes for Component {
type DiffConfig = ResourceDiffConfig;
type Object = obj::ObjInfo;
fn run_diff(
left: Option<ObjectBorrow>,
right: Option<ObjectBorrow>,
diff_config: DiffConfigBorrow,
) -> Result<Vec<u8>, String> {
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
let result = run_diff_internal(
left.as_ref().map(|o| o.get()),
right.as_ref().map(|o| o.get()),
&diff_config,
&diff::MappingConfig::default(),
)
.map_err(|e| e.to_string())?;
Ok(result.encode_to_vec())
}
}
impl GuestDiffConfig for ResourceDiffConfig {
fn new() -> Self { Self(RefCell::new(diff::DiffObjConfig::default())) }
fn set_property(&self, key: String, value: String) -> Result<(), String> {
let id = diff::ConfigPropertyId::from_str(&key)
.map_err(|_| format!("Invalid property key {:?}", key))?;
self.0
.borrow_mut()
.set_property_value_str(id, &value)
.map_err(|_| format!("Invalid property value {:?}", value))
}
fn get_property(&self, key: String) -> Result<String, String> {
let id = diff::ConfigPropertyId::from_str(&key)
.map_err(|_| format!("Invalid property key {:?}", key))?;
Ok(self.0.borrow().get_property_value(id).to_string())
}
}
impl GuestObject for obj::ObjInfo {
fn parse(data: Vec<u8>, diff_config: DiffConfigBorrow) -> Result<Object, String> {
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
obj::read::parse(&data, &diff_config).map(|o| Object::new(o)).map_err(|e| e.to_string())
}
}
fn run_diff_internal(
left: Option<&obj::ObjInfo>,
right: Option<&obj::ObjInfo>,
diff_config: &diff::DiffObjConfig,
mapping_config: &diff::MappingConfig,
) -> anyhow::Result<DiffResult> {
log::debug!("Running diff with config: {:?}", diff_config);
let result = diff::diff_objs(diff_config, mapping_config, left, right, None)?;
let left = left.and_then(|o| result.left.as_ref().map(|d| (o, d)));
let right = right.and_then(|o| result.right.as_ref().map(|d| (o, d)));
Ok(DiffResult::new(left, right))
}
export!(Component);

View File

@ -1,18 +0,0 @@
mod api;
#[cfg(not(feature = "std"))]
mod cabi_realloc;
#[cfg(not(feature = "std"))]
static mut ARENA: [u8; 10000] = [0; 10000];
#[cfg(not(feature = "std"))]
#[global_allocator]
static ALLOCATOR: talc::Talck<spin::Mutex<()>, talc::ClaimOnOom> = talc::Talc::new(unsafe {
talc::ClaimOnOom::new(talc::Span::from_array(core::ptr::addr_of!(ARENA) as *mut [u8; 10000]))
})
.lock();
#[cfg(not(feature = "std"))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! { loop {} }

View File

@ -0,0 +1,79 @@
use objdiff_core::{
diff::{self, display},
obj,
obj::SectionKind,
};
mod common;
#[test]
#[cfg(feature = "ppc")]
fn read_ppc() {
let diff_config = diff::DiffObjConfig::default();
let obj = obj::read::parse(include_object!("data/ppc/IObj.o"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj);
let symbol_idx =
obj.symbols.iter().position(|s| s.name == "Type2Text__10SObjectTagFUi").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);
}
#[test]
#[cfg(feature = "ppc")]
fn read_dwarf1_line_info() {
let diff_config = diff::DiffObjConfig::default();
let obj = obj::read::parse(include_object!("data/ppc/m_Do_hostIO.o"), &diff_config).unwrap();
let line_infos = obj
.sections
.iter()
.filter(|s| s.kind == SectionKind::Code)
.map(|s| s.line_info.clone())
.collect::<Vec<_>>();
insta::assert_debug_snapshot!(line_infos);
}
#[test]
#[cfg(feature = "ppc")]
fn diff_ppc() {
let diff_config = diff::DiffObjConfig::default();
let mapping_config = diff::MappingConfig::default();
let target_obj =
obj::read::parse(include_object!("data/ppc/CDamageVulnerability_target.o"), &diff_config)
.unwrap();
let base_obj =
obj::read::parse(include_object!("data/ppc/CDamageVulnerability_base.o"), &diff_config)
.unwrap();
let diff =
diff::diff_objs(Some(&target_obj), Some(&base_obj), None, &diff_config, &mapping_config)
.unwrap();
let target_diff = diff.left.as_ref().unwrap();
let base_diff = diff.right.as_ref().unwrap();
let sections_display = display::display_sections(
&target_obj,
&target_diff,
display::SymbolFilter::None,
false,
false,
true,
);
insta::assert_debug_snapshot!(sections_display);
let target_symbol_idx = target_obj
.symbols
.iter()
.position(|s| s.name == "WeaponHurts__20CDamageVulnerabilityCFRC11CWeaponModei")
.unwrap();
let target_symbol_diff = &target_diff.symbols[target_symbol_idx];
let base_symbol_idx = base_obj
.symbols
.iter()
.position(|s| s.name == "WeaponHurts__20CDamageVulnerabilityCFRC11CWeaponModei")
.unwrap();
let base_symbol_diff = &base_diff.symbols[base_symbol_idx];
assert_eq!(target_symbol_diff.target_symbol, Some(base_symbol_idx));
assert_eq!(base_symbol_diff.target_symbol, Some(target_symbol_idx));
insta::assert_debug_snapshot!((target_symbol_diff, base_symbol_diff));
}

View File

@ -0,0 +1,17 @@
use objdiff_core::{diff, obj};
mod common;
#[test]
#[cfg(feature = "x86")]
fn read_x86() {
let diff_config = diff::DiffObjConfig::default();
let obj = obj::read::parse(include_object!("data/x86/rtest.obj"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj);
let symbol_idx =
obj.symbols.iter().position(|s| s.name == "Type2Text__10SObjectTagFUi").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);
}

View File

@ -0,0 +1,57 @@
use objdiff_core::{
diff::{DiffObjConfig, SymbolDiff},
obj::Object,
};
pub fn display_diff(
obj: &Object,
diff: &SymbolDiff,
symbol_idx: usize,
diff_config: &DiffObjConfig,
) -> String {
let mut output = String::new();
for row in &diff.instruction_rows {
output.push('[');
let mut separator = false;
objdiff_core::diff::display::display_row(
&obj,
symbol_idx,
row,
&diff_config,
|text, diff_idx| {
if separator {
output.push_str(", ");
} else {
separator = true;
}
output.push_str(&format!("({:?}, {:?})", text, diff_idx.get()));
Ok(())
},
)
.unwrap();
output.push_str("]\n");
}
output
}
#[repr(C)]
pub struct AlignedAs<Align, Bytes: ?Sized> {
pub _align: [Align; 0],
pub bytes: Bytes,
}
#[macro_export]
macro_rules! include_bytes_align_as {
($align_ty:ty, $path:literal) => {{
static ALIGNED: &common::AlignedAs<$align_ty, [u8]> =
&common::AlignedAs { _align: [], bytes: *include_bytes!($path) };
&ALIGNED.bytes
}};
}
#[macro_export]
macro_rules! include_object {
($path:literal) => {
include_bytes_align_as!(u32, $path)
};
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,90 @@
---
source: objdiff-core/tests/arch_ppc.rs
expression: sections_display
---
[
SectionDisplay {
id: ".comm",
name: ".comm",
size: 0,
match_percent: None,
symbols: [
SectionDisplaySymbol {
symbol: 11,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 12,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 13,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 14,
is_mapping_symbol: false,
},
],
},
SectionDisplay {
id: ".ctors-0",
name: ".ctors",
size: 4,
match_percent: Some(
100.0,
),
symbols: [
SectionDisplaySymbol {
symbol: 2,
is_mapping_symbol: false,
},
],
},
SectionDisplay {
id: ".text-0",
name: ".text",
size: 3060,
match_percent: Some(
59.02353,
),
symbols: [
SectionDisplaySymbol {
symbol: 1,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 3,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 10,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 9,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 8,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 7,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 6,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 5,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 4,
is_mapping_symbol: false,
},
],
},
]

View File

@ -0,0 +1,40 @@
---
source: objdiff-core/tests/arch_ppc.rs
expression: line_infos
---
[
{
0: 13,
4: 16,
32: 17,
44: 18,
60: 20,
76: 21,
84: 23,
92: 25,
108: 26,
124: 27,
136: 28,
144: 29,
152: 31,
164: 34,
184: 35,
212: 39,
228: 40,
236: 41,
260: 43,
288: 44,
292: 45,
300: 48,
436: 0,
},
{
0: 48,
132: 35,
244: 26,
304: 22,
312: 23,
316: 24,
320: 0,
},
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,70 @@
---
source: objdiff-core/tests/arch_ppc.rs
expression: output
---
[(Address(0), None), (Spacing(4), None), (Opcode("srwi", 60), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Opaque("24")), None), (Eol, None)]
[(Address(4), None), (Spacing(4), None), (Opcode("cmpwi", 38), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Signed(-1)), None), (Eol, None)]
[(Address(8), None), (Spacing(4), None), (Opcode("bne", 43), None), (BranchDest(20), None), (Basic(" ~>"), Some(0)), (Eol, None)]
[(Address(12), None), (Spacing(4), None), (Opcode("li", 41), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Signed(-1)), None), (Eol, None)]
[(Address(16), None), (Spacing(4), None), (Opcode("b", 45), None), (BranchDest(32), None), (Basic(" ~>"), Some(1)), (Eol, None)]
[(Address(20), None), (Basic(" ~> "), Some(0)), (Opcode("lis", 42), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Unsigned(0)), None), (Eol, None)]
[(Address(24), None), (Spacing(4), None), (Opcode("addi", 41), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Signed(0)), None), (Eol, None)]
[(Address(28), None), (Spacing(4), None), (Opcode("lbzx", 94), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Opaque("r0")), None), (Eol, None)]
[(Address(32), None), (Basic(" ~> "), Some(1)), (Opcode("extrwi", 60), None), (Argument(Opaque("r5")), None), (Basic(", "), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Opaque("8")), None), (Basic(", "), None), (Argument(Opaque("8")), None), (Eol, None)]
[(Address(36), None), (Spacing(4), None), (Opcode("stb", 166), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), None), (Basic("@sda21"), None), (Eol, None)]
[(Address(40), None), (Spacing(4), None), (Opcode("cmpwi", 38), None), (Argument(Opaque("r5")), None), (Basic(", "), None), (Argument(Signed(-1)), None), (Eol, None)]
[(Address(44), None), (Spacing(4), None), (Opcode("bne", 43), None), (BranchDest(56), None), (Basic(" ~>"), Some(2)), (Eol, None)]
[(Address(48), None), (Spacing(4), None), (Opcode("li", 41), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Signed(-1)), None), (Eol, None)]
[(Address(52), None), (Spacing(4), None), (Opcode("b", 45), None), (BranchDest(68), None), (Basic(" ~>"), Some(3)), (Eol, None)]
[(Address(56), None), (Basic(" ~> "), Some(2)), (Opcode("lis", 42), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Unsigned(0)), None), (Eol, None)]
[(Address(60), None), (Spacing(4), None), (Opcode("addi", 41), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Signed(0)), None), (Eol, None)]
[(Address(64), None), (Spacing(4), None), (Opcode("lbzx", 94), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Opaque("r5")), None), (Eol, None)]
[(Address(68), None), (Basic(" ~> "), Some(3)), (Opcode("extrwi", 60), None), (Argument(Opaque("r5")), None), (Basic(", "), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Opaque("8")), None), (Basic(", "), None), (Argument(Opaque("16")), None), (Eol, None)]
[(Address(72), None), (Spacing(4), None), (Opcode("li", 41), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), None), (Basic("@sda21"), None), (Eol, None)]
[(Address(76), None), (Spacing(4), None), (Opcode("cmpwi", 38), None), (Argument(Opaque("r5")), None), (Basic(", "), None), (Argument(Signed(-1)), None), (Eol, None)]
[(Address(80), None), (Spacing(4), None), (Opcode("stb", 166), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Signed(1)), None), (Basic("("), None), (Argument(Opaque("r4")), None), (Basic(")"), None), (Eol, None)]
[(Address(84), None), (Spacing(4), None), (Opcode("bne", 43), None), (BranchDest(96), None), (Basic(" ~>"), Some(4)), (Eol, None)]
[(Address(88), None), (Spacing(4), None), (Opcode("li", 41), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Signed(-1)), None), (Eol, None)]
[(Address(92), None), (Spacing(4), None), (Opcode("b", 45), None), (BranchDest(108), None), (Basic(" ~>"), Some(5)), (Eol, None)]
[(Address(96), None), (Basic(" ~> "), Some(4)), (Opcode("lis", 42), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Unsigned(0)), None), (Eol, None)]
[(Address(100), None), (Spacing(4), None), (Opcode("addi", 41), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Signed(0)), None), (Eol, None)]
[(Address(104), None), (Spacing(4), None), (Opcode("lbzx", 94), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Opaque("r5")), None), (Eol, None)]
[(Address(108), None), (Basic(" ~> "), Some(5)), (Opcode("clrlwi", 60), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Opaque("24")), None), (Eol, None)]
[(Address(112), None), (Spacing(4), None), (Opcode("li", 41), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), None), (Basic("@sda21"), None), (Eol, None)]
[(Address(116), None), (Spacing(4), None), (Opcode("cmpwi", 38), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Signed(-1)), None), (Eol, None)]
[(Address(120), None), (Spacing(4), None), (Opcode("stb", 166), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Signed(2)), None), (Basic("("), None), (Argument(Opaque("r3")), None), (Basic(")"), None), (Eol, None)]
[(Address(124), None), (Spacing(4), None), (Opcode("bne", 43), None), (BranchDest(136), None), (Basic(" ~>"), Some(6)), (Eol, None)]
[(Address(128), None), (Spacing(4), None), (Opcode("li", 41), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Signed(-1)), None), (Eol, None)]
[(Address(132), None), (Spacing(4), None), (Opcode("b", 45), None), (BranchDest(148), None), (Basic(" ~>"), Some(7)), (Eol, None)]
[(Address(136), None), (Basic(" ~> "), Some(6)), (Opcode("lis", 42), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Unsigned(0)), None), (Eol, None)]
[(Address(140), None), (Spacing(4), None), (Opcode("addi", 41), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Signed(0)), None), (Eol, None)]
[(Address(144), None), (Spacing(4), None), (Opcode("lbzx", 94), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Opaque("r4")), None), (Eol, None)]
[(Address(148), None), (Basic(" ~> "), Some(7)), (Opcode("li", 41), None), (Argument(Opaque("r5")), None), (Basic(", "), None), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), None), (Basic("@sda21"), None), (Eol, None)]
[(Address(152), None), (Spacing(4), None), (Opcode("li", 41), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Signed(0)), None), (Eol, None)]
[(Address(156), None), (Spacing(4), None), (Opcode("stb", 166), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Signed(3)), None), (Basic("("), None), (Argument(Opaque("r5")), None), (Basic(")"), None), (Eol, None)]
[(Address(160), None), (Spacing(4), None), (Opcode("lis", 42), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Unsigned(0)), None), (Eol, None)]
[(Address(164), None), (Spacing(4), None), (Opcode("addi", 41), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Signed(0)), None), (Eol, None)]
[(Address(168), None), (Spacing(4), None), (Opcode("stb", 166), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Signed(4)), None), (Basic("("), None), (Argument(Opaque("r5")), None), (Basic(")"), None), (Eol, None)]
[(Address(172), None), (Spacing(4), None), (Opcode("li", 41), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Signed(45)), None), (Eol, None)]
[(Address(176), None), (Spacing(4), None), (Opcode("lbz", 162), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), None), (Basic("@sda21"), None), (Eol, None)]
[(Address(180), None), (Spacing(4), None), (Opcode("lbzx", 94), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Opaque("r3")), None), (Eol, None)]
[(Address(184), None), (Spacing(4), None), (Opcode("andi.", 66), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Unsigned(220)), None), (Eol, None)]
[(Address(188), None), (Spacing(4), None), (Opcode("bne", 43), None), (BranchDest(196), None), (Basic(" ~>"), Some(8)), (Eol, None)]
[(Address(192), None), (Spacing(4), None), (Opcode("stb", 166), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Signed(0)), None), (Basic("("), None), (Argument(Opaque("r5")), None), (Basic(")"), None), (Eol, None)]
[(Address(196), None), (Basic(" ~> "), Some(8)), (Opcode("lbzu", 163), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Signed(1)), None), (Basic("("), None), (Argument(Opaque("r5")), None), (Basic(")"), None), (Eol, None)]
[(Address(200), None), (Spacing(4), None), (Opcode("lbzx", 94), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Opaque("r3")), None), (Eol, None)]
[(Address(204), None), (Spacing(4), None), (Opcode("andi.", 66), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Unsigned(220)), None), (Eol, None)]
[(Address(208), None), (Spacing(4), None), (Opcode("bne", 43), None), (BranchDest(216), None), (Basic(" ~>"), Some(9)), (Eol, None)]
[(Address(212), None), (Spacing(4), None), (Opcode("stb", 166), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Signed(0)), None), (Basic("("), None), (Argument(Opaque("r5")), None), (Basic(")"), None), (Eol, None)]
[(Address(216), None), (Basic(" ~> "), Some(9)), (Opcode("lbzu", 163), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Signed(1)), None), (Basic("("), None), (Argument(Opaque("r5")), None), (Basic(")"), None), (Eol, None)]
[(Address(220), None), (Spacing(4), None), (Opcode("lbzx", 94), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Opaque("r3")), None), (Eol, None)]
[(Address(224), None), (Spacing(4), None), (Opcode("andi.", 66), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Unsigned(220)), None), (Eol, None)]
[(Address(228), None), (Spacing(4), None), (Opcode("bne", 43), None), (BranchDest(236), None), (Basic(" ~>"), Some(10)), (Eol, None)]
[(Address(232), None), (Spacing(4), None), (Opcode("stb", 166), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Signed(0)), None), (Basic("("), None), (Argument(Opaque("r5")), None), (Basic(")"), None), (Eol, None)]
[(Address(236), None), (Basic(" ~> "), Some(10)), (Opcode("lbzu", 163), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Signed(1)), None), (Basic("("), None), (Argument(Opaque("r5")), None), (Basic(")"), None), (Eol, None)]
[(Address(240), None), (Spacing(4), None), (Opcode("lbzx", 94), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Opaque("r4")), None), (Basic(", "), None), (Argument(Opaque("r3")), None), (Eol, None)]
[(Address(244), None), (Spacing(4), None), (Opcode("andi.", 66), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Argument(Unsigned(220)), None), (Eol, None)]
[(Address(248), None), (Spacing(4), None), (Opcode("bne", 43), None), (BranchDest(256), None), (Basic(" ~>"), Some(11)), (Eol, None)]
[(Address(252), None), (Spacing(4), None), (Opcode("stb", 166), None), (Argument(Opaque("r0")), None), (Basic(", "), None), (Argument(Signed(0)), None), (Basic("("), None), (Argument(Opaque("r5")), None), (Basic(")"), None), (Eol, None)]
[(Address(256), None), (Basic(" ~> "), Some(11)), (Opcode("li", 41), None), (Argument(Opaque("r3")), None), (Basic(", "), None), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), None), (Basic("@sda21"), None), (Eol, None)]
[(Address(260), None), (Spacing(4), None), (Opcode("blr", 47), None), (Eol, None)]

View File

@ -0,0 +1,489 @@
---
source: objdiff-core/tests/arch_ppc.rs
expression: obj
---
Object {
arch: ArchPpc {
extab: None,
},
symbols: [
Symbol {
name: "IObj.cpp",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Local),
align: None,
virtual_address: Some(
0,
),
},
Symbol {
name: "[.text]",
demangled_name: None,
address: 0,
size: 0,
kind: Section,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: Some(
2150895620,
),
},
Symbol {
name: "[.ctors]",
demangled_name: None,
address: 0,
size: 0,
kind: Section,
section: Some(
1,
),
flags: FlagSet(Local),
align: None,
virtual_address: Some(
2151461704,
),
},
Symbol {
name: "[.sbss]",
demangled_name: None,
address: 0,
size: 0,
kind: Section,
section: Some(
2,
),
flags: FlagSet(Local),
align: None,
virtual_address: Some(
2153420048,
),
},
Symbol {
name: "__sinit_IObj_cpp",
demangled_name: None,
address: 264,
size: 20,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: Some(
2150895884,
),
},
Symbol {
name: "text$52",
demangled_name: None,
address: 8,
size: 5,
kind: Object,
section: Some(
2,
),
flags: FlagSet(Local),
align: None,
virtual_address: Some(
2153420056,
),
},
Symbol {
name: "Type2Text__10SObjectTagFUi",
demangled_name: Some(
"SObjectTag::Type2Text(unsigned int)",
),
address: 0,
size: 264,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global),
align: None,
virtual_address: Some(
2150895620,
),
},
Symbol {
name: "gkInvalidObjectTag",
demangled_name: None,
address: 0,
size: 8,
kind: Object,
section: Some(
2,
),
flags: FlagSet(Global),
align: None,
virtual_address: Some(
2153420048,
),
},
Symbol {
name: "__upper_map",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Global),
align: None,
virtual_address: Some(
0,
),
},
Symbol {
name: "__ctype_map",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Global),
align: None,
virtual_address: Some(
0,
),
},
],
sections: [
Section {
id: ".text-0",
name: ".text",
address: 0,
size: 284,
kind: Code,
data: SectionData(
284,
),
flags: FlagSet(),
relocations: [
Relocation {
flags: Elf(
6,
),
address: 22,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
4,
),
address: 26,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
109,
),
address: 36,
target_symbol: 5,
addend: 0,
},
Relocation {
flags: Elf(
6,
),
address: 58,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
4,
),
address: 62,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
109,
),
address: 72,
target_symbol: 5,
addend: 0,
},
Relocation {
flags: Elf(
6,
),
address: 98,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
4,
),
address: 102,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
109,
),
address: 112,
target_symbol: 5,
addend: 0,
},
Relocation {
flags: Elf(
6,
),
address: 138,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
4,
),
address: 142,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
109,
),
address: 148,
target_symbol: 5,
addend: 0,
},
Relocation {
flags: Elf(
6,
),
address: 162,
target_symbol: 9,
addend: 0,
},
Relocation {
flags: Elf(
4,
),
address: 166,
target_symbol: 9,
addend: 0,
},
Relocation {
flags: Elf(
109,
),
address: 176,
target_symbol: 5,
addend: 0,
},
Relocation {
flags: Elf(
109,
),
address: 256,
target_symbol: 5,
addend: 0,
},
Relocation {
flags: Elf(
109,
),
address: 268,
target_symbol: 7,
addend: 0,
},
Relocation {
flags: Elf(
109,
),
address: 272,
target_symbol: 7,
addend: 0,
},
],
line_info: {},
virtual_address: Some(
2150895620,
),
},
Section {
id: ".ctors-0",
name: ".ctors",
address: 0,
size: 4,
kind: Data,
data: SectionData(
4,
),
flags: FlagSet(),
relocations: [
Relocation {
flags: Elf(
1,
),
address: 0,
target_symbol: 4,
addend: 0,
},
],
line_info: {},
virtual_address: Some(
2151461704,
),
},
Section {
id: ".sbss-0",
name: ".sbss",
address: 0,
size: 16,
kind: Bss,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: Some(
2153420048,
),
},
Section {
id: ".rela.text-0",
name: ".rela.text",
address: 0,
size: 216,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rela.ctors-0",
name: ".rela.ctors",
address: 0,
size: 12,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".symtab-0",
name: ".symtab",
address: 0,
size: 176,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".strtab-0",
name: ".strtab",
address: 0,
size: 105,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".shstrtab-0",
name: ".shstrtab",
address: 0,
size: 77,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".comment-0",
name: ".comment",
address: 0,
size: 132,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".note.split-0",
name: ".note.split",
address: 0,
size: 152,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
],
split_meta: Some(
SplitMeta {
generator: Some(
"decomp-toolkit 1.4.0",
),
module_name: Some(
"main",
),
module_id: Some(
0,
),
virtual_addresses: Some(
[
0,
0,
2150895620,
2151461704,
2153420048,
2150895884,
2153420056,
2150895620,
2153420048,
0,
0,
],
),
},
),
path: None,
timestamp: None,
}

View File

@ -1,29 +0,0 @@
package objdiff:core;
interface diff {
resource diff-config {
constructor();
set-property: func(id: string, value: string) -> result<_, string>;
get-property: func(id: string) -> result<string, string>;
}
resource object {
parse: static func(
data: list<u8>,
config: borrow<diff-config>,
) -> result<object, string>;
}
run-diff: func(
left: option<borrow<object>>,
right: option<borrow<object>>,
config: borrow<diff-config>,
) -> result<list<u8>, string>;
}
world api {
export diff;
export init: func() -> result<_, string>;
export version: func() -> string;
}

View File

@ -38,7 +38,7 @@ float-ord = "0.3"
font-kit = "0.14" font-kit = "0.14"
globset = { version = "0.4", features = ["serde1"] } globset = { version = "0.4", features = ["serde1"] }
log = "0.4" log = "0.4"
objdiff-core = { path = "../objdiff-core", features = ["all"] } objdiff-core = { path = "../objdiff-core", features = ["ppc", "arm", "arm64", "mips", "std", "config", "dwarf", "bindings", "serde", "build"] }
open = "5.3" open = "5.3"
png = "0.17" png = "0.17"
pollster = "0.4" pollster = "0.4"
@ -52,6 +52,8 @@ shell-escape = "0.1"
strum = { version = "0.26", features = ["derive"] } strum = { version = "0.26", features = ["derive"] }
time = { version = "0.3", features = ["formatting", "local-offset"] } time = { version = "0.3", features = ["formatting", "local-offset"] }
typed-path = "0.10" typed-path = "0.10"
winit = { version = "0.30", features = ["wayland-csd-adwaita"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Keep version in sync with egui # Keep version in sync with egui
[dependencies.eframe] [dependencies.eframe]
@ -81,15 +83,6 @@ winapi = "0.3"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
exec = "0.3" exec = "0.3"
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1"
tracing-wasm = "0.2"
[build-dependencies] [build-dependencies]
anyhow = "1.0" anyhow = "1.0"

View File

@ -783,7 +783,8 @@ impl eframe::App for App {
let mut action = None; let mut action = None;
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
action = diff_view_ui(ui, diff_state, appearance); let state = state.read().unwrap();
action = diff_view_ui(ui, diff_state, appearance, &state.config.diff_obj_config);
}); });
project_window(ctx, state, show_project_config, config_state, appearance); project_window(ctx, state, show_project_config, config_state, appearance);

View File

@ -273,6 +273,7 @@ impl DiffObjConfigV1 {
}, },
space_between_args: self.space_between_args, space_between_args: self.space_between_args,
combine_data_sections: self.combine_data_sections, combine_data_sections: self.combine_data_sections,
combine_text_sections: false,
x86_formatter: self.x86_formatter, x86_formatter: self.x86_formatter,
mips_abi: self.mips_abi, mips_abi: self.mips_abi,
mips_instr_category: self.mips_instr_category, mips_instr_category: self.mips_instr_category,

View File

@ -1,3 +1,4 @@
#![allow(clippy::too_many_arguments)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
mod app; mod app;
@ -37,8 +38,6 @@ fn load_icon() -> Result<egui::IconData> {
const APP_NAME: &str = "objdiff"; const APP_NAME: &str = "objdiff";
// When compiling natively:
#[cfg(not(target_arch = "wasm32"))]
fn main() -> ExitCode { fn main() -> ExitCode {
// Log to stdout (if you run with `RUST_LOG=debug`). // Log to stdout (if you run with `RUST_LOG=debug`).
tracing_subscriber::fmt() tracing_subscriber::fmt()
@ -229,21 +228,3 @@ fn run_eframe(
}), }),
) )
} }
// when compiling to web using trunk.
#[cfg(target_arch = "wasm32")]
fn main() {
// Make sure panics are logged using `console.error`.
console_error_panic_hook::set_once();
// Redirect tracing to console.log and friends:
tracing_wasm::set_as_global_default();
let web_options = eframe::WebOptions::default();
eframe::start_web(
"the_canvas_id", // hardcode it
web_options,
Box::new(|cc| Box::new(eframe_template::TemplateApp::new(cc))),
)
.expect("failed to start eframe");
}

View File

@ -1,13 +1,9 @@
use std::{ use std::{cmp::min, default::Default, mem::take};
cmp::{min, Ordering},
default::Default,
mem::take,
};
use egui::{text::LayoutJob, Label, Sense, Widget}; use egui::{text::LayoutJob, Label, Sense, Widget};
use objdiff_core::{ use objdiff_core::{
diff::{ObjDataDiff, ObjDataDiffKind, ObjDataRelocDiff}, diff::{DataDiff, DataDiffKind, DataRelocationDiff},
obj::ObjInfo, obj::Object,
}; };
use crate::views::{appearance::Appearance, write_text}; use crate::views::{appearance::Appearance, write_text};
@ -16,108 +12,110 @@ pub(crate) const BYTES_PER_ROW: usize = 16;
fn data_row_hover_ui( fn data_row_hover_ui(
ui: &mut egui::Ui, ui: &mut egui::Ui,
obj: &ObjInfo, _obj: &Object,
diffs: &[(ObjDataDiff, Vec<ObjDataRelocDiff>)], _diffs: &[(DataDiff, Vec<DataRelocationDiff>)],
appearance: &Appearance, _appearance: &Appearance,
) { ) {
ui.scope(|ui| { ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
let reloc_diffs = diffs.iter().flat_map(|(_, reloc_diffs)| reloc_diffs); // TODO
let mut prev_reloc = None; // let reloc_diffs = diffs.iter().flat_map(|(_, reloc_diffs)| reloc_diffs);
for reloc_diff in reloc_diffs { // let mut prev_reloc = None;
let reloc = &reloc_diff.reloc; // for reloc_diff in reloc_diffs {
if prev_reloc == Some(reloc) { // let reloc = &reloc_diff.reloc;
// Avoid showing consecutive duplicate relocations. // if prev_reloc == Some(reloc) {
// We do this because a single relocation can span across multiple diffs if the // // Avoid showing consecutive duplicate relocations.
// bytes in the relocation changed (e.g. first byte is added, second is unchanged). // // We do this because a single relocation can span across multiple diffs if the
continue; // // bytes in the relocation changed (e.g. first byte is added, second is unchanged).
} // continue;
prev_reloc = Some(reloc); // }
// prev_reloc = Some(reloc);
let color = get_color_for_diff_kind(reloc_diff.kind, appearance); //
// let color = get_color_for_diff_kind(reloc_diff.kind, appearance);
// TODO: Most of this code is copy-pasted from ins_hover_ui. //
// Try to separate this out into a shared function. // // TODO: Most of this code is copy-pasted from ins_hover_ui.
ui.label(format!("Relocation type: {}", obj.arch.display_reloc(reloc.flags))); // // Try to separate this out into a shared function.
ui.label(format!("Relocation address: {:x}", reloc.address)); // ui.label(format!("Relocation type: {}", obj.arch.display_reloc(reloc.flags)));
let addend_str = match reloc.addend.cmp(&0i64) { // ui.label(format!("Relocation address: {:x}", reloc.address));
Ordering::Greater => format!("+{:x}", reloc.addend), // let addend_str = match reloc.addend.cmp(&0i64) {
Ordering::Less => format!("-{:x}", -reloc.addend), // Ordering::Greater => format!("+{:x}", reloc.addend),
_ => "".to_string(), // Ordering::Less => format!("-{:x}", -reloc.addend),
}; // _ => "".to_string(),
ui.colored_label(color, format!("Name: {}{}", reloc.target.name, addend_str)); // };
if let Some(orig_section_index) = reloc.target.orig_section_index { // ui.colored_label(color, format!("Name: {}{}", reloc.target.name, addend_str));
if let Some(section) = // if let Some(orig_section_index) = reloc.target.orig_section_index {
obj.sections.iter().find(|s| s.orig_index == orig_section_index) // if let Some(section) =
{ // obj.sections.iter().find(|s| s.orig_index == orig_section_index)
ui.colored_label(color, format!("Section: {}", section.name)); // {
} // ui.colored_label(color, format!("Section: {}", section.name));
ui.colored_label( // }
color, // ui.colored_label(
format!("Address: {:x}{}", reloc.target.address, addend_str), // color,
); // format!("Address: {:x}{}", reloc.target.address, addend_str),
ui.colored_label(color, format!("Size: {:x}", reloc.target.size)); // );
if reloc.addend >= 0 && reloc.target.bytes.len() > reloc.addend as usize {} // ui.colored_label(color, format!("Size: {:x}", reloc.target.size));
} else { // if reloc.addend >= 0 && reloc.target.bytes.len() > reloc.addend as usize {}
ui.colored_label(color, "Extern".to_string()); // } else {
} // ui.colored_label(color, "Extern".to_string());
} // }
// }
}); });
} }
fn data_row_context_menu(ui: &mut egui::Ui, diffs: &[(ObjDataDiff, Vec<ObjDataRelocDiff>)]) { fn data_row_context_menu(ui: &mut egui::Ui, _diffs: &[(DataDiff, Vec<DataRelocationDiff>)]) {
ui.scope(|ui| { ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
let reloc_diffs = diffs.iter().flat_map(|(_, reloc_diffs)| reloc_diffs); // TODO
let mut prev_reloc = None; // let reloc_diffs = diffs.iter().flat_map(|(_, reloc_diffs)| reloc_diffs);
for reloc_diff in reloc_diffs { // let mut prev_reloc = None;
let reloc = &reloc_diff.reloc; // for reloc_diff in reloc_diffs {
if prev_reloc == Some(reloc) { // let reloc = &reloc_diff.reloc;
// Avoid showing consecutive duplicate relocations. // if prev_reloc == Some(reloc) {
// We do this because a single relocation can span across multiple diffs if the // // Avoid showing consecutive duplicate relocations.
// bytes in the relocation changed (e.g. first byte is added, second is unchanged). // // We do this because a single relocation can span across multiple diffs if the
continue; // // bytes in the relocation changed (e.g. first byte is added, second is unchanged).
} // continue;
prev_reloc = Some(reloc); // }
// prev_reloc = Some(reloc);
// TODO: This code is copy-pasted from ins_context_menu. //
// Try to separate this out into a shared function. // // TODO: This code is copy-pasted from ins_context_menu.
if let Some(name) = &reloc.target.demangled_name { // // Try to separate this out into a shared function.
if ui.button(format!("Copy \"{name}\"")).clicked() { // if let Some(name) = &reloc.target.demangled_name {
ui.output_mut(|output| output.copied_text.clone_from(name)); // if ui.button(format!("Copy \"{name}\"")).clicked() {
ui.close_menu(); // ui.output_mut(|output| output.copied_text.clone_from(name));
} // ui.close_menu();
} // }
if ui.button(format!("Copy \"{}\"", reloc.target.name)).clicked() { // }
ui.output_mut(|output| output.copied_text.clone_from(&reloc.target.name)); // if ui.button(format!("Copy \"{}\"", reloc.target.name)).clicked() {
ui.close_menu(); // ui.output_mut(|output| output.copied_text.clone_from(&reloc.target.name));
} // ui.close_menu();
} // }
// }
}); });
} }
fn get_color_for_diff_kind(diff_kind: ObjDataDiffKind, appearance: &Appearance) -> egui::Color32 { fn get_color_for_diff_kind(diff_kind: DataDiffKind, appearance: &Appearance) -> egui::Color32 {
match diff_kind { match diff_kind {
ObjDataDiffKind::None => appearance.text_color, DataDiffKind::None => appearance.text_color,
ObjDataDiffKind::Replace => appearance.replace_color, DataDiffKind::Replace => appearance.replace_color,
ObjDataDiffKind::Delete => appearance.delete_color, DataDiffKind::Delete => appearance.delete_color,
ObjDataDiffKind::Insert => appearance.insert_color, DataDiffKind::Insert => appearance.insert_color,
} }
} }
pub(crate) fn data_row_ui( pub(crate) fn data_row_ui(
ui: &mut egui::Ui, ui: &mut egui::Ui,
obj: Option<&ObjInfo>, obj: Option<&Object>,
address: usize, address: usize,
diffs: &[(ObjDataDiff, Vec<ObjDataRelocDiff>)], diffs: &[(DataDiff, Vec<DataRelocationDiff>)],
appearance: &Appearance, appearance: &Appearance,
) { ) {
if diffs.iter().any(|(dd, rds)| { if diffs.iter().any(|(dd, rds)| {
dd.kind != ObjDataDiffKind::None || rds.iter().any(|rd| rd.kind != ObjDataDiffKind::None) dd.kind != DataDiffKind::None || rds.iter().any(|rd| rd.kind != DataDiffKind::None)
}) { }) {
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color); ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
} }
@ -145,7 +143,7 @@ pub(crate) fn data_row_ui(
for byte in &diff.data { for byte in &diff.data {
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.iter().find(|reloc_diff| {
reloc_diff.kind != ObjDataDiffKind::None reloc_diff.kind != DataDiffKind::None
&& reloc_diff.range.contains(&cur_addr_actual) && reloc_diff.range.contains(&cur_addr_actual)
}) { }) {
byte_color = get_color_for_diff_kind(reloc_diff.kind, appearance); byte_color = get_color_for_diff_kind(reloc_diff.kind, appearance);
@ -200,11 +198,11 @@ pub(crate) fn data_row_ui(
} }
pub(crate) fn split_diffs( pub(crate) fn split_diffs(
diffs: &[ObjDataDiff], diffs: &[DataDiff],
reloc_diffs: &[ObjDataRelocDiff], reloc_diffs: &[DataRelocationDiff],
) -> Vec<Vec<(ObjDataDiff, Vec<ObjDataRelocDiff>)>> { ) -> Vec<Vec<(DataDiff, Vec<DataRelocationDiff>)>> {
let mut split_diffs = Vec::<Vec<(ObjDataDiff, Vec<ObjDataRelocDiff>)>>::new(); let mut split_diffs = Vec::<Vec<(DataDiff, Vec<DataRelocationDiff>)>>::new();
let mut row_diffs = Vec::<(ObjDataDiff, Vec<ObjDataRelocDiff>)>::new(); let mut row_diffs = Vec::<(DataDiff, Vec<DataRelocationDiff>)>::new();
// The offset shown on the side of the GUI, shifted by insertions/deletions. // The offset shown on the side of the GUI, shifted by insertions/deletions.
let mut cur_addr = 0usize; let mut cur_addr = 0usize;
// The offset into the actual bytes of the section on this side, ignoring differences. // The offset into the actual bytes of the section on this side, ignoring differences.
@ -216,7 +214,7 @@ pub(crate) fn split_diffs(
let mut remaining_in_row = BYTES_PER_ROW - (cur_addr % BYTES_PER_ROW); let mut remaining_in_row = BYTES_PER_ROW - (cur_addr % BYTES_PER_ROW);
let len = min(remaining_len, remaining_in_row); let len = min(remaining_len, remaining_in_row);
let data_diff = ObjDataDiff { let data_diff = DataDiff {
data: if diff.data.is_empty() { data: if diff.data.is_empty() {
Vec::new() Vec::new()
} else { } else {
@ -226,7 +224,7 @@ pub(crate) fn split_diffs(
len, len,
symbol: String::new(), // TODO symbol: String::new(), // TODO
}; };
let row_reloc_diffs: Vec<ObjDataRelocDiff> = if diff.data.is_empty() { let row_reloc_diffs: Vec<DataRelocationDiff> = if diff.data.is_empty() {
Vec::new() Vec::new()
} else { } else {
let diff_range = cur_addr_actual + cur_len..cur_addr_actual + cur_len + len; let diff_range = cur_addr_actual + cur_len..cur_addr_actual + cur_len + len;

View File

@ -1,8 +1,8 @@
use egui::{Id, Layout, RichText, ScrollArea, TextEdit, Ui, Widget}; use egui::{Id, Layout, RichText, ScrollArea, TextEdit, Ui, Widget};
use objdiff_core::{ use objdiff_core::{
build::BuildStatus, build::BuildStatus,
diff::{ObjDiff, ObjSectionDiff, ObjSymbolDiff}, diff::{display::SymbolFilter, DiffObjConfig, ObjectDiff, SectionDiff, SymbolDiff},
obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef}, obj::{Object, Section, SectionKind, Symbol},
}; };
use time::format_description; use time::format_description;
@ -16,30 +16,30 @@ use crate::{
function_diff::{asm_col_ui, FunctionDiffContext}, function_diff::{asm_col_ui, FunctionDiffContext},
symbol_diff::{ symbol_diff::{
match_color_for_symbol, symbol_list_ui, DiffViewAction, DiffViewNavigation, match_color_for_symbol, symbol_list_ui, DiffViewAction, DiffViewNavigation,
DiffViewState, SymbolDiffContext, SymbolFilter, SymbolRefByName, View, DiffViewState, SymbolDiffContext, SymbolRefByName, View,
}, },
}, },
}; };
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
enum SelectedSymbol { enum SelectedSymbol {
Symbol(SymbolRef), Symbol(usize),
Section(usize), Section(usize),
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct DiffColumnContext<'a> { struct DiffColumnContext<'a> {
status: &'a BuildStatus, status: &'a BuildStatus,
obj: Option<&'a (ObjInfo, ObjDiff)>, obj: Option<&'a (Object, ObjectDiff)>,
section: Option<(&'a ObjSection, &'a ObjSectionDiff)>, section: Option<(&'a Section, &'a SectionDiff, usize)>,
symbol: Option<(&'a ObjSymbol, &'a ObjSymbolDiff)>, symbol: Option<(&'a Symbol, &'a SymbolDiff, usize)>,
} }
impl<'a> DiffColumnContext<'a> { impl<'a> DiffColumnContext<'a> {
pub fn new( pub fn new(
view: View, view: View,
status: &'a BuildStatus, status: &'a BuildStatus,
obj: Option<&'a (ObjInfo, ObjDiff)>, obj: Option<&'a (Object, ObjectDiff)>,
selected_symbol: Option<&SymbolRefByName>, selected_symbol: Option<&SymbolRefByName>,
) -> Self { ) -> Self {
let selected_symbol = match view { let selected_symbol = match view {
@ -57,15 +57,18 @@ impl<'a> DiffColumnContext<'a> {
}; };
let (section, symbol) = match (obj, selected_symbol) { let (section, symbol) = match (obj, selected_symbol) {
(Some((obj, obj_diff)), Some(SelectedSymbol::Symbol(symbol_ref))) => { (Some((obj, obj_diff)), Some(SelectedSymbol::Symbol(symbol_ref))) => {
let (section, symbol) = obj.section_symbol(symbol_ref); let symbol = &obj.symbols[symbol_ref];
( (
section.map(|s| (s, obj_diff.section_diff(symbol_ref.section_idx))), symbol.section.map(|section_idx| {
Some((symbol, obj_diff.symbol_diff(symbol_ref))), (&obj.sections[section_idx], &obj_diff.sections[section_idx], section_idx)
}),
Some((symbol, &obj_diff.symbols[symbol_ref], symbol_ref)),
) )
} }
(Some((obj, obj_diff)), Some(SelectedSymbol::Section(section_idx))) => { (Some((obj, obj_diff)), Some(SelectedSymbol::Section(section_idx))) => (
(Some((&obj.sections[section_idx], obj_diff.section_diff(section_idx))), None) Some((&obj.sections[section_idx], &obj_diff.sections[section_idx], section_idx)),
} None,
),
_ => (None, None), _ => (None, None),
}; };
Self { status, obj, section, symbol } Self { status, obj, section, symbol }
@ -77,8 +80,8 @@ impl<'a> DiffColumnContext<'a> {
#[inline] #[inline]
pub fn id(&self) -> Option<&str> { pub fn id(&self) -> Option<&str> {
self.symbol self.symbol
.map(|(symbol, _)| symbol.name.as_str()) .map(|(symbol, _, _)| symbol.name.as_str())
.or_else(|| self.section.map(|(section, _)| section.name.as_str())) .or_else(|| self.section.map(|(section, _, _)| section.name.as_str()))
} }
} }
@ -87,6 +90,7 @@ pub fn diff_view_ui(
ui: &mut Ui, ui: &mut Ui,
state: &DiffViewState, state: &DiffViewState,
appearance: &Appearance, appearance: &Appearance,
diff_config: &DiffObjConfig,
) -> Option<DiffViewAction> { ) -> Option<DiffViewAction> {
let mut ret = None; let mut ret = None;
let Some(result) = &state.build else { let Some(result) = &state.build else {
@ -113,12 +117,15 @@ pub fn diff_view_ui(
right_symbol: state.symbol_state.right_symbol.clone(), right_symbol: state.symbol_state.right_symbol.clone(),
}; };
let mut navigation = current_navigation.clone(); let mut navigation = current_navigation.clone();
if let Some((_symbol, symbol_diff)) = left_ctx.symbol { if let Some((_symbol, symbol_diff, _symbol_idx)) = left_ctx.symbol {
// If a matching symbol appears, select it // If a matching symbol appears, select it
if !right_ctx.has_symbol() { if !right_ctx.has_symbol() {
if let Some(target_symbol_ref) = symbol_diff.target_symbol { if let Some(target_symbol_ref) = symbol_diff.target_symbol {
let (target_section, target_symbol) = let (right_obj, _) = right_ctx.obj.unwrap();
right_ctx.obj.unwrap().0.section_symbol(target_symbol_ref); let target_symbol = &right_obj.symbols[target_symbol_ref];
let target_section = target_symbol
.section
.and_then(|section_idx| right_obj.sections.get(section_idx));
navigation.right_symbol = Some(SymbolRefByName::new(target_symbol, target_section)); navigation.right_symbol = Some(SymbolRefByName::new(target_symbol, target_section));
} }
} }
@ -129,12 +136,15 @@ pub fn diff_view_ui(
// Clear selection if symbol goes missing // Clear selection if symbol goes missing
navigation.left_symbol = None; navigation.left_symbol = None;
} }
if let Some((_symbol, symbol_diff)) = right_ctx.symbol { if let Some((_symbol, symbol_diff, _symbol_idx)) = right_ctx.symbol {
// If a matching symbol appears, select it // If a matching symbol appears, select it
if !left_ctx.has_symbol() { if !left_ctx.has_symbol() {
if let Some(target_symbol_ref) = symbol_diff.target_symbol { if let Some(target_symbol_ref) = symbol_diff.target_symbol {
let (target_section, target_symbol) = let (left_obj, _) = left_ctx.obj.unwrap();
left_ctx.obj.unwrap().0.section_symbol(target_symbol_ref); let target_symbol = &left_obj.symbols[target_symbol_ref];
let target_section = target_symbol
.section
.and_then(|section_idx| left_obj.sections.get(section_idx));
navigation.left_symbol = Some(SymbolRefByName::new(target_symbol, target_section)); navigation.left_symbol = Some(SymbolRefByName::new(target_symbol, target_section));
} }
} }
@ -170,7 +180,7 @@ pub fn diff_view_ui(
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff())); ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
} }
if let Some((symbol, _)) = left_ctx.symbol { if let Some((symbol, _, _)) = left_ctx.symbol {
ui.separator(); ui.separator();
if ui if ui
.add_enabled( .add_enabled(
@ -210,13 +220,13 @@ pub fn diff_view_ui(
.color(appearance.replace_color), .color(appearance.replace_color),
); );
} }
} else if let Some((symbol, _)) = left_ctx.symbol { } else if let Some((symbol, _, _)) = left_ctx.symbol {
ui.label( ui.label(
RichText::new(symbol.demangled_name.as_deref().unwrap_or(&symbol.name)) RichText::new(symbol.demangled_name.as_deref().unwrap_or(&symbol.name))
.font(appearance.code_font.clone()) .font(appearance.code_font.clone())
.color(appearance.highlight_color), .color(appearance.highlight_color),
); );
} else if let Some((section, _)) = left_ctx.section { } else if let Some((section, _, _)) = left_ctx.section {
ui.label( ui.label(
RichText::new(section.name.clone()) RichText::new(section.name.clone())
.font(appearance.code_font.clone()) .font(appearance.code_font.clone())
@ -331,13 +341,13 @@ pub fn diff_view_ui(
.color(appearance.replace_color), .color(appearance.replace_color),
); );
} }
} else if let Some((symbol, _)) = right_ctx.symbol { } else if let Some((symbol, _, _)) = right_ctx.symbol {
ui.label( ui.label(
RichText::new(symbol.demangled_name.as_deref().unwrap_or(&symbol.name)) RichText::new(symbol.demangled_name.as_deref().unwrap_or(&symbol.name))
.font(appearance.code_font.clone()) .font(appearance.code_font.clone())
.color(appearance.highlight_color), .color(appearance.highlight_color),
); );
} else if let Some((section, _)) = right_ctx.section { } else if let Some((section, _, _)) = right_ctx.section {
ui.label( ui.label(
RichText::new(section.name.clone()) RichText::new(section.name.clone())
.font(appearance.code_font.clone()) .font(appearance.code_font.clone())
@ -359,16 +369,29 @@ pub fn diff_view_ui(
// Third row // Third row
ui.horizontal(|ui| { ui.horizontal(|ui| {
if let Some((_, symbol_diff)) = right_ctx.symbol { if let Some((_, symbol_diff, _symbol_idx)) = right_ctx.symbol {
let mut needs_separator = false;
if let Some(match_percent) = symbol_diff.match_percent { if let Some(match_percent) = symbol_diff.match_percent {
ui.label( let response = ui.label(
RichText::new(format!("{:.0}%", match_percent.floor())) RichText::new(format!("{:.2}%", match_percent))
.font(appearance.code_font.clone()) .font(appearance.code_font.clone())
.color(match_color_for_symbol(match_percent, appearance)), .color(match_color_for_symbol(match_percent, appearance)),
); );
if let Some((diff_score, max_score)) = symbol_diff.diff_score {
response.on_hover_ui_at_pointer(|ui| {
ui.label(
RichText::new(format!("Score: {}/{}", diff_score, max_score))
.font(appearance.code_font.clone())
.color(appearance.text_color),
);
});
}
needs_separator = true;
} }
if state.current_view == View::FunctionDiff && left_ctx.has_symbol() { if state.current_view == View::FunctionDiff && left_ctx.has_symbol() {
if needs_separator {
ui.separator(); ui.separator();
}
if ui if ui
.button("Change base") .button("Change base")
.on_hover_text_at_pointer( .on_hover_text_at_pointer(
@ -413,17 +436,17 @@ pub fn diff_view_ui(
View::FunctionDiff, View::FunctionDiff,
Some((left_obj, left_diff)), Some((left_obj, left_diff)),
Some((right_obj, right_diff)), Some((right_obj, right_diff)),
Some((_, left_symbol_diff)), Some((_, left_symbol_diff, left_symbol_idx)),
Some((_, right_symbol_diff)), Some((_, right_symbol_diff, right_symbol_idx)),
) = (state.current_view, left_ctx.obj, right_ctx.obj, left_ctx.symbol, right_ctx.symbol) ) = (state.current_view, left_ctx.obj, right_ctx.obj, left_ctx.symbol, right_ctx.symbol)
{ {
// Joint diff view // Joint diff view
hotkeys::check_scroll_hotkeys(ui, true); hotkeys::check_scroll_hotkeys(ui, true);
if left_symbol_diff.instructions.len() != right_symbol_diff.instructions.len() { if left_symbol_diff.instruction_rows.len() != right_symbol_diff.instruction_rows.len() {
ui.label("Instruction count mismatch"); ui.label("Instruction count mismatch");
return; return;
} }
let instructions_len = left_symbol_diff.instructions.len(); let instructions_len = left_symbol_diff.instruction_rows.len();
render_table( render_table(
ui, ui,
available_width, available_width,
@ -437,10 +460,11 @@ pub fn diff_view_ui(
FunctionDiffContext { FunctionDiffContext {
obj: left_obj, obj: left_obj,
diff: left_diff, diff: left_diff,
symbol_ref: Some(left_symbol_diff.symbol_ref), symbol_ref: Some(left_symbol_idx),
}, },
appearance, appearance,
&state.function_state, &state.function_state,
diff_config,
column, column,
) { ) {
ret = Some(action); ret = Some(action);
@ -451,10 +475,11 @@ pub fn diff_view_ui(
FunctionDiffContext { FunctionDiffContext {
obj: right_obj, obj: right_obj,
diff: right_diff, diff: right_diff,
symbol_ref: Some(right_symbol_diff.symbol_ref), symbol_ref: Some(right_symbol_idx),
}, },
appearance, appearance,
&state.function_state, &state.function_state,
diff_config,
column, column,
) { ) {
ret = Some(action); ret = Some(action);
@ -469,8 +494,8 @@ pub fn diff_view_ui(
View::DataDiff, View::DataDiff,
Some((left_obj, _left_diff)), Some((left_obj, _left_diff)),
Some((right_obj, _right_diff)), Some((right_obj, _right_diff)),
Some((_left_section, left_section_diff)), Some((_left_section, left_section_diff, _left_symbol_idx)),
Some((_right_section, right_section_diff)), Some((_right_section, right_section_diff, _right_symbol_idx)),
) = ) =
(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)
{ {
@ -522,6 +547,7 @@ pub fn diff_view_ui(
right_ctx, right_ctx,
available_width, available_width,
open_sections.0, open_sections.0,
diff_config,
) { ) {
ret = Some(action); ret = Some(action);
} }
@ -535,6 +561,7 @@ pub fn diff_view_ui(
left_ctx, left_ctx,
available_width, available_width,
open_sections.1, open_sections.1,
diff_config,
) { ) {
ret = Some(action); ret = Some(action);
} }
@ -547,7 +574,6 @@ pub fn diff_view_ui(
} }
#[must_use] #[must_use]
#[allow(clippy::too_many_arguments)]
fn diff_col_ui( fn diff_col_ui(
ui: &mut Ui, ui: &mut Ui,
state: &DiffViewState, state: &DiffViewState,
@ -557,14 +583,15 @@ fn diff_col_ui(
other_ctx: DiffColumnContext, other_ctx: DiffColumnContext,
available_width: f32, available_width: f32,
open_sections: Option<bool>, open_sections: Option<bool>,
diff_config: &DiffObjConfig,
) -> Option<DiffViewAction> { ) -> Option<DiffViewAction> {
let mut ret = None; let mut ret = None;
if !ctx.status.success { if !ctx.status.success {
build_log_ui(ui, ctx.status, appearance); build_log_ui(ui, ctx.status, appearance);
} else if let Some((obj, diff)) = ctx.obj { } else if let Some((obj, diff)) = ctx.obj {
if let Some((_symbol, symbol_diff)) = ctx.symbol { if let Some((_symbol, symbol_diff, symbol_idx)) = ctx.symbol {
hotkeys::check_scroll_hotkeys(ui, false); hotkeys::check_scroll_hotkeys(ui, false);
let ctx = FunctionDiffContext { obj, diff, symbol_ref: Some(symbol_diff.symbol_ref) }; let ctx = FunctionDiffContext { obj, diff, symbol_ref: Some(symbol_idx) };
if state.current_view == View::ExtabDiff { if state.current_view == View::ExtabDiff {
extab_ui(ui, ctx, appearance, column); extab_ui(ui, ctx, appearance, column);
} else { } else {
@ -573,11 +600,16 @@ fn diff_col_ui(
available_width / 2.0, available_width / 2.0,
1, 1,
appearance.code_font.size, appearance.code_font.size,
symbol_diff.instructions.len(), symbol_diff.instruction_rows.len(),
|row, column| { |row, column| {
if let Some(action) = if let Some(action) = asm_col_ui(
asm_col_ui(row, ctx, appearance, &state.function_state, column) row,
{ ctx,
appearance,
&state.function_state,
diff_config,
column,
) {
ret = Some(action); ret = Some(action);
} }
if row.response().clicked() { if row.response().clicked() {
@ -586,7 +618,7 @@ fn diff_col_ui(
}, },
); );
} }
} else if let Some((_section, section_diff)) = ctx.section { } else if let Some((_section, section_diff, _section_idx)) = ctx.section {
hotkeys::check_scroll_hotkeys(ui, false); hotkeys::check_scroll_hotkeys(ui, false);
let total_bytes = let total_bytes =
section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len); section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
@ -610,8 +642,8 @@ fn diff_col_ui(
}, },
); );
} else if let ( } else if let (
Some((other_section, _other_section_diff)), Some((other_section, _other_section_diff, _other_section_idx)),
Some((other_symbol, other_symbol_diff)), Some((other_symbol, _other_symbol_diff, other_symbol_idx)),
) = (other_ctx.section, other_ctx.symbol) ) = (other_ctx.section, other_ctx.symbol)
{ {
if let Some(action) = symbol_list_ui( if let Some(action) = symbol_list_ui(
@ -619,7 +651,7 @@ fn diff_col_ui(
SymbolDiffContext { obj, diff }, SymbolDiffContext { obj, diff },
None, None,
&state.symbol_state, &state.symbol_state,
SymbolFilter::Mapping(other_symbol_diff.symbol_ref, None), SymbolFilter::Mapping(other_symbol_idx, None),
appearance, appearance,
column, column,
open_sections, open_sections,
@ -634,7 +666,7 @@ fn diff_col_ui(
) => { ) => {
ret = Some(DiffViewAction::SetMapping( ret = Some(DiffViewAction::SetMapping(
match other_section.kind { match other_section.kind {
ObjSectionKind::Code => View::FunctionDiff, SectionKind::Code => View::FunctionDiff,
_ => View::SymbolDiff, _ => View::SymbolDiff,
}, },
left_symbol_ref, left_symbol_ref,
@ -650,7 +682,7 @@ fn diff_col_ui(
) => { ) => {
ret = Some(DiffViewAction::SetMapping( ret = Some(DiffViewAction::SetMapping(
match other_section.kind { match other_section.kind {
ObjSectionKind::Code => View::FunctionDiff, SectionKind::Code => View::FunctionDiff,
_ => View::SymbolDiff, _ => View::SymbolDiff,
}, },
SymbolRefByName::new(other_symbol, Some(other_section)), SymbolRefByName::new(other_symbol, Some(other_section)),
@ -725,17 +757,10 @@ fn missing_obj_ui(ui: &mut Ui, appearance: &Appearance) {
}); });
} }
fn find_symbol(obj: &ObjInfo, selected_symbol: &SymbolRefByName) -> Option<SymbolRef> { fn find_symbol(obj: &Object, selected_symbol: &SymbolRefByName) -> Option<usize> {
for (section_idx, section) in obj.sections.iter().enumerate() { obj.symbols.iter().position(|symbol| symbol.name == selected_symbol.symbol_name)
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
if symbol.name == selected_symbol.symbol_name {
return Some(SymbolRef { section_idx, symbol_idx });
}
}
}
None
} }
fn find_section(obj: &ObjInfo, 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,7 +1,7 @@
use egui::ScrollArea; use egui::ScrollArea;
use objdiff_core::{ use objdiff_core::{
arch::ppc::ExceptionInfo, arch::ppc::ExceptionInfo,
obj::{ObjInfo, ObjSymbol}, obj::{Object, Symbol},
}; };
use crate::views::{appearance::Appearance, function_diff::FunctionDiffContext}; use crate::views::{appearance::Appearance, function_diff::FunctionDiffContext};
@ -26,14 +26,16 @@ fn decode_extab(extab: &ExceptionInfo) -> String {
text text
} }
fn find_extab_entry<'a>(obj: &'a ObjInfo, symbol: &ObjSymbol) -> Option<&'a ExceptionInfo> { fn find_extab_entry<'a>(_obj: &'a Object, _symbol: &Symbol) -> Option<&'a ExceptionInfo> {
obj.arch.ppc().and_then(|ppc| ppc.extab_for_symbol(symbol)) // TODO
// obj.arch.ppc().and_then(|ppc| ppc.extab_for_symbol(symbol))
None
} }
fn extab_text_ui( fn extab_text_ui(
ui: &mut egui::Ui, ui: &mut egui::Ui,
ctx: FunctionDiffContext<'_>, ctx: FunctionDiffContext<'_>,
symbol: &ObjSymbol, symbol: &Symbol,
appearance: &Appearance, appearance: &Appearance,
) -> Option<()> { ) -> Option<()> {
if let Some(extab_entry) = find_extab_entry(ctx.obj, symbol) { if let Some(extab_entry) = find_extab_entry(ctx.obj, symbol) {
@ -56,8 +58,8 @@ pub(crate) fn extab_ui(
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
if let Some((_section, symbol)) = if let Some(symbol) =
ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref)) ctx.symbol_ref.and_then(|symbol_ref| ctx.obj.symbols.get(symbol_ref))
{ {
extab_text_ui(ui, ctx, symbol, appearance); extab_text_ui(ui, ctx, symbol, appearance);
} }

View File

@ -61,14 +61,9 @@ impl FrameHistory {
); );
egui::warn_if_debug_build(ui); egui::warn_if_debug_build(ui);
if !cfg!(target_arch = "wasm32") { egui::CollapsingHeader::new("📊 CPU usage history").default_open(false).show(ui, |ui| {
egui::CollapsingHeader::new("📊 CPU usage history").default_open(false).show(
ui,
|ui| {
self.graph(ui); self.graph(ui);
}, });
);
}
} }
fn graph(&mut self, ui: &mut egui::Ui) -> egui::Response { fn graph(&mut self, ui: &mut egui::Ui) -> egui::Response {

View File

@ -4,10 +4,14 @@ use egui::{text::LayoutJob, Label, Response, Sense, Widget};
use egui_extras::TableRow; use egui_extras::TableRow;
use objdiff_core::{ use objdiff_core::{
diff::{ diff::{
display::{display_diff, DiffText, HighlightKind}, display::{display_row, DiffText, HighlightKind},
ObjDiff, ObjInsDiff, ObjInsDiffKind, DiffObjConfig, InstructionArgDiffIndex, InstructionDiffKind, InstructionDiffRow,
ObjectDiff,
},
obj::{
InstructionArg, InstructionArgValue, InstructionRef, Object, ParsedInstruction,
ResolvedRelocation, Section, Symbol,
}, },
obj::{ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjSection, ObjSymbol, SymbolRef},
}; };
use crate::views::{appearance::Appearance, symbol_diff::DiffViewAction}; use crate::views::{appearance::Appearance, symbol_diff::DiffViewAction};
@ -63,43 +67,94 @@ impl FunctionViewState {
} }
} }
#[expect(unused)]
#[derive(Clone, Copy)]
pub struct ResolvedInstructionRef<'obj> {
pub symbol: &'obj Symbol,
pub section_idx: usize,
pub section: &'obj Section,
pub data: &'obj [u8],
pub relocation: Option<ResolvedRelocation<'obj>>,
}
fn resolve_instruction_ref(
obj: &Object,
symbol_idx: usize,
ins_ref: InstructionRef,
) -> Option<ResolvedInstructionRef> {
let symbol = &obj.symbols[symbol_idx];
let section_idx = symbol.section?;
let section = &obj.sections[section_idx];
let offset = ins_ref.address.checked_sub(section.address)?;
let data = section.data.get(offset as usize..offset as usize + ins_ref.size as usize)?;
let relocation = section.relocation_at(ins_ref.address, obj);
Some(ResolvedInstructionRef { symbol, section, section_idx, data, relocation })
}
fn resolve_instruction<'obj>(
obj: &'obj Object,
symbol_idx: usize,
ins_ref: InstructionRef,
diff_config: &DiffObjConfig,
) -> Option<(ResolvedInstructionRef<'obj>, ParsedInstruction)> {
let resolved = resolve_instruction_ref(obj, symbol_idx, ins_ref)?;
let ins = obj
.arch
.process_instruction(
ins_ref,
resolved.data,
resolved.relocation,
resolved.symbol.address..resolved.symbol.address + resolved.symbol.size,
resolved.section_idx,
diff_config,
)
.ok()?;
Some((resolved, ins))
}
fn ins_hover_ui( fn ins_hover_ui(
ui: &mut egui::Ui, ui: &mut egui::Ui,
obj: &ObjInfo, obj: &Object,
section: &ObjSection, symbol_idx: usize,
ins: &ObjIns, ins_ref: InstructionRef,
symbol: &ObjSymbol, diff_config: &DiffObjConfig,
appearance: &Appearance, appearance: &Appearance,
) { ) {
let Some((
ResolvedInstructionRef { symbol, section_idx: _, section: _, data, relocation },
ins,
)) = resolve_instruction(obj, symbol_idx, ins_ref, diff_config)
else {
ui.colored_label(appearance.delete_color, "Failed to resolve instruction");
return;
};
ui.scope(|ui| { ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
let offset = ins.address - section.address; ui.label(format!("{:02x?}", data));
ui.label(format!(
"{:02x?}",
&section.data[offset as usize..(offset + ins.size as u64) as usize]
));
if let Some(virtual_address) = symbol.virtual_address { if let Some(virtual_address) = symbol.virtual_address {
let offset = ins.address - symbol.address; let offset = ins_ref.address - symbol.address;
ui.colored_label( ui.colored_label(
appearance.replace_color, appearance.replace_color,
format!("Virtual address: {:#x}", virtual_address + offset), format!("Virtual address: {:#x}", virtual_address + offset),
); );
} }
if let Some(orig) = &ins.orig { // TODO
ui.label(format!("Original: {}", orig)); // if let Some(orig) = &ins.orig {
} // ui.label(format!("Original: {}", orig));
// }
for arg in &ins.args { for arg in &ins.args {
if let ObjInsArg::Arg(arg) = arg { if let InstructionArg::Value(arg) = arg {
match arg { match arg {
ObjInsArgValue::Signed(v) => { InstructionArgValue::Signed(v) => {
ui.label(format!("{arg} == {v}")); ui.label(format!("{arg} == {v}"));
} }
ObjInsArgValue::Unsigned(v) => { InstructionArgValue::Unsigned(v) => {
ui.label(format!("{arg} == {v}")); ui.label(format!("{arg} == {v}"));
} }
_ => {} _ => {}
@ -107,66 +162,76 @@ fn ins_hover_ui(
} }
} }
if let Some(reloc) = &ins.reloc { if let Some(resolved) = relocation {
ui.label(format!("Relocation type: {}", obj.arch.display_reloc(reloc.flags))); ui.label(format!(
let addend_str = match reloc.addend.cmp(&0i64) { "Relocation type: {}",
Ordering::Greater => format!("+{:x}", reloc.addend), obj.arch.display_reloc(resolved.relocation.flags)
Ordering::Less => format!("-{:x}", -reloc.addend), ));
let addend_str = match resolved.relocation.addend.cmp(&0i64) {
Ordering::Greater => format!("+{:x}", resolved.relocation.addend),
Ordering::Less => format!("-{:x}", -resolved.relocation.addend),
_ => "".to_string(), _ => "".to_string(),
}; };
ui.colored_label( ui.colored_label(
appearance.highlight_color, appearance.highlight_color,
format!("Name: {}{}", reloc.target.name, addend_str), format!("Name: {}{}", resolved.symbol.name, addend_str),
); );
if let Some(orig_section_index) = reloc.target.orig_section_index { if let Some(orig_section_index) = resolved.symbol.section {
if let Some(section) = let section = &obj.sections[orig_section_index];
obj.sections.iter().find(|s| s.orig_index == orig_section_index) ui.colored_label(appearance.highlight_color, format!("Section: {}", section.name));
{
ui.colored_label( ui.colored_label(
appearance.highlight_color, appearance.highlight_color,
format!("Section: {}", section.name), format!("Address: {:x}{}", resolved.symbol.address, addend_str),
);
}
ui.colored_label(
appearance.highlight_color,
format!("Address: {:x}{}", reloc.target.address, addend_str),
); );
ui.colored_label( ui.colored_label(
appearance.highlight_color, appearance.highlight_color,
format!("Size: {:x}", reloc.target.size), format!("Size: {:x}", resolved.symbol.size),
); );
for label in obj.arch.display_ins_data_labels(ins) { // TODO
ui.colored_label(appearance.highlight_color, label); // for label in obj.arch.display_ins_data_labels(ins) {
} // ui.colored_label(appearance.highlight_color, label);
// }
} else { } else {
ui.colored_label(appearance.highlight_color, "Extern".to_string()); ui.colored_label(appearance.highlight_color, "Extern".to_string());
} }
} }
if let Some(decoded) = rlwinmdec::decode(&ins.formatted) { // TODO
ui.colored_label(appearance.highlight_color, decoded.trim()); // if let Some(decoded) = rlwinmdec::decode(&ins.formatted) {
} // ui.colored_label(appearance.highlight_color, decoded.trim());
// }
}); });
} }
fn ins_context_menu( fn ins_context_menu(
ui: &mut egui::Ui, ui: &mut egui::Ui,
obj: &ObjInfo, obj: &Object,
section: &ObjSection, symbol_idx: usize,
ins: &ObjIns, ins_ref: InstructionRef,
symbol: &ObjSymbol, diff_config: &DiffObjConfig,
appearance: &Appearance,
) { ) {
let Some((
ResolvedInstructionRef { symbol, section_idx: _, section: _, data, relocation },
ins,
)) = resolve_instruction(obj, symbol_idx, ins_ref, diff_config)
else {
ui.colored_label(appearance.delete_color, "Failed to resolve instruction");
return;
};
ui.scope(|ui| { ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
if ui.button(format!("Copy \"{}\"", ins.formatted)).clicked() { // TODO
ui.output_mut(|output| output.copied_text.clone_from(&ins.formatted)); // if ui.button(format!("Copy \"{}\"", ins.formatted)).clicked() {
ui.close_menu(); // ui.output_mut(|output| output.copied_text.clone_from(&ins.formatted));
} // ui.close_menu();
// }
let mut hex_string = "0x".to_string(); let mut hex_string = "0x".to_string();
for byte in &section.data[ins.address as usize..(ins.address + ins.size as u64) as usize] { for byte in data {
hex_string.push_str(&format!("{:02x}", byte)); hex_string.push_str(&format!("{:02x}", byte));
} }
if ui.button(format!("Copy \"{hex_string}\" (instruction bytes)")).clicked() { if ui.button(format!("Copy \"{hex_string}\" (instruction bytes)")).clicked() {
@ -175,7 +240,7 @@ fn ins_context_menu(
} }
if let Some(virtual_address) = symbol.virtual_address { if let Some(virtual_address) = symbol.virtual_address {
let offset = ins.address - symbol.address; let offset = ins_ref.address - symbol.address;
let offset_string = format!("{:#x}", virtual_address + offset); let offset_string = format!("{:#x}", virtual_address + offset);
if ui.button(format!("Copy \"{offset_string}\" (virtual address)")).clicked() { if ui.button(format!("Copy \"{offset_string}\" (virtual address)")).clicked() {
ui.output_mut(|output| output.copied_text = offset_string); ui.output_mut(|output| output.copied_text = offset_string);
@ -184,9 +249,9 @@ fn ins_context_menu(
} }
for arg in &ins.args { for arg in &ins.args {
if let ObjInsArg::Arg(arg) = arg { if let InstructionArg::Value(arg) = arg {
match arg { match arg {
ObjInsArgValue::Signed(v) => { InstructionArgValue::Signed(v) => {
if ui.button(format!("Copy \"{arg}\"")).clicked() { if ui.button(format!("Copy \"{arg}\"")).clicked() {
ui.output_mut(|output| output.copied_text = arg.to_string()); ui.output_mut(|output| output.copied_text = arg.to_string());
ui.close_menu(); ui.close_menu();
@ -196,7 +261,7 @@ fn ins_context_menu(
ui.close_menu(); ui.close_menu();
} }
} }
ObjInsArgValue::Unsigned(v) => { InstructionArgValue::Unsigned(v) => {
if ui.button(format!("Copy \"{arg}\"")).clicked() { if ui.button(format!("Copy \"{arg}\"")).clicked() {
ui.output_mut(|output| output.copied_text = arg.to_string()); ui.output_mut(|output| output.copied_text = arg.to_string());
ui.close_menu(); ui.close_menu();
@ -210,21 +275,23 @@ fn ins_context_menu(
} }
} }
} }
if let Some(reloc) = &ins.reloc {
for literal in obj.arch.display_ins_data_literals(ins) { if let Some(resolved) = relocation {
if ui.button(format!("Copy \"{literal}\"")).clicked() { // TODO
ui.output_mut(|output| output.copied_text.clone_from(&literal)); // for literal in obj.arch.display_ins_data_literals(ins) {
ui.close_menu(); // if ui.button(format!("Copy \"{literal}\"")).clicked() {
} // ui.output_mut(|output| output.copied_text.clone_from(&literal));
} // ui.close_menu();
if let Some(name) = &reloc.target.demangled_name { // }
// }
if let Some(name) = &resolved.symbol.demangled_name {
if ui.button(format!("Copy \"{name}\"")).clicked() { if ui.button(format!("Copy \"{name}\"")).clicked() {
ui.output_mut(|output| output.copied_text.clone_from(name)); ui.output_mut(|output| output.copied_text.clone_from(name));
ui.close_menu(); ui.close_menu();
} }
} }
if ui.button(format!("Copy \"{}\"", reloc.target.name)).clicked() { if ui.button(format!("Copy \"{}\"", resolved.symbol.name)).clicked() {
ui.output_mut(|output| output.copied_text.clone_from(&reloc.target.name)); ui.output_mut(|output| output.copied_text.clone_from(&resolved.symbol.name));
ui.close_menu(); ui.close_menu();
} }
} }
@ -232,11 +299,11 @@ fn ins_context_menu(
} }
#[must_use] #[must_use]
#[expect(clippy::too_many_arguments)]
fn diff_text_ui( fn diff_text_ui(
ui: &mut egui::Ui, ui: &mut egui::Ui,
text: DiffText<'_>, text: DiffText<'_>,
ins_diff: &ObjInsDiff, diff: InstructionArgDiffIndex,
ins_diff: &InstructionDiffRow,
appearance: &Appearance, appearance: &Appearance,
ins_view_state: &FunctionViewState, ins_view_state: &FunctionViewState,
column: usize, column: usize,
@ -246,22 +313,18 @@ fn diff_text_ui(
let mut ret = None; let mut ret = None;
let label_text; let label_text;
let mut base_color = match ins_diff.kind { let mut base_color = match ins_diff.kind {
ObjInsDiffKind::None | ObjInsDiffKind::OpMismatch | ObjInsDiffKind::ArgMismatch => { InstructionDiffKind::None
appearance.text_color | InstructionDiffKind::OpMismatch
} | InstructionDiffKind::ArgMismatch => appearance.text_color,
ObjInsDiffKind::Replace => appearance.replace_color, InstructionDiffKind::Replace => appearance.replace_color,
ObjInsDiffKind::Delete => appearance.delete_color, InstructionDiffKind::Delete => appearance.delete_color,
ObjInsDiffKind::Insert => appearance.insert_color, InstructionDiffKind::Insert => appearance.insert_color,
}; };
let mut pad_to = 0; let mut pad_to = 0;
match text { match text {
DiffText::Basic(text) => { DiffText::Basic(text) => {
label_text = text.to_string(); label_text = text.to_string();
} }
DiffText::BasicColor(s, idx) => {
label_text = s.to_string();
base_color = appearance.diff_colors[idx % appearance.diff_colors.len()];
}
DiffText::Line(num) => { DiffText::Line(num) => {
label_text = num.to_string(); label_text = num.to_string();
base_color = appearance.deemphasized_text_color; base_color = appearance.deemphasized_text_color;
@ -273,44 +336,30 @@ fn diff_text_ui(
} }
DiffText::Opcode(mnemonic, _op) => { DiffText::Opcode(mnemonic, _op) => {
label_text = mnemonic.to_string(); label_text = mnemonic.to_string();
if ins_diff.kind == ObjInsDiffKind::OpMismatch { if ins_diff.kind == InstructionDiffKind::OpMismatch {
base_color = appearance.replace_color; base_color = appearance.replace_color;
} }
pad_to = 8; pad_to = 8;
} }
DiffText::Argument(arg, diff) => { DiffText::Argument(arg) => {
label_text = arg.to_string(); label_text = arg.to_string();
if let Some(diff) = diff {
base_color = appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
} }
} DiffText::BranchDest(addr) => {
DiffText::BranchDest(addr, diff) => {
label_text = format!("{addr:x}"); label_text = format!("{addr:x}");
if let Some(diff) = diff {
base_color = appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
} }
} DiffText::Symbol(sym) => {
DiffText::Symbol(sym, diff) => {
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name); let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
label_text = name.clone(); label_text = name.clone();
if let Some(diff) = diff {
base_color = appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
} else {
base_color = appearance.emphasized_text_color; base_color = appearance.emphasized_text_color;
} }
} DiffText::Addend(addend) => {
DiffText::Addend(addend, diff) => {
label_text = match addend.cmp(&0i64) { label_text = match addend.cmp(&0i64) {
Ordering::Greater => format!("+{:#x}", addend), Ordering::Greater => format!("+{:#x}", addend),
Ordering::Less => format!("-{:#x}", -addend), Ordering::Less => format!("-{:#x}", -addend),
_ => "".to_string(), _ => "".to_string(),
}; };
if let Some(diff) = diff {
base_color = appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
} else {
base_color = appearance.emphasized_text_color; base_color = appearance.emphasized_text_color;
} }
}
DiffText::Spacing(n) => { DiffText::Spacing(n) => {
ui.add_space(n as f32 * space_width); ui.add_space(n as f32 * space_width);
return ret; return ret;
@ -319,6 +368,9 @@ fn diff_text_ui(
label_text = "\n".to_string(); label_text = "\n".to_string();
} }
} }
if let Some(diff_idx) = diff.get() {
base_color = appearance.diff_colors[diff_idx as usize % appearance.diff_colors.len()];
}
let len = label_text.len(); let len = label_text.len();
let highlight = *ins_view_state.highlight(column) == text; let highlight = *ins_view_state.highlight(column) == text;
@ -341,24 +393,27 @@ fn diff_text_ui(
#[must_use] #[must_use]
fn asm_row_ui( fn asm_row_ui(
ui: &mut egui::Ui, ui: &mut egui::Ui,
ins_diff: &ObjInsDiff, obj: &Object,
symbol: &ObjSymbol, ins_diff: &InstructionDiffRow,
symbol_idx: usize,
appearance: &Appearance, appearance: &Appearance,
ins_view_state: &FunctionViewState, ins_view_state: &FunctionViewState,
diff_config: &DiffObjConfig,
column: usize, column: usize,
response_cb: impl Fn(Response) -> Response, response_cb: impl Fn(Response) -> Response,
) -> Option<DiffViewAction> { ) -> Option<DiffViewAction> {
let mut ret = None; let mut ret = None;
ui.spacing_mut().item_spacing.x = 0.0; ui.spacing_mut().item_spacing.x = 0.0;
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
if ins_diff.kind != ObjInsDiffKind::None { if ins_diff.kind != InstructionDiffKind::None {
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color); ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
} }
let space_width = ui.fonts(|f| f.glyph_width(&appearance.code_font, ' ')); let space_width = ui.fonts(|f| f.glyph_width(&appearance.code_font, ' '));
display_diff(ins_diff, symbol.address, |text| { display_row(obj, symbol_idx, ins_diff, diff_config, |text, diff| {
if let Some(action) = diff_text_ui( if let Some(action) = diff_text_ui(
ui, ui,
text, text,
diff,
ins_diff, ins_diff,
appearance, appearance,
ins_view_state, ins_view_state,
@ -368,7 +423,7 @@ fn asm_row_ui(
) { ) {
ret = Some(action); ret = Some(action);
} }
Ok::<_, ()>(()) Ok(())
}) })
.unwrap(); .unwrap();
ret ret
@ -380,27 +435,36 @@ pub(crate) fn asm_col_ui(
ctx: FunctionDiffContext<'_>, ctx: FunctionDiffContext<'_>,
appearance: &Appearance, appearance: &Appearance,
ins_view_state: &FunctionViewState, ins_view_state: &FunctionViewState,
diff_config: &DiffObjConfig,
column: usize, column: usize,
) -> Option<DiffViewAction> { ) -> Option<DiffViewAction> {
let mut ret = None; let mut ret = None;
let symbol_ref = ctx.symbol_ref?; let symbol_ref = ctx.symbol_ref?;
let (section, symbol) = ctx.obj.section_symbol(symbol_ref); let ins_row = &ctx.diff.symbols[symbol_ref].instruction_rows[row.index()];
let section = section?;
let ins_diff = &ctx.diff.symbol_diff(symbol_ref).instructions[row.index()];
let response_cb = |response: Response| { let response_cb = |response: Response| {
if let Some(ins) = &ins_diff.ins { if let Some(ins_ref) = ins_row.ins_ref {
response.context_menu(|ui| ins_context_menu(ui, ctx.obj, section, ins, symbol)); response.context_menu(|ui| {
ins_context_menu(ui, ctx.obj, symbol_ref, ins_ref, diff_config, appearance)
});
response.on_hover_ui_at_pointer(|ui| { response.on_hover_ui_at_pointer(|ui| {
ins_hover_ui(ui, ctx.obj, section, ins, symbol, appearance) ins_hover_ui(ui, ctx.obj, symbol_ref, ins_ref, diff_config, appearance)
}) })
} else { } else {
response response
} }
}; };
let (_, response) = row.col(|ui| { let (_, response) = row.col(|ui| {
if let Some(action) = if let Some(action) = asm_row_ui(
asm_row_ui(ui, ins_diff, symbol, appearance, ins_view_state, column, response_cb) ui,
{ ctx.obj,
ins_row,
symbol_ref,
appearance,
ins_view_state,
diff_config,
column,
response_cb,
) {
ret = Some(action); ret = Some(action);
} }
}); });
@ -410,7 +474,7 @@ pub(crate) fn asm_col_ui(
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct FunctionDiffContext<'a> { pub struct FunctionDiffContext<'a> {
pub obj: &'a ObjInfo, pub obj: &'a Object,
pub diff: &'a ObjDiff, pub diff: &'a ObjectDiff,
pub symbol_ref: Option<SymbolRef>, pub symbol_ref: Option<usize>,
} }

View File

@ -1,16 +1,19 @@
use std::{collections::BTreeMap, mem::take, ops::Bound}; use std::mem::take;
use egui::{ use egui::{
style::ScrollAnimation, text::LayoutJob, CollapsingHeader, Color32, Id, OpenUrl, ScrollArea, style::ScrollAnimation, text::LayoutJob, CollapsingHeader, Color32, Id, OpenUrl, ScrollArea,
SelectableLabel, Ui, Widget, SelectableLabel, Ui, Widget,
}; };
use objdiff_core::{ use objdiff_core::{
arch::ObjArch, diff::{
diff::{display::HighlightKind, ObjDiff, ObjSymbolDiff}, display::{
jobs::{create_scratch::CreateScratchResult, objdiff::ObjDiffResult, Job, JobQueue, JobResult}, display_sections, symbol_context, symbol_hover, ContextMenuItem, HighlightKind,
obj::{ HoverItem, HoverItemColor, SectionDisplay, SymbolFilter,
ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags, SymbolRef, SECTION_COMMON,
}, },
ObjectDiff, SymbolDiff,
},
jobs::{create_scratch::CreateScratchResult, objdiff::ObjDiffResult, Job, JobQueue, JobResult},
obj::{Object, Section, SectionKind, Symbol, SymbolFlag},
}; };
use regex::{Regex, RegexBuilder}; use regex::{Regex, RegexBuilder};
@ -28,7 +31,7 @@ pub struct SymbolRefByName {
} }
impl SymbolRefByName { impl SymbolRefByName {
pub fn new(symbol: &ObjSymbol, section: Option<&ObjSection>) -> Self { pub fn new(symbol: &Symbol, section: Option<&Section>) -> Self {
Self { symbol_name: symbol.name.clone(), section_name: section.map(|s| s.name.clone()) } Self { symbol_name: symbol.name.clone(), section_name: section.map(|s| s.name.clone()) }
} }
} }
@ -50,7 +53,7 @@ pub enum DiffViewAction {
/// Navigate to a new diff view /// Navigate to a new diff view
Navigate(DiffViewNavigation), Navigate(DiffViewNavigation),
/// Set the highlighted symbols in the symbols view, optionally scrolling them into view. /// Set the highlighted symbols in the symbols view, optionally scrolling them into view.
SetSymbolHighlight(Option<SymbolRef>, Option<SymbolRef>, bool), SetSymbolHighlight(Option<usize>, Option<usize>, bool),
/// Set the symbols view search filter /// Set the symbols view search filter
SetSearch(String), SetSearch(String),
/// Submit the current function to decomp.me /// Submit the current function to decomp.me
@ -88,15 +91,17 @@ impl DiffViewNavigation {
pub fn with_symbols( pub fn with_symbols(
view: View, view: View,
other_ctx: Option<SymbolDiffContext<'_>>, other_ctx: Option<SymbolDiffContext<'_>>,
symbol: &ObjSymbol, symbol: &Symbol,
section: &ObjSection, section: &Section,
symbol_diff: &ObjSymbolDiff, symbol_diff: &SymbolDiff,
column: usize, column: usize,
) -> Self { ) -> Self {
let symbol1 = Some(SymbolRefByName::new(symbol, Some(section))); let symbol1 = Some(SymbolRefByName::new(symbol, Some(section)));
let symbol2 = symbol_diff.target_symbol.and_then(|symbol_ref| { let symbol2 = symbol_diff.target_symbol.and_then(|symbol_ref| {
other_ctx.map(|ctx| { other_ctx.map(|ctx| {
let (section, symbol) = ctx.obj.section_symbol(symbol_ref); let symbol = &ctx.obj.symbols[symbol_ref];
let section =
symbol.section.and_then(|section_idx| ctx.obj.sections.get(section_idx));
SymbolRefByName::new(symbol, section) SymbolRefByName::new(symbol, section)
}) })
}); });
@ -107,7 +112,7 @@ impl DiffViewNavigation {
} }
} }
pub fn data_diff(section: &ObjSection, column: usize) -> Self { pub fn data_diff(section: &Section, column: usize) -> Self {
let symbol = Some(SymbolRefByName { let symbol = Some(SymbolRefByName {
symbol_name: "".to_string(), symbol_name: "".to_string(),
section_name: Some(section.name.clone()), section_name: Some(section.name.clone()),
@ -143,7 +148,7 @@ pub struct DiffViewState {
#[derive(Default)] #[derive(Default)]
pub struct SymbolViewState { pub struct SymbolViewState {
pub highlighted_symbol: (Option<SymbolRef>, Option<SymbolRef>), pub highlighted_symbol: (Option<usize>, Option<usize>),
pub autoscroll_to_highlighted_symbols: bool, pub autoscroll_to_highlighted_symbols: bool,
pub left_symbol: Option<SymbolRefByName>, pub left_symbol: Option<SymbolRefByName>,
pub right_symbol: Option<SymbolRefByName>, pub right_symbol: Option<SymbolRefByName>,
@ -365,9 +370,9 @@ fn symbol_context_menu_ui(
ui: &mut Ui, ui: &mut Ui,
ctx: SymbolDiffContext<'_>, ctx: SymbolDiffContext<'_>,
other_ctx: Option<SymbolDiffContext<'_>>, other_ctx: Option<SymbolDiffContext<'_>>,
symbol: &ObjSymbol, symbol: &Symbol,
symbol_diff: &ObjSymbolDiff, symbol_diff: &SymbolDiff,
section: Option<&ObjSection>, section: Option<&Section>,
column: usize, column: usize,
) -> Option<DiffViewNavigation> { ) -> Option<DiffViewNavigation> {
let mut ret = None; let mut ret = None;
@ -375,37 +380,37 @@ fn symbol_context_menu_ui(
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
if let Some(name) = &symbol.demangled_name { for item in symbol_context(ctx.obj, symbol) {
if ui.button(format!("Copy \"{name}\"")).clicked() { match item {
ui.output_mut(|output| output.copied_text.clone_from(name)); ContextMenuItem::Copy { value, label } => {
let label = if let Some(extra) = label {
format!("Copy \"{value}\" ({extra})")
} else {
format!("Copy \"{value}\"")
};
if ui.button(label).clicked() {
ui.output_mut(|output| output.copied_text = value);
ui.close_menu(); ui.close_menu();
} }
} }
if ui.button(format!("Copy \"{}\"", symbol.name)).clicked() { ContextMenuItem::Navigate { label } => {
ui.output_mut(|output| output.copied_text.clone_from(&symbol.name)); if ui.button(label).clicked() {
ui.close_menu(); // TODO other navigation
}
if let Some(address) = symbol.virtual_address {
if ui.button(format!("Copy \"{:#x}\" (virtual address)", address)).clicked() {
ui.output_mut(|output| output.copied_text = format!("{:#x}", address));
ui.close_menu();
}
}
if let Some(section) = section {
let has_extab =
ctx.obj.arch.ppc().and_then(|ppc| ppc.extab_for_symbol(symbol)).is_some();
if has_extab && ui.button("Decode exception table").clicked() {
ret = Some(DiffViewNavigation::with_symbols( ret = Some(DiffViewNavigation::with_symbols(
View::ExtabDiff, View::ExtabDiff,
other_ctx, other_ctx,
symbol, symbol,
section, section.unwrap(),
symbol_diff, symbol_diff,
column, column,
)); ));
ui.close_menu(); ui.close_menu();
} }
}
}
}
if let Some(section) = section {
if ui.button("Map symbol").clicked() { if ui.button("Map symbol").clicked() {
let symbol_ref = SymbolRefByName::new(symbol, Some(section)); let symbol_ref = SymbolRefByName::new(symbol, Some(section));
if column == 0 { if column == 0 {
@ -428,54 +433,41 @@ fn symbol_context_menu_ui(
ret ret
} }
fn symbol_hover_ui(ui: &mut Ui, arch: &dyn ObjArch, symbol: &ObjSymbol, appearance: &Appearance) { fn symbol_hover_ui(
ui: &mut Ui,
ctx: SymbolDiffContext<'_>,
symbol: &Symbol,
appearance: &Appearance,
) {
ui.scope(|ui| { ui.scope(|ui| {
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
ui.colored_label(appearance.highlight_color, format!("Name: {}", symbol.name)); for HoverItem { text, color } in symbol_hover(ctx.obj, symbol) {
ui.colored_label(appearance.highlight_color, format!("Address: {:x}", symbol.address)); let color = match color {
if symbol.size_known { HoverItemColor::Normal => appearance.text_color,
ui.colored_label(appearance.highlight_color, format!("Size: {:x}", symbol.size)); HoverItemColor::Emphasized => appearance.highlight_color,
} else { HoverItemColor::Special => appearance.replace_color,
ui.colored_label( };
appearance.highlight_color, ui.colored_label(color, text);
format!("Size: {:x} (assumed)", symbol.size),
);
}
if let Some(address) = symbol.virtual_address {
ui.colored_label(appearance.replace_color, format!("Virtual address: {:#x}", address));
}
if let Some(extab) = arch.ppc().and_then(|ppc| ppc.extab_for_symbol(symbol)) {
ui.colored_label(
appearance.highlight_color,
format!("extab symbol: {}", &extab.etb_symbol.name),
);
ui.colored_label(
appearance.highlight_color,
format!("extabindex symbol: {}", &extab.eti_symbol.name),
);
} }
}); });
} }
#[must_use] #[must_use]
#[expect(clippy::too_many_arguments)]
fn symbol_ui( fn symbol_ui(
ui: &mut Ui, ui: &mut Ui,
ctx: SymbolDiffContext<'_>, ctx: SymbolDiffContext<'_>,
other_ctx: Option<SymbolDiffContext<'_>>, other_ctx: Option<SymbolDiffContext<'_>>,
symbol: &ObjSymbol, symbol: &Symbol,
symbol_diff: &ObjSymbolDiff, symbol_diff: &SymbolDiff,
section: Option<&ObjSection>, symbol_idx: usize,
section: Option<&Section>,
state: &SymbolViewState, state: &SymbolViewState,
appearance: &Appearance, appearance: &Appearance,
column: usize, column: usize,
) -> Option<DiffViewAction> { ) -> Option<DiffViewAction> {
let mut ret = None; let mut ret = None;
if symbol.flags.0.contains(ObjSymbolFlags::Hidden) && !state.show_hidden_symbols {
return ret;
}
let mut job = LayoutJob::default(); let mut job = LayoutJob::default();
let name: &str = let name: &str =
if let Some(demangled) = &symbol.demangled_name { demangled } else { &symbol.name }; if let Some(demangled) = &symbol.demangled_name { demangled } else { &symbol.name };
@ -483,24 +475,24 @@ fn symbol_ui(
if let Some(sym_ref) = if let Some(sym_ref) =
if column == 0 { state.highlighted_symbol.0 } else { state.highlighted_symbol.1 } if column == 0 { state.highlighted_symbol.0 } else { state.highlighted_symbol.1 }
{ {
selected = symbol_diff.symbol_ref == sym_ref; selected = symbol_idx == sym_ref;
} }
if !symbol.flags.0.is_empty() { if !symbol.flags.is_empty() {
write_text("[", appearance.text_color, &mut job, appearance.code_font.clone()); write_text("[", appearance.text_color, &mut job, appearance.code_font.clone());
if symbol.flags.0.contains(ObjSymbolFlags::Common) { if symbol.flags.contains(SymbolFlag::Common) {
write_text("c", appearance.replace_color, &mut job, appearance.code_font.clone()); write_text("c", appearance.replace_color, &mut job, appearance.code_font.clone());
} else if symbol.flags.0.contains(ObjSymbolFlags::Global) { } else if symbol.flags.contains(SymbolFlag::Global) {
write_text("g", appearance.insert_color, &mut job, appearance.code_font.clone()); write_text("g", appearance.insert_color, &mut job, appearance.code_font.clone());
} else if symbol.flags.0.contains(ObjSymbolFlags::Local) { } else if symbol.flags.contains(SymbolFlag::Local) {
write_text("l", appearance.text_color, &mut job, appearance.code_font.clone()); write_text("l", appearance.text_color, &mut job, appearance.code_font.clone());
} }
if symbol.flags.0.contains(ObjSymbolFlags::Weak) { if symbol.flags.contains(SymbolFlag::Weak) {
write_text("w", appearance.text_color, &mut job, appearance.code_font.clone()); write_text("w", appearance.text_color, &mut job, appearance.code_font.clone());
} }
if symbol.flags.0.contains(ObjSymbolFlags::HasExtra) { if symbol.flags.contains(SymbolFlag::HasExtra) {
write_text("e", appearance.text_color, &mut job, appearance.code_font.clone()); write_text("e", appearance.text_color, &mut job, appearance.code_font.clone());
} }
if symbol.flags.0.contains(ObjSymbolFlags::Hidden) { if symbol.flags.contains(SymbolFlag::Hidden) {
write_text( write_text(
"h", "h",
appearance.deemphasized_text_color, appearance.deemphasized_text_color,
@ -521,9 +513,9 @@ fn symbol_ui(
write_text(") ", appearance.text_color, &mut job, appearance.code_font.clone()); write_text(") ", appearance.text_color, &mut job, appearance.code_font.clone());
} }
write_text(name, appearance.highlight_color, &mut job, appearance.code_font.clone()); write_text(name, appearance.highlight_color, &mut job, appearance.code_font.clone());
let response = SelectableLabel::new(selected, job).ui(ui).on_hover_ui_at_pointer(|ui| { let response = SelectableLabel::new(selected, job)
symbol_hover_ui(ui, ctx.obj.arch.as_ref(), symbol, appearance) .ui(ui)
}); .on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, ctx, symbol, appearance));
response.context_menu(|ui| { response.context_menu(|ui| {
if let Some(result) = if let Some(result) =
symbol_context_menu_ui(ui, ctx, other_ctx, symbol, symbol_diff, section, column) symbol_context_menu_ui(ui, ctx, other_ctx, symbol, symbol_diff, section, column)
@ -542,7 +534,7 @@ fn symbol_ui(
if response.clicked() || (selected && hotkeys::enter_pressed(ui.ctx())) { if response.clicked() || (selected && hotkeys::enter_pressed(ui.ctx())) {
if let Some(section) = section { if let Some(section) = section {
match section.kind { match section.kind {
ObjSectionKind::Code => { SectionKind::Code => {
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::with_symbols( ret = Some(DiffViewAction::Navigate(DiffViewNavigation::with_symbols(
View::FunctionDiff, View::FunctionDiff,
other_ctx, other_ctx,
@ -552,62 +544,56 @@ fn symbol_ui(
column, column,
))); )));
} }
ObjSectionKind::Data => { SectionKind::Data => {
ret = Some(DiffViewAction::Navigate(DiffViewNavigation::data_diff( ret = Some(DiffViewAction::Navigate(DiffViewNavigation::data_diff(
section, column, section, column,
))); )));
} }
ObjSectionKind::Bss => {} _ => {}
} }
} }
} else if response.hovered() { } else if response.hovered() {
ret = Some(if column == 0 { ret = Some(if column == 0 {
DiffViewAction::SetSymbolHighlight( DiffViewAction::SetSymbolHighlight(Some(symbol_idx), symbol_diff.target_symbol, false)
Some(symbol_diff.symbol_ref),
symbol_diff.target_symbol,
false,
)
} else { } else {
DiffViewAction::SetSymbolHighlight( DiffViewAction::SetSymbolHighlight(symbol_diff.target_symbol, Some(symbol_idx), false)
symbol_diff.target_symbol,
Some(symbol_diff.symbol_ref),
false,
)
}); });
} }
ret ret
} }
fn symbol_matches_filter( fn find_prev_symbol(section_display: &[SectionDisplay], current: usize) -> Option<usize> {
symbol: &ObjSymbol, section_display
diff: &ObjSymbolDiff, .iter()
filter: SymbolFilter<'_>, .flat_map(|s| s.symbols.iter())
) -> bool { .rev()
match filter { .skip_while(|s| s.symbol != current)
SymbolFilter::None => true, .nth(1)
SymbolFilter::Search(regex) => { .map(|s| s.symbol)
regex.is_match(&symbol.name) // Wrap around to the last symbol if we're at the beginning of the list
|| symbol.demangled_name.as_deref().is_some_and(|s| regex.is_match(s)) .or_else(|| find_last_symbol(section_display))
}
SymbolFilter::Mapping(symbol_ref, regex) => {
diff.target_symbol == Some(symbol_ref)
&& regex.is_none_or(|r| {
r.is_match(&symbol.name)
|| symbol.demangled_name.as_deref().is_some_and(|s| r.is_match(s))
})
}
}
} }
#[derive(Copy, Clone)] fn find_next_symbol(section_display: &[SectionDisplay], current: usize) -> Option<usize> {
pub enum SymbolFilter<'a> { section_display
None, .iter()
Search(&'a Regex), .flat_map(|s| s.symbols.iter())
Mapping(SymbolRef, Option<&'a Regex>), .skip_while(|s| s.symbol != current)
.nth(1)
.map(|s| s.symbol)
// Wrap around to the first symbol if we're at the end of the list
.or_else(|| find_first_symbol(section_display))
}
fn find_first_symbol(section_display: &[SectionDisplay]) -> Option<usize> {
section_display.iter().flat_map(|s| s.symbols.iter()).next().map(|s| s.symbol)
}
fn find_last_symbol(section_display: &[SectionDisplay]) -> Option<usize> {
section_display.iter().flat_map(|s| s.symbols.iter()).next_back().map(|s| s.symbol)
} }
#[must_use] #[must_use]
#[expect(clippy::too_many_arguments)]
pub fn symbol_list_ui( pub fn symbol_list_ui(
ui: &mut Ui, ui: &mut Ui,
ctx: SymbolDiffContext<'_>, ctx: SymbolDiffContext<'_>,
@ -620,41 +606,20 @@ pub fn symbol_list_ui(
) -> Option<DiffViewAction> { ) -> Option<DiffViewAction> {
let mut ret = None; let mut ret = None;
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| { ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
let mut mapping = BTreeMap::new();
if let SymbolFilter::Mapping(_, _) = filter {
let mut show_mapped_symbols = state.show_mapped_symbols; let mut show_mapped_symbols = state.show_mapped_symbols;
if let SymbolFilter::Mapping(_, _) = filter {
if ui.checkbox(&mut show_mapped_symbols, "Show mapped symbols").changed() { if ui.checkbox(&mut show_mapped_symbols, "Show mapped symbols").changed() {
ret = Some(DiffViewAction::SetShowMappedSymbols(show_mapped_symbols)); ret = Some(DiffViewAction::SetShowMappedSymbols(show_mapped_symbols));
} }
for mapping_diff in &ctx.diff.mapping_symbols {
let symbol = ctx.obj.section_symbol(mapping_diff.symbol_ref).1;
if !symbol_matches_filter(symbol, mapping_diff, filter) {
continue;
}
if !show_mapped_symbols {
let symbol_diff = ctx.diff.symbol_diff(mapping_diff.symbol_ref);
if symbol_diff.target_symbol.is_some() {
continue;
}
}
mapping.insert(mapping_diff.symbol_ref, mapping_diff);
}
} else {
for (symbol, diff) in ctx.obj.common.iter().zip(&ctx.diff.common) {
if !symbol_matches_filter(symbol, diff, filter) {
continue;
}
mapping.insert(diff.symbol_ref, diff);
}
for (section, section_diff) in ctx.obj.sections.iter().zip(&ctx.diff.sections) {
for (symbol, symbol_diff) in section.symbols.iter().zip(&section_diff.symbols) {
if !symbol_matches_filter(symbol, symbol_diff, filter) {
continue;
}
mapping.insert(symbol_diff.symbol_ref, symbol_diff);
}
}
} }
let section_display = display_sections(
ctx.obj,
ctx.diff,
filter,
state.show_hidden_symbols,
show_mapped_symbols,
state.reverse_fn_order,
);
hotkeys::check_scroll_hotkeys(ui, false); hotkeys::check_scroll_hotkeys(ui, false);
@ -669,14 +634,11 @@ pub fn symbol_list_ui(
} else { } else {
None None
}; };
if let Some(mut up) = up { if let Some(up) = up {
if state.reverse_fn_order {
up = !up;
}
new_key_value_to_highlight = if up { new_key_value_to_highlight = if up {
mapping.range(..sym_ref).next_back() find_prev_symbol(&section_display, sym_ref)
} else { } else {
mapping.range((Bound::Excluded(sym_ref), Bound::Unbounded)).next() find_next_symbol(&section_display, sym_ref)
}; };
}; };
} else { } else {
@ -685,26 +647,15 @@ pub fn symbol_list_ui(
// we do when a symbol is highlighted. This is so that if only one column has a symbol // we do when a symbol is highlighted. This is so that if only one column has a symbol
// highlighted, that one takes precedence over the one with nothing highlighted. // highlighted, that one takes precedence over the one with nothing highlighted.
if hotkeys::up_pressed(ui.ctx()) || hotkeys::down_pressed(ui.ctx()) { if hotkeys::up_pressed(ui.ctx()) || hotkeys::down_pressed(ui.ctx()) {
new_key_value_to_highlight = if state.reverse_fn_order { new_key_value_to_highlight = find_first_symbol(&section_display);
mapping.last_key_value()
} else {
mapping.first_key_value()
};
} }
} }
if let Some((new_sym_ref, new_symbol_diff)) = new_key_value_to_highlight { if let Some(new_sym_ref) = new_key_value_to_highlight {
let target_symbol = ctx.diff.symbols[new_sym_ref].target_symbol;
ret = Some(if column == 0 { ret = Some(if column == 0 {
DiffViewAction::SetSymbolHighlight( DiffViewAction::SetSymbolHighlight(Some(new_sym_ref), target_symbol, true)
Some(*new_sym_ref),
new_symbol_diff.target_symbol,
true,
)
} else { } else {
DiffViewAction::SetSymbolHighlight( DiffViewAction::SetSymbolHighlight(target_symbol, Some(new_sym_ref), true)
new_symbol_diff.target_symbol,
Some(*new_sym_ref),
true,
)
}); });
} }
@ -712,44 +663,21 @@ pub fn symbol_list_ui(
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
// Skip sections with all symbols filtered out for section_display in section_display {
if mapping.keys().any(|symbol_ref| symbol_ref.section_idx == SECTION_COMMON) {
CollapsingHeader::new(".comm").default_open(true).show(ui, |ui| {
for (symbol_ref, symbol_diff) in mapping
.iter()
.filter(|(symbol_ref, _)| symbol_ref.section_idx == SECTION_COMMON)
{
let symbol = ctx.obj.section_symbol(*symbol_ref).1;
if let Some(result) = symbol_ui(
ui,
ctx,
other_ctx,
symbol,
symbol_diff,
None,
state,
appearance,
column,
) {
ret = Some(result);
}
}
});
}
for ((section_index, section), section_diff) in
ctx.obj.sections.iter().enumerate().zip(&ctx.diff.sections)
{
// Skip sections with all symbols filtered out
if !mapping.keys().any(|symbol_ref| symbol_ref.section_idx == section_index) {
continue;
}
let mut header = LayoutJob::simple_singleline( let mut header = LayoutJob::simple_singleline(
format!("{} ({:x})", section.name, section.size), section_display.name.clone(),
appearance.code_font.clone(), appearance.code_font.clone(),
Color32::PLACEHOLDER, Color32::PLACEHOLDER,
); );
if let Some(match_percent) = section_diff.match_percent { if section_display.size > 0 {
write_text(
&format!(" ({:x})", section_display.size),
Color32::PLACEHOLDER,
&mut header,
appearance.code_font.clone(),
);
}
if let Some(match_percent) = section_display.match_percent {
write_text( write_text(
" (", " (",
Color32::PLACEHOLDER, Color32::PLACEHOLDER,
@ -770,44 +698,33 @@ pub fn symbol_list_ui(
); );
} }
CollapsingHeader::new(header) CollapsingHeader::new(header)
.id_salt(Id::new(section.name.clone()).with(section.orig_index)) .id_salt(Id::new(&section_display.id))
.default_open(true) .default_open(true)
.open(open_sections) .open(open_sections)
.show(ui, |ui| { .show(ui, |ui| {
if section.kind == ObjSectionKind::Code && state.reverse_fn_order { for symbol_display in &section_display.symbols {
for (symbol, symbol_diff) in mapping let symbol = &ctx.obj.symbols[symbol_display.symbol];
let section = symbol
.section
.and_then(|section_idx| ctx.obj.sections.get(section_idx));
let symbol_diff = if symbol_display.is_mapping_symbol {
ctx.diff
.mapping_symbols
.iter() .iter()
.filter(|(symbol_ref, _)| symbol_ref.section_idx == section_index) .find(|d| d.symbol_index == symbol_display.symbol)
.rev() .map(|d| &d.symbol_diff)
{ .unwrap()
let symbol = ctx.obj.section_symbol(*symbol).1;
if let Some(result) = symbol_ui(
ui,
ctx,
other_ctx,
symbol,
symbol_diff,
Some(section),
state,
appearance,
column,
) {
ret = Some(result);
}
}
} else { } else {
for (symbol, symbol_diff) in mapping &ctx.diff.symbols[symbol_display.symbol]
.iter() };
.filter(|(symbol_ref, _)| symbol_ref.section_idx == section_index)
{
let symbol = ctx.obj.section_symbol(*symbol).1;
if let Some(result) = symbol_ui( if let Some(result) = symbol_ui(
ui, ui,
ctx, ctx,
other_ctx, other_ctx,
symbol, symbol,
symbol_diff, symbol_diff,
Some(section), symbol_display.symbol,
section,
state, state,
appearance, appearance,
column, column,
@ -815,7 +732,6 @@ pub fn symbol_list_ui(
ret = Some(result); ret = Some(result);
} }
} }
}
}); });
} }
}); });
@ -825,6 +741,6 @@ pub fn symbol_list_ui(
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct SymbolDiffContext<'a> { pub struct SymbolDiffContext<'a> {
pub obj: &'a ObjInfo, pub obj: &'a Object,
pub diff: &'a ObjDiff, pub diff: &'a ObjectDiff,
} }

39
objdiff-wasm/Cargo.toml Normal file
View File

@ -0,0 +1,39 @@
[package]
name = "objdiff-wasm"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
readme = "../README.md"
description = """
A local diffing tool for decompilation projects.
"""
publish = false
build = "build.rs"
[lib]
crate-type = ["cdylib"]
[features]
default = ["std"]
std = ["objdiff-core/std"]
[dependencies]
log = { version = "0.4", default-features = false }
regex = { version = "1.11", default-features = false }
[dependencies.objdiff-core]
path = "../objdiff-core"
default-features = false
features = ["arm", "arm64", "mips", "ppc", "dwarf"]
[target.'cfg(target_family = "wasm")'.dependencies]
talc = { version = "4.4", default-features = false, features = ["lock_api"] }
[target.'cfg(target_os = "wasi")'.dependencies]
wit-bindgen = { version = "0.39", default-features = false, features = ["macros"] }
[build-dependencies]
wit-deps = "0.5"

31
objdiff-wasm/biome.json Normal file
View File

@ -0,0 +1,31 @@
{
"$schema": "https://biomejs.dev/schemas/1.8.0/schema.json",
"organizeImports": {
"enabled": true
},
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"formatter": {
"indentStyle": "space"
},
"javascript": {
"formatter": {
"quoteStyle": "single"
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"a11y": {
"all": false
},
"suspicious": {
"noExplicitAny": "off"
}
}
}
}

4
objdiff-wasm/build.rs Normal file
View File

@ -0,0 +1,4 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
wit_deps::lock_sync!()?;
Ok(())
}

View File

@ -1,28 +0,0 @@
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
export default [
{files: ["**/*.{js,mjs,cjs,ts}"]},
{languageOptions: {globals: globals.browser}},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
"semi": [2, "always"],
"@typescript-eslint/no-unused-vars": [
"error",
// https://typescript-eslint.io/rules/no-unused-vars/#benefits-over-typescript
{
"args": "all",
"argsIgnorePattern": "^_",
"caughtErrors": "all",
"caughtErrorsIgnorePattern": "^_",
"destructuredArrayIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"ignoreRestSiblings": true
},
],
}
},
];

View File

@ -0,0 +1,25 @@
import type { WasiLoggingLogging010Draft as logging } from '../pkg/objdiff';
export const log: typeof logging.log = (level, context, message) => {
const msg = `[${context}] ${message}`;
switch (level) {
case 'trace':
console.trace(msg);
break;
case 'debug':
console.debug(msg);
break;
case 'info':
console.info(msg);
break;
case 'warn':
console.warn(msg);
break;
case 'error':
console.error(msg);
break;
case 'critical':
console.error(msg);
break;
}
};

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "objdiff-wasm", "name": "objdiff-wasm",
"version": "2.7.1", "version": "3.0.0-alpha.1",
"description": "A local diffing tool for decompilation projects.", "description": "A local diffing tool for decompilation projects.",
"author": { "author": {
"name": "Luke Street", "name": "Luke Street",
@ -12,28 +12,19 @@
"type": "git", "type": "git",
"url": "git+https://github.com/encounter/objdiff.git" "url": "git+https://github.com/encounter/objdiff.git"
}, },
"files": [ "files": ["dist/*"],
"dist/*" "main": "dist/objdiff.js",
], "types": "dist/objdiff.d.ts",
"main": "dist/main.js",
"types": "dist/main.d.ts",
"scripts": { "scripts": {
"build": "tsup", "build": "npm run build:wasm && npm run build:transpile && npm run build:lib",
"build:all": "npm run build:wasm && npm run build:proto && npm run build", "build:wasm": "cargo +nightly -Zbuild-std=panic_abort,core,alloc -Zbuild-std-features=compiler-builtins-mem build --target wasm32-wasip2 --release --no-default-features",
"build:proto": "protoc --ts_out=gen --ts_opt add_pb_suffix,eslint_disable,ts_nocheck,use_proto_field_name --proto_path=../objdiff-core/protos ../objdiff-core/protos/*.proto", "build:transpile": "jco transpile ../target/wasm32-wasip2/release/objdiff_wasm.wasm --no-nodejs-compat --no-wasi-shim --no-namespaced-exports --map wasi:logging/logging=./wasi-logging.js -o pkg --name objdiff",
"build:wasm": "cd ../objdiff-core && wasm-pack build --out-dir ../objdiff-wasm/pkg --target web -- --features arm,arm64,dwarf,config,ppc,x86,wasm" "build:lib": "rslib build"
},
"dependencies": {
"@protobuf-ts/runtime": "^2.9.4"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.9.0", "@biomejs/biome": "^1.9.3",
"@protobuf-ts/plugin": "^2.9.4", "@bytecodealliance/jco": "^1.9.1",
"@types/node": "^22.4.1", "@rslib/core": "^0.4.1",
"esbuild": "^0.23.1", "typescript": "^5.7.2"
"eslint": "^9.9.0",
"globals": "^15.9.0",
"tsup": "^8.2.4",
"typescript-eslint": "^8.2.0"
} }
} }

View File

@ -0,0 +1,19 @@
import { defineConfig } from '@rslib/core';
export default defineConfig({
source: {
entry: {
'wasi-logging': 'lib/wasi-logging.ts',
},
},
lib: [
{
format: 'esm',
syntax: 'es2022',
},
],
output: {
target: 'web',
copy: [{ from: 'pkg' }, { from: '../objdiff-core/config-schema.json' }],
},
});

321
objdiff-wasm/src/api.rs Normal file
View File

@ -0,0 +1,321 @@
use alloc::{
format,
rc::Rc,
str::FromStr,
string::{String, ToString},
vec::Vec,
};
use core::cell::RefCell;
use objdiff_core::{diff, obj};
use regex::RegexBuilder;
use super::logging;
wit_bindgen::generate!({
world: "api",
with: {
"wasi:logging/logging@0.1.0-draft": logging::wasi_logging,
},
});
use exports::objdiff::core::{
diff::Guest as GuestDiff,
diff_types::{
DiffConfigBorrow, DiffResult, Guest as GuestDiffTypes, GuestDiffConfig, GuestObject,
GuestObjectDiff, Object, ObjectBorrow, ObjectDiff, ObjectDiffBorrow,
},
display_types::{
ContextMenuItem, DiffText, DiffTextOpcode, DiffTextSegment, DiffTextSymbol, DisplayConfig,
HoverItem, InstructionDiffKind, InstructionDiffRow, SectionDisplay, SectionDisplaySymbol,
SymbolDisplay, SymbolFilter, SymbolFlags, SymbolKind, SymbolRef,
},
};
struct Component;
impl Guest for Component {
fn init(level: logging::wasi_logging::Level) { logging::init(level); }
fn version() -> String { env!("CARGO_PKG_VERSION").to_string() }
}
#[repr(transparent)]
struct ResourceObject(Rc<obj::Object>);
struct ResourceObjectDiff(Rc<obj::Object>, diff::ObjectDiff);
#[repr(transparent)]
struct ResourceDiffConfig(RefCell<diff::DiffObjConfig>);
impl GuestDiffTypes for Component {
type DiffConfig = ResourceDiffConfig;
type Object = ResourceObject;
type ObjectDiff = ResourceObjectDiff;
}
impl GuestDiff for Component {
fn run_diff(
left: Option<ObjectBorrow>,
right: Option<ObjectBorrow>,
diff_config: DiffConfigBorrow,
) -> Result<DiffResult, String> {
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
log::debug!("Running diff with config: {:?}", diff_config);
let result = diff::diff_objs(
left.as_ref().map(|o| o.get::<ResourceObject>().0.as_ref()),
right.as_ref().map(|o| o.get::<ResourceObject>().0.as_ref()),
None,
&diff_config,
&diff::MappingConfig::default(),
)
.map_err(|e| e.to_string())?;
Ok(DiffResult {
left: result.left.map(|d| {
ObjectDiff::new(ResourceObjectDiff(
left.unwrap().get::<ResourceObject>().0.clone(),
d,
))
}),
right: result.right.map(|d| {
ObjectDiff::new(ResourceObjectDiff(
right.unwrap().get::<ResourceObject>().0.clone(),
d,
))
}),
})
}
fn symbol_context(_obj: ObjectBorrow, _symbol: SymbolRef) -> Vec<ContextMenuItem> { todo!() }
fn symbol_hover(_obj: ObjectBorrow, _symbol: SymbolRef) -> Vec<HoverItem> { todo!() }
fn display_sections(
diff: ObjectDiffBorrow,
filter: SymbolFilter,
config: DisplayConfig,
) -> Vec<SectionDisplay> {
let regex = filter.regex.as_ref().and_then(|s| {
RegexBuilder::new(s).case_insensitive(true).build().ok().or_else(|| {
// Use the string as a literal if the regex fails to compile
let escaped = regex::escape(s);
RegexBuilder::new(&escaped).case_insensitive(true).build().ok()
})
});
let filter = if let Some(mapping) = filter.mapping {
diff::display::SymbolFilter::Mapping(mapping as usize, regex.as_ref())
} else if let Some(regex) = &regex {
diff::display::SymbolFilter::Search(regex)
} else {
diff::display::SymbolFilter::None
};
let obj_diff = diff.get::<ResourceObjectDiff>();
diff::display::display_sections(
obj_diff.0.as_ref(),
&obj_diff.1,
filter,
config.show_hidden_symbols,
config.show_mapped_symbols,
config.reverse_fn_order,
)
.into_iter()
.map(|d| SectionDisplay {
id: d.id,
name: d.name,
size: d.size,
match_percent: d.match_percent,
symbols: d
.symbols
.into_iter()
.map(|s| SectionDisplaySymbol {
symbol: s.symbol as SymbolRef,
is_mapping_symbol: s.is_mapping_symbol,
})
.collect(),
})
.collect()
}
fn display_symbol(
diff: ObjectDiffBorrow,
symbol_display: SectionDisplaySymbol,
) -> SymbolDisplay {
let obj_diff = diff.get::<ResourceObjectDiff>();
let obj = obj_diff.0.as_ref();
let obj_diff = &obj_diff.1;
let symbol_idx = symbol_display.symbol as usize;
let symbol = &obj.symbols[symbol_idx];
let symbol_diff = if symbol_display.is_mapping_symbol {
obj_diff
.mapping_symbols
.iter()
.find(|s| s.symbol_index == symbol_idx)
.map(|s| &s.symbol_diff)
.unwrap()
} else {
&obj_diff.symbols[symbol_idx]
};
SymbolDisplay {
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),
flags: SymbolFlags::from(symbol.flags),
align: symbol.align.map(|a| a.get()),
virtual_address: symbol.virtual_address,
target_symbol: symbol_diff.target_symbol.map(|s| s as u32),
match_percent: symbol_diff.match_percent,
diff_score: symbol_diff.diff_score,
row_count: symbol_diff.instruction_rows.len() as u32,
}
}
fn display_instruction_row(
diff: ObjectDiffBorrow,
symbol_display: SectionDisplaySymbol,
row_index: u32,
diff_config: DiffConfigBorrow,
) -> InstructionDiffRow {
let mut segments = Vec::with_capacity(16);
let obj_diff = diff.get::<ResourceObjectDiff>();
let obj = obj_diff.0.as_ref();
let obj_diff = &obj_diff.1;
let symbol_idx = symbol_display.symbol as usize;
let symbol_diff = if symbol_display.is_mapping_symbol {
obj_diff
.mapping_symbols
.iter()
.find(|s| s.symbol_index == symbol_idx)
.map(|s| &s.symbol_diff)
.unwrap()
} else {
&obj_diff.symbols[symbol_idx]
};
let row = &symbol_diff.instruction_rows[row_index as usize];
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
diff::display::display_row(obj, symbol_idx, row, &diff_config, |text, idx| {
segments.push(DiffTextSegment { text: DiffText::from(text), diff_index: idx.get() });
Ok(())
})
.unwrap();
InstructionDiffRow { segments, diff_kind: InstructionDiffKind::from(row.kind) }
}
}
impl From<obj::SymbolKind> for SymbolKind {
fn from(kind: obj::SymbolKind) -> Self {
match kind {
obj::SymbolKind::Unknown => SymbolKind::Unknown,
obj::SymbolKind::Function => SymbolKind::Function,
obj::SymbolKind::Object => SymbolKind::Object,
obj::SymbolKind::Section => SymbolKind::Section,
}
}
}
impl From<obj::SymbolFlagSet> for SymbolFlags {
fn from(flags: obj::SymbolFlagSet) -> SymbolFlags {
let mut out = SymbolFlags::empty();
for flag in flags {
out |= match flag {
obj::SymbolFlag::Global => SymbolFlags::GLOBAL,
obj::SymbolFlag::Local => SymbolFlags::LOCAL,
obj::SymbolFlag::Weak => SymbolFlags::WEAK,
obj::SymbolFlag::Common => SymbolFlags::COMMON,
obj::SymbolFlag::Hidden => SymbolFlags::HIDDEN,
obj::SymbolFlag::HasExtra => SymbolFlags::HAS_EXTRA,
obj::SymbolFlag::SizeInferred => SymbolFlags::SIZE_INFERRED,
};
}
out
}
}
impl From<diff::display::DiffText<'_>> for DiffText {
fn from(text: diff::display::DiffText) -> Self {
match text {
diff::display::DiffText::Basic(v) => DiffText::Basic(v.to_string()),
diff::display::DiffText::Line(v) => DiffText::Line(v),
diff::display::DiffText::Address(v) => DiffText::Address(v),
diff::display::DiffText::Opcode(n, op) => {
DiffText::Opcode(DiffTextOpcode { mnemonic: n.to_string(), opcode: op })
}
diff::display::DiffText::Argument(s) => match s {
obj::InstructionArgValue::Signed(v) => DiffText::Signed(*v),
obj::InstructionArgValue::Unsigned(v) => DiffText::Unsigned(*v),
obj::InstructionArgValue::Opaque(v) => DiffText::Opaque(v.to_string()),
},
diff::display::DiffText::BranchDest(v) => DiffText::BranchDest(v),
diff::display::DiffText::Symbol(s) => DiffText::Symbol(DiffTextSymbol {
name: s.name.clone(),
demangled_name: s.demangled_name.clone(),
}),
diff::display::DiffText::Addend(v) => DiffText::Addend(v),
diff::display::DiffText::Spacing(v) => DiffText::Spacing(v as u32),
diff::display::DiffText::Eol => DiffText::Eol,
}
}
}
impl From<diff::InstructionDiffKind> for InstructionDiffKind {
fn from(kind: diff::InstructionDiffKind) -> Self {
match kind {
diff::InstructionDiffKind::None => InstructionDiffKind::None,
diff::InstructionDiffKind::OpMismatch => InstructionDiffKind::OpMismatch,
diff::InstructionDiffKind::ArgMismatch => InstructionDiffKind::ArgMismatch,
diff::InstructionDiffKind::Replace => InstructionDiffKind::Replace,
diff::InstructionDiffKind::Insert => InstructionDiffKind::Insert,
diff::InstructionDiffKind::Delete => InstructionDiffKind::Delete,
}
}
}
impl GuestDiffConfig for ResourceDiffConfig {
fn new() -> Self { Self(RefCell::new(diff::DiffObjConfig::default())) }
fn set_property(&self, key: String, value: String) -> Result<(), String> {
let id = diff::ConfigPropertyId::from_str(&key)
.map_err(|_| format!("Invalid property key {:?}", key))?;
self.0
.borrow_mut()
.set_property_value_str(id, &value)
.map_err(|_| format!("Invalid property value {:?}", value))
}
fn get_property(&self, key: String) -> Result<String, String> {
let id = diff::ConfigPropertyId::from_str(&key)
.map_err(|_| format!("Invalid property key {:?}", key))?;
Ok(self.0.borrow().get_property_value(id).to_string())
}
}
impl GuestObject for ResourceObject {
fn parse(data: Vec<u8>, diff_config: DiffConfigBorrow) -> Result<Object, String> {
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
obj::read::parse(&data, &diff_config)
.map(|o| Object::new(ResourceObject(Rc::new(o))))
.map_err(|e| e.to_string())
}
}
impl GuestObjectDiff for ResourceObjectDiff {
fn find_symbol(&self, name: String, section_name: Option<String>) -> Option<SymbolRef> {
let obj = self.0.as_ref();
obj.symbols
.iter()
.position(|s| {
s.name == name
&& match section_name.as_deref() {
Some(section_name) => {
s.section.is_some_and(|n| obj.sections[n].name == section_name)
}
None => true,
}
})
.map(|i| i as SymbolRef)
}
}
export!(Component);

View File

@ -1,107 +0,0 @@
import {ArgumentValue, InstructionDiff, RelocationTarget} from "../gen/diff_pb";
export type DiffText =
DiffTextBasic
| DiffTextBasicColor
| DiffTextAddress
| DiffTextLine
| DiffTextOpcode
| DiffTextArgument
| DiffTextSymbol
| DiffTextBranchDest
| DiffTextSpacing;
type DiffTextBase = {
diff_index?: number,
};
export type DiffTextBasic = DiffTextBase & {
type: 'basic',
text: string,
};
export type DiffTextBasicColor = DiffTextBase & {
type: 'basic_color',
text: string,
index: number,
};
export type DiffTextAddress = DiffTextBase & {
type: 'address',
address: bigint,
};
export type DiffTextLine = DiffTextBase & {
type: 'line',
line_number: number,
};
export type DiffTextOpcode = DiffTextBase & {
type: 'opcode',
mnemonic: string,
opcode: number,
};
export type DiffTextArgument = DiffTextBase & {
type: 'argument',
value: ArgumentValue,
};
export type DiffTextSymbol = DiffTextBase & {
type: 'symbol',
target: RelocationTarget,
};
export type DiffTextBranchDest = DiffTextBase & {
type: 'branch_dest',
address: bigint,
};
export type DiffTextSpacing = DiffTextBase & {
type: 'spacing',
count: number,
};
// Native JavaScript implementation of objdiff_core::diff::display::display_diff
export function displayDiff(diff: InstructionDiff, baseAddr: bigint, cb: (text: DiffText) => void) {
const ins = diff.instruction;
if (!ins) {
return;
}
if (ins.line_number != null) {
cb({type: 'line', line_number: ins.line_number});
}
cb({type: 'address', address: ins.address - baseAddr});
if (diff.branch_from) {
cb({type: 'basic_color', text: ' ~> ', index: diff.branch_from.branch_index});
} else {
cb({type: 'spacing', count: 4});
}
cb({type: 'opcode', mnemonic: ins.mnemonic, opcode: ins.opcode});
let arg_diff_idx = 0; // non-PlainText argument index
for (let i = 0; i < ins.arguments.length; i++) {
if (i === 0) {
cb({type: 'spacing', count: 1});
}
const arg = ins.arguments[i].value;
let diff_index: number | undefined;
if (arg.oneofKind !== 'plain_text') {
diff_index = diff.arg_diff[arg_diff_idx]?.diff_index;
arg_diff_idx++;
}
switch (arg.oneofKind) {
case "plain_text":
cb({type: 'basic', text: arg.plain_text, diff_index});
break;
case "argument":
cb({type: 'argument', value: arg.argument, diff_index});
break;
case "relocation": {
const reloc = ins.relocation!;
cb({type: 'symbol', target: reloc.target!, diff_index});
break;
}
case "branch_dest":
if (arg.branch_dest < baseAddr) {
cb({type: 'basic', text: '<unknown>', diff_index});
} else {
cb({type: 'branch_dest', address: arg.branch_dest - baseAddr, diff_index});
}
break;
}
}
if (diff.branch_to) {
cb({type: 'basic_color', text: ' ~> ', index: diff.branch_to.branch_index});
}
}

14
objdiff-wasm/src/lib.rs Normal file
View File

@ -0,0 +1,14 @@
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[cfg(target_os = "wasi")]
mod api;
#[cfg(target_os = "wasi")]
mod logging;
#[cfg(all(target_os = "wasi", not(feature = "std")))]
mod cabi_realloc;
#[cfg(all(target_family = "wasm", not(feature = "std")))]
#[global_allocator]
static ALLOCATOR: talc::TalckWasm = unsafe { talc::TalckWasm::new_global() };

View File

@ -0,0 +1,52 @@
wit_bindgen::generate!({
world: "imports",
path: "wit/deps/logging",
});
use alloc::format;
pub use wasi::logging::logging as wasi_logging;
struct WasiLogger;
impl log::Log for WasiLogger {
fn enabled(&self, metadata: &log::Metadata) -> bool { metadata.level() <= log::max_level() }
fn log(&self, record: &log::Record) {
if !self.enabled(record.metadata()) {
return;
}
let level = match record.level() {
log::Level::Error => wasi_logging::Level::Error,
log::Level::Warn => wasi_logging::Level::Warn,
log::Level::Info => wasi_logging::Level::Info,
log::Level::Debug => wasi_logging::Level::Debug,
log::Level::Trace => wasi_logging::Level::Trace,
};
wasi_logging::log(level, record.target(), &format!("{}", record.args()));
}
fn flush(&self) {}
}
static LOGGER: WasiLogger = WasiLogger;
pub fn init(level: wasi_logging::Level) {
let _ = log::set_logger(&LOGGER);
log::set_max_level(match level {
wasi_logging::Level::Error => log::LevelFilter::Error,
wasi_logging::Level::Warn => log::LevelFilter::Warn,
wasi_logging::Level::Info => log::LevelFilter::Info,
wasi_logging::Level::Debug => log::LevelFilter::Debug,
wasi_logging::Level::Trace => log::LevelFilter::Trace,
wasi_logging::Level::Critical => log::LevelFilter::Off,
});
}
#[cfg(not(feature = "std"))]
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
use alloc::string::ToString;
wasi_logging::log(wasi_logging::Level::Critical, "objdiff_core::panic", &info.to_string());
core::arch::wasm32::unreachable();
}

View File

@ -1,132 +0,0 @@
import {DiffResult} from "../gen/diff_pb";
import type {
ConfigProperty,
MappingConfig,
SymbolMappings,
} from '../pkg';
import {AnyHandlerData, InMessage, OutMessage} from './worker';
// Export wasm types
export {ConfigProperty, MappingConfig, SymbolMappings};
// Export protobuf types
export * from '../gen/diff_pb';
// Export display types
export * from './display';
interface PromiseCallbacks<T> {
start: number;
resolve: (value: T | PromiseLike<T>) => void;
reject: (reason?: string) => void;
}
let workerInit = false;
let workerCallbacks: PromiseCallbacks<Worker>;
const workerReady = new Promise<Worker>((resolve, reject) => {
workerCallbacks = {start: performance.now(), resolve, reject};
});
export async function initialize(data?: {
workerUrl?: string | URL,
wasmUrl?: string | URL, // Relative to worker URL
}): Promise<Worker> {
if (workerInit) {
return workerReady;
}
workerInit = true;
let {workerUrl, wasmUrl} = data || {};
if (!workerUrl) {
try {
// Bundlers will convert this into an asset URL
workerUrl = new URL('./worker.js', import.meta.url);
} catch (_) {
workerUrl = 'worker.js';
}
}
if (!wasmUrl) {
try {
// Bundlers will convert this into an asset URL
wasmUrl = new URL('./objdiff_core_bg.wasm', import.meta.url);
} catch (_) {
wasmUrl = 'objdiff_core_bg.js';
}
}
const worker = new Worker(workerUrl, {
name: 'objdiff',
type: 'module',
});
worker.onmessage = onMessage;
worker.onerror = (event) => {
console.error("Worker error", event);
workerCallbacks.reject("Worker failed to initialize, wrong URL?");
};
defer<void>({
type: 'init',
// URL can't be sent directly
wasmUrl: wasmUrl.toString(),
}, worker).then(() => {
workerCallbacks.resolve(worker);
}, (e) => {
workerCallbacks.reject(e);
});
return workerReady;
}
let globalMessageId = 0;
const messageCallbacks = new Map<number, PromiseCallbacks<never>>();
function onMessage(event: MessageEvent<OutMessage>) {
switch (event.data.type) {
case 'result': {
const {result, error, messageId} = event.data;
const callbacks = messageCallbacks.get(messageId);
if (callbacks) {
const end = performance.now();
console.debug(`Message ${messageId} took ${end - callbacks.start}ms`);
messageCallbacks.delete(messageId);
if (error != null) {
callbacks.reject(error);
} else {
callbacks.resolve(result as never);
}
} else {
console.warn(`Unknown message ID ${messageId}`);
}
break;
}
}
}
async function defer<T>(message: AnyHandlerData, worker?: Worker): Promise<T> {
worker = worker || await initialize();
const messageId = globalMessageId++;
const promise = new Promise<T>((resolve, reject) => {
messageCallbacks.set(messageId, {start: performance.now(), resolve, reject});
});
worker.postMessage({
...message,
messageId
} as InMessage);
return promise;
}
export async function runDiff(
left: Uint8Array | null | undefined,
right: Uint8Array | null | undefined,
properties?: ConfigProperty[],
mappingConfig?: MappingConfig,
): Promise<DiffResult> {
const data = await defer<Uint8Array>({
type: 'run_diff_proto',
left,
right,
properties,
mappingConfig,
});
const parseStart = performance.now();
const result = DiffResult.fromBinary(data, {readUnknownField: false});
const end = performance.now();
console.debug(`Parsing message took ${end - parseStart}ms`);
return result;
}

View File

@ -1,93 +0,0 @@
import wasmInit, * as exports from '../pkg';
const handlers = {
init: init,
run_diff_proto: run_diff_proto,
} as const;
type ExtractData<T> = T extends (arg: infer U) => Promise<unknown> ? U : never;
type HandlerData = {
[K in keyof typeof handlers]: { type: K } & ExtractData<typeof handlers[K]>;
};
let wasmReady: Promise<void> | null = null;
async function init({wasmUrl}: { wasmUrl?: string }): Promise<void> {
if (wasmReady != null) {
throw new Error('Already initialized');
}
wasmReady = wasmInit({module_or_path: wasmUrl})
.then(() => {
});
return wasmReady;
}
async function initIfNeeded() {
if (wasmReady == null) {
await init({});
}
return wasmReady;
}
async function run_diff_proto({left, right, properties, mappingConfig}: {
left: Uint8Array | null | undefined,
right: Uint8Array | null | undefined,
properties?: exports.ConfigProperty[],
mappingConfig?: exports.MappingConfig,
}): Promise<Uint8Array> {
const diffConfig = exports.config_from_properties(properties || []);
const leftObj = left ? exports.parse_object(left, diffConfig) : null;
const rightObj = right ? exports.parse_object(right, diffConfig) : null;
return exports.run_diff(leftObj, rightObj, diffConfig, mappingConfig || {});
}
export type AnyHandlerData = HandlerData[keyof HandlerData];
export type InMessage = AnyHandlerData & { messageId: number };
export type OutMessage = {
type: 'result',
result: unknown | null,
error: string | null,
messageId: number,
};
self.onmessage = (event: MessageEvent<InMessage>) => {
const data = event.data;
const messageId = data?.messageId;
(async () => {
if (!data) {
throw new Error('No data');
}
const handler = handlers[data.type];
if (handler) {
if (data.type !== 'init') {
await initIfNeeded();
}
const start = performance.now();
const result = await handler(data as never);
const end = performance.now();
console.debug(`Worker message ${data.messageId} took ${end - start}ms`);
let transfer: Transferable[] = [];
if (result instanceof Uint8Array) {
console.log("Transferring!", result.byteLength);
transfer = [result.buffer];
} else {
console.log("Didn't transfer", typeof result);
}
self.postMessage({
type: 'result',
result: result,
error: null,
messageId,
} as OutMessage, {transfer});
} else {
throw new Error(`No handler for ${data.type}`);
}
})().catch(error => {
self.postMessage({
type: 'result',
result: null,
error: error.toString(),
messageId,
} as OutMessage);
});
};

View File

@ -1,9 +1,17 @@
{ {
"compilerOptions": { "compilerOptions": {
"esModuleInterop": true, "lib": ["DOM", "ES2023"],
"module": "ES2022",
"moduleResolution": "Node",
"strict": true,
"target": "ES2022", "target": "ES2022",
} "noEmit": true,
"skipLibCheck": true,
"useDefineForClassFields": true,
"module": "ESNext",
"isolatedModules": true,
"resolveJsonModule": true,
"moduleResolution": "Bundler",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": ["lib"]
} }

View File

@ -1,34 +0,0 @@
import {defineConfig} from 'tsup';
import fs from 'node:fs/promises';
export default defineConfig([
// Build main library
{
entry: ['src/main.ts'],
clean: true,
dts: true,
format: 'esm',
outDir: 'dist',
skipNodeModulesBundle: true,
sourcemap: true,
splitting: false,
target: 'es2022',
},
// Build web worker
{
entry: ['src/worker.ts'],
clean: true,
dts: true,
format: 'esm', // type: 'module'
minify: true,
outDir: 'dist',
sourcemap: true,
splitting: false,
target: 'es2022',
// https://github.com/egoist/tsup/issues/278
async onSuccess() {
await fs.copyFile('pkg/objdiff_core_bg.wasm', 'dist/objdiff_core_bg.wasm');
await fs.copyFile('../objdiff-core/config-schema.json', 'dist/config-schema.json');
}
}
]);

1
objdiff-wasm/wit/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
deps/

View File

@ -0,0 +1,4 @@
[logging]
url = "https://github.com/WebAssembly/wasi-logging/archive/d31c41d0d9eed81aabe02333d0025d42acf3fb75.tar.gz"
sha256 = "ad81d8b7f7a8ceb729cf551f1d24586f0de9560a43eea57a9bb031d2175804e1"
sha512 = "1687ad9a02ab3e689443e67d1a0605f58fc5dea828d2e4d2c7825c6002714fac9bd4289b1a68b61a37dcca6c3b421f4c8ed4b1e6cc29f6460e0913cf1bf11c04"

View File

@ -0,0 +1 @@
logging = "https://github.com/WebAssembly/wasi-logging/archive/d31c41d0d9eed81aabe02333d0025d42acf3fb75.tar.gz"

View File

@ -0,0 +1,249 @@
package objdiff:core;
use wasi:logging/logging@0.1.0-draft;
interface diff-types {
resource diff-config {
constructor();
set-property: func(id: string, value: string) -> result<_, string>;
get-property: func(id: string) -> result<string, string>;
}
record mapping-config {
mappings: list<tuple<string, string>>,
selecting-left: option<string>,
selecting-right: option<string>,
}
resource object {
parse: static func(
data: list<u8>,
config: borrow<diff-config>,
) -> result<object, string>;
}
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>,
}
}
interface display-types {
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 {
unknown,
function,
object,
section,
}
flags symbol-flags {
global,
local,
weak,
common,
hidden,
has-extra,
size-inferred,
}
record symbol-display {
name: string,
demangled-name: option<string>,
address: u64,
size: u64,
kind: symbol-kind,
section: option<u32>,
%flags: symbol-flags,
align: option<u32>,
virtual-address: option<u64>,
target-symbol: option<symbol-ref>,
match-percent: option<f32>,
diff-score: option<tuple<u64, u64>>,
row-count: u32,
}
record context-menu-item-copy {
value: string,
label: option<string>,
}
record context-menu-item-navigate {
label: string,
}
variant context-menu-item {
copy(context-menu-item-copy),
navigate(context-menu-item-navigate),
}
enum hover-item-color {
normal,
emphasized,
special,
}
record hover-item {
text: string,
color: hover-item-color,
}
record diff-text-opcode {
mnemonic: string,
opcode: u16,
}
record diff-text-symbol {
name: string,
demangled-name: option<string>,
}
variant diff-text {
// Basic text (not semantically meaningful)
basic(string),
// Line number
line(u32),
// Instruction address
address(u64),
// Instruction mnemonic
opcode(diff-text-opcode),
// Instruction argument (signed)
signed(s64),
// Instruction argument (unsigned)
unsigned(u64),
// Instruction argument (opaque)
opaque(string),
// Instruction argument (branch destination)
branch-dest(u64),
// Relocation target name
symbol(diff-text-symbol),
// Relocation addend
addend(s64),
// Number of spaces
spacing(u32),
// End of line
eol,
}
record diff-text-segment {
// Text to display
text: diff-text,
// Index for colorization
diff-index: option<u32>,
}
record instruction-diff-row {
// Text segments
segments: list<diff-text-segment>,
// Diff kind
diff-kind: instruction-diff-kind,
}
enum instruction-diff-kind {
none,
op-mismatch,
arg-mismatch,
replace,
insert,
delete,
}
}
interface diff {
use diff-types.{
object,
object-diff,
diff-config,
diff-result
};
use display-types.{
section-display-symbol,
section-display,
symbol-ref,
symbol-filter,
symbol-display,
context-menu-item,
hover-item,
display-config,
instruction-diff-row
};
run-diff: func(
left: option<borrow<object>>,
right: option<borrow<object>>,
config: borrow<diff-config>,
) -> result<diff-result, string>;
display-sections: func(
diff: borrow<object-diff>,
filter: symbol-filter,
config: display-config,
) -> list<section-display>;
display-symbol: func(
diff: borrow<object-diff>,
symbol: section-display-symbol,
) -> symbol-display;
symbol-context: func(
object: borrow<object>,
symbol: symbol-ref,
) -> list<context-menu-item>;
symbol-hover: func(
object: borrow<object>,
symbol: symbol-ref,
) -> list<hover-item>;
display-instruction-row: func(
diff: borrow<object-diff>,
symbol: section-display-symbol,
row-index: u32,
config: borrow<diff-config>,
) -> instruction-diff-row;
}
world api {
import logging;
use logging.{level};
export diff;
export diff-types;
export display-types;
export init: func(level: level);
export version: func() -> string;
}