From a1f2a535e5f92bfc79048cd02e4322e48c32e7b6 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 2 Mar 2025 15:20:29 -0700 Subject: [PATCH] Unify context menu / hover tooltip code + UI improvements --- Cargo.lock | 15 +- deny.toml | 7 +- objdiff-core/Cargo.toml | 18 +- objdiff-core/src/arch/arm.rs | 53 +- objdiff-core/src/arch/arm64.rs | 179 ++---- objdiff-core/src/arch/mips.rs | 53 +- objdiff-core/src/arch/mod.rs | 184 +++---- objdiff-core/src/arch/ppc.rs | 188 ++++--- objdiff-core/src/arch/x86.rs | 128 +++-- objdiff-core/src/diff/code.rs | 82 +-- objdiff-core/src/diff/data.rs | 16 +- objdiff-core/src/diff/display.rs | 432 ++++++++++----- objdiff-core/src/obj/mod.rs | 88 ++- objdiff-core/src/obj/read.rs | 1 + objdiff-core/tests/arch_ppc.rs | 8 + objdiff-core/tests/data/ppc/NMWException.o | Bin 0 -> 1840 bytes .../tests/snapshots/arch_ppc__read_extab.snap | 521 ++++++++++++++++++ .../tests/snapshots/arch_ppc__read_ppc.snap | 1 + .../tests/snapshots/arch_x86__read_x86.snap | 1 + objdiff-gui/Cargo.toml | 6 +- objdiff-gui/src/app.rs | 4 +- objdiff-gui/src/main.rs | 3 +- objdiff-gui/src/views/diff.rs | 215 ++++++-- objdiff-gui/src/views/function_diff.rs | 242 ++------ objdiff-gui/src/views/graphics.rs | 32 +- objdiff-gui/src/views/symbol_diff.rs | 348 +++++++----- 26 files changed, 1730 insertions(+), 1095 deletions(-) create mode 100644 objdiff-core/tests/data/ppc/NMWException.o create mode 100644 objdiff-core/tests/snapshots/arch_ppc__read_extab.snap diff --git a/Cargo.lock b/Cargo.lock index 7342b47..7b1c2a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -947,7 +947,7 @@ checksum = "c2e06f9bce634a3c898eb1e5cb949ff63133cbb218af93cc9b38b31d6f3ea285" [[package]] name = "cwextab" version = "1.0.4" -source = "git+https://github.com/encounter/cwextab.git#15c344ac3302c32adbb8777c70f5ce739f432a6b" +source = "git+https://github.com/CelestialAmber/cwextab.git#a81d42980912afebf6d3243dddf7309b138c4e28" dependencies = [ "thiserror 2.0.11", ] @@ -1498,8 +1498,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flagset" version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3ea1ec5f8307826a5b71094dd91fc04d4ae75d5709b20ad351c7fb4815c86ec" +source = "git+https://github.com/enarx/flagset.git?rev=a1fe9369b3741e43fec45da1998e83b9d78966a2#a1fe9369b3741e43fec45da1998e83b9d78966a2" [[package]] name = "flate2" @@ -3268,7 +3267,6 @@ version = "3.0.0-alpha.1" dependencies = [ "anyhow", "arm-attr", - "byteorder", "cpp_demangle", "cwdemangle", "cwextab", @@ -3299,13 +3297,13 @@ dependencies = [ "rabbitizer", "regex", "reqwest 0.12.12", + "rlwinmdec", "self_update", "semver", "serde", "serde_json", "shell-escape", "similar 2.7.0 (git+https://github.com/encounter/similar.git?branch=no_std)", - "strum", "syn", "tempfile", "time", @@ -3321,7 +3319,6 @@ name = "objdiff-gui" version = "3.0.0-alpha.1" dependencies = [ "anyhow", - "bytes", "cfg-if", "const_format", "cwdemangle", @@ -3347,7 +3344,6 @@ dependencies = [ "serde", "serde_json", "shell-escape", - "strum", "tauri-winres", "time", "tracing-subscriber", @@ -4188,9 +4184,8 @@ dependencies = [ [[package]] name = "rlwinmdec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2076dbc187938f3db71c03c85d143febf01026631189dc8ca85f8c886d90ea12" +version = "1.1.0" +source = "git+https://github.com/CelestialAmber/rlwinmdec.git#06e9a86eec1f21466a97cc48a752e9177985e1eb" dependencies = [ "anyhow", ] diff --git a/deny.toml b/deny.toml index e2bd725..b0f2902 100644 --- a/deny.toml +++ b/deny.toml @@ -239,7 +239,12 @@ allow-git = [] [sources.allow-org] # github.com organizations to allow git sources for -github = ["encounter"] +github = [ + "CelestialAmber", # cwextab, rlwinmdec + "Decompollaborate", # rabbitizer + "enarx", # flagset + "encounter", +] # gitlab.com organizations to allow git sources for gitlab = [] # bitbucket.org organizations to allow git sources for diff --git a/objdiff-core/Cargo.toml b/objdiff-core/Cargo.toml index f5dc872..6c00dd6 100644 --- a/objdiff-core/Cargo.toml +++ b/objdiff-core/Cargo.toml @@ -30,7 +30,6 @@ all = [ ] # Implicit, used to check if any arch is enabled any-arch = [ - "dep:byteorder", "dep:flagset", "dep:heck", "dep:log", @@ -40,7 +39,6 @@ any-arch = [ "dep:quote", "dep:regex", "dep:similar", - "dep:strum", "dep:syn", ] bindings = [ @@ -71,14 +69,12 @@ serde = [ ] std = [ "anyhow/std", - "byteorder?/std", "flagset?/std", "log?/std", "num-traits?/std", "object/std", "prost?/std", "serde?/std", - "strum?/std", "typed-path?/std", "dep:filetime", "dep:memmap2", @@ -92,6 +88,7 @@ ppc = [ "dep:cwdemangle", "dep:cwextab", "dep:ppc750cl", + "dep:rlwinmdec", ] x86 = [ "any-arch", @@ -117,21 +114,19 @@ features = ["all"] [dependencies] anyhow = { version = "1.0", default-features = false } -byteorder = { version = "1.5", default-features = false, optional = true } filetime = { version = "0.2", optional = true } -flagset = { version = "0.4", default-features = false, optional = true } +flagset = { version = "0.4", default-features = false, optional = true, git = "https://github.com/enarx/flagset.git", rev = "a1fe9369b3741e43fec45da1998e83b9d78966a2" } +itertools = { version = "0.14", default-features = false, features = ["use_alloc"] } log = { version = "0.4", default-features = false, optional = true } memmap2 = { version = "0.9", optional = true } num-traits = { version = "0.2", default-features = false, optional = true } object = { version = "0.36", default-features = false, features = ["read_core", "elf", "pe"] } pbjson = { version = "0.7", default-features = false, optional = true } prost = { version = "0.13", default-features = false, features = ["prost-derive"], optional = true } +regex = { version = "1.11", default-features = false, features = [], optional = true } serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } similar = { version = "2.7", default-features = false, optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" } -strum = { version = "0.26", default-features = false, features = ["derive"], 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 globset = { version = "0.4", default-features = false, optional = true } @@ -143,8 +138,9 @@ gimli = { version = "0.31", default-features = false, features = ["read"], optio # ppc cwdemangle = { version = "1.0", optional = true } -cwextab = { version = "1.0", optional = true, git = "https://github.com/encounter/cwextab.git" } +cwextab = { version = "1.0", optional = true, git = "https://github.com/CelestialAmber/cwextab.git" } ppc750cl = { version = "0.3", optional = true } +rlwinmdec = { version = "1.1", optional = true, git = "https://github.com/CelestialAmber/rlwinmdec.git" } # mips rabbitizer = { git = "https://github.com/Decompollaborate/rabbitizer.git", branch = "🦀", default-features = false, optional = true } @@ -196,4 +192,4 @@ syn = { version = "2.0", optional = true } [dev-dependencies] # Enable all features for tests objdiff-core = { path = ".", features = ["all"] } -insta = "1.42.1" +insta = "1.42" diff --git a/objdiff-core/src/arch/arm.rs b/objdiff-core/src/arch/arm.rs index 05cbd0b..3a489a8 100644 --- a/objdiff-core/src/arch/arm.rs +++ b/objdiff-core/src/arch/arm.rs @@ -1,11 +1,9 @@ use alloc::{ - borrow::Cow, collections::BTreeMap, format, string::{String, ToString}, vec::Vec, }; -use core::ops::Range; use anyhow::{bail, Result}; use arm_attr::{enums::CpuArch, tag::Tag, BuildAttrs}; @@ -16,8 +14,8 @@ use crate::{ arch::Arch, diff::{display::InstructionPart, ArmArchVersion, ArmR9Usage, DiffObjConfig}, obj::{ - InstructionRef, RelocationFlags, ResolvedRelocation, ScannedInstruction, SymbolFlag, - SymbolFlagSet, SymbolKind, + InstructionRef, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation, + ScannedInstruction, SymbolFlag, SymbolFlagSet, SymbolKind, }, }; @@ -252,23 +250,19 @@ impl Arch for ArchArm { fn display_instruction( &self, - ins_ref: InstructionRef, - code: &[u8], - relocation: Option, - _function_range: Range, - _section_index: usize, + resolved: ResolvedInstructionRef, 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(parsed_ins.mnemonic, ins_ref.opcode))?; - if ins == unarm::Ins::Data && relocation.is_some() { + let (ins, parsed_ins) = self.parse_ins_ref(resolved.ins_ref, resolved.code, diff_config)?; + cb(InstructionPart::opcode(parsed_ins.mnemonic, resolved.ins_ref.opcode))?; + if ins == unarm::Ins::Data && resolved.relocation.is_some() { cb(InstructionPart::reloc())?; } else { push_args( &parsed_ins, - relocation, - ins_ref.address as u32, + resolved.relocation, + resolved.ins_ref.address as u32, self.display_options(diff_config), cb, )?; @@ -325,17 +319,38 @@ impl Arch for ArchArm { .and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok()) } - fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> { - Cow::Owned(format!("<{flags:?}>")) - } - - fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize { + fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> { match flags { RelocationFlags::Elf(r_type) => match r_type { + elf::R_ARM_NONE => Some("R_ARM_NONE"), + elf::R_ARM_ABS32 => Some("R_ARM_ABS32"), + elf::R_ARM_REL32 => Some("R_ARM_REL32"), + elf::R_ARM_ABS16 => Some("R_ARM_ABS16"), + elf::R_ARM_ABS8 => Some("R_ARM_ABS8"), + elf::R_ARM_THM_PC22 => Some("R_ARM_THM_PC22"), + elf::R_ARM_THM_XPC22 => Some("R_ARM_THM_XPC22"), + elf::R_ARM_PC24 => Some("R_ARM_PC24"), + elf::R_ARM_XPC25 => Some("R_ARM_XPC25"), + elf::R_ARM_CALL => Some("R_ARM_CALL"), + _ => None, + }, + _ => None, + } + } + + fn data_reloc_size(&self, flags: RelocationFlags) -> usize { + match flags { + RelocationFlags::Elf(r_type) => match r_type { + elf::R_ARM_NONE => 0, elf::R_ARM_ABS32 => 4, elf::R_ARM_REL32 => 4, elf::R_ARM_ABS16 => 2, elf::R_ARM_ABS8 => 1, + elf::R_ARM_THM_PC22 => 4, + elf::R_ARM_THM_XPC22 => 4, + elf::R_ARM_PC24 => 4, + elf::R_ARM_XPC25 => 4, + elf::R_ARM_CALL => 4, _ => 1, }, _ => 1, diff --git a/objdiff-core/src/arch/arm64.rs b/objdiff-core/src/arch/arm64.rs index f9bbf19..bc21a65 100644 --- a/objdiff-core/src/arch/arm64.rs +++ b/objdiff-core/src/arch/arm64.rs @@ -1,10 +1,9 @@ use alloc::{ - borrow::Cow, format, string::{String, ToString}, vec::Vec, }; -use core::{cmp::Ordering, ops::Range}; +use core::cmp::Ordering; use anyhow::{bail, Result}; use object::elf; @@ -17,7 +16,10 @@ use yaxpeax_arm::armv8::a64::{ use crate::{ arch::Arch, diff::{display::InstructionPart, DiffObjConfig}, - obj::{InstructionRef, RelocationFlags, ResolvedRelocation, ScannedInstruction}, + obj::{ + InstructionRef, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation, + ScannedInstruction, + }, }; #[derive(Debug)] @@ -75,15 +77,11 @@ impl Arch for ArchArm64 { fn display_instruction( &self, - ins_ref: InstructionRef, - code: &[u8], - relocation: Option, - function_range: Range, - _section_index: usize, + resolved: ResolvedInstructionRef, _diff_config: &DiffObjConfig, cb: &mut dyn FnMut(InstructionPart) -> Result<()>, ) -> Result<()> { - let mut reader = U8Reader::new(code); + let mut reader = U8Reader::new(resolved.code); let decoder = InstDecoder::default(); let mut ins = Instruction::default(); if decoder.decode_into(&mut ins, &mut reader).is_err() { @@ -92,135 +90,22 @@ impl Arch for ArchArm64 { } let mut ctx = DisplayCtx { - address: ins_ref.address, + address: resolved.ins_ref.address, section_index: 0, - start_address: function_range.start, - end_address: function_range.end, - reloc: relocation, + start_address: resolved.symbol.address, + end_address: resolved.symbol.address + resolved.symbol.size, + reloc: resolved.relocation, }; let mut display_args = Vec::with_capacity(16); let mnemonic = display_instruction(&mut |ret| display_args.push(ret), &ins, &mut ctx); - cb(InstructionPart::opcode(mnemonic, ins_ref.opcode))?; + cb(InstructionPart::opcode(mnemonic, resolved.ins_ref.opcode))?; for arg in display_args { cb(arg)?; } Ok(()) } - // fn process_code( - // &self, - // address: u64, - // code: &[u8], - // section_index: usize, - // relocations: &[ObjReloc], - // line_info: &BTreeMap, - // config: &DiffObjConfig, - // ) -> Result { - // 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 - // + as Reader< - // ::Address, - // ::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(""), - // 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(" "))); - // 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( &self, _file: &object::File<'_>, @@ -238,30 +123,30 @@ impl Arch for ArchArm64 { .and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok()) } - fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> { + fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> { match flags { - RelocationFlags::Elf(elf::R_AARCH64_ADR_PREL_PG_HI21) => { - Cow::Borrowed("R_AARCH64_ADR_PREL_PG_HI21") - } - RelocationFlags::Elf(elf::R_AARCH64_ADD_ABS_LO12_NC) => { - Cow::Borrowed("R_AARCH64_ADD_ABS_LO12_NC") - } - RelocationFlags::Elf(elf::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) => { - Cow::Borrowed("R_AARCH64_LDST32_ABS_LO12_NC") - } - RelocationFlags::Elf(elf::R_AARCH64_ADR_GOT_PAGE) => { - Cow::Borrowed("R_AARCH64_ADR_GOT_PAGE") - } - RelocationFlags::Elf(elf::R_AARCH64_LD64_GOT_LO12_NC) => { - Cow::Borrowed("R_AARCH64_LD64_GOT_LO12_NC") - } - _ => Cow::Owned(format!("<{flags:?}>")), + RelocationFlags::Elf(r_type) => match r_type { + elf::R_AARCH64_NONE => Some("R_AARCH64_NONE"), + elf::R_AARCH64_ABS64 => Some("R_AARCH64_ABS64"), + elf::R_AARCH64_ABS32 => Some("R_AARCH64_ABS32"), + elf::R_AARCH64_ABS16 => Some("R_AARCH64_ABS16"), + elf::R_AARCH64_PREL64 => Some("R_AARCH64_PREL64"), + elf::R_AARCH64_PREL32 => Some("R_AARCH64_PREL32"), + elf::R_AARCH64_PREL16 => Some("R_AARCH64_PREL16"), + elf::R_AARCH64_ADR_PREL_PG_HI21 => Some("R_AARCH64_ADR_PREL_PG_HI21"), + elf::R_AARCH64_ADD_ABS_LO12_NC => Some("R_AARCH64_ADD_ABS_LO12_NC"), + elf::R_AARCH64_JUMP26 => Some("R_AARCH64_JUMP26"), + elf::R_AARCH64_CALL26 => Some("R_AARCH64_CALL26"), + elf::R_AARCH64_LDST32_ABS_LO12_NC => Some("R_AARCH64_LDST32_ABS_LO12_NC"), + elf::R_AARCH64_ADR_GOT_PAGE => Some("R_AARCH64_ADR_GOT_PAGE"), + elf::R_AARCH64_LD64_GOT_LO12_NC => Some("R_AARCH64_LD64_GOT_LO12_NC"), + _ => None, + }, + _ => None, } } - fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize { + fn data_reloc_size(&self, flags: RelocationFlags) -> usize { match flags { RelocationFlags::Elf(r_type) => match r_type { elf::R_AARCH64_ABS64 => 8, diff --git a/objdiff-core/src/arch/mips.rs b/objdiff-core/src/arch/mips.rs index 9377491..a90e66a 100644 --- a/objdiff-core/src/arch/mips.rs +++ b/objdiff-core/src/arch/mips.rs @@ -1,4 +1,4 @@ -use alloc::{borrow::Cow, format, string::ToString, vec::Vec}; +use alloc::{string::ToString, vec::Vec}; use core::ops::Range; use anyhow::{bail, Result}; @@ -15,7 +15,7 @@ use crate::{ diff::{display::InstructionPart, DiffObjConfig, MipsAbi, MipsInstrCategory}, obj::{ InstructionArg, InstructionArgValue, InstructionRef, Relocation, RelocationFlags, - ResolvedRelocation, ScannedInstruction, + ResolvedInstructionRef, ResolvedRelocation, ScannedInstruction, }, }; @@ -148,19 +148,24 @@ impl Arch for ArchMips { fn display_instruction( &self, - ins_ref: InstructionRef, - code: &[u8], - relocation: Option, - function_range: Range, - section_index: usize, + resolved: ResolvedInstructionRef, diff_config: &DiffObjConfig, cb: &mut dyn FnMut(InstructionPart) -> Result<()>, ) -> Result<()> { - let instruction = self.parse_ins_ref(ins_ref, code, diff_config)?; + let instruction = self.parse_ins_ref(resolved.ins_ref, resolved.code, diff_config)?; let display_flags = self.instruction_display_flags(diff_config); let opcode = instruction.opcode(); cb(InstructionPart::opcode(opcode.name(), opcode as u16))?; - push_args(&instruction, relocation, function_range, section_index, &display_flags, cb)?; + let start_address = resolved.symbol.address; + let function_range = start_address..start_address + resolved.symbol.size; + push_args( + &instruction, + resolved.relocation, + function_range, + resolved.section_index, + &display_flags, + cb, + )?; Ok(()) } @@ -202,26 +207,28 @@ impl Arch for ArchMips { }) } - fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> { + fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> { match flags { RelocationFlags::Elf(r_type) => match r_type { - elf::R_MIPS_32 => Cow::Borrowed("R_MIPS_32"), - elf::R_MIPS_26 => Cow::Borrowed("R_MIPS_26"), - elf::R_MIPS_HI16 => Cow::Borrowed("R_MIPS_HI16"), - elf::R_MIPS_LO16 => Cow::Borrowed("R_MIPS_LO16"), - elf::R_MIPS_GPREL16 => Cow::Borrowed("R_MIPS_GPREL16"), - elf::R_MIPS_LITERAL => Cow::Borrowed("R_MIPS_LITERAL"), - elf::R_MIPS_GOT16 => Cow::Borrowed("R_MIPS_GOT16"), - elf::R_MIPS_PC16 => Cow::Borrowed("R_MIPS_PC16"), - elf::R_MIPS_CALL16 => Cow::Borrowed("R_MIPS_CALL16"), - R_MIPS15_S3 => Cow::Borrowed("R_MIPS15_S3"), - _ => Cow::Owned(format!("<{flags:?}>")), + elf::R_MIPS_NONE => Some("R_MIPS_NONE"), + elf::R_MIPS_16 => Some("R_MIPS_16"), + elf::R_MIPS_32 => Some("R_MIPS_32"), + elf::R_MIPS_26 => Some("R_MIPS_26"), + elf::R_MIPS_HI16 => Some("R_MIPS_HI16"), + elf::R_MIPS_LO16 => Some("R_MIPS_LO16"), + elf::R_MIPS_GPREL16 => Some("R_MIPS_GPREL16"), + elf::R_MIPS_LITERAL => Some("R_MIPS_LITERAL"), + elf::R_MIPS_GOT16 => Some("R_MIPS_GOT16"), + elf::R_MIPS_PC16 => Some("R_MIPS_PC16"), + elf::R_MIPS_CALL16 => Some("R_MIPS_CALL16"), + R_MIPS15_S3 => Some("R_MIPS15_S3"), + _ => None, }, - _ => Cow::Owned(format!("<{flags:?}>")), + _ => None, } } - fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize { + fn data_reloc_size(&self, flags: RelocationFlags) -> usize { match flags { RelocationFlags::Elf(r_type) => match r_type { elf::R_MIPS_16 => 2, diff --git a/objdiff-core/src/arch/mod.rs b/objdiff-core/src/arch/mod.rs index fa92b06..e90aeff 100644 --- a/objdiff-core/src/arch/mod.rs +++ b/objdiff-core/src/arch/mod.rs @@ -1,23 +1,25 @@ -use alloc::{borrow::Cow, boxed::Box, format, string::String, vec, vec::Vec}; -use core::{ffi::CStr, fmt, fmt::Debug, ops::Range}; +use alloc::{borrow::Cow, boxed::Box, format, string::String, vec::Vec}; +use core::{ffi::CStr, fmt, fmt::Debug}; use anyhow::{bail, Result}; -use byteorder::ByteOrder; -use object::{File, Relocation, Section}; +use object::Endian as _; use crate::{ - diff::{display::InstructionPart, DiffObjConfig}, + diff::{ + display::{ContextItem, HoverItem, InstructionPart}, + DiffObjConfig, + }, obj::{ - InstructionArg, InstructionRef, ParsedInstruction, RelocationFlags, ResolvedRelocation, + InstructionArg, Object, ParsedInstruction, RelocationFlags, ResolvedInstructionRef, ScannedInstruction, SymbolFlagSet, SymbolKind, }, util::ReallySigned, }; #[cfg(feature = "arm")] -mod arm; +pub mod arm; #[cfg(feature = "arm64")] -mod arm64; +pub mod arm64; #[cfg(feature = "mips")] pub mod mips; #[cfg(feature = "ppc")] @@ -31,7 +33,6 @@ pub enum DataType { Int16, Int32, Int64, - Int128, Float, Double, Bytes, @@ -45,7 +46,6 @@ impl fmt::Display for DataType { DataType::Int16 => write!(f, "Int16"), DataType::Int32 => write!(f, "Int32"), DataType::Int64 => write!(f, "Int64"), - DataType::Int128 => write!(f, "Int128"), DataType::Float => write!(f, "Float"), DataType::Double => write!(f, "Double"), DataType::Bytes => write!(f, "Bytes"), @@ -55,15 +55,15 @@ impl fmt::Display for DataType { } impl DataType { - pub fn display_labels(&self, bytes: &[u8]) -> Vec { + pub fn display_labels(&self, endian: object::Endianness, bytes: &[u8]) -> Vec { let mut strs = Vec::new(); - for literal in self.display_literals::(bytes) { + for literal in self.display_literals(endian, bytes) { strs.push(format!("{}: {}", self, literal)) } strs } - pub fn display_literals(&self, bytes: &[u8]) -> Vec { + pub fn display_literals(&self, endian: object::Endianness, bytes: &[u8]) -> Vec { let mut strs = Vec::new(); if self.required_len().is_some_and(|l| bytes.len() < l) { log::warn!("Failed to display a symbol value for a symbol whose size is too small for instruction referencing it."); @@ -92,7 +92,7 @@ impl DataType { } } DataType::Int16 => { - let i = Endian::read_i16(bytes); + let i = endian.read_i16_bytes(bytes.try_into().unwrap()); strs.push(format!("{:#x}", i)); if i < 0 { @@ -100,7 +100,7 @@ impl DataType { } } DataType::Int32 => { - let i = Endian::read_i32(bytes); + let i = endian.read_i32_bytes(bytes.try_into().unwrap()); strs.push(format!("{:#x}", i)); if i < 0 { @@ -108,15 +108,7 @@ impl DataType { } } DataType::Int64 => { - let i = Endian::read_i64(bytes); - strs.push(format!("{:#x}", i)); - - if i < 0 { - strs.push(format!("{:#x}", ReallySigned(i))); - } - } - DataType::Int128 => { - let i = Endian::read_i128(bytes); + let i = endian.read_i64_bytes(bytes.try_into().unwrap()); strs.push(format!("{:#x}", i)); if i < 0 { @@ -124,10 +116,18 @@ impl DataType { } } DataType::Float => { - strs.push(format!("{:?}f", Endian::read_f32(bytes))); + let bytes: [u8; 4] = bytes.try_into().unwrap(); + strs.push(format!("{:?}f", match endian { + object::Endianness::Little => f32::from_le_bytes(bytes), + object::Endianness::Big => f32::from_be_bytes(bytes), + })); } DataType::Double => { - strs.push(format!("{:?}", Endian::read_f64(bytes))); + let bytes: [u8; 8] = bytes.try_into().unwrap(); + strs.push(format!("{:?}f", match endian { + object::Endianness::Little => f64::from_le_bytes(bytes), + object::Endianness::Big => f64::from_be_bytes(bytes), + })); } DataType::Bytes => { strs.push(format!("{:#?}", bytes)); @@ -148,7 +148,6 @@ impl DataType { DataType::Int16 => Some(2), DataType::Int32 => Some(4), DataType::Int64 => Some(8), - DataType::Int128 => Some(16), DataType::Float => Some(4), DataType::Double => Some(8), DataType::Bytes => None, @@ -177,37 +176,29 @@ pub trait Arch: Send + Sync + Debug { /// 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, - function_range: Range, - section_index: usize, + resolved: ResolvedInstructionRef, diff_config: &DiffObjConfig, ) -> Result { 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(Cow::Owned(m.into_owned())), - InstructionPart::Arg(arg) => args.push(arg.into_static()), - _ => {} - } - Ok(()) - }, - )?; + self.display_instruction(resolved, diff_config, &mut |part| { + match part { + InstructionPart::Opcode(m, _) => mnemonic = Some(Cow::Owned(m.into_owned())), + InstructionPart::Arg(arg) => args.push(arg.into_static()), + _ => {} + } + Ok(()) + })?; // If the instruction has a relocation, but we didn't format it in the display, add it to // the end of the arguments list. - if relocation.is_some() && !args.contains(&InstructionArg::Reloc) { + if resolved.relocation.is_some() && !args.contains(&InstructionArg::Reloc) { args.push(InstructionArg::Reloc); } - Ok(ParsedInstruction { ins_ref, mnemonic: mnemonic.unwrap_or_default(), args }) + Ok(ParsedInstruction { + ins_ref: resolved.ins_ref, + mnemonic: mnemonic.unwrap_or_default(), + args, + }) } /// Format an instruction for display. @@ -216,11 +207,7 @@ pub trait Arch: Send + Sync + Debug { /// mnemonic and arguments, plus any separators and visual formatting. fn display_instruction( &self, - ins_ref: InstructionRef, - code: &[u8], - relocation: Option, - function_range: Range, - section_index: usize, + resolved: ResolvedInstructionRef, diff_config: &DiffObjConfig, cb: &mut dyn FnMut(InstructionPart) -> Result<()>, ) -> Result<()>; @@ -236,9 +223,9 @@ pub trait Arch: Send + Sync + Debug { fn demangle(&self, _name: &str) -> Option { None } - fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str>; + fn reloc_name(&self, _flags: RelocationFlags) -> Option<&'static str> { None } - fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize; + fn data_reloc_size(&self, flags: RelocationFlags) -> usize; fn symbol_address(&self, address: u64, _kind: SymbolKind) -> u64 { address } @@ -246,62 +233,25 @@ pub trait Arch: Send + Sync + Debug { SymbolFlagSet::default() } - fn guess_data_type( + fn guess_data_type(&self, _resolved: ResolvedInstructionRef) -> Option { None } + + fn symbol_hover(&self, _obj: &Object, _symbol_index: usize) -> Vec { Vec::new() } + + fn symbol_context(&self, _obj: &Object, _symbol_index: usize) -> Vec { Vec::new() } + + fn instruction_hover( &self, - _ins_ref: InstructionRef, - _code: &[u8], - _relocation: Option, - ) -> Option { - None - } - - fn display_data_labels(&self, _ty: DataType, bytes: &[u8]) -> Vec { - vec![format!("Bytes: {:#x?}", bytes)] - } - - fn display_data_literals(&self, _ty: DataType, bytes: &[u8]) -> Vec { - vec![format!("{:#?}", bytes)] - } - - fn display_ins_data_labels( - &self, - _ins_ref: InstructionRef, - _code: &[u8], - _relocation: Option, - ) -> Vec { - // TODO - // let Some(reloc) = relocation else { - // return Vec::new(); - // }; - // 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(); - // } + _obj: &Object, + _resolved: ResolvedInstructionRef, + ) -> Vec { Vec::new() } - fn display_ins_data_literals( + fn instruction_context( &self, - _ins_ref: InstructionRef, - _code: &[u8], - _relocation: Option, - ) -> Vec { - // TODO - // let Some(reloc) = ins.reloc.as_ref() else { - // return Vec::new(); - // }; - // 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(); - // } + _obj: &Object, + _resolved: ResolvedInstructionRef, + ) -> Vec { Vec::new() } } @@ -345,11 +295,7 @@ impl Arch for ArchDummy { fn display_instruction( &self, - _ins_ref: InstructionRef, - _code: &[u8], - _relocation: Option, - _function_range: Range, - _section_index: usize, + _resolved: ResolvedInstructionRef, _diff_config: &DiffObjConfig, _cb: &mut dyn FnMut(InstructionPart) -> Result<()>, ) -> Result<()> { @@ -358,18 +304,14 @@ impl Arch for ArchDummy { fn implcit_addend( &self, - _file: &File<'_>, - _section: &Section, + _file: &object::File<'_>, + _section: &object::Section, _address: u64, - _relocation: &Relocation, + _relocation: &object::Relocation, _flags: RelocationFlags, ) -> Result { Ok(0) } - fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> { - format!("{flags:?}").into() - } - - fn get_reloc_byte_size(&self, _flags: RelocationFlags) -> usize { 0 } + fn data_reloc_size(&self, _flags: RelocationFlags) -> usize { 0 } } diff --git a/objdiff-core/src/arch/ppc.rs b/objdiff-core/src/arch/ppc.rs index 8096948..d98ee6e 100644 --- a/objdiff-core/src/arch/ppc.rs +++ b/objdiff-core/src/arch/ppc.rs @@ -1,25 +1,24 @@ use alloc::{ - borrow::Cow, collections::BTreeMap, - format, string::{String, ToString}, vec, vec::Vec, }; -use core::ops::Range; use anyhow::{bail, ensure, Result}; -use byteorder::BigEndian; use cwextab::{decode_extab, ExceptionTableData}; use flagset::Flags; use object::{elf, Object as _, ObjectSection as _, ObjectSymbol as _}; use crate::{ arch::{Arch, DataType}, - diff::{display::InstructionPart, DiffObjConfig}, + diff::{ + display::{ContextItem, HoverItem, HoverItemColor, InstructionPart, SymbolNavigationKind}, + DiffObjConfig, + }, obj::{ - InstructionRef, Relocation, RelocationFlags, ResolvedRelocation, ScannedInstruction, - Symbol, SymbolFlag, SymbolFlagSet, + InstructionRef, Object, Relocation, RelocationFlags, ResolvedInstructionRef, + ResolvedRelocation, ScannedInstruction, SymbolFlag, SymbolFlagSet, }, }; @@ -54,6 +53,15 @@ impl ArchPpc { Ok(Self { extab: decode_exception_info(file)? }) } + fn parse_ins_ref(&self, resolved: ResolvedInstructionRef) -> Result { + let mut code = u32::from_be_bytes(resolved.code.try_into()?); + if let Some(reloc) = resolved.relocation { + code = zero_reloc(code, reloc.relocation); + } + let op = ppc750cl::Opcode::from(resolved.ins_ref.opcode as u8); + Ok(ppc750cl::Ins { code, op }) + } + fn find_reloc_arg( &self, ins: &ppc750cl::ParsedIns, @@ -98,24 +106,15 @@ impl Arch for ArchPpc { fn display_instruction( &self, - ins_ref: InstructionRef, - code: &[u8], - relocation: Option, - _function_range: Range, - _section_index: usize, + resolved: ResolvedInstructionRef, _diff_config: &DiffObjConfig, cb: &mut dyn FnMut(InstructionPart) -> Result<()>, ) -> Result<()> { - let mut code = u32::from_be_bytes(code.try_into()?); - if let Some(resolved) = relocation { - code = zero_reloc(code, resolved.relocation); - } - let op = ppc750cl::Opcode::from(ins_ref.opcode as u8); - let ins = ppc750cl::Ins { code, op }.simplified(); + let ins = self.parse_ins_ref(resolved)?.simplified(); - cb(InstructionPart::opcode(ins.mnemonic, ins_ref.opcode))?; + cb(InstructionPart::opcode(ins.mnemonic, resolved.ins_ref.opcode))?; - let reloc_arg = self.find_reloc_arg(&ins, relocation); + let reloc_arg = self.find_reloc_arg(&ins, resolved.relocation); let mut writing_offset = false; for (idx, arg) in ins.args_iter().enumerate() { @@ -124,10 +123,10 @@ impl Arch for ArchPpc { } if reloc_arg == Some(idx) { - let resolved = relocation.unwrap(); - display_reloc(resolved, cb)?; + let reloc = resolved.relocation.unwrap(); + display_reloc(reloc, cb)?; // For @sda21, we can omit the register argument - if matches!(resolved.relocation.flags, RelocationFlags::Elf(elf::R_PPC_EMB_SDA21)) + if matches!(reloc.relocation.flags, RelocationFlags::Elf(elf::R_PPC_EMB_SDA21)) // Sanity check: the next argument should be r0 && matches!(ins.args.get(idx + 1), Some(ppc750cl::Argument::GPR(ppc750cl::GPR(0)))) { @@ -139,7 +138,7 @@ impl Arch for ArchPpc { ppc750cl::Argument::Uimm(uimm) => cb(InstructionPart::unsigned(uimm.0)), ppc750cl::Argument::Offset(offset) => cb(InstructionPart::signed(offset.0)), ppc750cl::Argument::BranchDest(dest) => cb(InstructionPart::branch_dest( - (ins_ref.address as u32).wrapping_add_signed(dest.0), + (resolved.ins_ref.address as u32).wrapping_add_signed(dest.0), )), _ => cb(InstructionPart::opaque(arg.to_string())), }?; @@ -173,33 +172,25 @@ impl Arch for ArchPpc { cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default()) } - fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> { + fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> { match flags { RelocationFlags::Elf(r_type) => match r_type { - elf::R_PPC_NONE => Cow::Borrowed("R_PPC_NONE"), // We use this for fake pool relocs - elf::R_PPC_ADDR16_LO => Cow::Borrowed("R_PPC_ADDR16_LO"), - elf::R_PPC_ADDR16_HI => Cow::Borrowed("R_PPC_ADDR16_HI"), - elf::R_PPC_ADDR16_HA => Cow::Borrowed("R_PPC_ADDR16_HA"), - elf::R_PPC_EMB_SDA21 => Cow::Borrowed("R_PPC_EMB_SDA21"), - elf::R_PPC_ADDR32 => Cow::Borrowed("R_PPC_ADDR32"), - elf::R_PPC_UADDR32 => Cow::Borrowed("R_PPC_UADDR32"), - elf::R_PPC_REL24 => Cow::Borrowed("R_PPC_REL24"), - elf::R_PPC_REL14 => Cow::Borrowed("R_PPC_REL14"), - _ => Cow::Owned(format!("<{flags:?}>")), + elf::R_PPC_NONE => Some("R_PPC_NONE"), // We use this for fake pool relocs + elf::R_PPC_ADDR16_LO => Some("R_PPC_ADDR16_LO"), + elf::R_PPC_ADDR16_HI => Some("R_PPC_ADDR16_HI"), + elf::R_PPC_ADDR16_HA => Some("R_PPC_ADDR16_HA"), + elf::R_PPC_EMB_SDA21 => Some("R_PPC_EMB_SDA21"), + elf::R_PPC_ADDR32 => Some("R_PPC_ADDR32"), + elf::R_PPC_UADDR32 => Some("R_PPC_UADDR32"), + elf::R_PPC_REL24 => Some("R_PPC_REL24"), + elf::R_PPC_REL14 => Some("R_PPC_REL14"), + _ => None, }, - _ => Cow::Owned(format!("<{flags:?}>")), + _ => None, } } - fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet { - if self.extab.as_ref().is_some_and(|extab| extab.contains_key(&symbol.index().0)) { - SymbolFlag::HasExtra.into() - } else { - SymbolFlag::none() - } - } - - fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize { + fn data_reloc_size(&self, flags: RelocationFlags) -> usize { match flags { RelocationFlags::Elf(r_type) => match r_type { elf::R_PPC_ADDR32 => 4, @@ -210,33 +201,102 @@ impl Arch for ArchPpc { } } - fn guess_data_type( - &self, - ins_ref: InstructionRef, - _code: &[u8], - relocation: Option, - ) -> Option { - if relocation.is_some_and(|r| r.symbol.name.starts_with("@stringBase")) { + fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet { + if self.extab.as_ref().is_some_and(|extab| extab.contains_key(&(symbol.index().0 - 1))) { + SymbolFlag::HasExtra.into() + } else { + SymbolFlag::none() + } + } + + fn guess_data_type(&self, resolved: ResolvedInstructionRef) -> Option { + if resolved.relocation.is_some_and(|r| r.symbol.name.starts_with("@stringBase")) { return Some(DataType::String); } - - guess_data_type_from_load_store_inst_op(ppc750cl::Opcode::from(ins_ref.opcode as u8)) + let opcode = ppc750cl::Opcode::from(resolved.ins_ref.opcode as u8); + guess_data_type_from_load_store_inst_op(opcode) } - fn display_data_labels(&self, ty: DataType, bytes: &[u8]) -> Vec { - ty.display_labels::(bytes) + fn symbol_hover(&self, _obj: &Object, symbol_index: usize) -> Vec { + let mut out = Vec::new(); + if let Some(extab) = self.extab_for_symbol(symbol_index) { + out.push(HoverItem::Text { + label: "extab symbol".into(), + value: extab.etb_symbol.name.clone(), + color: HoverItemColor::Special, + }); + out.push(HoverItem::Text { + label: "extabindex symbol".into(), + value: extab.eti_symbol.name.clone(), + color: HoverItemColor::Special, + }); + } + out } - fn display_data_literals(&self, ty: DataType, bytes: &[u8]) -> Vec { - ty.display_literals::(bytes) + fn symbol_context(&self, _obj: &Object, symbol_index: usize) -> Vec { + let mut out = Vec::new(); + if let Some(_extab) = self.extab_for_symbol(symbol_index) { + out.push(ContextItem::Navigate { + label: "Decode exception table".to_string(), + symbol_index, + kind: SymbolNavigationKind::Extab, + }); + } + out + } + + fn instruction_hover(&self, _obj: &Object, resolved: ResolvedInstructionRef) -> Vec { + let Ok(ins) = self.parse_ins_ref(resolved) else { + return Vec::new(); + }; + let orig = ins.basic().to_string(); + let simplified = ins.simplified().to_string(); + let show_orig = orig != simplified; + let rlwinm_decoded = rlwinmdec::decode(&orig); + let mut out = Vec::with_capacity(2); + if show_orig { + out.push(HoverItem::Text { + label: "Original".into(), + value: orig, + color: HoverItemColor::Normal, + }); + } + if let Some(decoded) = rlwinm_decoded { + for line in decoded.lines() { + out.push(HoverItem::Text { + label: Default::default(), + value: line.to_string(), + color: HoverItemColor::Special, + }); + } + } + out + } + + fn instruction_context( + &self, + _obj: &Object, + resolved: ResolvedInstructionRef, + ) -> Vec { + let Ok(ins) = self.parse_ins_ref(resolved) else { + return Vec::new(); + }; + let orig = ins.basic().to_string(); + let simplified = ins.simplified().to_string(); + let show_orig = orig != simplified; + let mut out = Vec::with_capacity(2); + out.push(ContextItem::Copy { value: simplified, label: None }); + if show_orig { + out.push(ContextItem::Copy { value: orig, label: Some("original".to_string()) }); + } + out } } impl ArchPpc { - pub fn extab_for_symbol(&self, _symbol: &Symbol) -> Option<&ExceptionInfo> { - // TODO - // symbol.original_index.and_then(|i| self.extab.as_ref()?.get(&i)) - None + pub fn extab_for_symbol(&self, symbol_index: usize) -> Option<&ExceptionInfo> { + self.extab.as_ref()?.get(&symbol_index) } } @@ -384,7 +444,7 @@ fn decode_exception_info( }; //Add the new entry to the list - result.insert(extab_func.index().0, ExceptionInfo { + result.insert(extab_func.index().0 - 1, ExceptionInfo { eti_symbol: make_symbol_ref(&extabindex)?, etb_symbol: make_symbol_ref(&extab)?, data, @@ -419,7 +479,7 @@ fn relocation_symbol<'data, 'file>( fn make_symbol_ref(symbol: &object::Symbol) -> Result { let name = symbol.name()?.to_string(); let demangled_name = cwdemangle::demangle(&name, &cwdemangle::DemangleOptions::default()); - Ok(ExtabSymbolRef { original_index: symbol.index().0, name, demangled_name }) + Ok(ExtabSymbolRef { original_index: symbol.index().0 - 1, name, demangled_name }) } fn guess_data_type_from_load_store_inst_op(inst_op: ppc750cl::Opcode) -> Option { diff --git a/objdiff-core/src/arch/x86.rs b/objdiff-core/src/arch/x86.rs index 0fa2e38..37ebe40 100644 --- a/objdiff-core/src/arch/x86.rs +++ b/objdiff-core/src/arch/x86.rs @@ -1,5 +1,4 @@ -use alloc::{borrow::Cow, boxed::Box, format, string::String, vec::Vec}; -use core::ops::Range; +use alloc::{boxed::Box, string::String, vec::Vec}; use anyhow::{anyhow, bail, Result}; use iced_x86::{ @@ -11,7 +10,7 @@ use object::{pe, Endian as _, Object as _, ObjectSection as _}; use crate::{ arch::Arch, diff::{display::InstructionPart, DiffObjConfig, X86Formatter}, - obj::{InstructionRef, RelocationFlags, ResolvedRelocation, ScannedInstruction}, + obj::{InstructionRef, RelocationFlags, ResolvedInstructionRef, ScannedInstruction}, }; #[derive(Debug)] @@ -74,15 +73,11 @@ impl Arch for ArchX86 { fn display_instruction( &self, - ins_ref: InstructionRef, - code: &[u8], - relocation: Option, - _function_range: Range, - _section_index: usize, + resolved: ResolvedInstructionRef, diff_config: &DiffObjConfig, cb: &mut dyn FnMut(InstructionPart) -> Result<()>, ) -> Result<()> { - let mut decoder = self.decoder(code, ins_ref.address); + let mut decoder = self.decoder(resolved.code, resolved.ins_ref.address); let mut formatter = self.formatter(diff_config); let mut instruction = Instruction::default(); decoder.decode_out(&mut instruction); @@ -92,11 +87,11 @@ impl Arch for ArchX86 { // doesn't provide enough information to know which number is the displacement inside a // memory operand. let mut reloc_replace = None; - if let Some(resolved) = relocation { - const PLACEHOLDER: u64 = 0x7BDEBE7D; // chosen by fair dice roll. + if let Some(reloc) = resolved.relocation { + const PLACEHOLDER: u64 = 0x7BDE3E7D; // chosen by fair dice roll. // guaranteed to be random. - let reloc_offset = resolved.relocation.address - ins_ref.address; - let reloc_size = reloc_size(resolved.relocation.flags).unwrap_or(usize::MAX); + let reloc_offset = reloc.relocation.address - resolved.ins_ref.address; + let reloc_size = reloc_size(reloc.relocation.flags).unwrap_or(usize::MAX); let offsets = decoder.get_constant_offsets(&instruction); if reloc_offset == offsets.displacement_offset() as u64 && reloc_size == offsets.displacement_size() @@ -172,20 +167,18 @@ impl Arch for ArchX86 { } } - fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> { + fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> { match flags { RelocationFlags::Coff(typ) => match typ { - pe::IMAGE_REL_I386_DIR32 => Cow::Borrowed("IMAGE_REL_I386_DIR32"), - pe::IMAGE_REL_I386_REL32 => Cow::Borrowed("IMAGE_REL_I386_REL32"), - _ => Cow::Owned(format!("<{flags:?}>")), + pe::IMAGE_REL_I386_DIR32 => Some("IMAGE_REL_I386_DIR32"), + pe::IMAGE_REL_I386_REL32 => Some("IMAGE_REL_I386_REL32"), + _ => None, }, - _ => Cow::Owned(format!("<{flags:?}>")), + _ => None, } } - fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize { - reloc_size(flags).unwrap_or(1) - } + fn data_reloc_size(&self, flags: RelocationFlags) -> usize { reloc_size(flags).unwrap_or(1) } } fn reloc_size(flags: RelocationFlags) -> Option { @@ -346,7 +339,7 @@ impl FormatterOutput for InstructionFormatterOutput<'_> { #[cfg(test)] mod test { use super::*; - use crate::obj::Relocation; + use crate::obj::{Relocation, ResolvedRelocation}; #[test] fn test_scan_instructions() { @@ -374,11 +367,11 @@ mod test { let opcode = iced_x86::Mnemonic::Mov as u16; let mut parts = Vec::new(); arch.display_instruction( - InstructionRef { address: 0x1234, size: 10, opcode }, - &code, - None, - 0x1234..0x2000, - 0, + ResolvedInstructionRef { + ins_ref: InstructionRef { address: 0x1234, size: 10, opcode }, + code: &code, + ..Default::default() + }, &DiffObjConfig::default(), &mut |part| { parts.push(part.into_static()); @@ -410,19 +403,20 @@ mod test { let opcode = iced_x86::Mnemonic::Mov as u16; let mut parts = Vec::new(); arch.display_instruction( - InstructionRef { address: 0x1234, size: 10, opcode }, - &code, - Some(ResolvedRelocation { - relocation: &Relocation { - flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32), - address: 0x1234 + 6, - target_symbol: 0, - addend: 0, - }, - symbol: &Default::default(), - }), - 0x1234..0x2000, - 0, + ResolvedInstructionRef { + ins_ref: InstructionRef { address: 0x1234, size: 10, opcode }, + code: &code, + relocation: Some(ResolvedRelocation { + relocation: &Relocation { + flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32), + address: 0x1234 + 6, + target_symbol: 0, + addend: 0, + }, + symbol: &Default::default(), + }), + ..Default::default() + }, &DiffObjConfig::default(), &mut |part| { parts.push(part.into_static()); @@ -454,19 +448,20 @@ mod test { let opcode = iced_x86::Mnemonic::Mov as u16; let mut parts = Vec::new(); arch.display_instruction( - InstructionRef { address: 0x1234, size: 7, opcode }, - &code, - Some(ResolvedRelocation { - relocation: &Relocation { - flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32), - address: 0x1234 + 3, - target_symbol: 0, - addend: 0, - }, - symbol: &Default::default(), - }), - 0x1234..0x2000, - 0, + ResolvedInstructionRef { + ins_ref: InstructionRef { address: 0x1234, size: 7, opcode }, + code: &code, + relocation: Some(ResolvedRelocation { + relocation: &Relocation { + flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32), + address: 0x1234 + 3, + target_symbol: 0, + addend: 0, + }, + symbol: &Default::default(), + }), + ..Default::default() + }, &DiffObjConfig::default(), &mut |part| { parts.push(part.into_static()); @@ -496,19 +491,20 @@ mod test { let opcode = iced_x86::Mnemonic::Call as u16; let mut parts = Vec::new(); arch.display_instruction( - InstructionRef { address: 0x1234, size: 5, opcode }, - &code, - Some(ResolvedRelocation { - relocation: &Relocation { - flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_REL32), - address: 0x1234 + 1, - target_symbol: 0, - addend: 0, - }, - symbol: &Default::default(), - }), - 0x1234..0x2000, - 0, + ResolvedInstructionRef { + ins_ref: InstructionRef { address: 0x1234, size: 5, opcode }, + code: &code, + relocation: Some(ResolvedRelocation { + relocation: &Relocation { + flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_REL32), + address: 0x1234 + 1, + target_symbol: 0, + addend: 0, + }, + symbol: &Default::default(), + }), + ..Default::default() + }, &DiffObjConfig::default(), &mut |part| { parts.push(part.into_static()); diff --git a/objdiff-core/src/diff/code.rs b/objdiff-core/src/diff/code.rs index ad5e5c4..2cc14b4 100644 --- a/objdiff-core/src/diff/code.rs +++ b/objdiff-core/src/diff/code.rs @@ -5,7 +5,7 @@ use alloc::{ vec::Vec, }; -use anyhow::{anyhow, ensure, Result}; +use anyhow::{anyhow, ensure, Context, Result}; use super::{ DiffObjConfig, FunctionRelocDiffs, InstructionArgDiffIndex, InstructionBranchFrom, @@ -196,7 +196,7 @@ fn diff_instructions( Ok((left_diff, right_diff)) } -fn arg_to_string(arg: &InstructionArg, reloc: Option<&ResolvedRelocation>) -> String { +fn arg_to_string(arg: &InstructionArg, reloc: Option) -> String { match arg { InstructionArg::Value(arg) => arg.to_string(), InstructionArg::Reloc => { @@ -226,7 +226,7 @@ fn resolve_branches( for ((i, ins_diff), ins) in rows.iter_mut().enumerate().filter(|(_, row)| row.ins_ref.is_some()).zip(ops) { - let branch_dest = if let Some(resolved) = section.relocation_at(ins.ins_ref, obj) { + let branch_dest = if let Some(resolved) = section.relocation_at(obj, ins.ins_ref) { if resolved.symbol.section == Some(section_index) { // If the relocation target is in the same section, use it as the branch destination resolved.symbol.address.checked_add_signed(resolved.relocation.addend) @@ -258,7 +258,7 @@ fn resolve_branches( } } -pub(crate) fn address_eq(left: &ResolvedRelocation, right: &ResolvedRelocation) -> bool { +pub(crate) fn address_eq(left: ResolvedRelocation, right: ResolvedRelocation) -> bool { if right.symbol.size == 0 && left.symbol.size != 0 { // 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 @@ -318,7 +318,7 @@ fn reloc_eq( section_name_eq(left_obj, right_obj, *sl, *sr) && (diff_config.function_reloc_diffs == FunctionRelocDiffs::DataValue || symbol_name_addend_matches - || address_eq(&left_reloc, &right_reloc)) + || address_eq(left_reloc, right_reloc)) && ( diff_config.function_reloc_diffs == FunctionRelocDiffs::NameAddress || left_reloc.symbol.kind != SymbolKind::Object @@ -427,54 +427,17 @@ fn diff_instruction( 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"))?; + let left_resolved = left_obj + .resolve_instruction_ref(left_symbol_idx, l) + .context("Failed to resolve left instruction")?; + let right_resolved = right_obj + .resolve_instruction_ref(right_symbol_idx, r) + .context("Failed to resolve right instruction")?; - // Resolve relocations - let left_reloc = left_section.relocation_at(l, left_obj); - let right_reloc = right_section.relocation_at(r, 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 left_resolved.code != right_resolved.code { // 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, - )?; + let left_ins = left_obj.arch.process_instruction(left_resolved, diff_config)?; + let right_ins = left_obj.arch.process_instruction(right_resolved, diff_config)?; if left_ins.args.len() != right_ins.args.len() { state.diff_score += PENALTY_REPLACE; return Ok(InstructionDiffResult::new(InstructionDiffKind::Replace)); @@ -492,8 +455,8 @@ fn diff_instruction( right_row, a, b, - left_reloc, - right_reloc, + left_resolved.relocation, + right_resolved.relocation, diff_config, ) { result.left_args_diff.push(InstructionArgDiffIndex::NONE); @@ -510,7 +473,7 @@ fn diff_instruction( if result.kind == InstructionDiffKind::None { result.kind = InstructionDiffKind::ArgMismatch; } - let a_str = arg_to_string(a, left_reloc.as_ref()); + let a_str = arg_to_string(a, left_resolved.relocation); let a_diff = match state.left_args_idx.entry(a_str) { btree_map::Entry::Vacant(e) => { let idx = state.left_arg_idx; @@ -520,7 +483,7 @@ fn diff_instruction( } btree_map::Entry::Occupied(e) => *e.get(), }; - let b_str = arg_to_string(b, right_reloc.as_ref()); + let b_str = arg_to_string(b, right_resolved.relocation); let b_diff = match state.right_args_idx.entry(b_str) { btree_map::Entry::Vacant(e) => { let idx = state.right_arg_idx; @@ -538,8 +501,15 @@ fn diff_instruction( } // Compare relocations - if !reloc_eq(left_obj, right_obj, left_reloc, right_reloc, diff_config) { + if !reloc_eq( + left_obj, + right_obj, + left_resolved.relocation, + right_resolved.relocation, + diff_config, + ) { state.diff_score += PENALTY_REG_DIFF; + // TODO add relocation diff to args return Ok(InstructionDiffResult::new(InstructionDiffKind::ArgMismatch)); } diff --git a/objdiff-core/src/diff/data.rs b/objdiff-core/src/diff/data.rs index e260e79..d0b6394 100644 --- a/objdiff-core/src/diff/data.rs +++ b/objdiff-core/src/diff/data.rs @@ -38,8 +38,8 @@ pub fn diff_bss_symbol( fn reloc_eq( left_obj: &Object, right_obj: &Object, - left: &ResolvedRelocation, - right: &ResolvedRelocation, + left: ResolvedRelocation, + right: ResolvedRelocation, ) -> bool { if left.relocation.flags != right.relocation.flags { return false; @@ -104,7 +104,7 @@ fn diff_data_relocs_for_range<'left, 'right>( continue; }; let right_reloc = resolve_relocation(right_obj, right_reloc); - if reloc_eq(left_obj, right_obj, &left_reloc, &right_reloc) { + if reloc_eq(left_obj, right_obj, left_reloc, right_reloc) { diffs.push((DataDiffKind::None, Some(left_reloc), Some(right_reloc))); } else { diffs.push((DataDiffKind::Replace, Some(left_reloc), Some(right_reloc))); @@ -251,13 +251,13 @@ pub fn diff_data_section( 0..right_max as usize, ) { if let Some(left_reloc) = left_reloc { - let len = left_obj.arch.get_reloc_byte_size(left_reloc.relocation.flags); + let len = left_obj.arch.data_reloc_size(left_reloc.relocation.flags); let range = left_reloc.relocation.address as usize ..left_reloc.relocation.address as usize + len; left_reloc_diffs.push(DataRelocationDiff { kind: diff_kind, range }); } if let Some(right_reloc) = right_reloc { - let len = right_obj.arch.get_reloc_byte_size(right_reloc.relocation.flags); + let len = right_obj.arch.data_reloc_size(right_reloc.relocation.flags); let range = right_reloc.relocation.address as usize ..right_reloc.relocation.address as usize + len; right_reloc_diffs.push(DataRelocationDiff { kind: diff_kind, range }); @@ -358,11 +358,9 @@ pub fn diff_data_symbol( let reloc_diff_len = match (left_reloc, right_reloc) { (None, None) => unreachable!(), (None, Some(right_reloc)) => { - 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) + right_obj.arch.data_reloc_size(right_reloc.relocation.flags) } + (Some(left_reloc), _) => left_obj.arch.data_reloc_size(left_reloc.relocation.flags), }; total_reloc_bytes += reloc_diff_len; if diff_kind == DataDiffKind::None { diff --git a/objdiff-core/src/diff/display.rs b/objdiff-core/src/diff/display.rs index 00d6c86..8a972a5 100644 --- a/objdiff-core/src/diff/display.rs +++ b/objdiff-core/src/diff/display.rs @@ -14,8 +14,8 @@ use regex::Regex; use crate::{ diff::{DiffObjConfig, InstructionDiffKind, InstructionDiffRow, ObjectDiff, SymbolDiff}, obj::{ - InstructionArg, InstructionArgValue, Object, SectionFlag, SectionKind, Symbol, SymbolFlag, - SymbolKind, + InstructionArg, InstructionArgValue, Object, ParsedInstruction, ResolvedInstructionRef, + SectionFlag, SectionKind, Symbol, SymbolFlag, SymbolKind, }, }; @@ -159,24 +159,24 @@ pub fn display_row( cb(EOL_SEGMENT)?; return Ok(()); }; - let symbol = &obj.symbols[symbol_index]; - let Some(section_index) = symbol.section else { + let Some(resolved) = obj.resolve_instruction_ref(symbol_index, ins_ref) else { cb(DiffTextSegment::basic("", DiffTextColor::Delete))?; cb(EOL_SEGMENT)?; return Ok(()); }; - let section = &obj.sections[section_index]; - let Some(data) = section.data_range(ins_ref.address, ins_ref.size as usize) else { - cb(DiffTextSegment::basic("", DiffTextColor::Delete))?; - cb(EOL_SEGMENT)?; - return Ok(()); + let base_color = match ins_row.kind { + InstructionDiffKind::Replace => DiffTextColor::Replace, + InstructionDiffKind::Delete => DiffTextColor::Delete, + InstructionDiffKind::Insert => DiffTextColor::Insert, + _ => DiffTextColor::Normal, }; - if let Some(line) = section.line_info.range(..=ins_ref.address).last().map(|(_, &b)| b) { + if let Some(line) = resolved.section.line_info.range(..=ins_ref.address).last().map(|(_, &b)| b) + { cb(DiffTextSegment { text: DiffText::Line(line), color: DiffTextColor::Dim, pad_to: 5 })?; } cb(DiffTextSegment { - text: DiffText::Address(ins_ref.address.saturating_sub(symbol.address)), - color: DiffTextColor::Normal, + text: DiffText::Address(ins_ref.address.saturating_sub(resolved.symbol.address)), + color: base_color, pad_to: 5, })?; if let Some(branch) = &ins_row.branch_from { @@ -185,95 +185,85 @@ pub fn display_row( cb(DiffTextSegment::spacing(4))?; } let mut arg_idx = 0; - let relocation = section.relocation_at(ins_ref, obj); let mut displayed_relocation = false; - 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) => { - if text.chars().all(|c| c == ' ') { - cb(DiffTextSegment::spacing(text.len() as u8)) - } else { - cb(DiffTextSegment::basic(&text, DiffTextColor::Normal)) - } + obj.arch.display_instruction(resolved, diff_config, &mut |part| match part { + InstructionPart::Basic(text) => { + if text.chars().all(|c| c == ' ') { + cb(DiffTextSegment::spacing(text.len() as u8)) + } else { + cb(DiffTextSegment::basic(&text, base_color)) } - InstructionPart::Opcode(mnemonic, opcode) => cb(DiffTextSegment { - text: DiffText::Opcode(mnemonic.as_ref(), opcode), - color: if ins_row.kind == InstructionDiffKind::OpMismatch { - DiffTextColor::Replace - } else { - DiffTextColor::Normal - }, - pad_to: 10, - }), - InstructionPart::Arg(arg) => { - let diff_index = ins_row.arg_diff.get(arg_idx).copied().unwrap_or_default(); - arg_idx += 1; - match arg { - InstructionArg::Value(value) => cb(DiffTextSegment { - text: DiffText::Argument(value), - color: diff_index - .get() - .map_or(DiffTextColor::Normal, |i| DiffTextColor::Rotating(i as u8)), + } + InstructionPart::Opcode(mnemonic, opcode) => cb(DiffTextSegment { + text: DiffText::Opcode(mnemonic.as_ref(), opcode), + color: match ins_row.kind { + InstructionDiffKind::OpMismatch => DiffTextColor::Replace, + _ => base_color, + }, + pad_to: 10, + }), + InstructionPart::Arg(arg) => { + let diff_index = ins_row.arg_diff.get(arg_idx).copied().unwrap_or_default(); + arg_idx += 1; + match arg { + InstructionArg::Value(value) => cb(DiffTextSegment { + text: DiffText::Argument(value), + color: diff_index + .get() + .map_or(base_color, |i| DiffTextColor::Rotating(i as u8)), + pad_to: 0, + }), + InstructionArg::Reloc => { + displayed_relocation = true; + let resolved = resolved.relocation.unwrap(); + let color = diff_index + .get() + .map_or(DiffTextColor::Bright, |i| DiffTextColor::Rotating(i as u8)); + cb(DiffTextSegment { + text: DiffText::Symbol(resolved.symbol), + color, pad_to: 0, - }), - InstructionArg::Reloc => { - displayed_relocation = true; - let resolved = relocation.unwrap(); - let color = diff_index - .get() - .map_or(DiffTextColor::Bright, |i| DiffTextColor::Rotating(i as u8)); + })?; + if resolved.relocation.addend != 0 { cb(DiffTextSegment { - text: DiffText::Symbol(resolved.symbol), + text: DiffText::Addend(resolved.relocation.addend), color, pad_to: 0, })?; - if resolved.relocation.addend != 0 { - cb(DiffTextSegment { - text: DiffText::Addend(resolved.relocation.addend), - color, - pad_to: 0, - })?; - } - Ok(()) } - InstructionArg::BranchDest(dest) => { - if let Some(addr) = dest.checked_sub(symbol.address) { - cb(DiffTextSegment { - text: DiffText::BranchDest(addr), - color: diff_index.get().map_or(DiffTextColor::Normal, |i| { - DiffTextColor::Rotating(i as u8) - }), - pad_to: 0, - }) - } else { - cb(DiffTextSegment { - text: DiffText::Argument(InstructionArgValue::Opaque( - Cow::Borrowed(""), - )), - color: diff_index.get().map_or(DiffTextColor::Normal, |i| { - DiffTextColor::Rotating(i as u8) - }), - pad_to: 0, - }) - } + Ok(()) + } + InstructionArg::BranchDest(dest) => { + if let Some(addr) = dest.checked_sub(resolved.symbol.address) { + cb(DiffTextSegment { + text: DiffText::BranchDest(addr), + color: diff_index + .get() + .map_or(base_color, |i| DiffTextColor::Rotating(i as u8)), + pad_to: 0, + }) + } else { + cb(DiffTextSegment { + text: DiffText::Argument(InstructionArgValue::Opaque(Cow::Borrowed( + "", + ))), + color: diff_index + .get() + .map_or(base_color, |i| DiffTextColor::Rotating(i as u8)), + pad_to: 0, + }) } } } - InstructionPart::Separator => { - cb(DiffTextSegment::basic(diff_config.separator(), DiffTextColor::Normal)) - } - }, - )?; + } + InstructionPart::Separator => { + cb(DiffTextSegment::basic(diff_config.separator(), base_color)) + } + })?; // Fallback for relocation that wasn't displayed - if relocation.is_some() && !displayed_relocation { - cb(DiffTextSegment::basic(" <", DiffTextColor::Normal))?; - let resolved = relocation.unwrap(); + if resolved.relocation.is_some() && !displayed_relocation { + cb(DiffTextSegment::basic(" <", base_color))?; + let resolved = resolved.relocation.unwrap(); let diff_index = ins_row.arg_diff.get(arg_idx).copied().unwrap_or_default(); let color = diff_index.get().map_or(DiffTextColor::Bright, |i| DiffTextColor::Rotating(i as u8)); @@ -285,7 +275,7 @@ pub fn display_row( pad_to: 0, })?; } - cb(DiffTextSegment::basic(">", DiffTextColor::Normal))?; + cb(DiffTextSegment::basic(">", base_color))?; } if let Some(branch) = &ins_row.branch_to { cb(DiffTextSegment::basic(" ~>", DiffTextColor::Rotating(branch.branch_idx as u8)))?; @@ -322,9 +312,17 @@ impl From<&DiffText<'_>> for HighlightKind { } } -pub enum ContextMenuItem { +pub enum ContextItem { Copy { value: String, label: Option }, - Navigate { label: String }, + Navigate { label: String, symbol_index: usize, kind: SymbolNavigationKind }, + Separator, +} + +#[derive(Debug, Clone, Default, Eq, PartialEq)] +pub enum SymbolNavigationKind { + #[default] + Normal, + Extab, } pub enum HoverItemColor { @@ -333,66 +331,200 @@ pub enum HoverItemColor { Special, // Blue } -pub struct HoverItem { - pub text: String, - pub color: HoverItemColor, +pub enum HoverItem { + Text { label: String, value: String, color: HoverItemColor }, + Separator, } -pub fn symbol_context(_obj: &Object, symbol: &Symbol) -> Vec { +pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec { + let symbol = &obj.symbols[symbol_index]; let mut out = Vec::new(); + out.push(ContextItem::Copy { value: symbol.name.clone(), label: None }); if let Some(name) = &symbol.demangled_name { - out.push(ContextMenuItem::Copy { value: name.clone(), label: None }); + out.push(ContextItem::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 symbol.section.is_some() { + if let Some(address) = symbol.virtual_address { + out.push(ContextItem::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.append(&mut obj.arch.symbol_context(obj, symbol_index)); out } -pub fn symbol_hover(_obj: &Object, symbol: &Symbol) -> Vec { +pub fn symbol_hover(obj: &Object, symbol_index: usize, addend: i64) -> Vec { + let symbol = &obj.symbols[symbol_index]; + let addend_str = match addend.cmp(&0i64) { + Ordering::Greater => format!("+{:x}", addend), + Ordering::Less => format!("-{:x}", -addend), + _ => String::new(), + }; let mut out = Vec::new(); - out.push(HoverItem { - text: format!("Name: {}", symbol.name), - color: HoverItemColor::Emphasized, + out.push(HoverItem::Text { + label: "Name".into(), + value: format!("{}{}", symbol.name, addend_str), + color: HoverItemColor::Normal, }); - 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, + if let Some(demangled_name) = &symbol.demangled_name { + out.push(HoverItem::Text { + label: "Demangled".into(), + value: demangled_name.into(), + color: HoverItemColor::Normal, }); + } + if let Some(section) = symbol.section { + out.push(HoverItem::Text { + label: "Section".into(), + value: obj.sections[section].name.clone(), + color: HoverItemColor::Normal, + }); + out.push(HoverItem::Text { + label: "Address".into(), + value: format!("{:x}{}", symbol.address, addend_str), + color: HoverItemColor::Normal, + }); + if symbol.flags.contains(SymbolFlag::SizeInferred) { + out.push(HoverItem::Text { + label: "Size".into(), + value: format!("{:x} (inferred)", symbol.size), + color: HoverItemColor::Normal, + }); + } else { + out.push(HoverItem::Text { + label: "Size".into(), + value: format!("{:x}", symbol.size), + color: HoverItemColor::Normal, + }); + } + if let Some(align) = symbol.align { + out.push(HoverItem::Text { + label: "Alignment".into(), + value: align.get().to_string(), + color: HoverItemColor::Normal, + }); + } + if let Some(address) = symbol.virtual_address { + out.push(HoverItem::Text { + label: "Virtual address".into(), + value: format!("{:#x}", address), + color: HoverItemColor::Special, + }); + } } else { - out.push(HoverItem { - text: format!("Size: {:x}", symbol.size), + out.push(HoverItem::Text { + label: Default::default(), + value: "Extern".into(), color: HoverItemColor::Emphasized, }); } - if let Some(address) = symbol.virtual_address { - out.push(HoverItem { - text: format!("Virtual address: {:#x}", address), + out.append(&mut obj.arch.symbol_hover(obj, symbol_index)); + out +} + +pub fn instruction_context( + obj: &Object, + resolved: ResolvedInstructionRef, + ins: &ParsedInstruction, +) -> Vec { + let mut out = Vec::new(); + let mut hex_string = String::new(); + for byte in resolved.code { + hex_string.push_str(&format!("{:02x}", byte)); + } + out.push(ContextItem::Copy { value: hex_string, label: Some("instruction bytes".to_string()) }); + out.append(&mut obj.arch.instruction_context(obj, resolved)); + if let Some(virtual_address) = resolved.symbol.virtual_address { + let offset = resolved.ins_ref.address - resolved.symbol.address; + out.push(ContextItem::Copy { + value: format!("{:x}", virtual_address + offset), + label: Some("virtual address".to_string()), + }); + } + for arg in &ins.args { + if let InstructionArg::Value(arg) = arg { + out.push(ContextItem::Copy { value: arg.to_string(), label: None }); + match arg { + InstructionArgValue::Signed(v) => { + out.push(ContextItem::Copy { value: v.to_string(), label: None }); + } + InstructionArgValue::Unsigned(v) => { + out.push(ContextItem::Copy { value: v.to_string(), label: None }); + } + _ => {} + } + } + } + if let Some(reloc) = resolved.relocation { + for literal in display_ins_data_literals(obj, resolved) { + out.push(ContextItem::Copy { value: literal, label: None }); + } + out.push(ContextItem::Separator); + out.append(&mut symbol_context(obj, reloc.relocation.target_symbol)); + } + out +} + +pub fn instruction_hover( + obj: &Object, + resolved: ResolvedInstructionRef, + ins: &ParsedInstruction, +) -> Vec { + let mut out = Vec::new(); + out.push(HoverItem::Text { + label: Default::default(), + value: format!("{:02x?}", resolved.code), + color: HoverItemColor::Normal, + }); + out.append(&mut obj.arch.instruction_hover(obj, resolved)); + if let Some(virtual_address) = resolved.symbol.virtual_address { + let offset = resolved.ins_ref.address - resolved.symbol.address; + out.push(HoverItem::Text { + label: "Virtual address".into(), + value: format!("{:#x}", virtual_address + offset), 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, - // }); - // } + for arg in &ins.args { + if let InstructionArg::Value(arg) = arg { + match arg { + InstructionArgValue::Signed(v) => { + out.push(HoverItem::Text { + label: Default::default(), + value: format!("{arg} == {v}"), + color: HoverItemColor::Normal, + }); + } + InstructionArgValue::Unsigned(v) => { + out.push(HoverItem::Text { + label: Default::default(), + value: format!("{arg} == {v}"), + color: HoverItemColor::Normal, + }); + } + _ => {} + } + } + } + if let Some(reloc) = resolved.relocation { + if let Some(name) = obj.arch.reloc_name(reloc.relocation.flags) { + out.push(HoverItem::Text { + label: "Relocation type".into(), + value: name.to_string(), + color: HoverItemColor::Normal, + }); + } else { + out.push(HoverItem::Text { + label: "Relocation type".into(), + value: format!("<{:?}>", reloc.relocation.flags), + color: HoverItemColor::Normal, + }); + } + out.push(HoverItem::Separator); + out.append(&mut symbol_hover(obj, reloc.relocation.target_symbol, reloc.relocation.addend)); + } out } @@ -556,3 +688,37 @@ fn symbol_sort(a: &Symbol, b: &Symbol) -> Ordering { 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)) } + +pub fn display_ins_data_labels(obj: &Object, resolved: ResolvedInstructionRef) -> Vec { + let Some(reloc) = resolved.relocation else { + return Vec::new(); + }; + if reloc.relocation.addend < 0 || reloc.relocation.addend as u64 >= reloc.symbol.size { + return Vec::new(); + } + let Some(data) = obj.symbol_data(reloc.relocation.target_symbol) else { + return Vec::new(); + }; + let bytes = &data[reloc.relocation.addend as usize..]; + obj.arch + .guess_data_type(resolved) + .map(|ty| ty.display_labels(obj.endianness, bytes)) + .unwrap_or_default() +} + +pub fn display_ins_data_literals(obj: &Object, resolved: ResolvedInstructionRef) -> Vec { + let Some(reloc) = resolved.relocation else { + return Vec::new(); + }; + if reloc.relocation.addend < 0 || reloc.relocation.addend as u64 >= reloc.symbol.size { + return Vec::new(); + } + let Some(data) = obj.symbol_data(reloc.relocation.target_symbol) else { + return Vec::new(); + }; + let bytes = &data[reloc.relocation.addend as usize..]; + obj.arch + .guess_data_type(resolved) + .map(|ty| ty.display_literals(obj.endianness, bytes)) + .unwrap_or_default() +} diff --git a/objdiff-core/src/obj/mod.rs b/objdiff-core/src/obj/mod.rs index c17df59..a9b2722 100644 --- a/objdiff-core/src/obj/mod.rs +++ b/objdiff-core/src/obj/mod.rs @@ -105,8 +105,8 @@ impl Section { pub fn relocation_at<'obj>( &'obj self, - ins_ref: InstructionRef, obj: &'obj Object, + ins_ref: InstructionRef, ) -> Option> { match self.relocations.binary_search_by_key(&ins_ref.address, |r| r.address) { Ok(i) => self.relocations.get(i), @@ -204,7 +204,7 @@ impl InstructionArg<'_> { } } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Default)] pub struct InstructionRef { pub address: u64, pub size: u8, @@ -251,6 +251,7 @@ pub struct Symbol { #[derive(Debug)] pub struct Object { pub arch: Box, + pub endianness: object::Endianness, pub symbols: Vec, pub sections: Vec
, /// Split object metadata (.note.split section) @@ -265,6 +266,7 @@ impl Default for Object { fn default() -> Self { Self { arch: ArchDummy::new(), + endianness: object::Endianness::Little, symbols: vec![], sections: vec![], split_meta: None, @@ -276,6 +278,38 @@ impl Default for Object { } } +impl Object { + pub fn resolve_instruction_ref( + &self, + symbol_index: usize, + ins_ref: InstructionRef, + ) -> Option { + let symbol = self.symbols.get(symbol_index)?; + let section_index = symbol.section?; + let section = self.sections.get(section_index)?; + let offset = ins_ref.address.checked_sub(section.address)?; + let code = section.data.get(offset as usize..offset as usize + ins_ref.size as usize)?; + let relocation = section.relocation_at(self, ins_ref); + Some(ResolvedInstructionRef { + ins_ref, + symbol_index, + symbol, + section, + section_index, + code, + relocation, + }) + } + + pub fn symbol_data(&self, symbol_index: usize) -> Option<&[u8]> { + let symbol = self.symbols.get(symbol_index)?; + let section_index = symbol.section?; + let section = self.sections.get(section_index)?; + let offset = symbol.address.checked_sub(section.address)?; + section.data.get(offset as usize..offset as usize + symbol.size as usize) + } +} + #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Relocation { pub flags: RelocationFlags, @@ -295,3 +329,53 @@ pub struct ResolvedRelocation<'a> { pub relocation: &'a Relocation, pub symbol: &'a Symbol, } + +#[derive(Debug, Copy, Clone)] +pub struct ResolvedInstructionRef<'obj> { + pub ins_ref: InstructionRef, + pub symbol_index: usize, + pub symbol: &'obj Symbol, + pub section_index: usize, + pub section: &'obj Section, + pub code: &'obj [u8], + pub relocation: Option>, +} + +static DUMMY_SYMBOL: Symbol = Symbol { + name: String::new(), + demangled_name: None, + address: 0, + size: 0, + kind: SymbolKind::Unknown, + section: None, + flags: SymbolFlagSet::empty(), + align: None, + virtual_address: None, +}; + +static DUMMY_SECTION: Section = Section { + id: String::new(), + name: String::new(), + address: 0, + size: 0, + kind: SectionKind::Unknown, + data: SectionData(Vec::new()), + flags: SectionFlagSet::empty(), + relocations: Vec::new(), + line_info: BTreeMap::new(), + virtual_address: None, +}; + +impl Default for ResolvedInstructionRef<'_> { + fn default() -> Self { + Self { + ins_ref: InstructionRef::default(), + symbol_index: 0, + symbol: &DUMMY_SYMBOL, + section_index: 0, + section: &DUMMY_SECTION, + code: &[], + relocation: None, + } + } +} diff --git a/objdiff-core/src/obj/read.rs b/objdiff-core/src/obj/read.rs index be04861..9639f5d 100644 --- a/objdiff-core/src/obj/read.rs +++ b/objdiff-core/src/obj/read.rs @@ -780,6 +780,7 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result { } Ok(Object { arch, + endianness: obj_file.endianness(), symbols, sections, split_meta, diff --git a/objdiff-core/tests/arch_ppc.rs b/objdiff-core/tests/arch_ppc.rs index 12a62dd..836e10e 100644 --- a/objdiff-core/tests/arch_ppc.rs +++ b/objdiff-core/tests/arch_ppc.rs @@ -34,6 +34,14 @@ fn read_dwarf1_line_info() { insta::assert_debug_snapshot!(line_infos); } +#[test] +#[cfg(feature = "ppc")] +fn read_extab() { + let diff_config = diff::DiffObjConfig::default(); + let obj = obj::read::parse(include_object!("data/ppc/NMWException.o"), &diff_config).unwrap(); + insta::assert_debug_snapshot!(obj); +} + #[test] #[cfg(feature = "ppc")] fn diff_ppc() { diff --git a/objdiff-core/tests/data/ppc/NMWException.o b/objdiff-core/tests/data/ppc/NMWException.o new file mode 100644 index 0000000000000000000000000000000000000000..dc92d2c6c13d7ddbfab2218fe92bbea534de689b GIT binary patch literal 1840 zcmb_c-%Aux6h1pU=BjHfvB;8IYc8>rEaopZgjfkh!o-Kb9)f9F7ZTLn)=g$%Y-Y8W zpa;PoGl)X^1A6c!F#HE3!naKEtzL>=79yta%-p+9rag7x-20s$_nhzCduOcSv5|lh zpxOvf6xe5|Gi-MD@kB{#qK0Q3&Yl@k)>i_QSly?#)%O%LcYBT1mlQSMT{g`2?>nm% zikQzk4eLOOjuOSKU)2fg=>hbgM%s3q#V5h_aa-*e+;yDfIik#xvu7jUGV(1W-!gJ7 zbB<7OJ>5a^ahu|(yDjSWr9lR@yG&=$K>eiosFmv{%unF4PchVwbNv;aLa>Kni>x3g zILE*z>2lfn0WK7fTx^|U*6x>RI!FPIvn%LJt!9R7=Oeg9OqDVjn{EL|lc+zmM5Evv z#=ekwzGKJQa(hiyDipOof19w}{D-}A{&diB+}y~|InX!f?n9m~^qoZC3G^KoeRqq# z|32>*o!)sbwwA%eu-7PJw}Xer@iuT+a2x|x?L{J!z|p&pF1GPKRMseL?cr{4FST=6 zq=?J2-OU*hJh!dQ`u@vpubqj)3WonQ>^wLA0QS3HCUF~oWoFv9v0k?V;GKie=2 zZ*k|U4{vx_QHf4B3VRz-v+s>INsNqj5{HSx65~zROC0mw- z=9*>jNhCbHnO=wX*xfb$`%Tg>FtJGp-ewZ59BIk2D(Yp zrgHP8V!o{D#Uiw9eh%uxY>9Qf%>I(5^$ciQK`)l3^%>9Z?Lt_-II_S&GnzIsu|Sk6 z<%|+R^n2bsJvWszC{@hO=$_AoUcey~QS;?l-k1UfZ@n*3Ha|O?n=8@v{8Vld)TZ;r zMnzQuIL($W+4{+=yluQPcl`IY`Q+OF_AoP+Qz8t1VkPO@BAM2UIPn&hJJ64dVa08-X w2-7zRuT2n+<0m`<;eBERs{m!GVU_1L4(y9lA3)cSiMuWK2$%W+;PuhC-_0u|y#N3J literal 0 HcmV?d00001 diff --git a/objdiff-core/tests/snapshots/arch_ppc__read_extab.snap b/objdiff-core/tests/snapshots/arch_ppc__read_extab.snap new file mode 100644 index 0000000..24c119b --- /dev/null +++ b/objdiff-core/tests/snapshots/arch_ppc__read_extab.snap @@ -0,0 +1,521 @@ +--- +source: objdiff-core/tests/arch_ppc.rs +expression: obj +--- +Object { + arch: ArchPpc { + extab: Some( + { + 10: ExceptionInfo { + eti_symbol: ExtabSymbolRef { + original_index: 5, + name: "@31", + demangled_name: None, + }, + etb_symbol: ExtabSymbolRef { + original_index: 4, + name: "@30", + demangled_name: None, + }, + data: ExceptionTableData { + flag_val: 8200, + has_elf_vector: false, + large_frame: true, + has_frame_pointer: false, + saved_cr: false, + fpr_save_range: 0, + gpr_save_range: 4, + et_field: 0, + pc_actions: [], + exception_actions: [], + relocations: [], + }, + dtors: [], + }, + 11: ExceptionInfo { + eti_symbol: ExtabSymbolRef { + original_index: 7, + name: "@52", + demangled_name: None, + }, + etb_symbol: ExtabSymbolRef { + original_index: 6, + name: "@51", + demangled_name: None, + }, + data: ExceptionTableData { + flag_val: 8200, + has_elf_vector: false, + large_frame: true, + has_frame_pointer: false, + saved_cr: false, + fpr_save_range: 0, + gpr_save_range: 4, + et_field: 0, + pc_actions: [ + PCAction { + start_pc: 96, + end_pc: 96, + action_offset: 16, + }, + ], + exception_actions: [ + ExceptionAction { + action_offset: 16, + action_type: DestroyLocal, + action_param: 0, + has_end_bit: true, + bytes: [ + 0, + 8, + 0, + 0, + 0, + 0, + ], + }, + ], + relocations: [ + Relocation { + offset: 20, + address: 0, + }, + ], + }, + dtors: [ + ExtabSymbolRef { + original_index: 12, + name: "__dt__26__partial_array_destructorFv", + demangled_name: Some( + "__partial_array_destructor::~__partial_array_destructor()", + ), + }, + ], + }, + 12: ExceptionInfo { + eti_symbol: ExtabSymbolRef { + original_index: 9, + name: "@60", + demangled_name: None, + }, + etb_symbol: ExtabSymbolRef { + original_index: 8, + name: "@59", + demangled_name: None, + }, + data: ExceptionTableData { + flag_val: 6152, + has_elf_vector: false, + large_frame: true, + has_frame_pointer: false, + saved_cr: false, + fpr_save_range: 0, + gpr_save_range: 3, + et_field: 0, + pc_actions: [], + exception_actions: [], + relocations: [], + }, + dtors: [], + }, + }, + ), + }, + endianness: Big, + symbols: [ + Symbol { + name: "NMWException.cpp", + demangled_name: None, + address: 0, + size: 0, + kind: Unknown, + section: None, + flags: FlagSet(Local), + align: None, + virtual_address: None, + }, + Symbol { + name: "[.text]", + demangled_name: None, + address: 0, + size: 0, + kind: Section, + section: Some( + 0, + ), + flags: FlagSet(Local), + align: None, + virtual_address: None, + }, + Symbol { + name: "[extab]", + demangled_name: None, + address: 0, + size: 0, + kind: Section, + section: Some( + 1, + ), + flags: FlagSet(Local), + align: None, + virtual_address: None, + }, + Symbol { + name: "[extabindex]", + demangled_name: None, + address: 0, + size: 0, + kind: Section, + section: Some( + 2, + ), + flags: FlagSet(Local), + align: None, + virtual_address: None, + }, + Symbol { + name: "@30", + demangled_name: None, + address: 0, + size: 8, + kind: Object, + section: Some( + 1, + ), + flags: FlagSet(Local), + align: None, + virtual_address: None, + }, + Symbol { + name: "@31", + demangled_name: None, + address: 0, + size: 12, + kind: Object, + section: Some( + 2, + ), + flags: FlagSet(Local), + align: None, + virtual_address: None, + }, + Symbol { + name: "@51", + demangled_name: None, + address: 8, + size: 24, + kind: Object, + section: Some( + 1, + ), + flags: FlagSet(Local), + align: None, + virtual_address: None, + }, + Symbol { + name: "@52", + demangled_name: None, + address: 12, + size: 12, + kind: Object, + section: Some( + 2, + ), + flags: FlagSet(Local), + align: None, + virtual_address: None, + }, + Symbol { + name: "@59", + demangled_name: None, + address: 32, + size: 8, + kind: Object, + section: Some( + 1, + ), + flags: FlagSet(Local), + align: None, + virtual_address: None, + }, + Symbol { + name: "@60", + demangled_name: None, + address: 24, + size: 12, + kind: Object, + section: Some( + 2, + ), + flags: FlagSet(Local), + align: None, + virtual_address: None, + }, + Symbol { + name: "__destroy_arr", + demangled_name: None, + address: 0, + size: 120, + kind: Function, + section: Some( + 0, + ), + flags: FlagSet(Global | HasExtra), + align: None, + virtual_address: None, + }, + Symbol { + name: "__construct_array", + demangled_name: None, + address: 120, + size: 248, + kind: Function, + section: Some( + 0, + ), + flags: FlagSet(Global | HasExtra), + align: None, + virtual_address: None, + }, + Symbol { + name: "__dt__26__partial_array_destructorFv", + demangled_name: Some( + "__partial_array_destructor::~__partial_array_destructor()", + ), + address: 368, + size: 184, + kind: Function, + section: Some( + 0, + ), + flags: FlagSet(Global | Weak | HasExtra), + align: None, + virtual_address: None, + }, + Symbol { + name: "__dl__FPv", + demangled_name: Some( + "operator delete(void*)", + ), + address: 0, + size: 0, + kind: Unknown, + section: None, + flags: FlagSet(Global), + align: None, + virtual_address: None, + }, + ], + sections: [ + Section { + id: ".text-0", + name: ".text", + address: 0, + size: 552, + kind: Code, + data: SectionData( + 552, + ), + flags: FlagSet(), + relocations: [ + Relocation { + flags: Elf( + 10, + ), + address: 516, + target_symbol: 13, + addend: 0, + }, + ], + line_info: {}, + virtual_address: None, + }, + Section { + id: "extab-0", + name: "extab", + address: 0, + size: 40, + kind: Data, + data: SectionData( + 40, + ), + flags: FlagSet(), + relocations: [ + Relocation { + flags: Elf( + 1, + ), + address: 28, + target_symbol: 12, + addend: 0, + }, + ], + line_info: {}, + virtual_address: None, + }, + Section { + id: "extabindex-0", + name: "extabindex", + address: 0, + size: 36, + kind: Data, + data: SectionData( + 36, + ), + flags: FlagSet(), + relocations: [ + Relocation { + flags: Elf( + 1, + ), + address: 0, + target_symbol: 10, + addend: 0, + }, + Relocation { + flags: Elf( + 1, + ), + address: 8, + target_symbol: 4, + addend: 0, + }, + Relocation { + flags: Elf( + 1, + ), + address: 12, + target_symbol: 11, + addend: 0, + }, + Relocation { + flags: Elf( + 1, + ), + address: 20, + target_symbol: 6, + addend: 0, + }, + Relocation { + flags: Elf( + 1, + ), + address: 24, + target_symbol: 12, + addend: 0, + }, + Relocation { + flags: Elf( + 1, + ), + address: 32, + target_symbol: 8, + addend: 0, + }, + ], + line_info: {}, + virtual_address: None, + }, + Section { + id: ".rela.text-0", + name: ".rela.text", + address: 0, + size: 12, + kind: Unknown, + data: SectionData( + 0, + ), + flags: FlagSet(), + relocations: [], + line_info: {}, + virtual_address: None, + }, + Section { + id: ".relaextab-0", + name: ".relaextab", + address: 0, + size: 12, + kind: Unknown, + data: SectionData( + 0, + ), + flags: FlagSet(), + relocations: [], + line_info: {}, + virtual_address: None, + }, + Section { + id: ".relaextabindex-0", + name: ".relaextabindex", + address: 0, + size: 72, + kind: Unknown, + data: SectionData( + 0, + ), + flags: FlagSet(), + relocations: [], + line_info: {}, + virtual_address: None, + }, + Section { + id: ".symtab-0", + name: ".symtab", + address: 0, + size: 240, + kind: Unknown, + data: SectionData( + 0, + ), + flags: FlagSet(), + relocations: [], + line_info: {}, + virtual_address: None, + }, + Section { + id: ".strtab-0", + name: ".strtab", + address: 0, + size: 121, + kind: Unknown, + data: SectionData( + 0, + ), + flags: FlagSet(), + relocations: [], + line_info: {}, + virtual_address: None, + }, + Section { + id: ".shstrtab-0", + name: ".shstrtab", + address: 0, + size: 97, + kind: Unknown, + data: SectionData( + 0, + ), + flags: FlagSet(), + relocations: [], + line_info: {}, + virtual_address: None, + }, + Section { + id: ".comment-0", + name: ".comment", + address: 0, + size: 164, + kind: Unknown, + data: SectionData( + 0, + ), + flags: FlagSet(), + relocations: [], + line_info: {}, + virtual_address: None, + }, + ], + split_meta: None, + path: None, + timestamp: None, +} diff --git a/objdiff-core/tests/snapshots/arch_ppc__read_ppc.snap b/objdiff-core/tests/snapshots/arch_ppc__read_ppc.snap index 89d1057..fa4c16e 100644 --- a/objdiff-core/tests/snapshots/arch_ppc__read_ppc.snap +++ b/objdiff-core/tests/snapshots/arch_ppc__read_ppc.snap @@ -6,6 +6,7 @@ Object { arch: ArchPpc { extab: None, }, + endianness: Big, symbols: [ Symbol { name: "IObj.cpp", diff --git a/objdiff-core/tests/snapshots/arch_x86__read_x86.snap b/objdiff-core/tests/snapshots/arch_x86__read_x86.snap index ec87e24..e802d9d 100644 --- a/objdiff-core/tests/snapshots/arch_x86__read_x86.snap +++ b/objdiff-core/tests/snapshots/arch_x86__read_x86.snap @@ -7,6 +7,7 @@ Object { bits: 32, endianness: Little, }, + endianness: Little, symbols: [ Symbol { name: "objdiffstaticdebug.cpp", diff --git a/objdiff-gui/Cargo.toml b/objdiff-gui/Cargo.toml index e2b9892..971ed1f 100644 --- a/objdiff-gui/Cargo.toml +++ b/objdiff-gui/Cargo.toml @@ -25,11 +25,10 @@ wsl = [] [dependencies] anyhow = "1.0" -bytes = "1.9" cfg-if = "1.0" const_format = "0.2" cwdemangle = "1.0" -cwextab = { version = "1.0", git = "https://github.com/encounter/cwextab.git" } +cwextab = { version = "1.0", git = "https://github.com/CelestialAmber/cwextab.git" } dirs = "5.0" egui = "0.30" egui_extras = "0.30" @@ -44,12 +43,11 @@ png = "0.17" pollster = "0.4" regex = "1.11" rfd = { version = "0.15" } #, default-features = false, features = ['xdg-portal'] -rlwinmdec = "1.0" +rlwinmdec = { version = "1.0", git = "https://github.com/CelestialAmber/rlwinmdec.git" } ron = "0.8" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" shell-escape = "0.1" -strum = { version = "0.26", features = ["derive"] } time = { version = "0.3", features = ["formatting", "local-offset"] } typed-path = "0.10" winit = { version = "0.30", features = ["wayland-csd-adwaita"] } diff --git a/objdiff-gui/src/app.rs b/objdiff-gui/src/app.rs index a58af1b..c6b0344 100644 --- a/objdiff-gui/src/app.rs +++ b/objdiff-gui/src/app.rs @@ -43,7 +43,7 @@ use crate::{ graphics::{graphics_window, GraphicsConfig, GraphicsViewState}, jobs::{jobs_menu_ui, jobs_window}, rlwinm::{rlwinm_decode_window, RlwinmDecodeViewState}, - symbol_diff::{DiffViewAction, DiffViewNavigation, DiffViewState, View}, + symbol_diff::{DiffViewAction, DiffViewState, ResolvedNavigation, View}, }, }; @@ -762,7 +762,7 @@ impl eframe::App for App { ui.separator(); if ui.button("Clear custom symbol mappings").clicked() { state.clear_mappings(); - diff_state.post_build_nav = Some(DiffViewNavigation::symbol_diff()); + diff_state.post_build_nav = Some(ResolvedNavigation::default()); state.queue_reload = true; } }); diff --git a/objdiff-gui/src/main.rs b/objdiff-gui/src/main.rs index e9e1e3e..961c3e5 100644 --- a/objdiff-gui/src/main.rs +++ b/objdiff-gui/src/main.rs @@ -25,8 +25,7 @@ use tracing_subscriber::EnvFilter; use crate::views::graphics::{load_graphics_config, GraphicsBackend, GraphicsConfig}; fn load_icon() -> Result { - use bytes::Buf; - let decoder = png::Decoder::new(include_bytes!("../assets/icon_64.png").reader()); + let decoder = png::Decoder::new(include_bytes!("../assets/icon_64.png").as_ref()); let mut reader = decoder.read_info()?; let mut buf = vec![0; reader.output_buffer_size()]; let info = reader.next_frame(&mut buf)?; diff --git a/objdiff-gui/src/views/diff.rs b/objdiff-gui/src/views/diff.rs index 4c4938b..02819ea 100644 --- a/objdiff-gui/src/views/diff.rs +++ b/objdiff-gui/src/views/diff.rs @@ -1,8 +1,11 @@ -use egui::{Id, Layout, RichText, ScrollArea, TextEdit, Ui, Widget}; +use egui::{text::LayoutJob, Id, Layout, RichText, ScrollArea, TextEdit, Ui, Widget}; use objdiff_core::{ build::BuildStatus, - diff::{display::SymbolFilter, DiffObjConfig, ObjectDiff, SectionDiff, SymbolDiff}, - obj::{Object, Section, SectionKind, Symbol}, + diff::{ + display::{ContextItem, HoverItem, HoverItemColor, SymbolFilter, SymbolNavigationKind}, + DiffObjConfig, ObjectDiff, SectionDiff, SymbolDiff, + }, + obj::{Object, Section, Symbol}, }; use time::format_description; @@ -15,9 +18,11 @@ use crate::{ extab_diff::extab_ui, function_diff::{asm_col_ui, FunctionDiffContext}, symbol_diff::{ - match_color_for_symbol, symbol_list_ui, DiffViewAction, DiffViewNavigation, - DiffViewState, SymbolDiffContext, SymbolRefByName, View, + match_color_for_symbol, symbol_context_menu_ui, symbol_hover_ui, symbol_list_ui, + DiffViewAction, DiffViewNavigation, DiffViewState, SymbolDiffContext, SymbolRefByName, + View, }, + write_text, }, }; @@ -112,21 +117,19 @@ pub fn diff_view_ui( // Check if we need to perform any navigation let current_navigation = DiffViewNavigation { - view: state.current_view, - left_symbol: state.symbol_state.left_symbol.clone(), - right_symbol: state.symbol_state.right_symbol.clone(), + kind: match state.current_view { + View::ExtabDiff => SymbolNavigationKind::Extab, + _ => SymbolNavigationKind::Normal, + }, + left_symbol: left_ctx.symbol.map(|(_, _, idx)| idx), + right_symbol: right_ctx.symbol.map(|(_, _, idx)| idx), }; let mut navigation = current_navigation.clone(); if let Some((_symbol, symbol_diff, _symbol_idx)) = left_ctx.symbol { // If a matching symbol appears, select it if !right_ctx.has_symbol() { if let Some(target_symbol_ref) = symbol_diff.target_symbol { - let (right_obj, _) = right_ctx.obj.unwrap(); - 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(target_symbol_ref); } } } else if navigation.left_symbol.is_some() @@ -140,12 +143,7 @@ pub fn diff_view_ui( // If a matching symbol appears, select it if !left_ctx.has_symbol() { if let Some(target_symbol_ref) = symbol_diff.target_symbol { - let (left_obj, _) = left_ctx.obj.unwrap(); - 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(target_symbol_ref); } } } else if navigation.right_symbol.is_some() @@ -157,7 +155,7 @@ pub fn diff_view_ui( } // If both sides are missing a symbol, switch to symbol diff view if navigation.left_symbol.is_none() && navigation.right_symbol.is_none() { - navigation.view = View::SymbolDiff; + navigation = DiffViewNavigation::default(); } // Execute navigation if it changed if navigation != current_navigation && state.post_build_nav.is_none() { @@ -177,7 +175,7 @@ pub fn diff_view_ui( } else { ui.horizontal(|ui| { if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) { - ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff())); + ret = Some(DiffViewAction::Navigate(DiffViewNavigation::default())); } if let Some((symbol, _, _)) = left_ctx.symbol { @@ -220,12 +218,12 @@ pub fn diff_view_ui( .color(appearance.replace_color), ); } - } else if let Some((symbol, _, _)) = left_ctx.symbol { - ui.label( - RichText::new(symbol.demangled_name.as_deref().unwrap_or(&symbol.name)) - .font(appearance.code_font.clone()) - .color(appearance.highlight_color), - ); + } else if let Some((symbol, _symbol_diff, symbol_idx)) = left_ctx.symbol { + if let Some(action) = + symbol_label_ui(ui, left_ctx, symbol, symbol_idx, column, appearance) + { + ret = Some(action); + } } else if let Some((section, _, _)) = left_ctx.section { ui.label( RichText::new(section.name.clone()) @@ -341,12 +339,12 @@ pub fn diff_view_ui( .color(appearance.replace_color), ); } - } else if let Some((symbol, _, _)) = right_ctx.symbol { - ui.label( - RichText::new(symbol.demangled_name.as_deref().unwrap_or(&symbol.name)) - .font(appearance.code_font.clone()) - .color(appearance.highlight_color), - ); + } else if let Some((symbol, _symbol_diff, symbol_idx)) = right_ctx.symbol { + if let Some(action) = + symbol_label_ui(ui, right_ctx, symbol, symbol_idx, column, appearance) + { + ret = Some(action); + } } else if let Some((section, _, _)) = right_ctx.section { ui.label( RichText::new(section.name.clone()) @@ -573,6 +571,38 @@ pub fn diff_view_ui( ret } +fn symbol_label_ui( + ui: &mut Ui, + ctx: DiffColumnContext, + symbol: &Symbol, + symbol_idx: usize, + column: usize, + appearance: &Appearance, +) -> Option { + let (obj, diff) = ctx.obj.unwrap(); + let ctx = SymbolDiffContext { obj, diff }; + let mut ret = None; + egui::Label::new( + RichText::new(symbol.demangled_name.as_deref().unwrap_or(&symbol.name)) + .font(appearance.code_font.clone()) + .color(appearance.highlight_color), + ) + .selectable(false) + // TODO .show_tooltip_when_elided(false) + // https://github.com/emilk/egui/commit/071e090e2b2601e5ed4726a63a753188503dfaf2 + .ui(ui) + .on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, ctx, symbol_idx, appearance)) + .context_menu(|ui| { + let section = symbol.section.and_then(|section_idx| ctx.obj.sections.get(section_idx)); + if let Some(result) = + symbol_context_menu_ui(ui, ctx, symbol_idx, symbol, section, column, appearance) + { + ret = Some(result); + } + }); + ret +} + #[must_use] fn diff_col_ui( ui: &mut Ui, @@ -641,15 +671,11 @@ fn diff_col_ui( }); }, ); - } else if let ( - Some((other_section, _other_section_diff, _other_section_idx)), - Some((other_symbol, _other_symbol_diff, other_symbol_idx)), - ) = (other_ctx.section, other_ctx.symbol) + } else if let Some((_other_symbol, _other_symbol_diff, other_symbol_idx)) = other_ctx.symbol { if let Some(action) = symbol_list_ui( ui, SymbolDiffContext { obj, diff }, - None, &state.symbol_state, SymbolFilter::Mapping(other_symbol_idx, None), appearance, @@ -660,34 +686,20 @@ fn diff_col_ui( ( 0, DiffViewAction::Navigate(DiffViewNavigation { - left_symbol: Some(left_symbol_ref), + left_symbol: Some(symbol_idx), .. }), ) => { - ret = Some(DiffViewAction::SetMapping( - match other_section.kind { - SectionKind::Code => View::FunctionDiff, - _ => View::SymbolDiff, - }, - left_symbol_ref, - SymbolRefByName::new(other_symbol, Some(other_section)), - )); + ret = Some(DiffViewAction::SetMapping(symbol_idx, other_symbol_idx)); } ( 1, DiffViewAction::Navigate(DiffViewNavigation { - right_symbol: Some(right_symbol_ref), + right_symbol: Some(symbol_idx), .. }), ) => { - ret = Some(DiffViewAction::SetMapping( - match other_section.kind { - SectionKind::Code => View::FunctionDiff, - _ => View::SymbolDiff, - }, - SymbolRefByName::new(other_symbol, Some(other_section)), - right_symbol_ref, - )); + ret = Some(DiffViewAction::SetMapping(other_symbol_idx, symbol_idx)); } (_, action) => { ret = Some(action); @@ -702,7 +714,6 @@ fn diff_col_ui( if let Some(result) = symbol_list_ui( ui, SymbolDiffContext { obj, diff }, - other_ctx.obj.map(|(obj, diff)| SymbolDiffContext { obj, diff }), &state.symbol_state, filter, appearance, @@ -764,3 +775,91 @@ fn find_symbol(obj: &Object, selected_symbol: &SymbolRefByName) -> Option fn find_section(obj: &Object, section_name: &str) -> Option { obj.sections.iter().position(|section| section.name == section_name) } + +pub fn hover_items_ui(ui: &mut Ui, items: Vec, appearance: &Appearance) { + for item in items { + match item { + HoverItem::Text { label, value, color } => { + let mut job = LayoutJob::default(); + if !label.is_empty() { + let label_color = match color { + HoverItemColor::Special => appearance.replace_color, + _ => appearance.highlight_color, + }; + write_text(&label, label_color, &mut job, appearance.code_font.clone()); + write_text(": ", label_color, &mut job, appearance.code_font.clone()); + } + write_text( + &value, + match color { + HoverItemColor::Emphasized => appearance.highlight_color, + _ => appearance.text_color, + }, + &mut job, + appearance.code_font.clone(), + ); + ui.label(job); + } + HoverItem::Separator => { + ui.separator(); + } + } + } +} + +pub fn context_menu_items_ui( + ui: &mut Ui, + items: Vec, + column: usize, + appearance: &Appearance, +) -> Option { + let mut ret = None; + for item in items { + match item { + ContextItem::Copy { value, label } => { + let mut job = LayoutJob::default(); + write_text( + "Copy \"", + appearance.text_color, + &mut job, + appearance.code_font.clone(), + ); + write_text( + &value, + appearance.highlight_color, + &mut job, + appearance.code_font.clone(), + ); + write_text("\"", appearance.text_color, &mut job, appearance.code_font.clone()); + if let Some(label) = label { + write_text(" (", appearance.text_color, &mut job, appearance.code_font.clone()); + write_text( + &label, + appearance.text_color, + &mut job, + appearance.code_font.clone(), + ); + write_text(")", appearance.text_color, &mut job, appearance.code_font.clone()); + } + if ui.button(job).clicked() { + ui.output_mut(|output| output.copied_text = value); + ui.close_menu(); + } + } + ContextItem::Navigate { label, symbol_index, kind } => { + if ui.button(label).clicked() { + ret = Some(DiffViewAction::Navigate(DiffViewNavigation::new( + kind, + symbol_index, + column, + ))); + ui.close_menu(); + } + } + ContextItem::Separator => { + ui.separator(); + } + } + } + ret +} diff --git a/objdiff-gui/src/views/function_diff.rs b/objdiff-gui/src/views/function_diff.rs index 808b5af..4819c38 100644 --- a/objdiff-gui/src/views/function_diff.rs +++ b/objdiff-gui/src/views/function_diff.rs @@ -4,17 +4,21 @@ use egui::{text::LayoutJob, Label, Response, Sense, Widget}; use egui_extras::TableRow; use objdiff_core::{ diff::{ - display::{display_row, DiffText, DiffTextColor, DiffTextSegment, HighlightKind}, + display::{ + display_row, instruction_context, instruction_hover, DiffText, DiffTextColor, + DiffTextSegment, HighlightKind, + }, DiffObjConfig, InstructionDiffKind, InstructionDiffRow, ObjectDiff, }, - obj::{ - InstructionArg, InstructionArgValue, InstructionRef, Object, ParsedInstruction, - ResolvedRelocation, Section, Symbol, - }, + obj::{InstructionArgValue, InstructionRef, Object}, util::ReallySigned, }; -use crate::views::{appearance::Appearance, symbol_diff::DiffViewAction}; +use crate::views::{ + appearance::Appearance, + diff::{context_menu_items_ui, hover_items_ui}, + symbol_diff::DiffViewAction, +}; #[derive(Default)] pub struct FunctionViewState { @@ -67,51 +71,6 @@ 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>, -} - -fn resolve_instruction_ref( - obj: &Object, - symbol_idx: usize, - ins_ref: InstructionRef, -) -> Option { - 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, 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( ui: &mut egui::Ui, obj: &Object, @@ -120,86 +79,25 @@ fn ins_hover_ui( 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 { + let Some(resolved) = obj.resolve_instruction_ref(symbol_idx, ins_ref) else { ui.colored_label(appearance.delete_color, "Failed to resolve instruction"); return; }; + let ins = match obj.arch.process_instruction(resolved, diff_config) { + Ok(ins) => ins, + Err(e) => { + ui.colored_label( + appearance.delete_color, + format!("Failed to process instruction: {e}"), + ); + return; + } + }; ui.scope(|ui| { ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); - ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); - - ui.label(format!("{:02x?}", data)); - - if let Some(virtual_address) = symbol.virtual_address { - let offset = ins_ref.address - symbol.address; - ui.colored_label( - appearance.replace_color, - format!("Virtual address: {:#x}", virtual_address + offset), - ); - } - - // TODO - // if let Some(orig) = &ins.orig { - // ui.label(format!("Original: {}", orig)); - // } - - for arg in &ins.args { - if let InstructionArg::Value(arg) = arg { - match arg { - InstructionArgValue::Signed(v) => { - ui.label(format!("{arg} == {v}")); - } - InstructionArgValue::Unsigned(v) => { - ui.label(format!("{arg} == {v}")); - } - _ => {} - } - } - } - - if let Some(resolved) = relocation { - ui.label(format!( - "Relocation type: {}", - obj.arch.display_reloc(resolved.relocation.flags) - )); - 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(), - }; - ui.colored_label( - appearance.highlight_color, - format!("Name: {}{}", resolved.symbol.name, addend_str), - ); - if let Some(orig_section_index) = resolved.symbol.section { - let section = &obj.sections[orig_section_index]; - ui.colored_label(appearance.highlight_color, format!("Section: {}", section.name)); - ui.colored_label( - appearance.highlight_color, - format!("Address: {:x}{}", resolved.symbol.address, addend_str), - ); - ui.colored_label( - appearance.highlight_color, - format!("Size: {:x}", resolved.symbol.size), - ); - // TODO - // for label in obj.arch.display_ins_data_labels(ins) { - // ui.colored_label(appearance.highlight_color, label); - // } - } else { - ui.colored_label(appearance.highlight_color, "Extern".to_string()); - } - } - - // TODO - // if let Some(decoded) = rlwinmdec::decode(&ins.formatted) { - // ui.colored_label(appearance.highlight_color, decoded.trim()); - // } + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Wrap); + hover_items_ui(ui, instruction_hover(obj, resolved, &ins), appearance); }); } @@ -208,93 +106,29 @@ fn ins_context_menu( obj: &Object, symbol_idx: usize, ins_ref: InstructionRef, + column: usize, 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 { + let Some(resolved) = obj.resolve_instruction_ref(symbol_idx, ins_ref) else { ui.colored_label(appearance.delete_color, "Failed to resolve instruction"); return; }; + let ins = match obj.arch.process_instruction(resolved, diff_config) { + Ok(ins) => ins, + Err(e) => { + ui.colored_label( + appearance.delete_color, + format!("Failed to process instruction: {e}"), + ); + return; + } + }; ui.scope(|ui| { ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); - ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); - - // TODO - // if ui.button(format!("Copy \"{}\"", ins.formatted)).clicked() { - // ui.output_mut(|output| output.copied_text.clone_from(&ins.formatted)); - // ui.close_menu(); - // } - - let mut hex_string = "0x".to_string(); - for byte in data { - hex_string.push_str(&format!("{:02x}", byte)); - } - if ui.button(format!("Copy \"{hex_string}\" (instruction bytes)")).clicked() { - ui.output_mut(|output| output.copied_text = hex_string); - ui.close_menu(); - } - - if let Some(virtual_address) = symbol.virtual_address { - let offset = ins_ref.address - symbol.address; - let offset_string = format!("{:#x}", virtual_address + offset); - if ui.button(format!("Copy \"{offset_string}\" (virtual address)")).clicked() { - ui.output_mut(|output| output.copied_text = offset_string); - ui.close_menu(); - } - } - - for arg in &ins.args { - if let InstructionArg::Value(arg) = arg { - match arg { - InstructionArgValue::Signed(v) => { - if ui.button(format!("Copy \"{arg}\"")).clicked() { - ui.output_mut(|output| output.copied_text = arg.to_string()); - ui.close_menu(); - } - if ui.button(format!("Copy \"{v}\"")).clicked() { - ui.output_mut(|output| output.copied_text = v.to_string()); - ui.close_menu(); - } - } - InstructionArgValue::Unsigned(v) => { - if ui.button(format!("Copy \"{arg}\"")).clicked() { - ui.output_mut(|output| output.copied_text = arg.to_string()); - ui.close_menu(); - } - if ui.button(format!("Copy \"{v}\"")).clicked() { - ui.output_mut(|output| output.copied_text = v.to_string()); - ui.close_menu(); - } - } - _ => {} - } - } - } - - if let Some(resolved) = relocation { - // TODO - // for literal in obj.arch.display_ins_data_literals(ins) { - // if ui.button(format!("Copy \"{literal}\"")).clicked() { - // ui.output_mut(|output| output.copied_text.clone_from(&literal)); - // ui.close_menu(); - // } - // } - if let Some(name) = &resolved.symbol.demangled_name { - if ui.button(format!("Copy \"{name}\"")).clicked() { - ui.output_mut(|output| output.copied_text.clone_from(name)); - ui.close_menu(); - } - } - if ui.button(format!("Copy \"{}\"", resolved.symbol.name)).clicked() { - ui.output_mut(|output| output.copied_text.clone_from(&resolved.symbol.name)); - ui.close_menu(); - } - } + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate); + context_menu_items_ui(ui, instruction_context(obj, resolved, &ins), column, appearance); }); } @@ -410,7 +244,7 @@ pub(crate) fn asm_col_ui( let response_cb = |response: Response| { if let Some(ins_ref) = ins_row.ins_ref { response.context_menu(|ui| { - ins_context_menu(ui, ctx.obj, symbol_ref, ins_ref, diff_config, appearance) + ins_context_menu(ui, ctx.obj, symbol_ref, ins_ref, column, diff_config, appearance) }); response.on_hover_ui_at_pointer(|ui| { ins_hover_ui(ui, ctx.obj, symbol_ref, ins_ref, diff_config, appearance) diff --git a/objdiff-gui/src/views/graphics.rs b/objdiff-gui/src/views/graphics.rs index 6b57e94..089ce85 100644 --- a/objdiff-gui/src/views/graphics.rs +++ b/objdiff-gui/src/views/graphics.rs @@ -7,7 +7,6 @@ use std::{ use anyhow::Result; use egui::{text::LayoutJob, Context, FontId, RichText, TextFormat, TextStyle, Window}; use serde::{Deserialize, Serialize}; -use strum::{EnumIter, EnumMessage, IntoEnumIterator}; use crate::views::{appearance::Appearance, frame_history::FrameHistory}; @@ -20,23 +19,24 @@ pub struct GraphicsViewState { pub should_relaunch: bool, } -#[derive( - Copy, Clone, Debug, Default, PartialEq, Eq, EnumIter, EnumMessage, Serialize, Deserialize, -)] +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum GraphicsBackend { #[default] - #[strum(message = "Auto")] Auto, - #[strum(message = "Vulkan")] Vulkan, - #[strum(message = "Metal")] Metal, - #[strum(message = "DirectX 12")] Dx12, - #[strum(message = "OpenGL")] OpenGL, } +static ALL_BACKENDS: &[GraphicsBackend] = &[ + GraphicsBackend::Auto, + GraphicsBackend::Vulkan, + GraphicsBackend::Metal, + GraphicsBackend::Dx12, + GraphicsBackend::OpenGL, +]; + #[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] pub struct GraphicsConfig { #[serde(default)] @@ -70,6 +70,16 @@ impl GraphicsBackend { GraphicsBackend::OpenGL => true, } } + + pub fn display_name(self) -> &'static str { + match self { + GraphicsBackend::Auto => "Auto", + GraphicsBackend::Vulkan => "Vulkan", + GraphicsBackend::Metal => "Metal", + GraphicsBackend::Dx12 => "DirectX 12", + GraphicsBackend::OpenGL => "OpenGL", + } + } } pub fn graphics_window( @@ -134,9 +144,9 @@ pub fn graphics_window( ui.add_enabled_ui(state.graphics_config_path.is_some(), |ui| { ui.horizontal(|ui| { ui.label("Desired backend:"); - for backend in GraphicsBackend::iter().filter(GraphicsBackend::is_supported) { + for backend in ALL_BACKENDS.iter().copied().filter(GraphicsBackend::is_supported) { let selected = state.graphics_config.desired_backend == backend; - if ui.selectable_label(selected, backend.get_message().unwrap()).clicked() { + if ui.selectable_label(selected, backend.display_name()).clicked() { let prev_backend = state.graphics_config.desired_backend; state.graphics_config.desired_backend = backend; match save_graphics_config( diff --git a/objdiff-gui/src/views/symbol_diff.rs b/objdiff-gui/src/views/symbol_diff.rs index bc77498..2dfbf24 100644 --- a/objdiff-gui/src/views/symbol_diff.rs +++ b/objdiff-gui/src/views/symbol_diff.rs @@ -7,8 +7,8 @@ use egui::{ use objdiff_core::{ diff::{ display::{ - display_sections, symbol_context, symbol_hover, ContextMenuItem, HighlightKind, - HoverItem, HoverItemColor, SectionDisplay, SymbolFilter, + display_sections, symbol_context, symbol_hover, HighlightKind, SectionDisplay, + SymbolFilter, SymbolNavigationKind, }, ObjectDiff, SymbolDiff, }, @@ -21,7 +21,12 @@ use crate::{ app::AppStateRef, hotkeys, jobs::{is_create_scratch_available, start_create_scratch}, - views::{appearance::Appearance, function_diff::FunctionViewState, write_text}, + views::{ + appearance::Appearance, + diff::{context_menu_items_ui, hover_items_ui}, + function_diff::FunctionViewState, + write_text, + }, }; #[derive(Debug, Clone, Eq, PartialEq)] @@ -71,62 +76,33 @@ pub enum DiffViewAction { /// The symbol reference is the left symbol to map to. SelectingRight(SymbolRefByName), /// Set a symbol mapping. - SetMapping(View, SymbolRefByName, SymbolRefByName), + SetMapping(usize, usize), /// Set the show_mapped_symbols flag SetShowMappedSymbols(bool), } #[derive(Debug, Clone, Default, Eq, PartialEq)] pub struct DiffViewNavigation { - pub view: View, - pub left_symbol: Option, - pub right_symbol: Option, + pub kind: SymbolNavigationKind, + pub left_symbol: Option, + pub right_symbol: Option, } impl DiffViewNavigation { - pub fn symbol_diff() -> Self { - Self { view: View::SymbolDiff, left_symbol: None, right_symbol: None } - } - - pub fn with_symbols( - view: View, - other_ctx: Option>, - symbol: &Symbol, - section: &Section, - symbol_diff: &SymbolDiff, - column: usize, - ) -> Self { - let symbol1 = Some(SymbolRefByName::new(symbol, Some(section))); - let symbol2 = symbol_diff.target_symbol.and_then(|symbol_ref| { - other_ctx.map(|ctx| { - 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) - }) - }); + pub fn new(kind: SymbolNavigationKind, symbol_idx: usize, column: usize) -> Self { match column { - 0 => Self { view, left_symbol: symbol1, right_symbol: symbol2 }, - 1 => Self { view, left_symbol: symbol2, right_symbol: symbol1 }, - _ => unreachable!("Invalid column index"), + 0 => Self { kind, left_symbol: Some(symbol_idx), right_symbol: None }, + 1 => Self { kind, left_symbol: None, right_symbol: Some(symbol_idx) }, + _ => panic!("Invalid column index"), } } +} - pub fn data_diff(section: &Section, column: usize) -> Self { - let symbol = Some(SymbolRefByName { - symbol_name: "".to_string(), - section_name: Some(section.name.clone()), - }); - match column { - 0 => Self { - view: View::DataDiff, - left_symbol: symbol.clone(), - right_symbol: symbol.clone(), - }, - 1 => Self { view: View::DataDiff, left_symbol: symbol.clone(), right_symbol: symbol }, - _ => unreachable!("Invalid column index"), - } - } +#[derive(Debug, Clone, Default, Eq, PartialEq)] +pub struct ResolvedNavigation { + pub view: View, + pub left_symbol: Option, + pub right_symbol: Option, } #[derive(Default)] @@ -142,7 +118,7 @@ pub struct DiffViewState { pub scratch_available: bool, pub scratch_running: bool, pub source_path_available: bool, - pub post_build_nav: Option, + pub post_build_nav: Option, pub object_name: String, } @@ -230,24 +206,42 @@ impl DiffViewState { let Ok(mut state) = state.write() else { return; }; - if (nav.left_symbol.is_some() && nav.right_symbol.is_some()) - || (nav.left_symbol.is_none() && nav.right_symbol.is_none()) - || nav.view != View::FunctionDiff + + let mut resolved_left = self.resolve_symbol(nav.left_symbol, 0); + let mut resolved_right = self.resolve_symbol(nav.right_symbol, 1); + if let Some(resolved_right) = &resolved_right { + if resolved_left.is_none() { + resolved_left = resolved_right + .target_symbol + .and_then(|idx| self.resolve_symbol(Some(idx), 0)); + } + } + if let Some(resolved_left) = &resolved_left { + if resolved_right.is_none() { + resolved_right = resolved_left + .target_symbol + .and_then(|idx| self.resolve_symbol(Some(idx), 1)); + } + } + let resolved_nav = resolve_navigation(nav.kind, resolved_left, resolved_right); + if (resolved_nav.left_symbol.is_some() && resolved_nav.right_symbol.is_some()) + || (resolved_nav.left_symbol.is_none() && resolved_nav.right_symbol.is_none()) + || resolved_nav.view != View::FunctionDiff { // Regular navigation if state.is_selecting_symbol() { // Cancel selection and reload state.clear_selection(); - self.post_build_nav = Some(nav); + self.post_build_nav = Some(resolved_nav); } else { // Navigate immediately - self.current_view = nav.view; - self.symbol_state.left_symbol = nav.left_symbol; - self.symbol_state.right_symbol = nav.right_symbol; + self.current_view = resolved_nav.view; + self.symbol_state.left_symbol = resolved_nav.left_symbol; + self.symbol_state.right_symbol = resolved_nav.right_symbol; } } else { // Enter selection mode - match (&nav.left_symbol, &nav.right_symbol) { + match (&resolved_nav.left_symbol, &resolved_nav.right_symbol) { (Some(left_ref), None) => { state.set_selecting_right(&left_ref.symbol_name); } @@ -257,7 +251,7 @@ impl DiffViewState { (Some(_), Some(_)) => unreachable!(), (None, None) => unreachable!(), } - self.post_build_nav = Some(nav); + self.post_build_nav = Some(resolved_nav); } } DiffViewAction::SetSymbolHighlight(left, right, autoscroll) => { @@ -306,7 +300,7 @@ impl DiffViewState { return; }; state.set_selecting_left(&right_ref.symbol_name); - self.post_build_nav = Some(DiffViewNavigation { + self.post_build_nav = Some(ResolvedNavigation { view: View::FunctionDiff, left_symbol: None, right_symbol: Some(right_ref), @@ -321,13 +315,13 @@ impl DiffViewState { return; }; state.set_selecting_right(&left_ref.symbol_name); - self.post_build_nav = Some(DiffViewNavigation { + self.post_build_nav = Some(ResolvedNavigation { view: View::FunctionDiff, left_symbol: Some(left_ref), right_symbol: None, }); } - DiffViewAction::SetMapping(view, left_ref, right_ref) => { + DiffViewAction::SetMapping(left_ref, right_ref) => { if self.post_build_nav.is_some() { // Ignore action if we're already navigating return; @@ -335,25 +329,133 @@ impl DiffViewState { let Ok(mut state) = state.write() else { return; }; - state.set_symbol_mapping( - left_ref.symbol_name.clone(), - right_ref.symbol_name.clone(), - ); - if view == View::SymbolDiff { - self.post_build_nav = Some(DiffViewNavigation::symbol_diff()); + let resolved_nav = if let (Some(left_ref), Some(right_ref)) = ( + self.resolve_symbol(Some(left_ref), 0), + self.resolve_symbol(Some(right_ref), 1), + ) { + state.set_symbol_mapping( + left_ref.symbol.name.clone(), + right_ref.symbol.name.clone(), + ); + resolve_navigation( + SymbolNavigationKind::Normal, + Some(left_ref), + Some(right_ref), + ) } else { - self.post_build_nav = Some(DiffViewNavigation { - view, - left_symbol: Some(left_ref), - right_symbol: Some(right_ref), - }); - } + ResolvedNavigation::default() + }; + self.post_build_nav = Some(resolved_nav); } DiffViewAction::SetShowMappedSymbols(value) => { self.symbol_state.show_mapped_symbols = value; } } } + + fn resolve_symbol(&self, symbol_idx: Option, column: usize) -> Option { + let symbol_idx = symbol_idx?; + let result = self.build.as_deref()?; + let (obj, diff) = match column { + 0 => result.first_obj.as_ref()?, + 1 => result.second_obj.as_ref()?, + _ => return None, + }; + let symbol = obj.symbols.get(symbol_idx)?; + let section_idx = symbol.section?; + let section = obj.sections.get(section_idx)?; + let symbol_diff = diff.symbols.get(symbol_idx)?; + Some(ResolvedSymbol { + symbol_ref: SymbolRefByName::new(symbol, Some(section)), + symbol, + section, + target_symbol: symbol_diff.target_symbol, + }) + } +} + +struct ResolvedSymbol<'obj> { + symbol_ref: SymbolRefByName, + symbol: &'obj Symbol, + section: &'obj Section, + target_symbol: Option, +} + +/// Determine the navigation target based on the resolved symbols. +fn resolve_navigation( + kind: SymbolNavigationKind, + resolved_left: Option, + resolved_right: Option, +) -> ResolvedNavigation { + match (resolved_left, resolved_right) { + (Some(left), Some(right)) => match (left.section.kind, right.section.kind) { + (SectionKind::Code, SectionKind::Code) => ResolvedNavigation { + view: match kind { + SymbolNavigationKind::Normal => View::FunctionDiff, + SymbolNavigationKind::Extab => View::ExtabDiff, + }, + left_symbol: Some(left.symbol_ref), + right_symbol: Some(right.symbol_ref), + }, + (SectionKind::Data, SectionKind::Data) => ResolvedNavigation { + view: View::DataDiff, + left_symbol: Some(SymbolRefByName { + symbol_name: "".to_string(), + section_name: Some(left.section.name.clone()), + }), + right_symbol: Some(SymbolRefByName { + symbol_name: "".to_string(), + section_name: Some(right.section.name.clone()), + }), + }, + _ => ResolvedNavigation::default(), + }, + (Some(left), None) => match left.section.kind { + SectionKind::Code => ResolvedNavigation { + view: match kind { + SymbolNavigationKind::Normal => View::FunctionDiff, + SymbolNavigationKind::Extab => View::ExtabDiff, + }, + left_symbol: Some(left.symbol_ref), + right_symbol: None, + }, + SectionKind::Data => ResolvedNavigation { + view: View::DataDiff, + left_symbol: Some(SymbolRefByName { + symbol_name: "".to_string(), + section_name: Some(left.section.name.clone()), + }), + right_symbol: Some(SymbolRefByName { + symbol_name: "".to_string(), + section_name: Some(left.section.name.clone()), + }), + }, + _ => ResolvedNavigation::default(), + }, + (None, Some(right)) => match right.section.kind { + SectionKind::Code => ResolvedNavigation { + view: match kind { + SymbolNavigationKind::Normal => View::FunctionDiff, + SymbolNavigationKind::Extab => View::ExtabDiff, + }, + left_symbol: None, + right_symbol: Some(right.symbol_ref), + }, + SectionKind::Data => ResolvedNavigation { + view: View::DataDiff, + left_symbol: Some(SymbolRefByName { + symbol_name: "".to_string(), + section_name: Some(right.section.name.clone()), + }), + right_symbol: Some(SymbolRefByName { + symbol_name: "".to_string(), + section_name: Some(right.section.name.clone()), + }), + }, + _ => ResolvedNavigation::default(), + }, + (None, None) => ResolvedNavigation::default(), + } } pub fn match_color_for_symbol(match_percent: f32, appearance: &Appearance) -> Color32 { @@ -366,65 +468,33 @@ pub fn match_color_for_symbol(match_percent: f32, appearance: &Appearance) -> Co } } -fn symbol_context_menu_ui( +pub fn symbol_context_menu_ui( ui: &mut Ui, ctx: SymbolDiffContext<'_>, - other_ctx: Option>, + symbol_idx: usize, symbol: &Symbol, - symbol_diff: &SymbolDiff, section: Option<&Section>, column: usize, -) -> Option { + appearance: &Appearance, +) -> Option { let mut ret = None; ui.scope(|ui| { 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::Truncate); - for item in symbol_context(ctx.obj, symbol) { - match item { - 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(); - } - } - ContextMenuItem::Navigate { label } => { - if ui.button(label).clicked() { - // TODO other navigation - ret = Some(DiffViewNavigation::with_symbols( - View::ExtabDiff, - other_ctx, - symbol, - section.unwrap(), - symbol_diff, - column, - )); - ui.close_menu(); - } - } - } + if let Some(action) = + context_menu_items_ui(ui, symbol_context(ctx.obj, symbol_idx), column, appearance) + { + ret = Some(action); } if let Some(section) = section { if ui.button("Map symbol").clicked() { let symbol_ref = SymbolRefByName::new(symbol, Some(section)); if column == 0 { - ret = Some(DiffViewNavigation { - view: View::FunctionDiff, - left_symbol: Some(symbol_ref), - right_symbol: None, - }); + ret = Some(DiffViewAction::SelectingRight(symbol_ref)); } else { - ret = Some(DiffViewNavigation { - view: View::FunctionDiff, - left_symbol: None, - right_symbol: Some(symbol_ref), - }); + ret = Some(DiffViewAction::SelectingLeft(symbol_ref)); } ui.close_menu(); } @@ -433,24 +503,16 @@ fn symbol_context_menu_ui( ret } -fn symbol_hover_ui( +pub fn symbol_hover_ui( ui: &mut Ui, ctx: SymbolDiffContext<'_>, - symbol: &Symbol, + symbol_idx: usize, appearance: &Appearance, ) { ui.scope(|ui| { ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); - ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); - - for HoverItem { text, color } in symbol_hover(ctx.obj, symbol) { - let color = match color { - HoverItemColor::Normal => appearance.text_color, - HoverItemColor::Emphasized => appearance.highlight_color, - HoverItemColor::Special => appearance.replace_color, - }; - ui.colored_label(color, text); - } + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Wrap); + hover_items_ui(ui, symbol_hover(ctx.obj, symbol_idx, 0), appearance); }); } @@ -458,7 +520,6 @@ fn symbol_hover_ui( fn symbol_ui( ui: &mut Ui, ctx: SymbolDiffContext<'_>, - other_ctx: Option>, symbol: &Symbol, symbol_diff: &SymbolDiff, symbol_idx: usize, @@ -515,12 +576,12 @@ fn symbol_ui( 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| symbol_hover_ui(ui, ctx, symbol, appearance)); + .on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, ctx, symbol_idx, appearance)); response.context_menu(|ui| { if let Some(result) = - symbol_context_menu_ui(ui, ctx, other_ctx, symbol, symbol_diff, section, column) + symbol_context_menu_ui(ui, ctx, symbol_idx, symbol, section, column, appearance) { - ret = Some(DiffViewAction::Navigate(result)); + ret = Some(result); } }); if selected && state.autoscroll_to_highlighted_symbols { @@ -532,26 +593,11 @@ fn symbol_ui( // manually scroll away. } if response.clicked() || (selected && hotkeys::enter_pressed(ui.ctx())) { - if let Some(section) = section { - match section.kind { - SectionKind::Code => { - ret = Some(DiffViewAction::Navigate(DiffViewNavigation::with_symbols( - View::FunctionDiff, - other_ctx, - symbol, - section, - symbol_diff, - column, - ))); - } - SectionKind::Data => { - ret = Some(DiffViewAction::Navigate(DiffViewNavigation::data_diff( - section, column, - ))); - } - _ => {} - } - } + ret = Some(DiffViewAction::Navigate(DiffViewNavigation::new( + SymbolNavigationKind::Normal, + symbol_idx, + column, + ))); } else if response.hovered() { ret = Some(if column == 0 { DiffViewAction::SetSymbolHighlight(Some(symbol_idx), symbol_diff.target_symbol, false) @@ -597,7 +643,6 @@ fn find_last_symbol(section_display: &[SectionDisplay]) -> Option { pub fn symbol_list_ui( ui: &mut Ui, ctx: SymbolDiffContext<'_>, - other_ctx: Option>, state: &SymbolViewState, filter: SymbolFilter<'_>, appearance: &Appearance, @@ -720,7 +765,6 @@ pub fn symbol_list_ui( if let Some(result) = symbol_ui( ui, ctx, - other_ctx, symbol, symbol_diff, symbol_display.symbol,