diff --git a/objdiff-core/src/arch/arm.rs b/objdiff-core/src/arch/arm.rs index 946e9e2e..a8feea72 100644 --- a/objdiff-core/src/arch/arm.rs +++ b/objdiff-core/src/arch/arm.rs @@ -224,7 +224,9 @@ impl Arch for ArchArm { } // Check how many bytes we can/should read - let num_code_bytes = if data.len() >= 4 { + let bytes_until_next_mapping = + next_mapping.map(|m| (m.address - address) as usize).unwrap_or(usize::MAX); + let num_code_bytes = if data.len() >= 4 && bytes_until_next_mapping >= 4 { if mode == unarm::ParseMode::Data && address & 3 != 0 { // 32-bit .word value should be aligned on a 4-byte boundary, otherwise use .hword 2 @@ -366,6 +368,22 @@ impl Arch for ArchArm { (imm22 << 1) << 9 >> 9 } + // Thumb unconditional branch (B, 11-bit offset) + elf::R_ARM_THM_PC11 => { + let data = section_data[address..address + 2].try_into()?; + let insn = self.endianness.read_u16_bytes(data) as i32; + let imm11 = insn & 0x7ff; + (imm11 << 1) << 20 >> 20 + } + + // Thumb conditional branch (B, 8-bit offset) + elf::R_ARM_THM_PC9 => { + let data = section_data[address..address + 2].try_into()?; + let insn = self.endianness.read_u16_bytes(data) as i32; + let imm8 = insn & 0xff; + (imm8 << 1) << 23 >> 23 + } + // Data elf::R_ARM_ABS32 => { let data = section_data[address..address + 4].try_into()?; @@ -399,6 +417,8 @@ impl Arch for ArchArm { elf::R_ARM_PC24 => Some("R_ARM_PC24"), elf::R_ARM_XPC25 => Some("R_ARM_XPC25"), elf::R_ARM_CALL => Some("R_ARM_CALL"), + elf::R_ARM_THM_PC11 => Some("R_ARM_THM_PC11"), + elf::R_ARM_THM_PC9 => Some("R_ARM_THM_PC9"), _ => None, }, _ => None, @@ -418,6 +438,8 @@ impl Arch for ArchArm { elf::R_ARM_PC24 => 4, elf::R_ARM_XPC25 => 4, elf::R_ARM_CALL => 4, + elf::R_ARM_THM_PC11 => 2, + elf::R_ARM_THM_PC9 => 2, _ => 1, }, _ => 1, @@ -544,7 +566,9 @@ impl unarm::FormatIns for ArgsFormatter<'_> { | RelocationFlags::Elf(elf::R_ARM_THM_PC22) | RelocationFlags::Elf(elf::R_ARM_PC24) | RelocationFlags::Elf(elf::R_ARM_XPC25) - | RelocationFlags::Elf(elf::R_ARM_CALL) => { + | RelocationFlags::Elf(elf::R_ARM_CALL) + | RelocationFlags::Elf(elf::R_ARM_THM_PC11) + | RelocationFlags::Elf(elf::R_ARM_THM_PC9) => { return self.write(InstructionPart::reloc()); } _ => {} diff --git a/objdiff-core/tests/arch_arm.rs b/objdiff-core/tests/arch_arm.rs index 918d099d..9c507e1d 100644 --- a/objdiff-core/tests/arch_arm.rs +++ b/objdiff-core/tests/arch_arm.rs @@ -57,6 +57,31 @@ fn combine_text_sections() { insta::assert_snapshot!(output); } +#[test] +#[cfg(feature = "arm")] +fn thumb_short_data_mapping() { + // When a .2byte directive is used in Thumb code, the assembler emits + // $d/$t mapping symbols for a 2-byte data region. The disassembler must + // not read 4 bytes as a .word when the next mapping symbol limits the + // data region to 2 bytes. + let diff_config = diff::DiffObjConfig::default(); + let obj = obj::read::parse( + include_object!("data/arm/code_1_vblank.o"), + &diff_config, + diff::DiffSide::Base, + ) + .unwrap(); + let symbol_idx = obj.symbols.iter().position(|s| s.name == "VBlankDMA_Level1").unwrap(); + let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap(); + let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config); + // .2byte data followed by Thumb code must not be merged into a 4-byte .word + assert!( + !output.contains(".word"), + "2-byte data regions should not be decoded as 4-byte .word values.\n\ + The disassembler must respect mapping symbol boundaries." + ); +} + #[test] #[cfg(feature = "arm")] fn trim_trailing_hword() { diff --git a/objdiff-core/tests/data/arm/code_1_vblank.o b/objdiff-core/tests/data/arm/code_1_vblank.o new file mode 100644 index 00000000..03d7ffee Binary files /dev/null and b/objdiff-core/tests/data/arm/code_1_vblank.o differ