From 0fb7f3901cd26a384a6d64ea8990ea6515ca9bd0 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Thu, 17 Jul 2025 21:19:06 -0600 Subject: [PATCH] Add PPC COFF tests; fix IMAGE_REL_PPC_PAIR handling --- objdiff-core/src/arch/arm.rs | 6 +- objdiff-core/src/arch/arm64.rs | 2 +- objdiff-core/src/arch/mips.rs | 8 +- objdiff-core/src/arch/mod.rs | 6 +- objdiff-core/src/arch/ppc/mod.rs | 59 +- objdiff-core/src/arch/superh/mod.rs | 2 +- objdiff-core/src/arch/x86.rs | 8 +- objdiff-core/src/obj/read.rs | 5 +- objdiff-core/tests/arch_ppc.rs | 28 + objdiff-core/tests/data/ppc/vmx128.obj | Bin 0 -> 12120 bytes .../arch_ppc__read_vmx128_coff-2.snap | 1826 ++++++++++++ .../arch_ppc__read_vmx128_coff-3.snap | 134 + .../snapshots/arch_ppc__read_vmx128_coff.snap | 2442 +++++++++++++++++ 13 files changed, 4485 insertions(+), 41 deletions(-) create mode 100644 objdiff-core/tests/data/ppc/vmx128.obj create mode 100644 objdiff-core/tests/snapshots/arch_ppc__read_vmx128_coff-2.snap create mode 100644 objdiff-core/tests/snapshots/arch_ppc__read_vmx128_coff-3.snap create mode 100644 objdiff-core/tests/snapshots/arch_ppc__read_vmx128_coff.snap diff --git a/objdiff-core/src/arch/arm.rs b/objdiff-core/src/arch/arm.rs index 0760187..fffc9cb 100644 --- a/objdiff-core/src/arch/arm.rs +++ b/objdiff-core/src/arch/arm.rs @@ -363,10 +363,10 @@ impl Arch for ArchArm { address: u64, _relocation: &object::Relocation, flags: RelocationFlags, - ) -> Result { + ) -> Result> { let section_data = section.data()?; let address = address as usize; - Ok(match flags { + Ok(Some(match flags { // ARM calls RelocationFlags::Elf(elf::R_ARM_PC24) | RelocationFlags::Elf(elf::R_ARM_XPC25) @@ -396,7 +396,7 @@ impl Arch for ArchArm { } flags => bail!("Unsupported ARM implicit relocation {flags:?}"), - } as i64) + } as i64)) } fn demangle(&self, name: &str) -> Option { diff --git a/objdiff-core/src/arch/arm64.rs b/objdiff-core/src/arch/arm64.rs index 2308bfc..5c4d972 100644 --- a/objdiff-core/src/arch/arm64.rs +++ b/objdiff-core/src/arch/arm64.rs @@ -115,7 +115,7 @@ impl Arch for ArchArm64 { address: u64, _relocation: &object::Relocation, flags: RelocationFlags, - ) -> Result { + ) -> Result> { bail!("Unsupported ARM64 implicit relocation {:#x}:{:?}", address, flags) } diff --git a/objdiff-core/src/arch/mips.rs b/objdiff-core/src/arch/mips.rs index 2608de6..fa6404b 100644 --- a/objdiff-core/src/arch/mips.rs +++ b/objdiff-core/src/arch/mips.rs @@ -232,7 +232,7 @@ impl Arch for ArchMips { address: u64, reloc: &object::Relocation, flags: RelocationFlags, - ) -> Result { + ) -> Result> { // Check for paired R_MIPS_HI16 and R_MIPS_LO16 relocations. if let RelocationFlags::Elf(elf::R_MIPS_HI16 | elf::R_MIPS_LO16) = flags { if let Some(addend) = self @@ -240,14 +240,14 @@ impl Arch for ArchMips { .get(section.index().0) .and_then(|m| m.get(&address).copied()) { - return Ok(addend); + return Ok(Some(addend)); } } let data = section.data()?; let code = data[address as usize..address as usize + 4].try_into()?; let addend = self.endianness.read_u32_bytes(code); - Ok(match flags { + Ok(Some(match flags { RelocationFlags::Elf(elf::R_MIPS_32) => addend as i64, RelocationFlags::Elf(elf::R_MIPS_26) => ((addend & 0x03FFFFFF) << 2) as i64, RelocationFlags::Elf(elf::R_MIPS_HI16) => ((addend & 0x0000FFFF) << 16) as i32 as i64, @@ -271,7 +271,7 @@ impl Arch for ArchMips { RelocationFlags::Elf(elf::R_MIPS_PC16) => 0, // PC-relative relocation RelocationFlags::Elf(R_MIPS15_S3) => ((addend & 0x001FFFC0) >> 3) as i64, flags => bail!("Unsupported MIPS implicit relocation {flags:?}"), - }) + })) } fn demangle(&self, name: &str) -> Option { diff --git a/objdiff-core/src/arch/mod.rs b/objdiff-core/src/arch/mod.rs index 6b5408a..6cd0c4f 100644 --- a/objdiff-core/src/arch/mod.rs +++ b/objdiff-core/src/arch/mod.rs @@ -364,7 +364,7 @@ pub trait Arch: Send + Sync + Debug { address: u64, relocation: &object::Relocation, flags: RelocationFlags, - ) -> Result; + ) -> Result>; fn demangle(&self, _name: &str) -> Option { None } @@ -463,8 +463,8 @@ impl Arch for ArchDummy { _address: u64, _relocation: &object::Relocation, _flags: RelocationFlags, - ) -> Result { - Ok(0) + ) -> Result> { + Ok(Some(0)) } fn data_reloc_size(&self, _flags: RelocationFlags) -> usize { 0 } diff --git a/objdiff-core/src/arch/ppc/mod.rs b/objdiff-core/src/arch/ppc/mod.rs index 48a71e7..83730c0 100644 --- a/objdiff-core/src/arch/ppc/mod.rs +++ b/objdiff-core/src/arch/ppc/mod.rs @@ -6,10 +6,10 @@ use alloc::{ vec::Vec, }; -use anyhow::{Result, bail, ensure}; +use anyhow::{Result, anyhow, bail, ensure}; use cwextab::{ExceptionTableData, decode_extab}; use flagset::Flags; -use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, elf, pe}; +use object::{Object as _, ObjectSection as _, ObjectSymbol as _, elf, pe}; use crate::{ arch::{Arch, DataType}, @@ -210,32 +210,35 @@ impl Arch for ArchPpc { fn implcit_addend( &self, - file: &object::File<'_>, + _file: &object::File<'_>, section: &object::Section, address: u64, _relocation: &object::Relocation, flags: RelocationFlags, - ) -> Result { - let section_data = section.data()?; - let address = address as usize; - let data = section_data - .get(address..address + 4) - .ok_or_else(|| anyhow::anyhow!("Invalid address {address} for section data"))? - .try_into()?; - let code = file.endianness().read_u32_bytes(data); - Ok(match flags { + ) -> Result> { + match flags { + // IMAGE_REL_PPC_PAIR contains the REF{HI,LO} displacement instead of a symbol index RelocationFlags::Coff(pe::IMAGE_REL_PPC_REFHI) - | RelocationFlags::Coff(pe::IMAGE_REL_PPC_REFLO) => (code & 0xffff) as i16 as i32, - RelocationFlags::Coff(pe::IMAGE_REL_PPC_REL24) => { - // let addend = (((code & 0x3fffffc) << 6) as i32) >> 6; - // println!("PPC_REL24 addend: {data:?} => {addend}"); - // addend - 0 - } - RelocationFlags::Coff(pe::IMAGE_REL_PPC_ADDR32) => code as i32, - RelocationFlags::Coff(pe::IMAGE_REL_PPC_PAIR) => 0, - flags => bail!("Unsupported PPC implicit relocation {flags:?}"), - } as i64) + | RelocationFlags::Coff(pe::IMAGE_REL_PPC_REFLO) => section + .relocations() + .skip_while(|&(a, _)| a < address) + .take_while(|&(a, _)| a == address) + .find(|(_, reloc)| { + matches!(reloc.flags(), object::RelocationFlags::Coff { + typ: pe::IMAGE_REL_PPC_PAIR + }) + }) + .map_or(Ok(Some(0)), |(_, reloc)| match reloc.target() { + object::RelocationTarget::Symbol(index) => { + Ok(Some(index.0 as u16 as i16 as i64)) + } + target => Err(anyhow!("Unsupported IMAGE_REL_PPC_PAIR target {target:?}")), + }), + // Skip PAIR relocations as they are handled by the previous case + RelocationFlags::Coff(pe::IMAGE_REL_PPC_PAIR) => Ok(None), + RelocationFlags::Coff(_) => Ok(Some(0)), + flags => Err(anyhow!("Unsupported PPC implicit relocation {flags:?}")), + } } fn demangle(&self, name: &str) -> Option { @@ -263,7 +266,15 @@ impl Arch for ArchPpc { elf::R_PPC_REL14 => Some("R_PPC_REL14"), _ => None, }, - _ => None, + RelocationFlags::Coff(r_type) => match r_type { + pe::IMAGE_REL_PPC_ADDR32 => Some("IMAGE_REL_PPC_ADDR32"), + pe::IMAGE_REL_PPC_REFHI => Some("IMAGE_REL_PPC_REFHI"), + pe::IMAGE_REL_PPC_REFLO => Some("IMAGE_REL_PPC_REFLO"), + pe::IMAGE_REL_PPC_REL24 => Some("IMAGE_REL_PPC_REL24"), + pe::IMAGE_REL_PPC_REL14 => Some("IMAGE_REL_PPC_REL14"), + pe::IMAGE_REL_PPC_PAIR => Some("IMAGE_REL_PPC_PAIR"), + _ => None, + }, } } diff --git a/objdiff-core/src/arch/superh/mod.rs b/objdiff-core/src/arch/superh/mod.rs index 0b98725..8c48953 100644 --- a/objdiff-core/src/arch/superh/mod.rs +++ b/objdiff-core/src/arch/superh/mod.rs @@ -139,7 +139,7 @@ impl Arch for ArchSuperH { address: u64, _relocation: &object::Relocation, flags: RelocationFlags, - ) -> Result { + ) -> Result> { bail!("Unsupported SuperH implicit relocation {:#x}:{:?}", address, flags) } diff --git a/objdiff-core/src/arch/x86.rs b/objdiff-core/src/arch/x86.rs index d89674e..6e3de36 100644 --- a/objdiff-core/src/arch/x86.rs +++ b/objdiff-core/src/arch/x86.rs @@ -232,14 +232,14 @@ impl Arch for ArchX86 { address: u64, _relocation: &object::Relocation, flags: RelocationFlags, - ) -> Result { + ) -> Result> { match self.arch { Architecture::X86 => match flags { RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32) | RelocationFlags::Elf(elf::R_386_32 | elf::R_386_PC32) => { let data = section.data()?[address as usize..address as usize + 4].try_into()?; - Ok(self.endianness.read_i32_bytes(data) as i64) + Ok(Some(self.endianness.read_i32_bytes(data) as i64)) } flags => bail!("Unsupported x86 implicit relocation {flags:?}"), }, @@ -248,13 +248,13 @@ impl Arch for ArchX86 { | RelocationFlags::Elf(elf::R_X86_64_32 | elf::R_X86_64_PC32) => { let data = section.data()?[address as usize..address as usize + 4].try_into()?; - Ok(self.endianness.read_i32_bytes(data) as i64) + Ok(Some(self.endianness.read_i32_bytes(data) as i64)) } RelocationFlags::Coff(pe::IMAGE_REL_AMD64_ADDR64) | RelocationFlags::Elf(elf::R_X86_64_64) => { let data = section.data()?[address as usize..address as usize + 8].try_into()?; - Ok(self.endianness.read_i64_bytes(data)) + Ok(Some(self.endianness.read_i64_bytes(data))) } flags => bail!("Unsupported x86-64 implicit relocation {flags:?}"), }, diff --git a/objdiff-core/src/obj/read.rs b/objdiff-core/src/obj/read.rs index 8196e37..51b863f 100644 --- a/objdiff-core/src/obj/read.rs +++ b/objdiff-core/src/obj/read.rs @@ -338,7 +338,10 @@ fn map_section_relocations( }; // TODO validate reloc here? let mut addend = if reloc.has_implicit_addend() { - arch.implcit_addend(obj_file, obj_section, address, &reloc, flags)? + match arch.implcit_addend(obj_file, obj_section, address, &reloc, flags)? { + Some(addend) => addend, + None => continue, // Skip relocation (e.g. COFF PAIR relocations) + } } else { reloc.addend() }; diff --git a/objdiff-core/tests/arch_ppc.rs b/objdiff-core/tests/arch_ppc.rs index 40e9d83..8086f14 100644 --- a/objdiff-core/tests/arch_ppc.rs +++ b/objdiff-core/tests/arch_ppc.rs @@ -85,3 +85,31 @@ fn diff_ppc() { assert_eq!(base_symbol_diff.target_symbol, Some(target_symbol_idx)); insta::assert_debug_snapshot!((target_symbol_diff, base_symbol_diff)); } + +#[test] +#[cfg(feature = "ppc")] +fn read_vmx128_coff() { + let diff_config = diff::DiffObjConfig { combine_data_sections: true, ..Default::default() }; + let obj = obj::read::parse(include_object!("data/ppc/vmx128.obj"), &diff_config).unwrap(); + insta::assert_debug_snapshot!(obj); + let symbol_idx = + obj.symbols.iter().position(|s| s.name == "?FloatingPointExample@@YAXXZ").unwrap(); + let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap(); + insta::assert_debug_snapshot!(diff.instruction_rows); + let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config); + insta::assert_snapshot!(output); +} + +#[test] +#[cfg(feature = "ppc")] +fn read_dummy() { + let diff_config = diff::DiffObjConfig { combine_data_sections: true, ..Default::default() }; + let obj = obj::read::parse(include_object!("data/ppc/dummy.obj"), &diff_config).unwrap(); + insta::assert_debug_snapshot!(obj); + let symbol_idx = + obj.symbols.iter().position(|s| s.name == "?FloatingPointExample@@YAXXZ").unwrap(); + let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap(); + insta::assert_debug_snapshot!(diff.instruction_rows); + let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config); + insta::assert_snapshot!(output); +} diff --git a/objdiff-core/tests/data/ppc/vmx128.obj b/objdiff-core/tests/data/ppc/vmx128.obj new file mode 100644 index 0000000000000000000000000000000000000000..a0494108dde30b70ec24bb1d2cdbec1e74e875d8 GIT binary patch literal 12120 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 o3*1G9s4jq*ZveN!#3&g>eX;?ndJ{t