From a8e48de0506684fc2fbab8e40252afa50718f9f5 Mon Sep 17 00:00:00 2001 From: Ivan Boldyrev Date: Sat, 21 Feb 2026 18:07:22 +0100 Subject: [PATCH 01/13] harm: refactor relocations Separate relocation tag from label, introducing `Rel64Tag` bare enum, and `Rel64` struct. Applying relocation requires real offsets, not labels. --- harm/src/instructions/control/branch_imm.rs | 18 +- harm/src/instructions/control/testbranch.rs | 8 +- .../dpimm/movewide/movewide_reloc.rs | 44 ++--- harm/src/instructions/ldst/ldr.rs | 4 +- harm/src/instructions/ldst/ldrsw.rs | 2 +- harm/src/reloc.rs | 187 +++++++++++++++--- 6 files changed, 198 insertions(+), 65 deletions(-) diff --git a/harm/src/instructions/control/branch_imm.rs b/harm/src/instructions/control/branch_imm.rs index 70ad4ed..8db9b48 100644 --- a/harm/src/instructions/control/branch_imm.rs +++ b/harm/src/instructions/control/branch_imm.rs @@ -127,7 +127,7 @@ impl RelocatableInstruction for Branch { let zero = BranchOffset::default(); let code = B_only_branch_imm(zero.into()); - let reloc = Rel64::Jump26(self.0); + let reloc = Rel64::jump26(self.0); (code, Some(reloc)) } @@ -186,7 +186,7 @@ impl RelocatableInstruction for Branch<(BranchCond, LabelRef)> { let (cond, label_ref) = self.0; let code = B_only_condbranch(zero.into(), cond.into()); - let reloc = Rel64::CondBr19(label_ref); + let reloc = Rel64::cond_br19(label_ref); (code, Some(reloc)) } @@ -254,7 +254,7 @@ impl RelocatableInstruction for BranchLink { let zero = BranchOffset::default(); let code = BL_only_branch_imm(zero.into()); - let reloc = Rel64::Call26(self.0); + let reloc = Rel64::call26(self.0); (code, Some(reloc)) } @@ -394,7 +394,7 @@ impl RelocatableInstruction for CompareBranch { } .to_code(); - let reloc = Rel64::CondBr19(self.offset); + let reloc = Rel64::cond_br19(self.offset); (code, Some(reloc)) } @@ -425,7 +425,7 @@ impl RelocatableInstruction for CompareBranch { } .to_code(); - let reloc = Rel64::CondBr19(self.offset); + let reloc = Rel64::cond_br19(self.offset); (code, Some(reloc)) } @@ -712,7 +712,7 @@ mod tests { let (code, reloc) = inst.to_code_with_reloc(); assert_eq!(code, cbz(X3, CompareBranchOffset::default()).to_code()); - assert_eq!(reloc, Some(Rel64::CondBr19(label))); + assert_eq!(reloc, Some(Rel64::cond_br19(label))); } #[test] @@ -725,7 +725,7 @@ mod tests { let (code, reloc) = inst.to_code_with_reloc(); assert_eq!(code, cbz(W4, CompareBranchOffset::default()).to_code()); - assert_eq!(reloc, Some(Rel64::CondBr19(label))); + assert_eq!(reloc, Some(Rel64::cond_br19(label))); } #[test] @@ -738,7 +738,7 @@ mod tests { let (code, reloc) = inst.to_code_with_reloc(); assert_eq!(code, cbnz(X5, CompareBranchOffset::default()).to_code()); - assert_eq!(reloc, Some(Rel64::CondBr19(label))); + assert_eq!(reloc, Some(Rel64::cond_br19(label))); } #[test] @@ -751,6 +751,6 @@ mod tests { let (code, reloc) = inst.to_code_with_reloc(); assert_eq!(code, cbnz(W6, CompareBranchOffset::default()).to_code()); - assert_eq!(reloc, Some(Rel64::CondBr19(label))); + assert_eq!(reloc, Some(Rel64::cond_br19(label))); } } diff --git a/harm/src/instructions/control/testbranch.rs b/harm/src/instructions/control/testbranch.rs index e428f75..8bee907 100644 --- a/harm/src/instructions/control/testbranch.rs +++ b/harm/src/instructions/control/testbranch.rs @@ -128,7 +128,7 @@ impl RelocatableInstruction for TestBranch Rel64 { - Rel64::MovWAbsG0(self.0) + Rel64::mov_w_abs_g0(self.0) } } @@ -176,7 +176,7 @@ impl MoveWideReloc64 for AbsG0Nc { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG0Nc(self.0) + Rel64::mov_w_abs_g0nc(self.0) } } @@ -188,7 +188,7 @@ impl MoveWideReloc64 for AbsG0S { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG0S(self.0) + Rel64::mov_w_abs_g0s(self.0) } } @@ -200,7 +200,7 @@ impl MoveWideReloc64 for AbsG1 { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG1(self.0) + Rel64::mov_w_abs_g1(self.0) } } @@ -212,7 +212,7 @@ impl MoveWideReloc64 for AbsG1Nc { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG1Nc(self.0) + Rel64::mov_w_abs_g1nc(self.0) } } @@ -224,7 +224,7 @@ impl MoveWideReloc64 for AbsG1S { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG1S(self.0) + Rel64::mov_w_abs_g1s(self.0) } } @@ -236,7 +236,7 @@ impl MoveWideReloc64 for AbsG2 { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG2(self.0) + Rel64::mov_w_abs_g2(self.0) } } @@ -248,7 +248,7 @@ impl MoveWideReloc64 for AbsG2Nc { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG2Nc(self.0) + Rel64::mov_w_abs_g2nc(self.0) } } @@ -260,7 +260,7 @@ impl MoveWideReloc64 for AbsG2S { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG2S(self.0) + Rel64::mov_w_abs_g2s(self.0) } } @@ -272,7 +272,7 @@ impl MoveWideReloc64 for AbsG3 { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG3(self.0) + Rel64::mov_w_abs_g3(self.0) } } @@ -414,7 +414,7 @@ mod tests { }; let inst = movz(X1, abs_g0(label)).to_code_with_reloc(); assert_eq!(inst.0, movz(X1, (0, 0)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG0(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g0(label))); } #[test] @@ -425,7 +425,7 @@ mod tests { }; let inst = movk(X2, abs_g1(label)).to_code_with_reloc(); assert_eq!(inst.0, movk(X2, (0, 16)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG1(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g1(label))); } #[test] @@ -437,7 +437,7 @@ mod tests { // Makes little sense, but gas accepts it. let inst = movn(X3, abs_g1_s(label)).to_code_with_reloc(); assert_eq!(inst.0, movn(X3, (0, 16)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG1S(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g1s(label))); } #[test] @@ -448,7 +448,7 @@ mod tests { }; let inst = movk(X4, abs_g1_nc(label)).to_code_with_reloc(); assert_eq!(inst.0, movk(X4, (0, 16)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG1Nc(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g1nc(label))); } #[test] @@ -459,7 +459,7 @@ mod tests { }; let inst = movz(X5, abs_g2(label)).to_code_with_reloc(); assert_eq!(inst.0, movz(X5, (0, 32)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG2(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g2(label))); } #[test] @@ -470,7 +470,7 @@ mod tests { }; let inst = movz(X6, abs_g2_s(label)).to_code_with_reloc(); assert_eq!(inst.0, movz(X6, (0, 32)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG2S(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g2s(label))); } #[test] @@ -481,7 +481,7 @@ mod tests { }; let inst = movk(X7, abs_g2_nc(label)).to_code_with_reloc(); assert_eq!(inst.0, movk(X7, (0, 32)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG2Nc(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g2nc(label))); } #[test] @@ -492,7 +492,7 @@ mod tests { }; let inst = movk(X8, abs_g3(label)).to_code_with_reloc(); assert_eq!(inst.0, movk(X8, (0, 48)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG3(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g3(label))); } #[test] @@ -503,7 +503,7 @@ mod tests { }; let inst = movz(W1, abs_g0(label)).to_code_with_reloc(); assert_eq!(inst.0, movz(W1, (0, 0)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG0(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g0(label))); } #[test] @@ -514,7 +514,7 @@ mod tests { }; let inst = movk(W2, abs_g1(label)).to_code_with_reloc(); assert_eq!(inst.0, movk(W2, (0, 16)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG1(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g1(label))); } #[test] @@ -526,7 +526,7 @@ mod tests { // Makes little sense, but gas accepts it. let inst = movn(W3, abs_g1_s(label)).to_code_with_reloc(); assert_eq!(inst.0, movn(W3, (0, 16)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG1S(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g1s(label))); } #[test] @@ -537,6 +537,6 @@ mod tests { }; let inst = movk(W4, abs_g1_nc(label)).to_code_with_reloc(); assert_eq!(inst.0, movk(W4, (0, 16)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG1Nc(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g1nc(label))); } } diff --git a/harm/src/instructions/ldst/ldr.rs b/harm/src/instructions/ldst/ldr.rs index 44b0caf..a93d39a 100644 --- a/harm/src/instructions/ldst/ldr.rs +++ b/harm/src/instructions/ldst/ldr.rs @@ -171,7 +171,7 @@ define_pc_offset_rules!( LDR, RegOrZero64, 64, - crate::reloc::Rel64::LdPrelLo19 + crate::reloc::Rel64::ld_prel_lo19 ); define_pc_offset_rules!( Ldr, @@ -179,7 +179,7 @@ define_pc_offset_rules!( LDR, RegOrZero32, 32, - crate::reloc::Rel64::LdPrelLo19 + crate::reloc::Rel64::ld_prel_lo19 ); // diff --git a/harm/src/instructions/ldst/ldrsw.rs b/harm/src/instructions/ldst/ldrsw.rs index d4b7404..dbef6d5 100644 --- a/harm/src/instructions/ldst/ldrsw.rs +++ b/harm/src/instructions/ldst/ldrsw.rs @@ -163,7 +163,7 @@ define_pc_offset_rules!( LDRSW, RegOrZero64, 64, - crate::reloc::Rel64::LdPrelLo19 + crate::reloc::Rel64::ld_prel_lo19 ); // diff --git a/harm/src/reloc.rs b/harm/src/reloc.rs index e6e3957..fa9691a 100644 --- a/harm/src/reloc.rs +++ b/harm/src/reloc.rs @@ -23,39 +23,172 @@ pub struct LabelRef { } #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Rel64 { +pub struct Rel64 { + pub rel: Rel64Tag, + pub label: LabelRef, +} + +impl Rel64 { + #[inline] + pub const fn new(rel: Rel64Tag, label: LabelRef) -> Self { + Self { rel, label } + } + + #[inline] + pub const fn ld_prel_lo19(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::LdPrelLo19, + label, + } + } + + #[inline] + pub const fn cond_br19(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::CondBr19, + label, + } + } + + #[inline] + pub const fn tst_br14(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::TstBr14, + label, + } + } + + #[inline] + pub const fn jump26(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::Jump26, + label, + } + } + + #[inline] + pub const fn call26(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::Call26, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g0(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG0, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g0nc(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG0Nc, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g0s(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG0S, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g1(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG1, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g1nc(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG1Nc, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g1s(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG1S, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g2(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG2, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g2nc(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG2Nc, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g2s(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG2S, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g3(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG3, + label, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Rel64Tag { None, // Static data relocations // ... - Abs64(LabelRef), - Abs32(LabelRef), - Abs16(LabelRef), - PRel64(LabelRef), - PRel32(LabelRef), - PRel16(LabelRef), - Plt32(LabelRef), + Abs64, + Abs32, + Abs16, + PRel64, + PRel32, + PRel16, + Plt32, // Static AArch64 relocations - LdPrelLo19(LabelRef), - AdrPrelLo21(LabelRef), - AdrPrelPgHi21(LabelRef), - AdrPrelPgHi21Nc(LabelRef), - AddAbsLo12Nc(LabelRef), + LdPrelLo19, + AdrPrelLo21, + AdrPrelPgHi21, + AdrPrelPgHi21Nc, + AddAbsLo12Nc, // Static control flow relocations - TstBr14(LabelRef), - CondBr19(LabelRef), - Jump26(LabelRef), - Call26(LabelRef), // same as Jump26 actually? + TstBr14, + CondBr19, + Jump26, + Call26, // same as Jump26 actually? // TODO `MOVW` and some `add`/`ldst`-related relocations - MovWAbsG0(LabelRef), - MovWAbsG0Nc(LabelRef), - MovWAbsG0S(LabelRef), - MovWAbsG1(LabelRef), - MovWAbsG1Nc(LabelRef), - MovWAbsG1S(LabelRef), - MovWAbsG2(LabelRef), - MovWAbsG2Nc(LabelRef), - MovWAbsG2S(LabelRef), - MovWAbsG3(LabelRef), + MovWAbsG0, + MovWAbsG0Nc, + MovWAbsG0S, + MovWAbsG1, + MovWAbsG1Nc, + MovWAbsG1S, + MovWAbsG2, + MovWAbsG2Nc, + MovWAbsG2S, + MovWAbsG3, } From f87cf438b14622f44fcb9d6789a488b949c229c3 Mon Sep 17 00:00:00 2001 From: Ivan Boldyrev Date: Sat, 21 Feb 2026 20:09:32 +0100 Subject: [PATCH 02/13] harm: Applying relocations --- harm/src/reloc.rs | 223 +++++++++++++++++++++++++++++++++++++- harm/src/reloc/addr.rs | 117 ++++++++++++++++++++ harm/src/reloc/control.rs | 92 ++++++++++++++++ harm/src/reloc/data.rs | 62 +++++++++++ harm/src/reloc/movs.rs | 97 +++++++++++++++++ 5 files changed, 590 insertions(+), 1 deletion(-) create mode 100644 harm/src/reloc/addr.rs create mode 100644 harm/src/reloc/control.rs create mode 100644 harm/src/reloc/data.rs create mode 100644 harm/src/reloc/movs.rs diff --git a/harm/src/reloc.rs b/harm/src/reloc.rs index fa9691a..97e025a 100644 --- a/harm/src/reloc.rs +++ b/harm/src/reloc.rs @@ -3,6 +3,19 @@ * This document is licensed under the BSD 3-clause license. */ +mod addr; +mod control; +mod data; +mod movs; + +use ::core::fmt; + +pub use self::addr::*; +pub use self::control::*; +pub use self::data::*; +pub use self::movs::*; +use crate::bits::BitError; + // TODO These type definitions should probably be moved to `harm-types` crate. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -155,6 +168,43 @@ impl Rel64 { } } +#[derive(Debug)] +pub enum Rel64Error { + NotEnoughMemory { offset: usize }, + InvalidOffset { offset: usize }, + InvalidValue(::core::num::TryFromIntError), + InvalidBits(BitError), +} + +impl fmt::Display for Rel64Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Rel64Error::NotEnoughMemory { offset } => { + write!(f, "Not enough memory at 0x{:x}", offset) + } + Rel64Error::InvalidOffset { offset } => write!(f, "Invalid offset 0x{:x}", offset), + Rel64Error::InvalidValue(try_from_int_error) => { + write!(f, "Invalid value: {try_from_int_error}") + } + Rel64Error::InvalidBits(bit_error) => write!(f, "Invalid bit value: {bit_error}"), + } + } +} + +impl ::core::error::Error for Rel64Error {} + +impl From<::core::num::TryFromIntError> for Rel64Error { + fn from(value: core::num::TryFromIntError) -> Self { + Rel64Error::InvalidValue(value) + } +} + +impl From for Rel64Error { + fn from(value: BitError) -> Self { + Rel64Error::InvalidBits(value) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum Rel64Tag { None, @@ -166,7 +216,6 @@ pub enum Rel64Tag { PRel64, PRel32, PRel16, - Plt32, // Static AArch64 relocations LdPrelLo19, @@ -192,3 +241,175 @@ pub enum Rel64Tag { MovWAbsG2S, MovWAbsG3, } + +impl Rel64Tag { + /// Applies the relocation to the given `memory` at the given `offset`, presuming the real target address is + /// `target` and the base (starting) address of the `memory` is `base` (the can be different from real `memory` + /// location for flexibility). + pub fn apply( + self, + base: Addr, + target: Addr, + memory: &mut [u8], + offset: usize, + ) -> Result<(), Rel64Error> { + match self { + Rel64Tag::None => { + // check parameters validity + get_bytes_mut::<0>(memory, offset)?; + Ok(()) + } + Rel64Tag::Abs64 => abs64_reloc(target, memory, offset), + Rel64Tag::Abs32 => abs32_reloc(target.try_into()?, memory, offset), + Rel64Tag::Abs16 => abs16_reloc(target.try_into()?, memory, offset), + Rel64Tag::PRel64 => prel64_reloc(base, target, memory, offset), + Rel64Tag::PRel32 => prel32_reloc(base, target, memory, offset), + Rel64Tag::PRel16 => prel16_reloc(base, target, memory, offset), + + Rel64Tag::LdPrelLo19 => ld_prel_lo19_reloc(base, target, memory, offset), + Rel64Tag::AdrPrelLo21 => adr_prel_lo21_reloc(base, target, memory, offset), + Rel64Tag::AdrPrelPgHi21 => adr_prel_pg_hi21_reloc(base, target, memory, offset), + Rel64Tag::AdrPrelPgHi21Nc => adr_prel_pg_hi21_nc_reloc(base, target, memory, offset), + Rel64Tag::AddAbsLo12Nc => add_abs_lo_12_nc_reloc(base, target, memory, offset), + + Rel64Tag::TstBr14 => tst_br14_reloc(base, target, memory, offset), + Rel64Tag::CondBr19 => cond_br19_reloc(base, target, memory, offset), + Rel64Tag::Jump26 | Rel64Tag::Call26 => jump26_reloc(base, target, memory, offset), + + Rel64Tag::MovWAbsG0 => mov_w_abs_g0_reloc(base, target, memory, offset), + Rel64Tag::MovWAbsG0Nc => mov_w_abs_g0nc_reloc(base, target, memory, offset), + Rel64Tag::MovWAbsG0S => mov_w_abs_g0s_reloc(base, target, memory, offset), + Rel64Tag::MovWAbsG1 => mov_w_abs_g1_reloc(base, target, memory, offset), + Rel64Tag::MovWAbsG1Nc => mov_w_abs_g1nc_reloc(base, target, memory, offset), + Rel64Tag::MovWAbsG1S => mov_w_abs_g1s_reloc(base, target, memory, offset), + Rel64Tag::MovWAbsG2 => mov_w_abs_g2_reloc(base, target, memory, offset), + Rel64Tag::MovWAbsG2Nc => mov_w_abs_g2nc_reloc(base, target, memory, offset), + Rel64Tag::MovWAbsG2S => mov_w_abs_g2s_reloc(base, target, memory, offset), + Rel64Tag::MovWAbsG3 => mov_w_abs_g3_reloc(base, target, memory, offset), + } + } +} + +fn get_bytes_mut( + mem: &mut [u8], + offset: usize, +) -> Result<&mut [u8; N], Rel64Error> { + let mem_chunk = mem + .get_mut(offset..(offset + N)) + .ok_or(Rel64Error::InvalidOffset { offset })?; + let bytes: &mut [u8; N] = mem_chunk + .as_mut_array() + .ok_or(Rel64Error::NotEnoughMemory { offset })?; + Ok(bytes) +} + +// A function for P - S. +fn calc_offset(base: u64, target: u64, offset: usize) -> Result { + let offset64 = offset + .try_into() + .map_err(|_e| Rel64Error::InvalidOffset { offset })?; + + let instruction_addr = base + .checked_add(offset64) + .ok_or_else(|| Rel64Error::InvalidOffset { offset })?; + + Ok(target.wrapping_sub(instruction_addr).cast_signed()) +} + +#[cfg(test)] +mod tests { + use crate::instructions::RawInstruction; + + use super::*; + + #[test] + fn test_none() { + const MEM: [u8; 8] = [0xfb, 0xa6, 0xd3, 0x67, 0x58, 0x50, 0x1d, 0x46]; + + let mut mem = MEM; + Rel64Tag::None.apply(0, 0, &mut mem, 0).unwrap(); + assert_eq!(mem, MEM); + } + + #[test] + fn test_abs64() { + let mut mem = [0; 8]; + Rel64Tag::Abs64 + .apply(0, 0x123456789abcdef0, &mut mem, 0) + .unwrap(); + assert_eq!(mem, 0x123456789abcdef0u64.to_le_bytes()); + } + + #[test] + fn test_abs64_invalid_offset() { + let mut mem = [0; 16]; + let err = Rel64Tag::Abs64.apply(0, 0, &mut mem, 17).unwrap_err(); + assert!( + matches!(err, Rel64Error::InvalidOffset { offset: 17 }), + "{err:?}" + ); + } + + #[test] + fn test_abs32() { + let mut mem = [0; 4]; + Rel64Tag::Abs32.apply(0, 0x12345678, &mut mem, 0).unwrap(); + assert_eq!(mem, 0x12345678u32.to_le_bytes()); + } + + #[test] + fn test_abs32_invalid_offset() { + let mut mem = [0; 16]; + let err = Rel64Tag::Abs32.apply(0, 0, &mut mem, 17).unwrap_err(); + assert!( + matches!(err, Rel64Error::InvalidOffset { offset: 17 }), + "{err:?}" + ); + } + + #[test] + fn test_prel64() { + let mut mem = [0; 8]; + Rel64Tag::PRel64 + .apply(0x1000, 0x123456789abcdef0, &mut mem, 0) + .unwrap(); + // TODO is it correct? + assert_eq!( + mem, + 0x123456789abcdef0u64.wrapping_sub(0x1000).to_le_bytes() + ); + } + + #[test] + fn test_prel64_invalid_offset() { + let mut mem = [0; 16]; + let err = Rel64Tag::PRel64 + .apply(0x1000, 0x123456789abcdef0, &mut mem, 17) + .unwrap_err(); + assert!( + matches!(err, Rel64Error::InvalidOffset { offset: 17 }), + "{err:?}" + ); + } + + #[test] + fn test_jump26() { + let mut mem = crate::instructions::control::b(40).unwrap().to_code().0; + let expected = crate::instructions::control::b(0x1020).unwrap().to_code().0; + Rel64Tag::Jump26.apply(0x1000, 0x2020, &mut mem, 0).unwrap(); + assert_eq!(mem, expected); + } + + #[test] + fn test_jump26_unaligned() { + let mut mem = crate::instructions::control::b(40).unwrap().to_code().0; + let err = Rel64Tag::Jump26.apply(0x1000, 0x2022, &mut mem, 0); + assert!( + matches!( + err, + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 2 })) + ), + "{err:?}" + ); + } +} diff --git a/harm/src/reloc/addr.rs b/harm/src/reloc/addr.rs new file mode 100644 index 0000000..3ec9f59 --- /dev/null +++ b/harm/src/reloc/addr.rs @@ -0,0 +1,117 @@ +/* Copyright (C) 2026 Ivan Boldyrev + * + * This document is licensed under the BSD 3-clause license. + */ + +use aarchmrs_types::InstructionCode; + +use super::{Rel64Error, cond_br19_reloc}; +use crate::reloc::get_bytes_mut; +use crate::{instructions::dpimm::AdrpOffset, reloc::calc_offset}; + +const ADR_ADRP_IMMHI_OFFSET: u32 = 5u32; +const ADR_ADRP_IMMHI_WIDTH: u32 = 19u32; +const ADR_ADRP_IMMLO_OFFSET: u32 = 29u32; +const ADR_ADRP_IMMLO_WIDTH: u32 = 2u32; + +const ADD_IMM12_OFFSET: u32 = 10u32; +const ADD_IMM12_WIDTH: u32 = 12u32; + +#[inline] +pub fn ld_prel_lo19_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + cond_br19_reloc(base, target, mem, offset) +} + +pub fn adr_prel_lo21_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + const VALUE_MASK: u32 = (1 << (ADR_ADRP_IMMHI_WIDTH + ADR_ADRP_IMMLO_WIDTH)) - 1; + + let bytes = get_bytes_mut(mem, offset)?; + // It is not checked. + let delta = calc_offset(base, target, offset)? as u32; + patch_adr_adrp(bytes, delta & VALUE_MASK); + + Ok(()) +} + +#[inline] +pub fn adr_prel_pg_hi21_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + const VALUE_MASK: u32 = (1 << (ADR_ADRP_IMMHI_WIDTH + ADR_ADRP_IMMLO_WIDTH)) - 1; + + let bytes = get_bytes_mut(mem, offset)?; + + let delta = calc_offset(base, target, offset)? >> 21; + let delta = AdrpOffset::new_i64(delta).map_err(Rel64Error::InvalidBits)?; + patch_adr_adrp(bytes, delta.bits() & VALUE_MASK); + + Ok(()) +} + +#[inline] +pub fn adr_prel_pg_hi21_nc_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + const VALUE_MASK: u32 = (1 << (ADR_ADRP_IMMHI_WIDTH + ADR_ADRP_IMMLO_WIDTH)) - 1; + + let bytes = get_bytes_mut(mem, offset)?; + // It is not checked. + let delta = calc_offset(base, target, offset)? >> 21; + + patch_adr_adrp(bytes, (delta as u32) & VALUE_MASK); + Ok(()) +} + +#[inline] +pub fn add_abs_lo_12_nc_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + const MASK: u32 = (1 << ADD_IMM12_WIDTH) - 1; + + let bytes = get_bytes_mut(mem, offset)?; + + // delta is semantically a signed value, but we are using here lower bits only, + // so we use an unsigned value. + let delta = calc_offset(base, target, offset)? as u32; + + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !(MASK << ADD_IMM12_OFFSET); + inst_code |= (delta & MASK) << ADD_IMM12_OFFSET; + + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) +} + +fn patch_adr_adrp(mem: &mut [u8; 4], checked_value: u32) { + const INST_MASK: u32 = ((1 << ADR_ADRP_IMMHI_WIDTH) - 1) << ADR_ADRP_IMMHI_OFFSET + | ((1 << ADR_ADRP_IMMLO_WIDTH) - 1) << ADR_ADRP_IMMLO_OFFSET; + + let lo = checked_value & ((1 << ADR_ADRP_IMMLO_WIDTH) - 1); + let hi = checked_value >> ADR_ADRP_IMMLO_WIDTH; + + let mut inst_code = InstructionCode(*mem).unpack(); + inst_code &= !INST_MASK; + inst_code |= (hi << ADR_ADRP_IMMHI_OFFSET) | (lo << ADR_ADRP_IMMLO_OFFSET); + + *mem = InstructionCode::from_u32(inst_code).0; +} diff --git a/harm/src/reloc/control.rs b/harm/src/reloc/control.rs new file mode 100644 index 0000000..87a62df --- /dev/null +++ b/harm/src/reloc/control.rs @@ -0,0 +1,92 @@ +/* Copyright (C) 2026 Ivan Boldyrev + * + * This document is licensed under the BSD 3-clause license. + */ + +use aarchmrs_types::InstructionCode; + +use super::Rel64Error; +use crate::instructions::control::{BranchCondOffset, BranchOffset, TestBranchOffset}; +use crate::reloc::get_bytes_mut; + +const JUMP_IMM26_OFFSET: u32 = 0u32; +const JUMP_IMM26_WIDTH: u32 = 26u32; + +const TST_BR_IMM14_OFFSET: u32 = 5u32; +const TST_BR_IMM14_WIDTH: u32 = 14u32; + +const COND_BR_IMM19_OFFSET: u32 = 5u32; +const COND_BR_IMM19_WIDTH: u32 = 19u32; + +pub fn jump26_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + let jump_offset64 = target.wrapping_sub(base).cast_signed(); + let jump_offset = BranchOffset::new_i64(jump_offset64)?; + let bytes = get_bytes_mut(mem, offset)?; + let code = InstructionCode(*bytes).unpack(); + + let jump_offset_bits = jump_offset.bits(); + + let code_cleaned = code & !(((1u32 << JUMP_IMM26_WIDTH) - 1) << JUMP_IMM26_OFFSET); + let code_patched = code_cleaned | ((jump_offset_bits as u32) << JUMP_IMM26_OFFSET); + + *bytes = InstructionCode::from_u32(code_patched).0; + + Ok(()) +} + +#[inline] +pub fn call26_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + jump26_reloc(base, target, mem, offset) +} + +pub fn tst_br14_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + let tst_offset64 = target.wrapping_sub(base).cast_signed(); + let tst_offset = TestBranchOffset::new_i64(tst_offset64)?; + let bytes = get_bytes_mut(mem, offset)?; + let code = InstructionCode(*bytes).unpack(); + + let jump_offset_bits = tst_offset.bits(); + + let code_cleaned = code & !(((1u32 << TST_BR_IMM14_WIDTH) - 1) << TST_BR_IMM14_OFFSET); + let code_patched = code_cleaned | ((jump_offset_bits as u32) << TST_BR_IMM14_OFFSET); + + *bytes = InstructionCode::from_u32(code_patched).0; + + Ok(()) +} + +pub fn cond_br19_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + let cond_offset64 = target.wrapping_sub(base).cast_signed(); + let cond_offset = BranchCondOffset::new_i64(cond_offset64)?; + let bytes = get_bytes_mut(mem, offset)?; + let code = InstructionCode(*bytes).unpack(); + + let jump_offset_bits = cond_offset.bits(); + + let code_cleaned = code & !(((1u32 << COND_BR_IMM19_WIDTH) - 1) << COND_BR_IMM19_OFFSET); + let code_patched = code_cleaned | ((jump_offset_bits as u32) << COND_BR_IMM19_OFFSET); + + *bytes = InstructionCode::from_u32(code_patched).0; + + Ok(()) +} diff --git a/harm/src/reloc/data.rs b/harm/src/reloc/data.rs new file mode 100644 index 0000000..d9a7a40 --- /dev/null +++ b/harm/src/reloc/data.rs @@ -0,0 +1,62 @@ +/* Copyright (C) 2026 Ivan Boldyrev + * + * This document is licensed under the BSD 3-clause license. + */ + +use super::{Rel64Error, calc_offset, get_bytes_mut}; + +pub fn abs64_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let bytes = get_bytes_mut(mem, offset)?; + *bytes = target.to_le_bytes(); + Ok(()) +} + +pub fn abs32_reloc(target: u32, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let bytes = get_bytes_mut(mem, offset)?; + *bytes = target.to_le_bytes(); + Ok(()) +} + +pub fn abs16_reloc(target: u16, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let bytes = get_bytes_mut(mem, offset)?; + *bytes = target.to_le_bytes(); + Ok(()) +} + +pub fn prel64_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + let bytes = get_bytes_mut(mem, offset)?; + + *bytes = calc_offset(base, target, offset)?.to_le_bytes(); + Ok(()) +} + +pub fn prel32_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + let bytes = get_bytes_mut(mem, offset)?; + + let value: i32 = calc_offset(base, target, offset)?.try_into()?; + *bytes = value.to_le_bytes(); + Ok(()) +} + +pub fn prel16_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + let bytes = get_bytes_mut(mem, offset)?; + + let value: i16 = calc_offset(base, target, offset)?.try_into()?; + *bytes = value.to_le_bytes(); + Ok(()) +} diff --git a/harm/src/reloc/movs.rs b/harm/src/reloc/movs.rs new file mode 100644 index 0000000..e9361d4 --- /dev/null +++ b/harm/src/reloc/movs.rs @@ -0,0 +1,97 @@ +/* Copyright (C) 2026 Ivan Boldyrev + * + * This document is licensed under the BSD 3-clause license. + */ +#![allow(unused)] + +use super::Rel64Error; + +pub fn mov_w_abs_g0_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + todo!() +} + +pub fn mov_w_abs_g0nc_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + todo!() +} + +pub fn mov_w_abs_g0s_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + todo!() +} + +pub fn mov_w_abs_g1_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + todo!() +} + +pub fn mov_w_abs_g1nc_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + todo!() +} + +pub fn mov_w_abs_g1s_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + todo!() +} + +pub fn mov_w_abs_g2_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + todo!() +} + +pub fn mov_w_abs_g2nc_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + todo!() +} + +pub fn mov_w_abs_g2s_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + todo!() +} + +pub fn mov_w_abs_g3_reloc( + base: u64, + target: u64, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + todo!() +} From 86e0805f5b413b2a97397beb086a9e3060ed0577 Mon Sep 17 00:00:00 2001 From: Ivan Boldyrev Date: Fri, 6 Mar 2026 15:17:26 +0100 Subject: [PATCH 03/13] Claude's tests --- harm/src/reloc.rs | 33 ++- harm/src/reloc/addr.rs | 18 +- harm/src/reloc/claude_tests.rs | 506 +++++++++++++++++++++++++++++++++ harm/src/reloc/data.rs | 6 +- harm/src/reloc/movs.rs | 30 +- 5 files changed, 547 insertions(+), 46 deletions(-) create mode 100644 harm/src/reloc/claude_tests.rs diff --git a/harm/src/reloc.rs b/harm/src/reloc.rs index 97e025a..5e087a2 100644 --- a/harm/src/reloc.rs +++ b/harm/src/reloc.rs @@ -168,7 +168,7 @@ impl Rel64 { } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum Rel64Error { NotEnoughMemory { offset: usize }, InvalidOffset { offset: usize }, @@ -260,32 +260,32 @@ impl Rel64Tag { Ok(()) } Rel64Tag::Abs64 => abs64_reloc(target, memory, offset), - Rel64Tag::Abs32 => abs32_reloc(target.try_into()?, memory, offset), - Rel64Tag::Abs16 => abs16_reloc(target.try_into()?, memory, offset), + Rel64Tag::Abs32 => abs32_reloc(target, memory, offset), + Rel64Tag::Abs16 => abs16_reloc(target, memory, offset), Rel64Tag::PRel64 => prel64_reloc(base, target, memory, offset), Rel64Tag::PRel32 => prel32_reloc(base, target, memory, offset), Rel64Tag::PRel16 => prel16_reloc(base, target, memory, offset), Rel64Tag::LdPrelLo19 => ld_prel_lo19_reloc(base, target, memory, offset), Rel64Tag::AdrPrelLo21 => adr_prel_lo21_reloc(base, target, memory, offset), - Rel64Tag::AdrPrelPgHi21 => adr_prel_pg_hi21_reloc(base, target, memory, offset), - Rel64Tag::AdrPrelPgHi21Nc => adr_prel_pg_hi21_nc_reloc(base, target, memory, offset), + Rel64Tag::AdrPrelPgHi21 => adrp_prel_pg_hi21_reloc(base, target, memory, offset), + Rel64Tag::AdrPrelPgHi21Nc => adrp_prel_pg_hi21_nc_reloc(base, target, memory, offset), Rel64Tag::AddAbsLo12Nc => add_abs_lo_12_nc_reloc(base, target, memory, offset), Rel64Tag::TstBr14 => tst_br14_reloc(base, target, memory, offset), Rel64Tag::CondBr19 => cond_br19_reloc(base, target, memory, offset), Rel64Tag::Jump26 | Rel64Tag::Call26 => jump26_reloc(base, target, memory, offset), - Rel64Tag::MovWAbsG0 => mov_w_abs_g0_reloc(base, target, memory, offset), - Rel64Tag::MovWAbsG0Nc => mov_w_abs_g0nc_reloc(base, target, memory, offset), - Rel64Tag::MovWAbsG0S => mov_w_abs_g0s_reloc(base, target, memory, offset), - Rel64Tag::MovWAbsG1 => mov_w_abs_g1_reloc(base, target, memory, offset), - Rel64Tag::MovWAbsG1Nc => mov_w_abs_g1nc_reloc(base, target, memory, offset), - Rel64Tag::MovWAbsG1S => mov_w_abs_g1s_reloc(base, target, memory, offset), - Rel64Tag::MovWAbsG2 => mov_w_abs_g2_reloc(base, target, memory, offset), - Rel64Tag::MovWAbsG2Nc => mov_w_abs_g2nc_reloc(base, target, memory, offset), - Rel64Tag::MovWAbsG2S => mov_w_abs_g2s_reloc(base, target, memory, offset), - Rel64Tag::MovWAbsG3 => mov_w_abs_g3_reloc(base, target, memory, offset), + Rel64Tag::MovWAbsG0 => mov_w_abs_g0_reloc(target.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG0Nc => mov_w_abs_g0nc_reloc(target.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG0S => mov_w_abs_g0s_reloc(target.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG1 => mov_w_abs_g1_reloc(target.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG1Nc => mov_w_abs_g1nc_reloc(target.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG1S => mov_w_abs_g1s_reloc(target.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG2 => mov_w_abs_g2_reloc(target.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG2Nc => mov_w_abs_g2nc_reloc(target.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG2S => mov_w_abs_g2s_reloc(target.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG3 => mov_w_abs_g3_reloc(target.cast_signed(), memory, offset), } } } @@ -316,6 +316,9 @@ fn calc_offset(base: u64, target: u64, offset: usize) -> Result Result<(), Rel64Error> { - const VALUE_MASK: u32 = (1 << (ADR_ADRP_IMMHI_WIDTH + ADR_ADRP_IMMLO_WIDTH)) - 1; - let bytes = get_bytes_mut(mem, offset)?; - // It is not checked. - let delta = calc_offset(base, target, offset)? as u32; - patch_adr_adrp(bytes, delta & VALUE_MASK); + + let delta = calc_offset(base, target, offset)?; + let delta = AdrOffset::new_i64(delta)?; + patch_adr_adrp(bytes, delta.bits()); Ok(()) } #[inline] -pub fn adr_prel_pg_hi21_reloc( +pub fn adrp_prel_pg_hi21_reloc( base: u64, target: u64, mem: &mut [u8], @@ -54,7 +54,7 @@ pub fn adr_prel_pg_hi21_reloc( let bytes = get_bytes_mut(mem, offset)?; - let delta = calc_offset(base, target, offset)? >> 21; + let delta = calc_offset(base, target, offset)? >> 12 << 12; let delta = AdrpOffset::new_i64(delta).map_err(Rel64Error::InvalidBits)?; patch_adr_adrp(bytes, delta.bits() & VALUE_MASK); @@ -62,7 +62,7 @@ pub fn adr_prel_pg_hi21_reloc( } #[inline] -pub fn adr_prel_pg_hi21_nc_reloc( +pub fn adrp_prel_pg_hi21_nc_reloc( base: u64, target: u64, mem: &mut [u8], diff --git a/harm/src/reloc/claude_tests.rs b/harm/src/reloc/claude_tests.rs new file mode 100644 index 0000000..1132fbb --- /dev/null +++ b/harm/src/reloc/claude_tests.rs @@ -0,0 +1,506 @@ +/* Tests based on code generated by Claude Sonnet 4.6. */ +use super::*; + +// opc values for MOVW instructions. +const MOVW_OPC_MOVZ: u32 = 0b10; +const MOVW_OPC_MOVN: u32 = 0b00; + +fn movw_opc(insn: u32) -> u32 { + (insn >> 29) & 0x3 +} +// ── Byte-level accessors ────────────────────────────────────────────────── + +fn u16_at(mem: &[u8], off: usize) -> u16 { + u16::from_le_bytes(mem[off..off + 2].try_into().unwrap()) +} + +fn u32_at(mem: &[u8], off: usize) -> u32 { + u32::from_le_bytes(mem[off..off + 4].try_into().unwrap()) +} + +fn u64_at(mem: &[u8], off: usize) -> u64 { + u64::from_le_bytes(mem[off..off + 8].try_into().unwrap()) +} + +// ── Instruction-field decoders ──────────────────────────────────────────── + +/// Sign-extend a `width`-bit unsigned value to i32. +fn sign_ext(raw: u32, width: u32) -> i32 { + let shift = 32 - width; + ((raw << shift) as i32) >> shift +} + +fn insn_imm14(insn: u32) -> i32 { + sign_ext((insn >> 5) & 0x3FFF, 14) +} +fn insn_imm19(insn: u32) -> i32 { + sign_ext((insn >> 5) & 0x0007_FFFF, 19) +} +fn insn_imm26(insn: u32) -> i32 { + sign_ext(insn & 0x03FF_FFFF, 26) +} + +fn insn_imm21_adr(insn: u32) -> i32 { + let immlo = (insn >> 29) & 0x3; + let immhi = (insn >> 5) & 0x0007_FFFF; + sign_ext((immhi << 2) | immlo, 21) +} + +fn insn_imm12_add(insn: u32) -> u32 { + (insn >> 10) & 0xFFF +} +fn insn_mov_w_imm(insn: u32) -> u16 { + ((insn >> 5) & 0xFFFF) as u16 +} + +// ── get_bytes_mut error-variant tests ───────────────────────────────────── + +#[test] +fn get_bytes_mut_ok() { + let mut mem = [1u8, 2, 3, 4, 5, 6, 7, 8]; + let arr: &mut [u8; 4] = get_bytes_mut(&mut mem, 2).unwrap(); + assert_eq!(*arr, [3, 4, 5, 6]); +} + +#[test] +fn get_bytes_mut_invalid_offset() { + let mut mem = [0u8; 4]; + assert_eq!( + get_bytes_mut::<4>(&mut mem, 5), + Err(Rel64Error::InvalidOffset { offset: 5 }) + ); +} + +#[test] +fn get_bytes_mut_not_enough_memory() { + let mut mem = [0u8; 4]; + // offset 2 is valid but only 2 bytes remain — not enough for 4. + assert_eq!( + get_bytes_mut::<4>(&mut mem, 2), + Err(Rel64Error::NotEnoughMemory { offset: 2 }) + ); +} + +// ── Static data relocations ─────────────────────────────────────────────── + +#[test] +fn abs64_basic() { + let mut mem = [0u8; 16]; + abs64_reloc(0xDEAD_BEEF_CAFE_1234, &mut mem, 4).unwrap(); + assert_eq!(u64_at(&mem, 4), 0xDEAD_BEEF_CAFE_1234); +} + +#[test] +fn abs32_ok() { + let mut mem = [0u8; 8]; + abs32_reloc(0x0000_ABCD, &mut mem, 2).unwrap(); + assert_eq!(u32_at(&mem, 2), 0x0000_ABCD); +} + +#[test] +fn abs32_overflow() { + let mut mem = [0u8; 8]; + assert!(matches!( + abs32_reloc(0x1_0000_0000, &mut mem, 0), + Err(Rel64Error::InvalidValue { .. }) + )); +} + +#[test] +fn abs16_ok() { + let mut mem = [0u8; 4]; + abs16_reloc(0xBEEF, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0), 0xBEEF); +} + +#[test] +fn abs16_overflow() { + let mut mem = [0u8; 4]; + assert!(matches!( + abs16_reloc(0x1_0000, &mut mem, 0), + Err(Rel64Error::InvalidValue { .. }) + )); +} + +#[test] +fn prel64_forward_and_backward() { + let mut mem = [0u8; 16]; + prel64_reloc(0x1000, 0x2000, &mut mem, 0).unwrap(); + assert_eq!(u64_at(&mem, 0), 0x1000u64); + prel64_reloc(0x2000, 0x1000, &mut mem, 0).unwrap(); + assert_eq!(u64_at(&mem, 0) as i64, -0x1000i64); +} + +#[test] +fn prel32_in_range() { + let mut mem = [0u8; 8]; + prel32_reloc(0x4000, 0x5000, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0) as i32, 0x1000); +} + +#[test] +fn prel32_overflow() { + let mut mem = [0u8; 8]; + assert!(matches!( + prel32_reloc(0x0, 0xC000_0000, &mut mem, 0), + Err(Rel64Error::InvalidValue { .. }) + )); +} + +#[test] +fn prel16_in_range() { + let mut mem = [0u8; 4]; + prel16_reloc(0x3FFF, 0x4000, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0) as i16, 1); +} + +#[test] +fn prel16_overflow() { + let mut mem = [0u8; 4]; + assert!(matches!( + prel16_reloc(0x0, 0x1_0000, &mut mem, 0), + Err(Rel64Error::InvalidValue { .. }) + )); +} + +// ── Buffer-size error variants ──────────────────────────────────────────── + +#[test] +fn invalid_offset_error() { + let mut mem = [0u8; 4]; + assert_eq!( + abs64_reloc(0, &mut mem, 8), + Err(Rel64Error::InvalidOffset { offset: 8 }) + ); +} + +#[test] +fn not_enough_memory_error() { + let mut mem = [0u8; 4]; + assert_eq!( + abs64_reloc(0, &mut mem, 0), + Err(Rel64Error::NotEnoughMemory { offset: 0 }) + ); +} + +// ── LDR literal (LdPrelLo19) ────────────────────────────────────────────── + +#[test] +fn ld_prel_lo19_forward() { + let mut mem = 0x5800_0000u32.to_le_bytes().to_vec(); + ld_prel_lo19_reloc(0x0000, 0x0008, &mut mem, 0).unwrap(); + assert_eq!(insn_imm19(u32_at(&mem, 0)), 2); +} + +#[test] +fn ld_prel_lo19_negative() { + let mut mem = 0x5800_0000u32.to_le_bytes().to_vec(); + ld_prel_lo19_reloc(0x0100, 0x0000, &mut mem, 0).unwrap(); + assert_eq!(insn_imm19(u32_at(&mem, 0)), -64); +} + +#[test] +fn ld_prel_lo19_unaligned() { + let mut mem = [0u8; 4]; + assert!(matches!( + ld_prel_lo19_reloc(0x0000, 0x0001, &mut mem, 0), + Err(Rel64Error::InvalidBits(..)) + )); +} + +#[test] +fn ld_prel_lo19_overflow() { + let mut mem = [0u8; 4]; + assert!(matches!( + ld_prel_lo19_reloc(0x0, 0x20_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(..)) + )); +} + +// ── ADR (AdrPrelLo21) ───────────────────────────────────────────────────── + +#[test] +fn adr_prel_lo21_basic() { + let mut mem = 0x0000_0001u32.to_le_bytes().to_vec(); + adr_prel_lo21_reloc(0x1000, 0x1004, &mut mem, 0).unwrap(); + assert_eq!(insn_imm21_adr(u32_at(&mem, 0)), 4); +} + +#[test] +fn adr_prel_lo21_negative() { + let mut mem = 0x0000_0001u32.to_le_bytes().to_vec(); + adr_prel_lo21_reloc(0x1000, 0x0FF8, &mut mem, 0).unwrap(); + assert_eq!(insn_imm21_adr(u32_at(&mem, 0)), -8); +} + +#[test] +fn adr_prel_lo21_overflow() { + let mut mem = [0u8; 4]; + let res = adr_prel_lo21_reloc(0x0, 0x20_0000, &mut mem, 0); + assert!(matches!(res, Err(Rel64Error::InvalidBits(..))), "{res:?}"); +} + +// ── ADRP (AdrPrelPgHi21) ────────────────────────────────────────────────── + +#[test] +fn adrp_same_page() { + let mut mem = [0u8; 4]; + adrp_prel_pg_hi21_reloc(0x1000, 0x1234, &mut mem, 0).unwrap(); + assert_eq!(insn_imm21_adr(u32_at(&mem, 0)), 0); +} + +#[test] +fn adrp_next_page() { + let mut mem = [0u8; 4]; + adrp_prel_pg_hi21_reloc(0x1ABC, 0x2ABC, &mut mem, 0).unwrap(); + assert_eq!(insn_imm21_adr(u32_at(&mem, 0)), 1); +} + +#[test] +fn adrp_overflow() { + let mut mem = [0u8; 4]; + assert!(matches!( + adrp_prel_pg_hi21_reloc(0x0, 0x1_0000_0000_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(..)) + )); +} + +#[test] +fn adrp_nc_no_overflow() { + let mut mem = [0u8; 4]; + // Would overflow the checked variant; NC must succeed silently. + adrp_prel_pg_hi21_nc_reloc(0x0, 0x1_0000_0000_0000, &mut mem, 0).unwrap(); +} + +// ── ADD lo12 (AddAbsLo12Nc) ─────────────────────────────────────────────── + +#[test] +fn add_abs_lo12_nc_basic() { + let mut mem = 0x9100_0020u32.to_le_bytes().to_vec(); + add_abs_lo_12_nc_reloc(0, 0x1ABC, &mut mem, 0).unwrap(); + assert_eq!(insn_imm12_add(u32_at(&mem, 0)), 0xABC); +} + +#[test] +fn add_abs_lo12_nc_masks_high_bits() { + let mut mem = 0x9100_0020u32.to_le_bytes().to_vec(); + add_abs_lo_12_nc_reloc(0, 0xFFFF_1234, &mut mem, 0).unwrap(); + assert_eq!(insn_imm12_add(u32_at(&mem, 0)), 0x234); +} + +// ── Control-flow relocations ────────────────────────────────────────────── + +#[test] +fn tst_br14_basic() { + let mut mem = 0x3600_0000u32.to_le_bytes().to_vec(); + tst_br14_reloc(0x1000, 0x1008, &mut mem, 0).unwrap(); + assert_eq!(insn_imm14(u32_at(&mem, 0)), 2); +} + +#[test] +fn tst_br14_overflow() { + let mut mem = [0u8; 4]; + assert!(matches!( + tst_br14_reloc(0x0, 0x1_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(..)) + )); +} + +#[test] +fn tst_br14_unaligned() { + let mut mem = [0u8; 4]; + assert!(matches!( + tst_br14_reloc(0x0, 0x1, &mut mem, 0), + Err(Rel64Error::InvalidBits(..)) + )); +} + +#[test] +fn cond_br19_basic() { + let mut mem = 0x5400_0000u32.to_le_bytes().to_vec(); + cond_br19_reloc(0x0000, 0x0004, &mut mem, 0).unwrap(); + assert_eq!(insn_imm19(u32_at(&mem, 0)), 1); +} + +#[test] +fn cond_br19_negative() { + let mut mem = 0x5400_0000u32.to_le_bytes().to_vec(); + cond_br19_reloc(0x0100, 0x0000, &mut mem, 0).unwrap(); + assert_eq!(insn_imm19(u32_at(&mem, 0)), -64); +} + +#[test] +fn jump26_basic() { + let mut mem = 0x1400_0000u32.to_le_bytes().to_vec(); + jump26_reloc(0x0000, 0x0010, &mut mem, 0).unwrap(); + assert_eq!(insn_imm26(u32_at(&mem, 0)), 4); +} + +#[test] +fn jump26_overflow() { + let mut mem = [0u8; 4]; + assert!(matches!( + jump26_reloc(0x0, 0x1000_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(..)) + )); +} + +#[test] +fn call26_basic() { + let mut mem = 0x9400_0000u32.to_le_bytes().to_vec(); + call26_reloc(0x0000, 0x0004, &mut mem, 0).unwrap(); + assert_eq!(insn_imm26(u32_at(&mem, 0)), 1); +} + +#[test] +fn call26_names_itself_in_error() { + // jump26 and call26 share implementation but must report distinct names. + let mut mem = [0u8; 4]; + assert!(matches!( + call26_reloc(0x0, 0x1000_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(..)) + )); +} + +// ── MOVW relocations ────────────────────────────────────────────────────── + +// MOVZ X0, #0: sf=1 opc=10 hw=00 → 0xD2800000 +const MOVZ_X0: u32 = 0xD280_0000; + +#[test] +fn mov_w_abs_g0_basic() { + let mut mem = MOVZ_X0.to_le_bytes().to_vec(); + mov_w_abs_g0_reloc(0xABCD, &mut mem, 0).unwrap(); + assert_eq!(insn_mov_w_imm(u32_at(&mem, 0)), 0xABCD); +} + +#[test] +fn mov_w_abs_g0_overflow() { + let mut mem = MOVZ_X0.to_le_bytes().to_vec(); + assert!(matches!( + mov_w_abs_g0_reloc(0x1_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(..)) + )); +} + +#[test] +fn mov_w_abs_g0nc_no_overflow() { + let mut mem = MOVZ_X0.to_le_bytes().to_vec(); + mov_w_abs_g0nc_reloc(0xDEAD_BEEF_0000_1234u64 as i64, &mut mem, 0).unwrap(); + assert_eq!(insn_mov_w_imm(u32_at(&mem, 0)), 0x1234); +} + +#[test] +fn mov_w_abs_g1_basic() { + let mut mem = MOVZ_X0.to_le_bytes().to_vec(); + mov_w_abs_g1_reloc(0x0001_0000, &mut mem, 0).unwrap(); + assert_eq!(insn_mov_w_imm(u32_at(&mem, 0)), 0x0001); +} + +#[test] +fn mov_w_abs_g1_overflow() { + let mut mem = MOVZ_X0.to_le_bytes().to_vec(); + assert!(matches!( + mov_w_abs_g1_reloc(0x1_0000_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(..)) + )); +} + +#[test] +fn mov_w_abs_g2_basic() { + let mut mem = MOVZ_X0.to_le_bytes().to_vec(); + mov_w_abs_g2_reloc(0x0001_0000_0000, &mut mem, 0).unwrap(); + assert_eq!(insn_mov_w_imm(u32_at(&mem, 0)), 0x0001); +} + +#[test] +fn mov_w_abs_g3_basic() { + let mut mem = MOVZ_X0.to_le_bytes().to_vec(); + mov_w_abs_g3_reloc(0xBEEF_0000_0000_0000u64 as i64, &mut mem, 0).unwrap(); + assert_eq!(insn_mov_w_imm(u32_at(&mem, 0)), 0xBEEF); +} + +#[test] +fn mov_w_abs_g0s_positive() { + let mut mem = MOVZ_X0.to_le_bytes().to_vec(); + mov_w_abs_g0s_reloc(0x1234, &mut mem, 0).unwrap(); + let insn = u32_at(&mem, 0); + assert_eq!(movw_opc(insn), MOVW_OPC_MOVZ); + assert_eq!(insn_mov_w_imm(insn), 0x1234); +} + +#[test] +fn mov_w_abs_g0s_minus_one() { + // -1: MOVN X0, #0 encodes ~0 = -1 + let mut mem = MOVZ_X0.to_le_bytes().to_vec(); + mov_w_abs_g0s_reloc(-1i64, &mut mem, 0).unwrap(); + let insn = u32_at(&mem, 0); + assert_eq!(movw_opc(insn), MOVW_OPC_MOVN); + assert_eq!(insn_mov_w_imm(insn), 0x0000); +} + +#[test] +fn mov_w_abs_g0s_negative_value() { + // -0x1234: MOVN with imm = ~(-0x1234)[15:0] = 0x1233 + let mut mem = MOVZ_X0.to_le_bytes().to_vec(); + mov_w_abs_g0s_reloc(-0x1234, &mut mem, 0).unwrap(); + let insn = u32_at(&mem, 0); + assert_eq!(movw_opc(insn), MOVW_OPC_MOVN); + assert_eq!(insn_mov_w_imm(insn), 0x1233); +} + +#[test] +fn mov_w_abs_g1s_negative() { + // -0x8000_0000: chunk 1 of ~(-0x8000_0000) >> 16 = 0x7FFF + let mut mem = MOVZ_X0.to_le_bytes().to_vec(); + mov_w_abs_g1s_reloc(-0x8000_0000, &mut mem, 0).unwrap(); + let insn = u32_at(&mem, 0); + assert_eq!(movw_opc(insn), MOVW_OPC_MOVN); + assert_eq!(insn_mov_w_imm(insn), 0x7FFF); +} + +#[test] +fn mov_w_abs_g0s_overflow() { + let mut mem = MOVZ_X0.to_le_bytes().to_vec(); + assert!(matches!( + mov_w_abs_g0s_reloc(0x1_0000, &mut mem, 0), + Err(Rel64Error::InvalidValue { .. }) + )); +} + +// ── Tag dispatch ────────────────────────────────────────────────────────── + +#[test] +fn tag_none_is_noop() { + let mut mem = [0xFFu8; 8]; + Rel64Tag::None.apply(0, 0, &mut mem, 0).unwrap(); + assert!(mem.iter().all(|&b| b == 0xFF)); +} + +#[test] +fn tag_abs64_roundtrip() { + let mut mem = [0u8; 8]; + Rel64Tag::Abs64 + .apply(0, 0x1122_3344_5566_7788, &mut mem, 0) + .unwrap(); + assert_eq!(u64_at(&mem, 0), 0x1122_3344_5566_7788); +} + +#[test] +fn tag_jump26_via_apply() { + let mut mem = 0x1400_0000u32.to_le_bytes().to_vec(); + // base=0, offset=0 → place=0; target=8 → imm26=2 + Rel64Tag::Jump26.apply(0x0, 0x0008, &mut mem, 0).unwrap(); + assert_eq!(insn_imm26(u32_at(&mem, 0)), 2); +} + +#[test] +fn tag_adrp_via_apply() { + let mut mem = [0u8; 4]; + // base=0x1100, offset=0 → place=0x1100 (page 0x1000) + // target=0x3500 (page 0x3000) → page diff = 2 + Rel64Tag::AdrPrelPgHi21 + .apply(0x1100, 0x3500, &mut mem, 0) + .unwrap(); + assert_eq!(insn_imm21_adr(u32_at(&mem, 0)), 2); +} diff --git a/harm/src/reloc/data.rs b/harm/src/reloc/data.rs index d9a7a40..c166141 100644 --- a/harm/src/reloc/data.rs +++ b/harm/src/reloc/data.rs @@ -11,13 +11,15 @@ pub fn abs64_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel Ok(()) } -pub fn abs32_reloc(target: u32, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { +pub fn abs32_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target: u32 = target.try_into()?; let bytes = get_bytes_mut(mem, offset)?; *bytes = target.to_le_bytes(); Ok(()) } -pub fn abs16_reloc(target: u16, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { +pub fn abs16_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target: u16 = target.try_into()?; let bytes = get_bytes_mut(mem, offset)?; *bytes = target.to_le_bytes(); Ok(()) diff --git a/harm/src/reloc/movs.rs b/harm/src/reloc/movs.rs index e9361d4..d922f8b 100644 --- a/harm/src/reloc/movs.rs +++ b/harm/src/reloc/movs.rs @@ -7,8 +7,7 @@ use super::Rel64Error; pub fn mov_w_abs_g0_reloc( - base: u64, - target: u64, + target: i64, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { @@ -16,8 +15,7 @@ pub fn mov_w_abs_g0_reloc( } pub fn mov_w_abs_g0nc_reloc( - base: u64, - target: u64, + target: i64, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { @@ -25,8 +23,7 @@ pub fn mov_w_abs_g0nc_reloc( } pub fn mov_w_abs_g0s_reloc( - base: u64, - target: u64, + target: i64, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { @@ -34,8 +31,7 @@ pub fn mov_w_abs_g0s_reloc( } pub fn mov_w_abs_g1_reloc( - base: u64, - target: u64, + target: i64, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { @@ -43,8 +39,7 @@ pub fn mov_w_abs_g1_reloc( } pub fn mov_w_abs_g1nc_reloc( - base: u64, - target: u64, + target: i64, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { @@ -52,8 +47,7 @@ pub fn mov_w_abs_g1nc_reloc( } pub fn mov_w_abs_g1s_reloc( - base: u64, - target: u64, + target: i64, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { @@ -61,8 +55,7 @@ pub fn mov_w_abs_g1s_reloc( } pub fn mov_w_abs_g2_reloc( - base: u64, - target: u64, + target: i64, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { @@ -70,8 +63,7 @@ pub fn mov_w_abs_g2_reloc( } pub fn mov_w_abs_g2nc_reloc( - base: u64, - target: u64, + target: i64, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { @@ -79,8 +71,7 @@ pub fn mov_w_abs_g2nc_reloc( } pub fn mov_w_abs_g2s_reloc( - base: u64, - target: u64, + target: i64, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { @@ -88,8 +79,7 @@ pub fn mov_w_abs_g2s_reloc( } pub fn mov_w_abs_g3_reloc( - base: u64, - target: u64, + target: i64, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { From eb59cc44969e09b409861491696369c57fff1f74 Mon Sep 17 00:00:00 2001 From: Ivan Boldyrev Date: Fri, 6 Mar 2026 20:22:13 +0100 Subject: [PATCH 04/13] mov relocations --- harm/src/reloc.rs | 14 +-- harm/src/reloc/claude_tests.rs | 8 +- harm/src/reloc/movs.rs | 213 +++++++++++++++++++++++---------- 3 files changed, 163 insertions(+), 72 deletions(-) diff --git a/harm/src/reloc.rs b/harm/src/reloc.rs index 5e087a2..4e93e0a 100644 --- a/harm/src/reloc.rs +++ b/harm/src/reloc.rs @@ -276,16 +276,16 @@ impl Rel64Tag { Rel64Tag::CondBr19 => cond_br19_reloc(base, target, memory, offset), Rel64Tag::Jump26 | Rel64Tag::Call26 => jump26_reloc(base, target, memory, offset), - Rel64Tag::MovWAbsG0 => mov_w_abs_g0_reloc(target.cast_signed(), memory, offset), - Rel64Tag::MovWAbsG0Nc => mov_w_abs_g0nc_reloc(target.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG0 => mov_w_abs_g0_reloc(target, memory, offset), + Rel64Tag::MovWAbsG0Nc => mov_w_abs_g0nc_reloc(target, memory, offset), Rel64Tag::MovWAbsG0S => mov_w_abs_g0s_reloc(target.cast_signed(), memory, offset), - Rel64Tag::MovWAbsG1 => mov_w_abs_g1_reloc(target.cast_signed(), memory, offset), - Rel64Tag::MovWAbsG1Nc => mov_w_abs_g1nc_reloc(target.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG1 => mov_w_abs_g1_reloc(target, memory, offset), + Rel64Tag::MovWAbsG1Nc => mov_w_abs_g1nc_reloc(target, memory, offset), Rel64Tag::MovWAbsG1S => mov_w_abs_g1s_reloc(target.cast_signed(), memory, offset), - Rel64Tag::MovWAbsG2 => mov_w_abs_g2_reloc(target.cast_signed(), memory, offset), - Rel64Tag::MovWAbsG2Nc => mov_w_abs_g2nc_reloc(target.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG2 => mov_w_abs_g2_reloc(target, memory, offset), + Rel64Tag::MovWAbsG2Nc => mov_w_abs_g2nc_reloc(target, memory, offset), Rel64Tag::MovWAbsG2S => mov_w_abs_g2s_reloc(target.cast_signed(), memory, offset), - Rel64Tag::MovWAbsG3 => mov_w_abs_g3_reloc(target.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG3 => mov_w_abs_g3_reloc(target, memory, offset), } } } diff --git a/harm/src/reloc/claude_tests.rs b/harm/src/reloc/claude_tests.rs index 1132fbb..ae758e4 100644 --- a/harm/src/reloc/claude_tests.rs +++ b/harm/src/reloc/claude_tests.rs @@ -379,14 +379,14 @@ fn mov_w_abs_g0_overflow() { let mut mem = MOVZ_X0.to_le_bytes().to_vec(); assert!(matches!( mov_w_abs_g0_reloc(0x1_0000, &mut mem, 0), - Err(Rel64Error::InvalidBits(..)) + Err(Rel64Error::InvalidValue(..)) )); } #[test] fn mov_w_abs_g0nc_no_overflow() { let mut mem = MOVZ_X0.to_le_bytes().to_vec(); - mov_w_abs_g0nc_reloc(0xDEAD_BEEF_0000_1234u64 as i64, &mut mem, 0).unwrap(); + mov_w_abs_g0nc_reloc(0xDEAD_BEEF_0000_1234u64, &mut mem, 0).unwrap(); assert_eq!(insn_mov_w_imm(u32_at(&mem, 0)), 0x1234); } @@ -402,7 +402,7 @@ fn mov_w_abs_g1_overflow() { let mut mem = MOVZ_X0.to_le_bytes().to_vec(); assert!(matches!( mov_w_abs_g1_reloc(0x1_0000_0000, &mut mem, 0), - Err(Rel64Error::InvalidBits(..)) + Err(Rel64Error::InvalidValue(..)) )); } @@ -416,7 +416,7 @@ fn mov_w_abs_g2_basic() { #[test] fn mov_w_abs_g3_basic() { let mut mem = MOVZ_X0.to_le_bytes().to_vec(); - mov_w_abs_g3_reloc(0xBEEF_0000_0000_0000u64 as i64, &mut mem, 0).unwrap(); + mov_w_abs_g3_reloc(0xBEEF_0000_0000_0000u64, &mut mem, 0).unwrap(); assert_eq!(insn_mov_w_imm(u32_at(&mem, 0)), 0xBEEF); } diff --git a/harm/src/reloc/movs.rs b/harm/src/reloc/movs.rs index d922f8b..1e2ccab 100644 --- a/harm/src/reloc/movs.rs +++ b/harm/src/reloc/movs.rs @@ -2,86 +2,177 @@ * * This document is licensed under the BSD 3-clause license. */ -#![allow(unused)] +use aarchmrs_types::InstructionCode; use super::Rel64Error; +use crate::reloc::get_bytes_mut; -pub fn mov_w_abs_g0_reloc( - target: i64, - mem: &mut [u8], - offset: usize, -) -> Result<(), Rel64Error> { - todo!() +const MOV_OPCODE_OFFSET: u32 = 29; +const MOV_OPCODE_WIDTH: u32 = 2; + +const MOV_OPCODE_MOVN: u32 = 0; +const MOV_OPCODE_MOVZ: u32 = 2; + +const MOV_IMM16_OFFSET: u32 = 5; +const MOV_IMM16_WIDTH: u32 = 16; + +pub fn mov_w_abs_g0_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; + + let target: u16 = target.try_into()?; + let bytes = get_bytes_mut(mem, offset)?; + + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !INST_MASK; + inst_code |= (target as u32) << MOV_IMM16_OFFSET; + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) } -pub fn mov_w_abs_g0nc_reloc( - target: i64, - mem: &mut [u8], - offset: usize, -) -> Result<(), Rel64Error> { - todo!() +pub fn mov_w_abs_g0nc_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; + + let target = target as u16; + let bytes = get_bytes_mut(mem, offset)?; + + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !INST_MASK; + inst_code |= (target as u32) << MOV_IMM16_OFFSET; + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) } -pub fn mov_w_abs_g0s_reloc( - target: i64, - mem: &mut [u8], - offset: usize, -) -> Result<(), Rel64Error> { - todo!() +pub fn mov_w_abs_g0s_reloc(target: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + const INST_MASK: u32 = (((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET) + | (((1 << MOV_OPCODE_WIDTH) - 1) << MOV_OPCODE_OFFSET); + + let target: i16 = target.try_into()?; + let bytes = get_bytes_mut(mem, offset)?; + + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !INST_MASK; + if target < 0 { + inst_code |= ((!target as u16) as u32) << MOV_IMM16_OFFSET; + inst_code |= MOV_OPCODE_MOVN << MOV_OPCODE_OFFSET; + } else { + inst_code |= ((target as u16) as u32) << MOV_IMM16_OFFSET; + inst_code |= MOV_OPCODE_MOVZ << MOV_OPCODE_OFFSET; + } + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) } -pub fn mov_w_abs_g1_reloc( - target: i64, - mem: &mut [u8], - offset: usize, -) -> Result<(), Rel64Error> { - todo!() +pub fn mov_w_abs_g1_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; + + let target: u16 = (target >> 16).try_into()?; + let bytes = get_bytes_mut(mem, offset)?; + + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !INST_MASK; + inst_code |= (target as u32) << MOV_IMM16_OFFSET; + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) } -pub fn mov_w_abs_g1nc_reloc( - target: i64, - mem: &mut [u8], - offset: usize, -) -> Result<(), Rel64Error> { - todo!() +pub fn mov_w_abs_g1nc_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; + + let target = (target >> 16) as u16; + let bytes = get_bytes_mut(mem, offset)?; + + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !INST_MASK; + inst_code |= (target as u32) << MOV_IMM16_OFFSET; + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) } -pub fn mov_w_abs_g1s_reloc( - target: i64, - mem: &mut [u8], - offset: usize, -) -> Result<(), Rel64Error> { - todo!() +pub fn mov_w_abs_g1s_reloc(target: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + const INST_MASK: u32 = (((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET) + | (((1 << MOV_OPCODE_WIDTH) - 1) << MOV_OPCODE_OFFSET); + + let target: i16 = (target >> 16).try_into()?; + let bytes = get_bytes_mut(mem, offset)?; + + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !INST_MASK; + if target < 0 { + inst_code |= ((!target as u16) as u32) << MOV_IMM16_OFFSET; + inst_code |= MOV_OPCODE_MOVN << MOV_OPCODE_OFFSET; + } else { + inst_code |= ((target as u16) as u32) << MOV_IMM16_OFFSET; + inst_code |= MOV_OPCODE_MOVZ << MOV_OPCODE_OFFSET; + } + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) } -pub fn mov_w_abs_g2_reloc( - target: i64, - mem: &mut [u8], - offset: usize, -) -> Result<(), Rel64Error> { - todo!() +pub fn mov_w_abs_g2_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; + + let target: u16 = (target >> 32).try_into()?; + let bytes = get_bytes_mut(mem, offset)?; + + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !INST_MASK; + inst_code |= (target as u32) << MOV_IMM16_OFFSET; + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) } -pub fn mov_w_abs_g2nc_reloc( - target: i64, - mem: &mut [u8], - offset: usize, -) -> Result<(), Rel64Error> { - todo!() +pub fn mov_w_abs_g2nc_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; + + let target = (target >> 32) as u16; + let bytes = get_bytes_mut(mem, offset)?; + + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !INST_MASK; + inst_code |= (target as u32) << MOV_IMM16_OFFSET; + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) } -pub fn mov_w_abs_g2s_reloc( - target: i64, - mem: &mut [u8], - offset: usize, -) -> Result<(), Rel64Error> { - todo!() +pub fn mov_w_abs_g2s_reloc(target: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + const INST_MASK: u32 = (((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET) + | (((1 << MOV_OPCODE_WIDTH) - 1) << MOV_OPCODE_OFFSET); + + let target: i16 = (target >> 32).try_into()?; + let bytes = get_bytes_mut(mem, offset)?; + + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !INST_MASK; + if target < 0 { + inst_code |= ((!target as u16) as u32) << MOV_IMM16_OFFSET; + inst_code |= MOV_OPCODE_MOVN << MOV_OPCODE_OFFSET; + } else { + inst_code |= ((target as u16) as u32) << MOV_IMM16_OFFSET; + inst_code |= MOV_OPCODE_MOVZ << MOV_OPCODE_OFFSET; + } + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) } -pub fn mov_w_abs_g3_reloc( - target: i64, - mem: &mut [u8], - offset: usize, -) -> Result<(), Rel64Error> { - todo!() +pub fn mov_w_abs_g3_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; + + let target = (target >> 48) as u32; + let bytes = get_bytes_mut(mem, offset)?; + + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !INST_MASK; + inst_code |= target << MOV_IMM16_OFFSET; + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) } From efd81d8b3d22d4599abdd9498166e3f648847828 Mon Sep 17 00:00:00 2001 From: Ivan Boldyrev Date: Sat, 7 Mar 2026 17:39:31 +0100 Subject: [PATCH 05/13] Claude's tests 2 --- harm/src/reloc/addr.rs | 4 +- harm/src/reloc/claude_tests.rs | 1275 +++++++++++++++++++++++++++----- harm/src/reloc/control.rs | 8 +- 3 files changed, 1085 insertions(+), 202 deletions(-) diff --git a/harm/src/reloc/addr.rs b/harm/src/reloc/addr.rs index 606c5d3..ecf4877 100644 --- a/harm/src/reloc/addr.rs +++ b/harm/src/reloc/addr.rs @@ -54,7 +54,7 @@ pub fn adrp_prel_pg_hi21_reloc( let bytes = get_bytes_mut(mem, offset)?; - let delta = calc_offset(base, target, offset)? >> 12 << 12; + let delta = calc_offset(base, target, offset)? & !0xFFF; let delta = AdrpOffset::new_i64(delta).map_err(Rel64Error::InvalidBits)?; patch_adr_adrp(bytes, delta.bits() & VALUE_MASK); @@ -72,7 +72,7 @@ pub fn adrp_prel_pg_hi21_nc_reloc( let bytes = get_bytes_mut(mem, offset)?; // It is not checked. - let delta = calc_offset(base, target, offset)? >> 21; + let delta = calc_offset(base, target, offset)? >> 12; patch_adr_adrp(bytes, (delta as u32) & VALUE_MASK); Ok(()) diff --git a/harm/src/reloc/claude_tests.rs b/harm/src/reloc/claude_tests.rs index ae758e4..7a39cd0 100644 --- a/harm/src/reloc/claude_tests.rs +++ b/harm/src/reloc/claude_tests.rs @@ -1,59 +1,89 @@ -/* Tests based on code generated by Claude Sonnet 4.6. */ +/* Tests based on code generated by Claude Sonnet 4.6 in March 2026. + */ use super::*; -// opc values for MOVW instructions. -const MOVW_OPC_MOVZ: u32 = 0b10; -const MOVW_OPC_MOVN: u32 = 0b00; +// ═══════════════════════════════════════════════════════════════════════ +// Test helpers +// ═══════════════════════════════════════════════════════════════════════ -fn movw_opc(insn: u32) -> u32 { - (insn >> 29) & 0x3 -} -// ── Byte-level accessors ────────────────────────────────────────────────── +const MOV_W_OPC_MOVZ: u32 = 0b10; +const MOV_W_OPC_MOVN: u32 = 0b00; + +// ── Byte-level readers ──────────────────────────────────────────────── fn u16_at(mem: &[u8], off: usize) -> u16 { u16::from_le_bytes(mem[off..off + 2].try_into().unwrap()) } - fn u32_at(mem: &[u8], off: usize) -> u32 { u32::from_le_bytes(mem[off..off + 4].try_into().unwrap()) } - fn u64_at(mem: &[u8], off: usize) -> u64 { u64::from_le_bytes(mem[off..off + 8].try_into().unwrap()) } -// ── Instruction-field decoders ──────────────────────────────────────────── +// ── Instruction-field decoders ──────────────────────────────────────── -/// Sign-extend a `width`-bit unsigned value to i32. +/// Sign-extend the lowest `width` bits of `raw` to i32. fn sign_ext(raw: u32, width: u32) -> i32 { - let shift = 32 - width; - ((raw << shift) as i32) >> shift + let sh = 32 - width; + ((raw << sh) as i32) >> sh } -fn insn_imm14(insn: u32) -> i32 { +fn dec_imm14(insn: u32) -> i32 { sign_ext((insn >> 5) & 0x3FFF, 14) } -fn insn_imm19(insn: u32) -> i32 { - sign_ext((insn >> 5) & 0x0007_FFFF, 19) +fn dec_imm19(insn: u32) -> i32 { + sign_ext((insn >> 5) & 0x007F_FFFF, 19) } -fn insn_imm26(insn: u32) -> i32 { +fn dec_imm26(insn: u32) -> i32 { sign_ext(insn & 0x03FF_FFFF, 26) } - -fn insn_imm21_adr(insn: u32) -> i32 { - let immlo = (insn >> 29) & 0x3; - let immhi = (insn >> 5) & 0x0007_FFFF; - sign_ext((immhi << 2) | immlo, 21) -} - -fn insn_imm12_add(insn: u32) -> u32 { +fn dec_imm12(insn: u32) -> u32 { (insn >> 10) & 0xFFF } -fn insn_mov_w_imm(insn: u32) -> u16 { +fn dec_movw(insn: u32) -> u16 { ((insn >> 5) & 0xFFFF) as u16 } -// ── get_bytes_mut error-variant tests ───────────────────────────────────── +fn dec_imm21_adr(insn: u32) -> i32 { + let lo = (insn >> 29) & 0x3; + let hi = (insn >> 5) & 0x0007_FFFF; + sign_ext((hi << 2) | lo, 21) +} + +fn mov_w_opc(insn: u32) -> u32 { + (insn >> 29) & 0x3 +} + +// ── Skeleton instruction words ──────────────────────────────────────── + +// LDR X0, #0 → 0x5800_0000 +const LDR_LIT: u32 = 0x5800_0000; +// ADR X1, #0 → 0x0000_0001 +const ADR: u32 = 0x0000_0001; +// ADRP X0, #0 → 0x9000_0000 +const ADRP: u32 = 0x9000_0000; +// ADD X0, X1, #0 → 0x9100_0020 +const ADD: u32 = 0x9100_0020; +// TBZ X0, #0, #0 → 0x3600_0000 +const TBZ: u32 = 0x3600_0000; +// B.EQ #0 → 0x5400_0000 +const BEQ: u32 = 0x5400_0000; +// B #0 → 0x1400_0000 +const B: u32 = 0x1400_0000; +// BL #0 → 0x9400_0000 +const BL: u32 = 0x9400_0000; +// MOVZ X0, #0 → 0xD280_0000 +const MOVZ: u32 = 0xD280_0000; + +/// Return a 4-byte buffer pre-loaded with an instruction word. +fn insn_buf(insn: u32) -> [u8; 4] { + insn.to_le_bytes() +} + +// ═══════════════════════════════════════════════════════════════════════ +// get_bytes_mut / get_bytes +// ═══════════════════════════════════════════════════════════════════════ #[test] fn get_bytes_mut_ok() { @@ -74,235 +104,623 @@ fn get_bytes_mut_invalid_offset() { #[test] fn get_bytes_mut_not_enough_memory() { let mut mem = [0u8; 4]; - // offset 2 is valid but only 2 bytes remain — not enough for 4. + // offset 2 is valid, but only 2 bytes remain — not enough for N=4 assert_eq!( get_bytes_mut::<4>(&mut mem, 2), Err(Rel64Error::NotEnoughMemory { offset: 2 }) ); } -// ── Static data relocations ─────────────────────────────────────────────── +// ═══════════════════════════════════════════════════════════════════════ +// Buffer-error variants (exercised through a public function) +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn invalid_offset_error() { + let mut mem = [0u8; 4]; + assert_eq!( + abs64_reloc(0, &mut mem, 8), + Err(Rel64Error::InvalidOffset { offset: 8 }) + ); +} + +#[test] +fn not_enough_memory_error() { + let mut mem = [0u8; 4]; + assert_eq!( + abs64_reloc(0, &mut mem, 0), + Err(Rel64Error::NotEnoughMemory { offset: 0 }) + ); +} + +// ═══════════════════════════════════════════════════════════════════════ +// abs64 (target, mem, offset) +// ═══════════════════════════════════════════════════════════════════════ #[test] fn abs64_basic() { let mut mem = [0u8; 16]; abs64_reloc(0xDEAD_BEEF_CAFE_1234, &mut mem, 4).unwrap(); assert_eq!(u64_at(&mem, 4), 0xDEAD_BEEF_CAFE_1234); + assert_eq!(&mem[..4], &[0u8; 4]); // guard bytes untouched } #[test] -fn abs32_ok() { +fn abs64_zero() { + let mut mem = [0xFFu8; 8]; + abs64_reloc(0, &mut mem, 0).unwrap(); + assert_eq!(u64_at(&mem, 0), 0); +} + +#[test] +fn abs64_max() { let mut mem = [0u8; 8]; - abs32_reloc(0x0000_ABCD, &mut mem, 2).unwrap(); - assert_eq!(u32_at(&mem, 2), 0x0000_ABCD); + abs64_reloc(u64::MAX, &mut mem, 0).unwrap(); + assert_eq!(u64_at(&mem, 0), u64::MAX); } +// ═══════════════════════════════════════════════════════════════════════ +// abs32 (target, mem, offset) +// ═══════════════════════════════════════════════════════════════════════ + #[test] -fn abs32_overflow() { +fn abs32_basic() { let mut mem = [0u8; 8]; + abs32_reloc(0xABCD_1234, &mut mem, 2).unwrap(); + assert_eq!(u32_at(&mem, 2), 0xABCD_1234); +} + +#[test] +fn abs32_max() { + let mut mem = [0u8; 4]; + abs32_reloc(0xFFFF_FFFF, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0), 0xFFFF_FFFF); +} + +#[test] +fn abs32_overflow() { + let mut mem = [0u8; 4]; assert!(matches!( abs32_reloc(0x1_0000_0000, &mut mem, 0), - Err(Rel64Error::InvalidValue { .. }) + Err(Rel64Error::InvalidValue(_)) )); } +// ═══════════════════════════════════════════════════════════════════════ +// abs16 (target, mem, offset) +// ═══════════════════════════════════════════════════════════════════════ + #[test] -fn abs16_ok() { +fn abs16_basic() { let mut mem = [0u8; 4]; abs16_reloc(0xBEEF, &mut mem, 0).unwrap(); assert_eq!(u16_at(&mem, 0), 0xBEEF); } +#[test] +fn abs16_max() { + let mut mem = [0u8; 2]; + abs16_reloc(0xFFFF, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0), 0xFFFF); +} + #[test] fn abs16_overflow() { - let mut mem = [0u8; 4]; + let mut mem = [0u8; 2]; assert!(matches!( abs16_reloc(0x1_0000, &mut mem, 0), - Err(Rel64Error::InvalidValue { .. }) + Err(Rel64Error::InvalidValue(_)) )); } +// ═══════════════════════════════════════════════════════════════════════ +// prel64 (base, target, mem, offset) +// +// place = base + offset +// written = target − place (wrapping u64) +// ═══════════════════════════════════════════════════════════════════════ + #[test] -fn prel64_forward_and_backward() { - let mut mem = [0u8; 16]; +fn prel64_forward() { + // place = 0x1000 + 0 = 0x1000; diff = 0x2000 − 0x1000 = +0x1000 + let mut mem = [0u8; 8]; prel64_reloc(0x1000, 0x2000, &mut mem, 0).unwrap(); - assert_eq!(u64_at(&mem, 0), 0x1000u64); + assert_eq!(u64_at(&mem, 0), 0x1000); +} + +#[test] +fn prel64_backward() { + // place = 0x2000; diff = 0x1000 − 0x2000 (wrapping) = −0x1000 + let mut mem = [0u8; 8]; prel64_reloc(0x2000, 0x1000, &mut mem, 0).unwrap(); assert_eq!(u64_at(&mem, 0) as i64, -0x1000i64); } #[test] -fn prel32_in_range() { - let mut mem = [0u8; 8]; +fn prel64_zero_diff() { + let mut mem = [0xFFu8; 8]; + prel64_reloc(0x4000, 0x4000, &mut mem, 0).unwrap(); + assert_eq!(u64_at(&mem, 0), 0); +} + +#[test] +fn prel64_nonzero_offset() { + // base=0x0FF8, offset=8 → place=0x1000; diff=0x2000−0x1000=0x1000 + let mut mem = [0u8; 16]; + prel64_reloc(0x0FF8, 0x2000, &mut mem, 8).unwrap(); + assert_eq!(u64_at(&mem, 8), 0x1000); +} + +// ═══════════════════════════════════════════════════════════════════════ +// prel32 (base, target, mem, offset) +// +// diff = target − place; must fit in [i32::MIN, i32::MAX] +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn prel32_forward() { + let mut mem = [0u8; 4]; prel32_reloc(0x4000, 0x5000, &mut mem, 0).unwrap(); assert_eq!(u32_at(&mem, 0) as i32, 0x1000); } #[test] -fn prel32_overflow() { - let mut mem = [0u8; 8]; +fn prel32_backward() { + // place=0x5000; diff = 0x4000 − 0x5000 = −0x1000 + let mut mem = [0u8; 4]; + prel32_reloc(0x5000, 0x4000, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0) as i32, -0x1000); +} + +#[test] +fn prel32_max_positive() { + // diff = i32::MAX = 0x7FFF_FFFF — just fits + let mut mem = [0u8; 4]; + prel32_reloc(0x0, 0x7FFF_FFFF, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0) as i32, i32::MAX); +} + +#[test] +fn prel32_max_negative() { + // place=0x8000_0000; diff = 0 − 0x8000_0000 = i32::MIN — just fits + let mut mem = [0u8; 4]; + prel32_reloc(0x8000_0000, 0x0, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0) as i32, i32::MIN); +} + +#[test] +fn prel32_overflow_positive() { + // diff = 0x8000_0000 = i32::MAX + 1 + let mut mem = [0u8; 4]; assert!(matches!( - prel32_reloc(0x0, 0xC000_0000, &mut mem, 0), - Err(Rel64Error::InvalidValue { .. }) + prel32_reloc(0x0, 0x8000_0000, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) )); } #[test] -fn prel16_in_range() { +fn prel32_overflow_negative() { + // place=0x8000_0001; diff = −0x8000_0001 < i32::MIN let mut mem = [0u8; 4]; + assert!(matches!( + prel32_reloc(0x8000_0001, 0x0, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +#[test] +fn prel32_nonzero_offset() { + // base=0x0, offset=8 → place=0x8; diff=0x1008−0x8=0x1000 + let mut mem = [0u8; 12]; + prel32_reloc(0x0, 0x1008, &mut mem, 8).unwrap(); + assert_eq!(u32_at(&mem, 8) as i32, 0x1000); +} + +// ═══════════════════════════════════════════════════════════════════════ +// prel16 (base, target, mem, offset) +// +// diff must fit in [i16::MIN, i16::MAX] +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn prel16_forward() { + // place=0x3FFF; diff=0x4000−0x3FFF=+1 + let mut mem = [0u8; 2]; prel16_reloc(0x3FFF, 0x4000, &mut mem, 0).unwrap(); assert_eq!(u16_at(&mem, 0) as i16, 1); } #[test] -fn prel16_overflow() { - let mut mem = [0u8; 4]; - assert!(matches!( - prel16_reloc(0x0, 0x1_0000, &mut mem, 0), - Err(Rel64Error::InvalidValue { .. }) - )); +fn prel16_backward() { + // place=0x4000; diff=0x3FFF−0x4000=−1 + let mut mem = [0u8; 2]; + prel16_reloc(0x4000, 0x3FFF, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0) as i16, -1); } -// ── Buffer-size error variants ──────────────────────────────────────────── +#[test] +fn prel16_max_positive() { + let mut mem = [0u8; 2]; + prel16_reloc(0x0, 0x7FFF, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0) as i16, i16::MAX); +} #[test] -fn invalid_offset_error() { - let mut mem = [0u8; 4]; - assert_eq!( - abs64_reloc(0, &mut mem, 8), - Err(Rel64Error::InvalidOffset { offset: 8 }) - ); +fn prel16_max_negative() { + // place=0x8000; diff=0−0x8000=i16::MIN — just fits + let mut mem = [0u8; 2]; + prel16_reloc(0x8000, 0x0, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0) as i16, i16::MIN); } #[test] -fn not_enough_memory_error() { - let mut mem = [0u8; 4]; - assert_eq!( - abs64_reloc(0, &mut mem, 0), - Err(Rel64Error::NotEnoughMemory { offset: 0 }) - ); +fn prel16_overflow_positive() { + let mut mem = [0u8; 2]; + assert!(matches!( + prel16_reloc(0x0, 0x8000, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); } -// ── LDR literal (LdPrelLo19) ────────────────────────────────────────────── +#[test] +fn prel16_overflow_negative() { + // place=0x8001; diff=−0x8001 < i16::MIN + let mut mem = [0u8; 2]; + assert!(matches!( + prel16_reloc(0x8001, 0x0, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +// ═══════════════════════════════════════════════════════════════════════ +// ld_prel_lo19 (base, target, mem, offset) +// +// imm19 = (target − place) / 4 +// Signed 19-bit word offset → byte range [−(1<<20), (1<<20)−4] +// target must be 4-byte aligned relative to place +// ═══════════════════════════════════════════════════════════════════════ #[test] fn ld_prel_lo19_forward() { - let mut mem = 0x5800_0000u32.to_le_bytes().to_vec(); - ld_prel_lo19_reloc(0x0000, 0x0008, &mut mem, 0).unwrap(); - assert_eq!(insn_imm19(u32_at(&mem, 0)), 2); + // place=0; diff=8; imm19=2 + let mut mem = insn_buf(LDR_LIT); + ld_prel_lo19_reloc(0x0, 0x0008, &mut mem, 0).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 0)), 2); } #[test] -fn ld_prel_lo19_negative() { - let mut mem = 0x5800_0000u32.to_le_bytes().to_vec(); +fn ld_prel_lo19_backward() { + // place=0x100; diff=−0x100; imm19=−64 + let mut mem = insn_buf(LDR_LIT); ld_prel_lo19_reloc(0x0100, 0x0000, &mut mem, 0).unwrap(); - assert_eq!(insn_imm19(u32_at(&mem, 0)), -64); + assert_eq!(dec_imm19(u32_at(&mem, 0)), -64); } #[test] -fn ld_prel_lo19_unaligned() { +fn ld_prel_lo19_max_positive() { + // imm19 max = (1<<18)−1 word offsets = 0x3_FFFF → byte diff = 0x0F_FFFC + let mut mem = insn_buf(LDR_LIT); + ld_prel_lo19_reloc(0x0, 0x0F_FFFC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 0)), (1 << 18) - 1); +} + +#[test] +fn ld_prel_lo19_max_negative() { + // imm19 min = −(1<<18) word offsets → byte diff = −0x10_0000 + // place=0x10_0000, target=0 + let mut mem = insn_buf(LDR_LIT); + ld_prel_lo19_reloc(0x10_0000, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 0)), -(1 << 18)); +} + +#[test] +fn ld_prel_lo19_overflow_positive() { + // byte diff = 0x10_0000 → word imm = 1<<18, one above max + let mut mem = [0u8; 4]; + assert!(matches!( + ld_prel_lo19_reloc(0x0, 0x10_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 19, + align: 2 + })) + )); +} + +#[test] +fn ld_prel_lo19_overflow_negative() { + // place=0x10_0004, target=0 → byte diff=−0x10_0004 → one below min let mut mem = [0u8; 4]; assert!(matches!( - ld_prel_lo19_reloc(0x0000, 0x0001, &mut mem, 0), - Err(Rel64Error::InvalidBits(..)) + ld_prel_lo19_reloc(0x10_0004, 0x0, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 19, + align: 2 + })) )); } #[test] -fn ld_prel_lo19_overflow() { +fn ld_prel_lo19_unaligned() { let mut mem = [0u8; 4]; assert!(matches!( - ld_prel_lo19_reloc(0x0, 0x20_0000, &mut mem, 0), - Err(Rel64Error::InvalidBits(..)) + ld_prel_lo19_reloc(0x0, 0x0002, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 2 })) )); } -// ── ADR (AdrPrelLo21) ───────────────────────────────────────────────────── +#[test] +fn ld_prel_lo19_nonzero_offset() { + // base=0, offset=4 → place=4; diff=0x10−4=0xC; imm19=3 + let mut mem = [0u8; 8]; + mem[4..8].copy_from_slice(&LDR_LIT.to_le_bytes()); + ld_prel_lo19_reloc(0x0, 0x10, &mut mem, 4).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 4)), 3); +} + +// ═══════════════════════════════════════════════════════════════════════ +// adr_prel_lo21 (base, target, mem, offset) +// +// imm21 = target − place (byte offset, not scaled) +// Signed 21-bit → byte range [−(1<<20), (1<<20)−1] +// ═══════════════════════════════════════════════════════════════════════ #[test] -fn adr_prel_lo21_basic() { - let mut mem = 0x0000_0001u32.to_le_bytes().to_vec(); +fn adr_prel_lo21_forward() { + // place=0x1000; diff=4; imm21=4 + let mut mem = insn_buf(ADR); adr_prel_lo21_reloc(0x1000, 0x1004, &mut mem, 0).unwrap(); - assert_eq!(insn_imm21_adr(u32_at(&mem, 0)), 4); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 4); } #[test] -fn adr_prel_lo21_negative() { - let mut mem = 0x0000_0001u32.to_le_bytes().to_vec(); +fn adr_prel_lo21_backward() { + // place=0x1000; diff=0x0FF8−0x1000=−8 + let mut mem = insn_buf(ADR); adr_prel_lo21_reloc(0x1000, 0x0FF8, &mut mem, 0).unwrap(); - assert_eq!(insn_imm21_adr(u32_at(&mem, 0)), -8); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), -8); +} + +#[test] +fn adr_prel_lo21_max_positive() { + // max diff = (1<<20)−1 = 0xF_FFFF + let mut mem = insn_buf(ADR); + adr_prel_lo21_reloc(0x0, 0x0F_FFFF, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), (1 << 20) - 1); } #[test] -fn adr_prel_lo21_overflow() { +fn adr_prel_lo21_max_negative() { + // min diff = −(1<<20) = −0x10_0000; place=0x10_0000, target=0 + let mut mem = insn_buf(ADR); + adr_prel_lo21_reloc(0x10_0000, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), -(1 << 20)); +} + +#[test] +fn adr_prel_lo21_overflow_positive() { + let mut mem = [0u8; 4]; + assert!(matches!( + adr_prel_lo21_reloc(0x0, 0x10_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 21, + align: 0 + })) + )); +} + +#[test] +fn adr_prel_lo21_overflow_negative() { let mut mem = [0u8; 4]; - let res = adr_prel_lo21_reloc(0x0, 0x20_0000, &mut mem, 0); - assert!(matches!(res, Err(Rel64Error::InvalidBits(..))), "{res:?}"); + assert!(matches!( + adr_prel_lo21_reloc(0x10_0001, 0x0, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 21, + align: 0 + })) + )); +} + +#[test] +fn adr_prel_lo21_nonzero_offset() { + // base=0x0FFC, offset=4 → place=0x1000; diff=0x1008−0x1000=8 + let mut mem = [0u8; 8]; + mem[4..8].copy_from_slice(&ADR.to_le_bytes()); + adr_prel_lo21_reloc(0x0FFC, 0x1008, &mut mem, 4).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 4)), 8); } -// ── ADRP (AdrPrelPgHi21) ────────────────────────────────────────────────── +// ═══════════════════════════════════════════════════════════════════════ +// adrp_prel_pg_hi21 (base, target, mem, offset) +// +// imm21 = (Page(target) − Page(place)) / 4096 +// Signed 21-bit page offset → ±4 GiB range +// ═══════════════════════════════════════════════════════════════════════ #[test] fn adrp_same_page() { - let mut mem = [0u8; 4]; + // Both on page 0x1000 → imm21=0 + let mut mem = insn_buf(ADRP); adrp_prel_pg_hi21_reloc(0x1000, 0x1234, &mut mem, 0).unwrap(); - assert_eq!(insn_imm21_adr(u32_at(&mem, 0)), 0); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 0); } #[test] fn adrp_next_page() { - let mut mem = [0u8; 4]; + // place page=0x1000; target page=0x2000 → imm21=1 + let mut mem = insn_buf(ADRP); adrp_prel_pg_hi21_reloc(0x1ABC, 0x2ABC, &mut mem, 0).unwrap(); - assert_eq!(insn_imm21_adr(u32_at(&mem, 0)), 1); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 1); } #[test] -fn adrp_overflow() { +fn adrp_prev_page() { + // place page=0x2000; target page=0x1000 → imm21=−1 + let mut mem = insn_buf(ADRP); + adrp_prel_pg_hi21_reloc(0x2ABC, 0x1ABC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), -1); +} + +#[test] +fn adrp_max_positive() { + // imm21 max = (1<<20)−1 page offsets + // target page = ((1<<20)−1) * 0x1000 = 0xF_FFFF_000 + let mut mem = insn_buf(ADRP); + adrp_prel_pg_hi21_reloc(0x0, 0xF_FFFF_FFF, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), (1 << 20) - 1); +} + +#[test] +fn adrp_max_negative() { + // imm21 min = −(1<<20); page diff = −(1<<20) + // place page = (1<<20)*0x1000 = 0x10_0000_000; target=0 + let mut mem = insn_buf(ADRP); + adrp_prel_pg_hi21_reloc(0x10_0000_000, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), -(1 << 20)); +} + +#[test] +fn adrp_overflow_positive() { let mut mem = [0u8; 4]; assert!(matches!( adrp_prel_pg_hi21_reloc(0x0, 0x1_0000_0000_0000, &mut mem, 0), - Err(Rel64Error::InvalidBits(..)) + // TODO Unsupported seems to have obsure semantics. + Err(Rel64Error::InvalidBits(BitError::Unsupported)) )); } +#[test] +fn adrp_overflow_negative() { + let mut mem = [0u8; 4]; + assert!(matches!( + adrp_prel_pg_hi21_reloc(0x1_0000_0000_0000, 0x0, &mut mem, 0), + // TODO Unsupported seems to have obsure semantics. + // Actually it should be BitError::Overflow. + Err(Rel64Error::InvalidBits(BitError::Unsupported)) + ),); +} + +#[test] +fn adrp_nc_identical_to_checked_in_range() { + let mut mc = insn_buf(ADRP); + let mut mn = insn_buf(ADRP); + adrp_prel_pg_hi21_reloc(0x1000, 0x5678, &mut mc, 0).unwrap(); + adrp_prel_pg_hi21_nc_reloc(0x1000, 0x5678, &mut mn, 0).unwrap(); + assert_eq!(mc, mn); +} + #[test] fn adrp_nc_no_overflow() { + // Would be rejected by the checked variant; NC silently truncates let mut mem = [0u8; 4]; - // Would overflow the checked variant; NC must succeed silently. adrp_prel_pg_hi21_nc_reloc(0x0, 0x1_0000_0000_0000, &mut mem, 0).unwrap(); } -// ── ADD lo12 (AddAbsLo12Nc) ─────────────────────────────────────────────── +#[test] +fn adrp_nonzero_offset() { + // base=0x0FFC, offset=4 → place=0x1000 (page 0x1000) + // target=0x3ABC → page=0x3000; imm21=2 + let mut mem = [0u8; 8]; + mem[4..8].copy_from_slice(&ADRP.to_le_bytes()); + adrp_prel_pg_hi21_reloc(0x0FFC, 0x3ABC, &mut mem, 4).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 4)), 2); +} + +// ═══════════════════════════════════════════════════════════════════════ +// add_abs_lo12_nc (target, mem, offset) +// +// imm12 = target & 0xFFF (no overflow check) +// ═══════════════════════════════════════════════════════════════════════ #[test] fn add_abs_lo12_nc_basic() { - let mut mem = 0x9100_0020u32.to_le_bytes().to_vec(); + let mut mem = insn_buf(ADD); add_abs_lo_12_nc_reloc(0, 0x1ABC, &mut mem, 0).unwrap(); - assert_eq!(insn_imm12_add(u32_at(&mem, 0)), 0xABC); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC); +} + +#[test] +fn add_abs_lo12_nc_zero() { + let mut mem = insn_buf(ADD); + add_abs_lo_12_nc_reloc(0, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0); +} + +#[test] +fn add_abs_lo12_nc_max_field() { + let mut mem = insn_buf(ADD); + add_abs_lo_12_nc_reloc(0, 0xFFF, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFFF); } #[test] fn add_abs_lo12_nc_masks_high_bits() { - let mut mem = 0x9100_0020u32.to_le_bytes().to_vec(); - add_abs_lo_12_nc_reloc(0, 0xFFFF_1234, &mut mem, 0).unwrap(); - assert_eq!(insn_imm12_add(u32_at(&mem, 0)), 0x234); + let mut mem = insn_buf(ADD); + add_abs_lo_12_nc_reloc(0, 0xDEAD_BEEF_FFFF_1234, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x234); } -// ── Control-flow relocations ────────────────────────────────────────────── +// ═══════════════════════════════════════════════════════════════════════ +// tst_br14 (base, target, mem, offset) +// +// imm14 = (target − place) / 4 +// Signed 14-bit word offset → byte range [−(1<<15), (1<<15)−4] +// ═══════════════════════════════════════════════════════════════════════ #[test] -fn tst_br14_basic() { - let mut mem = 0x3600_0000u32.to_le_bytes().to_vec(); +fn tst_br14_forward() { + // place=0x1000; diff=8; imm14=2 + let mut mem = insn_buf(TBZ); tst_br14_reloc(0x1000, 0x1008, &mut mem, 0).unwrap(); - assert_eq!(insn_imm14(u32_at(&mem, 0)), 2); + assert_eq!(dec_imm14(u32_at(&mem, 0)), 2); +} + +#[test] +fn tst_br14_backward() { + // place=0x1008; diff=−8; imm14=−2 + let mut mem = insn_buf(TBZ); + tst_br14_reloc(0x1008, 0x1000, &mut mem, 0).unwrap(); + assert_eq!(dec_imm14(u32_at(&mem, 0)), -2); +} + +#[test] +fn tst_br14_max_positive() { + // imm14 max = (1<<13)−1 = 8191 word offsets → byte diff = 0x7FFC + let mut mem = insn_buf(TBZ); + tst_br14_reloc(0x0, 0x7FFC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm14(u32_at(&mem, 0)), (1 << 13) - 1); } #[test] -fn tst_br14_overflow() { +fn tst_br14_max_negative() { + // imm14 min = −(1<<13) → byte diff = −0x8000; place=0x8000, target=0 + let mut mem = insn_buf(TBZ); + tst_br14_reloc(0x8000, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm14(u32_at(&mem, 0)), -(1 << 13)); +} + +#[test] +fn tst_br14_overflow_positive() { + // byte diff=0x8000 → word imm=1<<13, one above max + let mut mem = [0u8; 4]; + assert!(matches!( + tst_br14_reloc(0x0, 0x8000, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 14, + align: 2 + })) + )); +} + +#[test] +fn tst_br14_overflow_negative() { + // place=0x8004, target=0 → byte diff=−0x8004 → one below min let mut mem = [0u8; 4]; assert!(matches!( - tst_br14_reloc(0x0, 0x1_0000, &mut mem, 0), - Err(Rel64Error::InvalidBits(..)) + tst_br14_reloc(0x8004, 0x0, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 14, + align: 2 + })) )); } @@ -310,165 +728,567 @@ fn tst_br14_overflow() { fn tst_br14_unaligned() { let mut mem = [0u8; 4]; assert!(matches!( - tst_br14_reloc(0x0, 0x1, &mut mem, 0), - Err(Rel64Error::InvalidBits(..)) + tst_br14_reloc(0x0, 0x2, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 2 })) + )); +} + +// ═══════════════════════════════════════════════════════════════════════ +// cond_br19 (base, target, mem, offset) +// +// imm19 = (target − place) / 4 +// Signed 19-bit word offset → byte range [−(1<<20), (1<<20)−4] +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn cond_br19_forward() { + // diff=4; imm19=1 + let mut mem = insn_buf(BEQ); + cond_br19_reloc(0x0, 0x0004, &mut mem, 0).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 0)), 1); +} + +#[test] +fn cond_br19_backward() { + // place=0x100; diff=−0x100; imm19=−64 + let mut mem = insn_buf(BEQ); + cond_br19_reloc(0x100, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 0)), -64); +} + +#[test] +fn cond_br19_max_positive() { + // imm19 max = (1<<18)−1 → byte diff = 0x0F_FFFC + let mut mem = insn_buf(BEQ); + cond_br19_reloc(0x0, 0x0F_FFFC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 0)), (1 << 18) - 1); +} + +#[test] +fn cond_br19_max_negative() { + // imm19 min = −(1<<18) → byte diff = −0x10_0000; place=0x10_0000, target=0 + let mut mem = insn_buf(BEQ); + cond_br19_reloc(0x10_0000, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 0)), -(1 << 18)); +} + +#[test] +fn cond_br19_overflow_positive() { + // byte diff=0x10_0000 → word imm=1<<18, one above max + let mut mem = [0u8; 4]; + assert!(matches!( + cond_br19_reloc(0x0, 0x10_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 19, + align: 2 + })) + )); +} + +#[test] +fn cond_br19_overflow_negative() { + let mut mem = [0u8; 4]; + assert!(matches!( + cond_br19_reloc(0x10_0004, 0x0, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 19, + align: 2 + })) + )); +} + +#[test] +fn cond_br19_unaligned() { + let mut mem = [0u8; 4]; + assert!(matches!( + cond_br19_reloc(0x0, 0x2, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 2 })) + )); +} + +// ═══════════════════════════════════════════════════════════════════════ +// jump26 (base, target, mem, offset) +// +// imm26 = (target − place) / 4 +// Signed 26-bit word offset → byte range [−(1<<27), (1<<27)−4] +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn jump26_forward() { + // diff=0x10; imm26=4 + let mut mem = insn_buf(B); + jump26_reloc(0x0, 0x10, &mut mem, 0).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 0)), 4); +} + +#[test] +fn jump26_backward() { + // place=0x10; diff=−0x10; imm26=−4 + let mut mem = insn_buf(B); + jump26_reloc(0x10, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 0)), -4); +} + +#[test] +fn jump26_max_positive() { + // imm26 max = (1<<25)−1 → byte diff = 0x07FF_FFFC + let mut mem = insn_buf(B); + jump26_reloc(0x0, 0x07FF_FFFC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 0)), (1 << 25) - 1); +} + +#[test] +fn jump26_max_negative() { + // imm26 min = −(1<<25) → byte diff = −0x0800_0000 + // place=0x0800_0000, target=0 + let mut mem = insn_buf(B); + jump26_reloc(0x0800_0000, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 0)), -(1 << 25)); +} + +#[test] +fn jump26_overflow_positive() { + // byte diff=0x0800_0000 → word imm=1<<25, one above max + let mut mem = [0u8; 4]; + assert!(matches!( + jump26_reloc(0x0, 0x0800_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 26, + align: 2 + })) + )); +} + +#[test] +fn jump26_overflow_negative() { + // place=0x0800_0004, target=0 → one below min + let mut mem = [0u8; 4]; + assert!(matches!( + jump26_reloc(0x0800_0004, 0x0, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 26, + align: 2 + })) )); } #[test] -fn cond_br19_basic() { - let mut mem = 0x5400_0000u32.to_le_bytes().to_vec(); - cond_br19_reloc(0x0000, 0x0004, &mut mem, 0).unwrap(); - assert_eq!(insn_imm19(u32_at(&mem, 0)), 1); +fn jump26_unaligned() { + let mut mem = [0u8; 4]; + assert!(matches!( + jump26_reloc(0x0, 0x2, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 2 })) + )); } +// ═══════════════════════════════════════════════════════════════════════ +// call26 (base, target, mem, offset) +// ═══════════════════════════════════════════════════════════════════════ + #[test] -fn cond_br19_negative() { - let mut mem = 0x5400_0000u32.to_le_bytes().to_vec(); - cond_br19_reloc(0x0100, 0x0000, &mut mem, 0).unwrap(); - assert_eq!(insn_imm19(u32_at(&mem, 0)), -64); +fn call26_forward() { + let mut mem = insn_buf(BL); + call26_reloc(0x0, 0x0004, &mut mem, 0).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 0)), 1); } #[test] -fn jump26_basic() { - let mut mem = 0x1400_0000u32.to_le_bytes().to_vec(); - jump26_reloc(0x0000, 0x0010, &mut mem, 0).unwrap(); - assert_eq!(insn_imm26(u32_at(&mem, 0)), 4); +fn call26_backward() { + // place=4; diff=−4; imm26=−1 + let mut mem = insn_buf(BL); + call26_reloc(0x4, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 0)), -1); } #[test] -fn jump26_overflow() { +fn call26_overflow_positive() { let mut mem = [0u8; 4]; assert!(matches!( - jump26_reloc(0x0, 0x1000_0000, &mut mem, 0), - Err(Rel64Error::InvalidBits(..)) + call26_reloc(0x0, 0x0800_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 26, + align: 2 + })) )); } #[test] -fn call26_basic() { - let mut mem = 0x9400_0000u32.to_le_bytes().to_vec(); - call26_reloc(0x0000, 0x0004, &mut mem, 0).unwrap(); - assert_eq!(insn_imm26(u32_at(&mem, 0)), 1); +fn call26_overflow_negative() { + let mut mem = [0u8; 4]; + assert!(matches!( + call26_reloc(0x0800_0004, 0x0, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 26, + align: 2 + })) + )); } #[test] -fn call26_names_itself_in_error() { - // jump26 and call26 share implementation but must report distinct names. +fn call26_unaligned() { let mut mem = [0u8; 4]; assert!(matches!( - call26_reloc(0x0, 0x1000_0000, &mut mem, 0), - Err(Rel64Error::InvalidBits(..)) + call26_reloc(0x0, 0x2, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 2 })) )); } -// ── MOVW relocations ────────────────────────────────────────────────────── +// ═══════════════════════════════════════════════════════════════════════ +// MOV_W relocations (target, mem, offset) +// +// Unsigned Gk: S[k*16+15 : k*16] → imm16 +// Overflow if any bit above chunk k is set (except NC variants). +// Signed Gk: same chunk, sign-checked to (k+1)*16 bits. +// Negative → MOVN with ~chunk; non-negative → MOVZ. +// ═══════════════════════════════════════════════════════════════════════ -// MOVZ X0, #0: sf=1 opc=10 hw=00 → 0xD2800000 -const MOVZ_X0: u32 = 0xD280_0000; +// ── G0 ─────────────────────────────────────────────────────────────── #[test] fn mov_w_abs_g0_basic() { - let mut mem = MOVZ_X0.to_le_bytes().to_vec(); + let mut mem = insn_buf(MOVZ); mov_w_abs_g0_reloc(0xABCD, &mut mem, 0).unwrap(); - assert_eq!(insn_mov_w_imm(u32_at(&mem, 0)), 0xABCD); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xABCD); +} + +#[test] +fn mov_w_abs_g0_max() { + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0_reloc(0xFFFF, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xFFFF); } #[test] fn mov_w_abs_g0_overflow() { - let mut mem = MOVZ_X0.to_le_bytes().to_vec(); + let mut mem = insn_buf(MOVZ); assert!(matches!( mov_w_abs_g0_reloc(0x1_0000, &mut mem, 0), - Err(Rel64Error::InvalidValue(..)) + Err(Rel64Error::InvalidValue(_)) + )); +} + +// ── G0 NC ───────────────────────────────────────────────────────────── + +#[test] +fn mov_w_abs_g0nc_basic() { + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0nc_reloc(0x1234, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0x1234); +} + +#[test] +fn mov_w_abs_g0nc_strips_high_bits() { + // Only S[15:0] written; upper bits silently discarded + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0nc_reloc(0xDEAD_BEEF_0000_1234, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0x1234); +} + +// ── G0 S ────────────────────────────────────────────────────────────── + +#[test] +fn mov_w_abs_g0s_positive() { + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0s_reloc(0x1234, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVZ); + assert_eq!(dec_movw(i), 0x1234); +} + +#[test] +fn mov_w_abs_g0s_max_positive() { + // 0x7FFF = i16::MAX — largest that fits + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0s_reloc(0x7FFF, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVZ); + assert_eq!(dec_movw(i), 0x7FFF); +} + +#[test] +fn mov_w_abs_g0s_minus_one() { + // MOVN imm=0 encodes −1 + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0s_reloc(-1, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVN); + assert_eq!(dec_movw(i), 0x0000); // ~(−1)[15:0] = 0 +} + +#[test] +fn mov_w_abs_g0s_max_negative() { + // i16::MIN = −0x8000; imm = ~(−0x8000)[15:0] = 0x7FFF + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0s_reloc(-0x8000, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVN); + assert_eq!(dec_movw(i), 0x7FFF); +} + +#[test] +fn mov_w_abs_g0s_negative_value() { + // −0x1234; imm = ~(−0x1234)[15:0] = 0x1233 + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0s_reloc(-0x1234, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVN); + assert_eq!(dec_movw(i), 0x1233); +} + +#[test] +fn mov_w_abs_g0s_overflow_positive() { + // 0x8000 > i16::MAX + let mut mem = insn_buf(MOVZ); + assert!(matches!( + mov_w_abs_g0s_reloc(0x8000, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) )); } #[test] -fn mov_w_abs_g0nc_no_overflow() { - let mut mem = MOVZ_X0.to_le_bytes().to_vec(); - mov_w_abs_g0nc_reloc(0xDEAD_BEEF_0000_1234u64, &mut mem, 0).unwrap(); - assert_eq!(insn_mov_w_imm(u32_at(&mem, 0)), 0x1234); +fn mov_w_abs_g0s_overflow_negative() { + // −0x8001 < i16::MIN + let mut mem = insn_buf(MOVZ); + assert!(matches!( + mov_w_abs_g0s_reloc(-0x8001, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); } +// ── G1 ─────────────────────────────────────────────────────────────── + #[test] fn mov_w_abs_g1_basic() { - let mut mem = MOVZ_X0.to_le_bytes().to_vec(); - mov_w_abs_g1_reloc(0x0001_0000, &mut mem, 0).unwrap(); - assert_eq!(insn_mov_w_imm(u32_at(&mem, 0)), 0x0001); + // S[31:16] of 0xABCD_0000 = 0xABCD + let mut mem = insn_buf(MOVZ); + mov_w_abs_g1_reloc(0xABCD_0000, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xABCD); +} + +#[test] +fn mov_w_abs_g1_max() { + // 0xFFFF_FFFF — max value; S[31:16] = 0xFFFF + let mut mem = insn_buf(MOVZ); + mov_w_abs_g1_reloc(0xFFFF_FFFF, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xFFFF); } #[test] fn mov_w_abs_g1_overflow() { - let mut mem = MOVZ_X0.to_le_bytes().to_vec(); + let mut mem = insn_buf(MOVZ); assert!(matches!( mov_w_abs_g1_reloc(0x1_0000_0000, &mut mem, 0), - Err(Rel64Error::InvalidValue(..)) + Err(Rel64Error::InvalidValue(_)) + )); +} + +// ── G1 NC ───────────────────────────────────────────────────────────── + +#[test] +fn mov_w_abs_g1nc_basic() { + let mut mem = insn_buf(MOVZ); + mov_w_abs_g1nc_reloc(0xABCD_0000, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xABCD); +} + +#[test] +fn mov_w_abs_g1nc_strips_high_bits() { + // Only S[31:16] written; bits [63:32] discarded + let mut mem = insn_buf(MOVZ); + mov_w_abs_g1nc_reloc(0xDEAD_BEEF_ABCD_0000, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xABCD); +} + +// ── G1 S ────────────────────────────────────────────────────────────── + +#[test] +fn mov_w_abs_g1s_positive() { + // 0x7FFF_0000 fits in signed 32-bit; chunk 1 = 0x7FFF + let mut mem = insn_buf(MOVZ); + mov_w_abs_g1s_reloc(0x7FFF_0000, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVZ); + assert_eq!(dec_movw(i), 0x7FFF); +} + +#[test] +fn mov_w_abs_g1s_max_positive() { + // i32::MAX = 0x7FFF_FFFF; chunk 1 = 0x7FFF + let mut mem = insn_buf(MOVZ); + mov_w_abs_g1s_reloc(0x7FFF_FFFF, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVZ); + assert_eq!(dec_movw(i), 0x7FFF); +} + +#[test] +fn mov_w_abs_g1s_negative() { + // −0x8000_0000 = i32::MIN; ~target = 0x7FFF_FFFF; chunk 1 = 0x7FFF + let mut mem = insn_buf(MOVZ); + mov_w_abs_g1s_reloc(-0x8000_0000, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVN); + assert_eq!(dec_movw(i), 0x7FFF); +} + +#[test] +fn mov_w_abs_g1s_negative_value() { + // −0x1234_5678; ~target = 0x1234_5677; chunk 1 = 0x1234 + let mut mem = insn_buf(MOVZ); + mov_w_abs_g1s_reloc(-0x1234_5678, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVN); + assert_eq!(dec_movw(i), 0x1234); +} + +#[test] +fn mov_w_abs_g1s_overflow_positive() { + // 0x8000_0000 = i32::MAX + 1 + let mut mem = insn_buf(MOVZ); + assert!(matches!( + mov_w_abs_g1s_reloc(0x8000_0000, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) )); } +#[test] +fn mov_w_abs_g1s_overflow_negative() { + // −0x8000_0001 < i32::MIN + let mut mem = insn_buf(MOVZ); + assert!(matches!( + mov_w_abs_g1s_reloc(-0x8000_0001, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +// ── G2 ─────────────────────────────────────────────────────────────── + #[test] fn mov_w_abs_g2_basic() { - let mut mem = MOVZ_X0.to_le_bytes().to_vec(); + // S[47:32] of 0x0001_0000_0000 = 0x0001 + let mut mem = insn_buf(MOVZ); mov_w_abs_g2_reloc(0x0001_0000_0000, &mut mem, 0).unwrap(); - assert_eq!(insn_mov_w_imm(u32_at(&mem, 0)), 0x0001); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0x0001); } #[test] -fn mov_w_abs_g3_basic() { - let mut mem = MOVZ_X0.to_le_bytes().to_vec(); - mov_w_abs_g3_reloc(0xBEEF_0000_0000_0000u64, &mut mem, 0).unwrap(); - assert_eq!(insn_mov_w_imm(u32_at(&mem, 0)), 0xBEEF); +fn mov_w_abs_g2_max() { + // 0xFFFF_FFFF_FFFF — max value; S[47:32] = 0xFFFF + let mut mem = insn_buf(MOVZ); + mov_w_abs_g2_reloc(0xFFFF_FFFF_FFFF, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xFFFF); } #[test] -fn mov_w_abs_g0s_positive() { - let mut mem = MOVZ_X0.to_le_bytes().to_vec(); - mov_w_abs_g0s_reloc(0x1234, &mut mem, 0).unwrap(); - let insn = u32_at(&mem, 0); - assert_eq!(movw_opc(insn), MOVW_OPC_MOVZ); - assert_eq!(insn_mov_w_imm(insn), 0x1234); +fn mov_w_abs_g2_overflow() { + let mut mem = insn_buf(MOVZ); + assert!(matches!( + mov_w_abs_g2_reloc(0x0001_0000_0000_0000, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); } +// ── G2 NC ───────────────────────────────────────────────────────────── + #[test] -fn mov_w_abs_g0s_minus_one() { - // -1: MOVN X0, #0 encodes ~0 = -1 - let mut mem = MOVZ_X0.to_le_bytes().to_vec(); - mov_w_abs_g0s_reloc(-1i64, &mut mem, 0).unwrap(); - let insn = u32_at(&mem, 0); - assert_eq!(movw_opc(insn), MOVW_OPC_MOVN); - assert_eq!(insn_mov_w_imm(insn), 0x0000); +fn mov_w_abs_g2nc_basic() { + // S[47:32] of 0xDEAD_1234_0000_0000 = 0x1234 + let mut mem = insn_buf(MOVZ); + mov_w_abs_g2nc_reloc(0xDEAD_1234_0000_0000, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0x1234); } #[test] -fn mov_w_abs_g0s_negative_value() { - // -0x1234: MOVN with imm = ~(-0x1234)[15:0] = 0x1233 - let mut mem = MOVZ_X0.to_le_bytes().to_vec(); - mov_w_abs_g0s_reloc(-0x1234, &mut mem, 0).unwrap(); - let insn = u32_at(&mem, 0); - assert_eq!(movw_opc(insn), MOVW_OPC_MOVN); - assert_eq!(insn_mov_w_imm(insn), 0x1233); +fn mov_w_abs_g2nc_strips_high_bits() { + // Bits [63:48] discarded without error + let mut mem = insn_buf(MOVZ); + mov_w_abs_g2nc_reloc(0xFFFF_ABCD_0000_0000, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xABCD); } +// ── G2 S ────────────────────────────────────────────────────────────── + #[test] -fn mov_w_abs_g1s_negative() { - // -0x8000_0000: chunk 1 of ~(-0x8000_0000) >> 16 = 0x7FFF - let mut mem = MOVZ_X0.to_le_bytes().to_vec(); - mov_w_abs_g1s_reloc(-0x8000_0000, &mut mem, 0).unwrap(); - let insn = u32_at(&mem, 0); - assert_eq!(movw_opc(insn), MOVW_OPC_MOVN); - assert_eq!(insn_mov_w_imm(insn), 0x7FFF); +fn mov_w_abs_g2s_positive() { + // 0x0000_1234_0000_0000 fits in signed 48-bit; chunk 2 = 0x1234 + let mut mem = insn_buf(MOVZ); + mov_w_abs_g2s_reloc(0x0000_1234_0000_0000_i64, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVZ); + assert_eq!(dec_movw(i), 0x1234); +} + +#[test] +fn mov_w_abs_g2s_negative() { + // −0x1234_0000_0000; ~target = 0x1233_FFFF_FFFF; chunk 2 = 0x1233 + let mut mem = insn_buf(MOVZ); + mov_w_abs_g2s_reloc(-0x1234_0000_0000_i64, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVN); + assert_eq!(dec_movw(i), 0x1233); } #[test] -fn mov_w_abs_g0s_overflow() { - let mut mem = MOVZ_X0.to_le_bytes().to_vec(); +fn mov_w_abs_g2s_overflow_positive() { + // 2^47 — one above signed 48-bit max (2^47 − 1) + let mut mem = insn_buf(MOVZ); assert!(matches!( - mov_w_abs_g0s_reloc(0x1_0000, &mut mem, 0), - Err(Rel64Error::InvalidValue { .. }) + mov_w_abs_g2s_reloc(0x0000_8000_0000_0000_u64 as i64, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) )); } -// ── Tag dispatch ────────────────────────────────────────────────────────── +#[test] +fn mov_w_abs_g2s_overflow_negative() { + // −(2^47 + 1) — one below signed 48-bit min (−2^47) + let mut mem = insn_buf(MOVZ); + assert!(matches!( + mov_w_abs_g2s_reloc(-0x8000_0000_0001_i64, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +// ── G3 ─────────────────────────────────────────────────────────────── + +#[test] +fn mov_w_abs_g3_basic() { + // S[63:48] of 0xBEEF_0000_0000_0000 = 0xBEEF + let mut mem = insn_buf(MOVZ); + mov_w_abs_g3_reloc(0xBEEF_0000_0000_0000, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xBEEF); +} + +#[test] +fn mov_w_abs_g3_max() { + let mut mem = insn_buf(MOVZ); + mov_w_abs_g3_reloc(0xFFFF_0000_0000_0000, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xFFFF); +} + +#[test] +fn mov_w_abs_g3_zero() { + let mut mem = insn_buf(MOVZ); + mov_w_abs_g3_reloc(0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0x0000); +} + +// ── Cross-cutting: bits outside the immediate field are preserved ───── + +#[test] +fn patch_preserves_non_immediate_fields() { + // MOVK X5, #0 (opc=11, Rd=5) → 0xF280_0005 + let movk_x5: u32 = 0xF280_0005; + let mut mem = movk_x5.to_le_bytes().to_vec(); + mov_w_abs_g0nc_reloc(0xABCD, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(dec_movw(i), 0xABCD); + assert_eq!(mov_w_opc(i), 0b11); // opc preserved + assert_eq!(i & 0x1F, 5); // Rd preserved +} + +// ═══════════════════════════════════════════════════════════════════════ +// Rel64Tag::apply — checks dispatch and the place = base + offset formula +// ═══════════════════════════════════════════════════════════════════════ #[test] fn tag_none_is_noop() { @@ -486,21 +1306,84 @@ fn tag_abs64_roundtrip() { assert_eq!(u64_at(&mem, 0), 0x1122_3344_5566_7788); } +#[test] +fn tag_abs32_via_apply() { + let mut mem = [0u8; 4]; + Rel64Tag::Abs32.apply(0, 0xDEAD_BEEF, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0), 0xDEAD_BEEF); +} + +#[test] +fn tag_prel32_via_apply() { + // base=0, offset=0 → place=0; diff=i32::MAX + let mut mem = [0u8; 4]; + Rel64Tag::PRel32 + .apply(0x0, 0x7FFF_FFFF, &mut mem, 0) + .unwrap(); + assert_eq!(u32_at(&mem, 0) as i32, i32::MAX); +} + #[test] fn tag_jump26_via_apply() { - let mut mem = 0x1400_0000u32.to_le_bytes().to_vec(); - // base=0, offset=0 → place=0; target=8 → imm26=2 + // base=0, offset=0 → place=0; diff=8; imm26=2 + let mut mem = insn_buf(B); Rel64Tag::Jump26.apply(0x0, 0x0008, &mut mem, 0).unwrap(); - assert_eq!(insn_imm26(u32_at(&mem, 0)), 2); + assert_eq!(dec_imm26(u32_at(&mem, 0)), 2); +} + +#[test] +fn tag_call26_via_apply() { + // base=0, offset=0 → place=0; diff=4; imm26=1 + let mut mem = insn_buf(BL); + Rel64Tag::Call26.apply(0x0, 0x0004, &mut mem, 0).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 0)), 1); } #[test] fn tag_adrp_via_apply() { - let mut mem = [0u8; 4]; // base=0x1100, offset=0 → place=0x1100 (page 0x1000) - // target=0x3500 (page 0x3000) → page diff = 2 + // target=0x3500 → page 0x3000; imm21=2 + let mut mem = [0u8; 4]; Rel64Tag::AdrPrelPgHi21 .apply(0x1100, 0x3500, &mut mem, 0) .unwrap(); - assert_eq!(insn_imm21_adr(u32_at(&mem, 0)), 2); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +#[test] +fn tag_mov_w_g1nc_via_apply() { + let mut mem = insn_buf(MOVZ); + Rel64Tag::MovWAbsG1Nc + .apply(0, 0xDEAD_BEEF_ABCD_0000, &mut mem, 0) + .unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xABCD); +} + +#[test] +fn tag_mov_w_g2nc_via_apply() { + let mut mem = insn_buf(MOVZ); + Rel64Tag::MovWAbsG2Nc + .apply(0, 0xDEAD_1234_0000_0000, &mut mem, 0) + .unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0x1234); +} + +#[test] +fn tag_mov_w_g3_via_apply() { + let mut mem = insn_buf(MOVZ); + Rel64Tag::MovWAbsG3 + .apply(0, 0xBEEF_0000_0000_0000, &mut mem, 0) + .unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xBEEF); +} + +#[test] +fn tag_place_is_base_plus_offset() { + // Verifies apply() uses place = base + offset, not just base. + // B at mem[4]; base=0x0FFC, offset=4 → place=0x1000 + // target=0x1010; diff=0x10; imm26=4 + let mut mem = [0u8; 8]; + mem[4..8].copy_from_slice(&B.to_le_bytes()); + Rel64Tag::Jump26.apply(0x0FFC, 0x1010, &mut mem, 4).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 4)), 4); } diff --git a/harm/src/reloc/control.rs b/harm/src/reloc/control.rs index 87a62df..0984b46 100644 --- a/harm/src/reloc/control.rs +++ b/harm/src/reloc/control.rs @@ -5,7 +5,7 @@ use aarchmrs_types::InstructionCode; -use super::Rel64Error; +use super::{Rel64Error, calc_offset}; use crate::instructions::control::{BranchCondOffset, BranchOffset, TestBranchOffset}; use crate::reloc::get_bytes_mut; @@ -24,7 +24,7 @@ pub fn jump26_reloc( mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { - let jump_offset64 = target.wrapping_sub(base).cast_signed(); + let jump_offset64 = calc_offset(base, target, offset)?; let jump_offset = BranchOffset::new_i64(jump_offset64)?; let bytes = get_bytes_mut(mem, offset)?; let code = InstructionCode(*bytes).unpack(); @@ -55,7 +55,7 @@ pub fn tst_br14_reloc( mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { - let tst_offset64 = target.wrapping_sub(base).cast_signed(); + let tst_offset64 = calc_offset(base, target, offset)?; let tst_offset = TestBranchOffset::new_i64(tst_offset64)?; let bytes = get_bytes_mut(mem, offset)?; let code = InstructionCode(*bytes).unpack(); @@ -76,7 +76,7 @@ pub fn cond_br19_reloc( mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { - let cond_offset64 = target.wrapping_sub(base).cast_signed(); + let cond_offset64 = calc_offset(base, target, offset)?; let cond_offset = BranchCondOffset::new_i64(cond_offset64)?; let bytes = get_bytes_mut(mem, offset)?; let code = InstructionCode(*bytes).unpack(); From 6625179eb389c837711a4515da1fe562928d2cee Mon Sep 17 00:00:00 2001 From: Ivan Boldyrev Date: Sun, 8 Mar 2026 02:25:27 +0100 Subject: [PATCH 06/13] fix memory Err values --- harm/src/reloc.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/harm/src/reloc.rs b/harm/src/reloc.rs index 4e93e0a..c6b2204 100644 --- a/harm/src/reloc.rs +++ b/harm/src/reloc.rs @@ -295,10 +295,11 @@ fn get_bytes_mut( offset: usize, ) -> Result<&mut [u8; N], Rel64Error> { let mem_chunk = mem - .get_mut(offset..(offset + N)) + .get_mut(offset..) .ok_or(Rel64Error::InvalidOffset { offset })?; let bytes: &mut [u8; N] = mem_chunk - .as_mut_array() + .get_mut(..N) + .and_then(|chunk| chunk.as_mut_array()) .ok_or(Rel64Error::NotEnoughMemory { offset })?; Ok(bytes) } @@ -311,7 +312,7 @@ fn calc_offset(base: u64, target: u64, offset: usize) -> Result Date: Sun, 8 Mar 2026 11:43:01 +0100 Subject: [PATCH 07/13] Fixing adrp relocations --- harm/src/reloc.rs | 19 +++++ harm/src/reloc/addr.rs | 6 +- harm/src/reloc/claude_tests.rs | 130 +++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+), 3 deletions(-) diff --git a/harm/src/reloc.rs b/harm/src/reloc.rs index c6b2204..f1e1eb8 100644 --- a/harm/src/reloc.rs +++ b/harm/src/reloc.rs @@ -317,6 +317,25 @@ fn calc_offset(base: u64, target: u64, offset: usize) -> Result Result { + const PAGE_MASK: u64 = !0xfff; + let offset64 = offset + .try_into() + .map_err(|_e| Rel64Error::InvalidOffset { offset })?; + + let instruction_addr = base + .checked_add(offset64) + .ok_or(Rel64Error::InvalidOffset { offset })?; + + Ok((value & PAGE_MASK) + .wrapping_sub(instruction_addr & PAGE_MASK) + .cast_signed()) +} + #[cfg(test)] mod claude_tests; diff --git a/harm/src/reloc/addr.rs b/harm/src/reloc/addr.rs index ecf4877..0fd46a0 100644 --- a/harm/src/reloc/addr.rs +++ b/harm/src/reloc/addr.rs @@ -7,8 +7,8 @@ use aarchmrs_types::InstructionCode; use super::{Rel64Error, cond_br19_reloc}; use crate::instructions::dpimm::{AdrOffset, AdrpOffset}; -use crate::reloc::calc_offset; use crate::reloc::get_bytes_mut; +use crate::reloc::{calc_offset, calc_page_offset}; const ADR_ADRP_IMMHI_OFFSET: u32 = 5u32; const ADR_ADRP_IMMHI_WIDTH: u32 = 19u32; @@ -54,7 +54,7 @@ pub fn adrp_prel_pg_hi21_reloc( let bytes = get_bytes_mut(mem, offset)?; - let delta = calc_offset(base, target, offset)? & !0xFFF; + let delta = calc_page_offset(base, target, offset)?; let delta = AdrpOffset::new_i64(delta).map_err(Rel64Error::InvalidBits)?; patch_adr_adrp(bytes, delta.bits() & VALUE_MASK); @@ -72,7 +72,7 @@ pub fn adrp_prel_pg_hi21_nc_reloc( let bytes = get_bytes_mut(mem, offset)?; // It is not checked. - let delta = calc_offset(base, target, offset)? >> 12; + let delta = calc_page_offset(base, target, offset)? >> 12; patch_adr_adrp(bytes, (delta as u32) & VALUE_MASK); Ok(()) diff --git a/harm/src/reloc/claude_tests.rs b/harm/src/reloc/claude_tests.rs index 7a39cd0..eb54951 100644 --- a/harm/src/reloc/claude_tests.rs +++ b/harm/src/reloc/claude_tests.rs @@ -625,6 +625,136 @@ fn adrp_nonzero_offset() { assert_eq!(dec_imm21_adr(u32_at(&mem, 4)), 2); } +// Additionaly ADRP tests +#[test] +fn lo12_target_below_lo12_place_typical() { + // S+A = 0x4_300 → Page(S+A) = 0x4_000 (lo12 = 0x300) + // P = 0x2_A00 → Page(P) = 0x2_000 (lo12 = 0xA00) + // + // 0x300 < 0xA00, so formulas diverge: + // correct: (0x4_000 - 0x2_000) >> 12 = 2 + // buggy: Page(0x4_300 - 0x2_A00) = Page(0x1_900) >> 12 = 1 + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_reloc(0x2_A00, 0x4_300, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +#[test] +fn lo12_target_one_lo12_place_max() { + // Extreme: target lo12 nearly zero, place lo12 nearly 0xFFF. + // + // S+A = 0x3_001 → Page(S+A) = 0x3_000 (lo12 = 0x001) + // P = 0x1_FFF → Page(P) = 0x1_000 (lo12 = 0xFFF) + // + // correct: (0x3_000 - 0x1_000) >> 12 = 2 + // buggy: Page(0x3_001 - 0x1_FFF) = Page(0x1_002) >> 12 = 1 + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_reloc(0x1_FFF, 0x3_001, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +#[test] +fn lo12_target_zero_lo12_place_nonzero() { + // Target is page-aligned; any non-zero lo12 in place triggers the bug. + // + // S+A = 0x5_000 → Page(S+A) = 0x5_000 (lo12 = 0x000) + // P = 0x3_001 → Page(P) = 0x3_000 (lo12 = 0x001) + // + // correct: (0x5_000 - 0x3_000) >> 12 = 2 + // buggy: Page(0x5_000 - 0x3_001) = Page(0x1_FFF) >> 12 = 1 + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_reloc(0x3_001, 0x5_000, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +#[test] +fn lo12_target_below_lo12_place_negative_imm() { + // Backward reference — same divergence condition, negative result. + // + // S+A = 0x1_200 → Page(S+A) = 0x1_000 (lo12 = 0x200) + // P = 0x3_800 → Page(P) = 0x3_000 (lo12 = 0x800) + // + // correct: (0x1_000 - 0x3_000) >> 12 = -2 + // buggy: Page(0x1_200 - 0x3_800) wrapping = Page(…E400) >> 12 + // interpreted as signed: -3 ← one page off + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_reloc(0x3_800, 0x1_200, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), -2); +} + +#[test] +fn lo12_target_below_lo12_place_with_offset() { + // Confirms offset is included when computing place = base + offset. + // (With the current impl signature, pass the pre-computed place directly; + // here offset advances within the buffer, not changing the place value.) + // + // place = 0x2_600 (lo12 = 0x600) + // S+A = 0x4_300 (lo12 = 0x300) + // + // 0x300 < 0x600: + // correct: (0x4_000 - 0x2_000) >> 12 = 2 + // buggy: Page(0x4_300 - 0x2_600) = Page(0x1_D00) >> 12 = 1 + let mut mem = [0u8; 8]; + mem[4..8].copy_from_slice(&ADRP.to_le_bytes()); + adrp_prel_pg_hi21_reloc(0x2_600, 0x4_300, &mut mem, 4).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 4)), 2); +} + +// ───────────────────────────────────────────────────────────────────── +// Same diverging cases for the NC (no-overflow-check) variant +// ───────────────────────────────────────────────────────────────────── + +#[test] +fn nc_lo12_target_below_lo12_place_typical() { + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_nc_reloc(0x2_A00, 0x4_300, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +#[test] +fn nc_lo12_target_zero_lo12_place_nonzero() { + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_nc_reloc(0x3_001, 0x5_000, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +#[test] +fn nc_lo12_target_below_lo12_place_negative_imm() { + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_nc_reloc(0x3_800, 0x1_200, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), -2); +} + +// ───────────────────────────────────────────────────────────────────── +// Sanity: cases where the formulas agree (lo12 target >= lo12 place). +// Included to confirm the test logic and to show the reader why the +// failing cases above are the interesting ones. +// ───────────────────────────────────────────────────────────────────── + +#[test] +fn sanity_lo12_equal_formulas_agree() { + // lo12(S+A) = lo12(P) = 0x500 → no divergence, imm = 2 + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_reloc(0x2_500, 0x4_500, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +#[test] +fn sanity_lo12_target_above_lo12_place_formulas_agree() { + // lo12(S+A) = 0xF00 > lo12(P) = 0x100 → no divergence, imm = 2 + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_reloc(0x2_100, 0x4_F00, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +#[test] +fn sanity_page_aligned_place_formulas_always_agree() { + // lo12(P) = 0 → condition lo12(target) < lo12(place) can never hold + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_reloc(0x2_000, 0x4_300, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + // ═══════════════════════════════════════════════════════════════════════ // add_abs_lo12_nc (target, mem, offset) // From 96013ee655546fbd822259a458cab87b10ae854d Mon Sep 17 00:00:00 2001 From: Ivan Boldyrev Date: Sun, 8 Mar 2026 02:44:34 +0100 Subject: [PATCH 08/13] Comments --- harm/src/reloc.rs | 68 +++++++++++++++++----------------- harm/src/reloc/addr.rs | 32 ++++++++-------- harm/src/reloc/claude_tests.rs | 36 ++++++++++++------ harm/src/reloc/control.rs | 18 ++++----- harm/src/reloc/data.rs | 32 ++++++++-------- harm/src/reloc/movs.rs | 40 ++++++++++---------- 6 files changed, 120 insertions(+), 106 deletions(-) diff --git a/harm/src/reloc.rs b/harm/src/reloc.rs index f1e1eb8..bb8be68 100644 --- a/harm/src/reloc.rs +++ b/harm/src/reloc.rs @@ -243,13 +243,13 @@ pub enum Rel64Tag { } impl Rel64Tag { - /// Applies the relocation to the given `memory` at the given `offset`, presuming the real target address is - /// `target` and the base (starting) address of the `memory` is `base` (the can be different from real `memory` - /// location for flexibility). + /// Applies the relocation in the `memory` at the `offset`, presuming the real target address ('S+A') is `value`, + /// presuming that base (starting) address of the `memory` is `base` (the can be different from real `memory` + /// location for flexibility: the memory can be translated on another host). pub fn apply( self, base: Addr, - target: Addr, + value: Addr, memory: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { @@ -259,33 +259,33 @@ impl Rel64Tag { get_bytes_mut::<0>(memory, offset)?; Ok(()) } - Rel64Tag::Abs64 => abs64_reloc(target, memory, offset), - Rel64Tag::Abs32 => abs32_reloc(target, memory, offset), - Rel64Tag::Abs16 => abs16_reloc(target, memory, offset), - Rel64Tag::PRel64 => prel64_reloc(base, target, memory, offset), - Rel64Tag::PRel32 => prel32_reloc(base, target, memory, offset), - Rel64Tag::PRel16 => prel16_reloc(base, target, memory, offset), - - Rel64Tag::LdPrelLo19 => ld_prel_lo19_reloc(base, target, memory, offset), - Rel64Tag::AdrPrelLo21 => adr_prel_lo21_reloc(base, target, memory, offset), - Rel64Tag::AdrPrelPgHi21 => adrp_prel_pg_hi21_reloc(base, target, memory, offset), - Rel64Tag::AdrPrelPgHi21Nc => adrp_prel_pg_hi21_nc_reloc(base, target, memory, offset), - Rel64Tag::AddAbsLo12Nc => add_abs_lo_12_nc_reloc(base, target, memory, offset), - - Rel64Tag::TstBr14 => tst_br14_reloc(base, target, memory, offset), - Rel64Tag::CondBr19 => cond_br19_reloc(base, target, memory, offset), - Rel64Tag::Jump26 | Rel64Tag::Call26 => jump26_reloc(base, target, memory, offset), - - Rel64Tag::MovWAbsG0 => mov_w_abs_g0_reloc(target, memory, offset), - Rel64Tag::MovWAbsG0Nc => mov_w_abs_g0nc_reloc(target, memory, offset), - Rel64Tag::MovWAbsG0S => mov_w_abs_g0s_reloc(target.cast_signed(), memory, offset), - Rel64Tag::MovWAbsG1 => mov_w_abs_g1_reloc(target, memory, offset), - Rel64Tag::MovWAbsG1Nc => mov_w_abs_g1nc_reloc(target, memory, offset), - Rel64Tag::MovWAbsG1S => mov_w_abs_g1s_reloc(target.cast_signed(), memory, offset), - Rel64Tag::MovWAbsG2 => mov_w_abs_g2_reloc(target, memory, offset), - Rel64Tag::MovWAbsG2Nc => mov_w_abs_g2nc_reloc(target, memory, offset), - Rel64Tag::MovWAbsG2S => mov_w_abs_g2s_reloc(target.cast_signed(), memory, offset), - Rel64Tag::MovWAbsG3 => mov_w_abs_g3_reloc(target, memory, offset), + Rel64Tag::Abs64 => abs64_reloc(value, memory, offset), + Rel64Tag::Abs32 => abs32_reloc(value.cast_signed(), memory, offset), + Rel64Tag::Abs16 => abs16_reloc(value.cast_signed(), memory, offset), + Rel64Tag::PRel64 => prel64_reloc(base, value, memory, offset), + Rel64Tag::PRel32 => prel32_reloc(base, value, memory, offset), + Rel64Tag::PRel16 => prel16_reloc(base, value, memory, offset), + + Rel64Tag::LdPrelLo19 => ld_prel_lo19_reloc(base, value, memory, offset), + Rel64Tag::AdrPrelLo21 => adr_prel_lo21_reloc(base, value, memory, offset), + Rel64Tag::AdrPrelPgHi21 => adrp_prel_pg_hi21_reloc(base, value, memory, offset), + Rel64Tag::AdrPrelPgHi21Nc => adrp_prel_pg_hi21_nc_reloc(base, value, memory, offset), + Rel64Tag::AddAbsLo12Nc => add_abs_lo_12_nc_reloc(base, value, memory, offset), + + Rel64Tag::TstBr14 => tst_br14_reloc(base, value, memory, offset), + Rel64Tag::CondBr19 => cond_br19_reloc(base, value, memory, offset), + Rel64Tag::Jump26 | Rel64Tag::Call26 => jump26_reloc(base, value, memory, offset), + + Rel64Tag::MovWAbsG0 => mov_w_abs_g0_reloc(value, memory, offset), + Rel64Tag::MovWAbsG0Nc => mov_w_abs_g0nc_reloc(value, memory, offset), + Rel64Tag::MovWAbsG0S => mov_w_abs_g0s_reloc(value.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG1 => mov_w_abs_g1_reloc(value, memory, offset), + Rel64Tag::MovWAbsG1Nc => mov_w_abs_g1nc_reloc(value, memory, offset), + Rel64Tag::MovWAbsG1S => mov_w_abs_g1s_reloc(value.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG2 => mov_w_abs_g2_reloc(value, memory, offset), + Rel64Tag::MovWAbsG2Nc => mov_w_abs_g2nc_reloc(value, memory, offset), + Rel64Tag::MovWAbsG2S => mov_w_abs_g2s_reloc(value.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG3 => mov_w_abs_g3_reloc(value, memory, offset), } } } @@ -304,8 +304,8 @@ fn get_bytes_mut( Ok(bytes) } -// A function for P - S. -fn calc_offset(base: u64, target: u64, offset: usize) -> Result { +/// A function for calculating PC-relative relocation difference S - P, where P is `base + offset` and S is `value`. +pub fn calc_offset(base: u64, value: u64, offset: usize) -> Result { let offset64 = offset .try_into() .map_err(|_e| Rel64Error::InvalidOffset { offset })?; @@ -314,7 +314,7 @@ fn calc_offset(base: u64, target: u64, offset: usize) -> Result Result<(), Rel64Error> { - cond_br19_reloc(base, target, mem, offset) + cond_br19_reloc(base, symbol, mem, offset) } pub fn adr_prel_lo21_reloc( - base: u64, - target: u64, + base: Addr, + symbol: Addr, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { let bytes = get_bytes_mut(mem, offset)?; - let delta = calc_offset(base, target, offset)?; + let delta = calc_offset(base, symbol, offset)?; let delta = AdrOffset::new_i64(delta)?; patch_adr_adrp(bytes, delta.bits()); @@ -45,8 +45,8 @@ pub fn adr_prel_lo21_reloc( #[inline] pub fn adrp_prel_pg_hi21_reloc( - base: u64, - target: u64, + base: Addr, + symbol: Addr, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { @@ -54,7 +54,7 @@ pub fn adrp_prel_pg_hi21_reloc( let bytes = get_bytes_mut(mem, offset)?; - let delta = calc_page_offset(base, target, offset)?; + let delta = calc_page_offset(base, symbol, offset)?; let delta = AdrpOffset::new_i64(delta).map_err(Rel64Error::InvalidBits)?; patch_adr_adrp(bytes, delta.bits() & VALUE_MASK); @@ -63,8 +63,8 @@ pub fn adrp_prel_pg_hi21_reloc( #[inline] pub fn adrp_prel_pg_hi21_nc_reloc( - base: u64, - target: u64, + base: Addr, + symbol: Addr, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { @@ -72,7 +72,7 @@ pub fn adrp_prel_pg_hi21_nc_reloc( let bytes = get_bytes_mut(mem, offset)?; // It is not checked. - let delta = calc_page_offset(base, target, offset)? >> 12; + let delta = calc_page_offset(base, symbol, offset)? >> 12; patch_adr_adrp(bytes, (delta as u32) & VALUE_MASK); Ok(()) @@ -80,8 +80,8 @@ pub fn adrp_prel_pg_hi21_nc_reloc( #[inline] pub fn add_abs_lo_12_nc_reloc( - base: u64, - target: u64, + base: Addr, + symbol: Addr, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { @@ -91,7 +91,7 @@ pub fn add_abs_lo_12_nc_reloc( // delta is semantically a signed value, but we are using here lower bits only, // so we use an unsigned value. - let delta = calc_offset(base, target, offset)? as u32; + let delta = calc_offset(base, symbol, offset)? as u32; let mut inst_code = InstructionCode(*bytes).unpack(); inst_code &= !(MASK << ADD_IMM12_OFFSET); diff --git a/harm/src/reloc/claude_tests.rs b/harm/src/reloc/claude_tests.rs index eb54951..22c824b 100644 --- a/harm/src/reloc/claude_tests.rs +++ b/harm/src/reloc/claude_tests.rs @@ -166,22 +166,29 @@ fn abs64_max() { #[test] fn abs32_basic() { let mut mem = [0u8; 8]; - abs32_reloc(0xABCD_1234, &mut mem, 2).unwrap(); - assert_eq!(u32_at(&mem, 2), 0xABCD_1234); + abs32_reloc(0x7BCD_1234, &mut mem, 2).unwrap(); + assert_eq!(u32_at(&mem, 2), 0x7BCD_1234); +} + +#[test] +fn abs32_neg() { + let mut mem = [0u8; 4]; + abs32_reloc(-1, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0), (-1i32) as u32); } #[test] fn abs32_max() { let mut mem = [0u8; 4]; - abs32_reloc(0xFFFF_FFFF, &mut mem, 0).unwrap(); - assert_eq!(u32_at(&mem, 0), 0xFFFF_FFFF); + abs32_reloc(i32::MAX as i64, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0), i32::MAX as u32); } #[test] fn abs32_overflow() { let mut mem = [0u8; 4]; assert!(matches!( - abs32_reloc(0x1_0000_0000, &mut mem, 0), + abs32_reloc(0x1_0000_0000 as i64, &mut mem, 0), Err(Rel64Error::InvalidValue(_)) )); } @@ -193,15 +200,22 @@ fn abs32_overflow() { #[test] fn abs16_basic() { let mut mem = [0u8; 4]; - abs16_reloc(0xBEEF, &mut mem, 0).unwrap(); - assert_eq!(u16_at(&mem, 0), 0xBEEF); + abs16_reloc(0x7EEF, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0), 0x7EEF); +} + +#[test] +fn abs16_neg() { + let mut mem = [0u8; 4]; + abs16_reloc(-1, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0), (-1i16) as u16); } #[test] fn abs16_max() { let mut mem = [0u8; 2]; - abs16_reloc(0xFFFF, &mut mem, 0).unwrap(); - assert_eq!(u16_at(&mem, 0), 0xFFFF); + abs16_reloc(i16::MAX as _, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0), i16::MAX as u16); } #[test] @@ -1439,8 +1453,8 @@ fn tag_abs64_roundtrip() { #[test] fn tag_abs32_via_apply() { let mut mem = [0u8; 4]; - Rel64Tag::Abs32.apply(0, 0xDEAD_BEEF, &mut mem, 0).unwrap(); - assert_eq!(u32_at(&mem, 0), 0xDEAD_BEEF); + Rel64Tag::Abs32.apply(0, 0x7EAD_BEEF, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0), 0x7EAD_BEEF); } #[test] diff --git a/harm/src/reloc/control.rs b/harm/src/reloc/control.rs index 0984b46..6d7d063 100644 --- a/harm/src/reloc/control.rs +++ b/harm/src/reloc/control.rs @@ -5,7 +5,7 @@ use aarchmrs_types::InstructionCode; -use super::{Rel64Error, calc_offset}; +use super::{Addr, Rel64Error, calc_offset}; use crate::instructions::control::{BranchCondOffset, BranchOffset, TestBranchOffset}; use crate::reloc::get_bytes_mut; @@ -19,8 +19,8 @@ const COND_BR_IMM19_OFFSET: u32 = 5u32; const COND_BR_IMM19_WIDTH: u32 = 19u32; pub fn jump26_reloc( - base: u64, - target: u64, + base: Addr, + target: Addr, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { @@ -41,8 +41,8 @@ pub fn jump26_reloc( #[inline] pub fn call26_reloc( - base: u64, - target: u64, + base: Addr, + target: Addr, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { @@ -50,8 +50,8 @@ pub fn call26_reloc( } pub fn tst_br14_reloc( - base: u64, - target: u64, + base: Addr, + target: Addr, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { @@ -71,8 +71,8 @@ pub fn tst_br14_reloc( } pub fn cond_br19_reloc( - base: u64, - target: u64, + base: Addr, + target: Addr, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { diff --git a/harm/src/reloc/data.rs b/harm/src/reloc/data.rs index c166141..0e9795d 100644 --- a/harm/src/reloc/data.rs +++ b/harm/src/reloc/data.rs @@ -3,62 +3,62 @@ * This document is licensed under the BSD 3-clause license. */ -use super::{Rel64Error, calc_offset, get_bytes_mut}; +use super::{Addr, Rel64Error, calc_offset, get_bytes_mut}; -pub fn abs64_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { +pub fn abs64_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { let bytes = get_bytes_mut(mem, offset)?; - *bytes = target.to_le_bytes(); + *bytes = value.to_le_bytes(); Ok(()) } -pub fn abs32_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { - let target: u32 = target.try_into()?; +pub fn abs32_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target: i32 = value.try_into()?; let bytes = get_bytes_mut(mem, offset)?; *bytes = target.to_le_bytes(); Ok(()) } -pub fn abs16_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { - let target: u16 = target.try_into()?; +pub fn abs16_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target: i16 = value.try_into()?; let bytes = get_bytes_mut(mem, offset)?; *bytes = target.to_le_bytes(); Ok(()) } pub fn prel64_reloc( - base: u64, - target: u64, + base: Addr, + symbol: Addr, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { let bytes = get_bytes_mut(mem, offset)?; - *bytes = calc_offset(base, target, offset)?.to_le_bytes(); + *bytes = calc_offset(base, symbol, offset)?.to_le_bytes(); Ok(()) } pub fn prel32_reloc( - base: u64, - target: u64, + base: Addr, + symbol: Addr, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { let bytes = get_bytes_mut(mem, offset)?; - let value: i32 = calc_offset(base, target, offset)?.try_into()?; + let value: i32 = calc_offset(base, symbol, offset)?.try_into()?; *bytes = value.to_le_bytes(); Ok(()) } pub fn prel16_reloc( - base: u64, - target: u64, + base: Addr, + symbol: Addr, mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { let bytes = get_bytes_mut(mem, offset)?; - let value: i16 = calc_offset(base, target, offset)?.try_into()?; + let value: i16 = calc_offset(base, symbol, offset)?.try_into()?; *bytes = value.to_le_bytes(); Ok(()) } diff --git a/harm/src/reloc/movs.rs b/harm/src/reloc/movs.rs index 1e2ccab..d56e6ea 100644 --- a/harm/src/reloc/movs.rs +++ b/harm/src/reloc/movs.rs @@ -16,10 +16,10 @@ const MOV_OPCODE_MOVZ: u32 = 2; const MOV_IMM16_OFFSET: u32 = 5; const MOV_IMM16_WIDTH: u32 = 16; -pub fn mov_w_abs_g0_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { +pub fn mov_w_abs_g0_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; - let target: u16 = target.try_into()?; + let target: u16 = value.try_into()?; let bytes = get_bytes_mut(mem, offset)?; let mut inst_code = InstructionCode(*bytes).unpack(); @@ -30,10 +30,10 @@ pub fn mov_w_abs_g0_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result< Ok(()) } -pub fn mov_w_abs_g0nc_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { +pub fn mov_w_abs_g0nc_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; - let target = target as u16; + let target = value as u16; let bytes = get_bytes_mut(mem, offset)?; let mut inst_code = InstructionCode(*bytes).unpack(); @@ -44,11 +44,11 @@ pub fn mov_w_abs_g0nc_reloc(target: u64, mem: &mut [u8], offset: usize) -> Resul Ok(()) } -pub fn mov_w_abs_g0s_reloc(target: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { +pub fn mov_w_abs_g0s_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { const INST_MASK: u32 = (((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET) | (((1 << MOV_OPCODE_WIDTH) - 1) << MOV_OPCODE_OFFSET); - let target: i16 = target.try_into()?; + let target: i16 = value.try_into()?; let bytes = get_bytes_mut(mem, offset)?; let mut inst_code = InstructionCode(*bytes).unpack(); @@ -65,10 +65,10 @@ pub fn mov_w_abs_g0s_reloc(target: i64, mem: &mut [u8], offset: usize) -> Result Ok(()) } -pub fn mov_w_abs_g1_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { +pub fn mov_w_abs_g1_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; - let target: u16 = (target >> 16).try_into()?; + let target: u16 = (value >> 16).try_into()?; let bytes = get_bytes_mut(mem, offset)?; let mut inst_code = InstructionCode(*bytes).unpack(); @@ -79,10 +79,10 @@ pub fn mov_w_abs_g1_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result< Ok(()) } -pub fn mov_w_abs_g1nc_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { +pub fn mov_w_abs_g1nc_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; - let target = (target >> 16) as u16; + let target = (value >> 16) as u16; let bytes = get_bytes_mut(mem, offset)?; let mut inst_code = InstructionCode(*bytes).unpack(); @@ -93,11 +93,11 @@ pub fn mov_w_abs_g1nc_reloc(target: u64, mem: &mut [u8], offset: usize) -> Resul Ok(()) } -pub fn mov_w_abs_g1s_reloc(target: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { +pub fn mov_w_abs_g1s_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { const INST_MASK: u32 = (((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET) | (((1 << MOV_OPCODE_WIDTH) - 1) << MOV_OPCODE_OFFSET); - let target: i16 = (target >> 16).try_into()?; + let target: i16 = (value >> 16).try_into()?; let bytes = get_bytes_mut(mem, offset)?; let mut inst_code = InstructionCode(*bytes).unpack(); @@ -114,10 +114,10 @@ pub fn mov_w_abs_g1s_reloc(target: i64, mem: &mut [u8], offset: usize) -> Result Ok(()) } -pub fn mov_w_abs_g2_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { +pub fn mov_w_abs_g2_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; - let target: u16 = (target >> 32).try_into()?; + let target: u16 = (value >> 32).try_into()?; let bytes = get_bytes_mut(mem, offset)?; let mut inst_code = InstructionCode(*bytes).unpack(); @@ -128,10 +128,10 @@ pub fn mov_w_abs_g2_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result< Ok(()) } -pub fn mov_w_abs_g2nc_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { +pub fn mov_w_abs_g2nc_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; - let target = (target >> 32) as u16; + let target = (value >> 32) as u16; let bytes = get_bytes_mut(mem, offset)?; let mut inst_code = InstructionCode(*bytes).unpack(); @@ -142,11 +142,11 @@ pub fn mov_w_abs_g2nc_reloc(target: u64, mem: &mut [u8], offset: usize) -> Resul Ok(()) } -pub fn mov_w_abs_g2s_reloc(target: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { +pub fn mov_w_abs_g2s_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { const INST_MASK: u32 = (((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET) | (((1 << MOV_OPCODE_WIDTH) - 1) << MOV_OPCODE_OFFSET); - let target: i16 = (target >> 32).try_into()?; + let target: i16 = (value >> 32).try_into()?; let bytes = get_bytes_mut(mem, offset)?; let mut inst_code = InstructionCode(*bytes).unpack(); @@ -163,10 +163,10 @@ pub fn mov_w_abs_g2s_reloc(target: i64, mem: &mut [u8], offset: usize) -> Result Ok(()) } -pub fn mov_w_abs_g3_reloc(target: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { +pub fn mov_w_abs_g3_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; - let target = (target >> 48) as u32; + let target = (value >> 48) as u32; let bytes = get_bytes_mut(mem, offset)?; let mut inst_code = InstructionCode(*bytes).unpack(); From fe1a029204513466ae3843beabf518901cfb4efe Mon Sep 17 00:00:00 2001 From: Ivan Boldyrev Date: Sun, 8 Mar 2026 16:38:06 +0100 Subject: [PATCH 09/13] add_abs_lo_12_nc_reloc fix The relocation uses "S+A", not "S+A-P". --- harm/src/reloc.rs | 2 +- harm/src/reloc/addr.rs | 7 +-- harm/src/reloc/claude_tests.rs | 107 +++++++++++++++++++++++++++++++-- 3 files changed, 105 insertions(+), 11 deletions(-) diff --git a/harm/src/reloc.rs b/harm/src/reloc.rs index bb8be68..54b7977 100644 --- a/harm/src/reloc.rs +++ b/harm/src/reloc.rs @@ -270,7 +270,7 @@ impl Rel64Tag { Rel64Tag::AdrPrelLo21 => adr_prel_lo21_reloc(base, value, memory, offset), Rel64Tag::AdrPrelPgHi21 => adrp_prel_pg_hi21_reloc(base, value, memory, offset), Rel64Tag::AdrPrelPgHi21Nc => adrp_prel_pg_hi21_nc_reloc(base, value, memory, offset), - Rel64Tag::AddAbsLo12Nc => add_abs_lo_12_nc_reloc(base, value, memory, offset), + Rel64Tag::AddAbsLo12Nc => add_abs_lo_12_nc_reloc(value, memory, offset), Rel64Tag::TstBr14 => tst_br14_reloc(base, value, memory, offset), Rel64Tag::CondBr19 => cond_br19_reloc(base, value, memory, offset), diff --git a/harm/src/reloc/addr.rs b/harm/src/reloc/addr.rs index e9e129c..d8a1524 100644 --- a/harm/src/reloc/addr.rs +++ b/harm/src/reloc/addr.rs @@ -80,7 +80,6 @@ pub fn adrp_prel_pg_hi21_nc_reloc( #[inline] pub fn add_abs_lo_12_nc_reloc( - base: Addr, symbol: Addr, mem: &mut [u8], offset: usize, @@ -89,13 +88,9 @@ pub fn add_abs_lo_12_nc_reloc( let bytes = get_bytes_mut(mem, offset)?; - // delta is semantically a signed value, but we are using here lower bits only, - // so we use an unsigned value. - let delta = calc_offset(base, symbol, offset)? as u32; - let mut inst_code = InstructionCode(*bytes).unpack(); inst_code &= !(MASK << ADD_IMM12_OFFSET); - inst_code |= (delta & MASK) << ADD_IMM12_OFFSET; + inst_code |= (symbol as u32 & MASK) << ADD_IMM12_OFFSET; *bytes = InstructionCode::from_u32(inst_code).0; diff --git a/harm/src/reloc/claude_tests.rs b/harm/src/reloc/claude_tests.rs index 22c824b..f868792 100644 --- a/harm/src/reloc/claude_tests.rs +++ b/harm/src/reloc/claude_tests.rs @@ -778,30 +778,129 @@ fn sanity_page_aligned_place_formulas_always_agree() { #[test] fn add_abs_lo12_nc_basic() { let mut mem = insn_buf(ADD); - add_abs_lo_12_nc_reloc(0, 0x1ABC, &mut mem, 0).unwrap(); + add_abs_lo_12_nc_reloc(0x1ABC, &mut mem, 0).unwrap(); assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC); } #[test] fn add_abs_lo12_nc_zero() { let mut mem = insn_buf(ADD); - add_abs_lo_12_nc_reloc(0, 0x0, &mut mem, 0).unwrap(); + add_abs_lo_12_nc_reloc(0x0, &mut mem, 0).unwrap(); assert_eq!(dec_imm12(u32_at(&mem, 0)), 0); } #[test] fn add_abs_lo12_nc_max_field() { let mut mem = insn_buf(ADD); - add_abs_lo_12_nc_reloc(0, 0xFFF, &mut mem, 0).unwrap(); + add_abs_lo_12_nc_reloc(0xFFF, &mut mem, 0).unwrap(); assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFFF); } #[test] fn add_abs_lo12_nc_masks_high_bits() { let mut mem = insn_buf(ADD); - add_abs_lo_12_nc_reloc(0, 0xDEAD_BEEF_FFFF_1234, &mut mem, 0).unwrap(); + add_abs_lo_12_nc_reloc(0xDEAD_BEEF_FFFF_1234, &mut mem, 0).unwrap(); assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x234); } +// ───────────────────────────────────────────────────────────────────── +// Cases where (S+A) & 0xFFF ≠ (S+A − P) & 0xFFF +// ───────────────────────────────────────────────────────────────────── + +#[test] +fn place_nonzero_lo12_typical() { + // target = 0x2_ABC → correct imm12 = 0xABC + // place = 0x1_400 → buggy imm12 = (0x2_ABC - 0x1_400) & 0xFFF = 0x6BC + // + // base=0x1_000, offset=0x400 → place = 0x1_400 + let mut mem = [0u8; 0x404]; + mem[0x400..0x404].copy_from_slice(&ADD.to_le_bytes()); + Rel64Tag::AddAbsLo12Nc + .apply(0x1_000, 0x2_ABC, &mut mem, 0x400) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0x400)), 0xABC); +} + +#[test] +fn place_lo12_exceeds_target_lo12() { + // target = 0x2_200 → correct imm12 = 0x200 + // place = 0x1_800 → buggy imm12 = (0x2_200 - 0x1_800) & 0xFFF = 0xA00 + // + // base=0x1_000, offset=0x800 → place = 0x1_800 + let mut mem = [0u8; 0x804]; + mem[0x800..0x804].copy_from_slice(&ADD.to_le_bytes()); + Rel64Tag::AddAbsLo12Nc + .apply(0x1_000, 0x2_200, &mut mem, 0x800) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0x800)), 0x200); +} + +#[test] +fn place_lo12_equals_target_lo12() { + // target = 0x2_500 → correct imm12 = 0x500 + // place = 0x1_500 → buggy imm12 = (0x2_500 - 0x1_500) & 0xFFF = 0x000 + // + // base=0x1_000, offset=0x500 → place = 0x1_500 + let mut mem = [0u8; 0x504]; + mem[0x500..0x504].copy_from_slice(&ADD.to_le_bytes()); + Rel64Tag::AddAbsLo12Nc + .apply(0x1_000, 0x2_500, &mut mem, 0x500) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0x500)), 0x500); +} + +#[test] +fn target_page_aligned_place_not() { + // target = 0x3_000 → correct imm12 = 0x000 + // place = 0x1_ABC → buggy imm12 = (0x3_000 - 0x1_ABC) & 0xFFF = 0x544 + // + // base=0x1_000, offset=0xABC → place = 0x1_ABC + let mut mem = [0u8; 0xAC0]; + mem[0xABC..0xAC0].copy_from_slice(&ADD.to_le_bytes()); + Rel64Tag::AddAbsLo12Nc + .apply(0x1_000, 0x3_000, &mut mem, 0xABC) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0xABC)), 0x000); +} + +#[test] +fn place_from_nonzero_base_and_offset_both_contribute() { + // Checks that place = base + offset, not just base or just offset. + // + // target = 0x5_700 → correct imm12 = 0x700 + // base=0x1_200, offset=0x400 → place = 0x1_600 + // buggy: (0x5_700 - 0x1_600) & 0xFFF = 0x100 ← wrong + let mut mem = [0u8; 0x404]; + mem[0x400..0x404].copy_from_slice(&ADD.to_le_bytes()); + Rel64Tag::AddAbsLo12Nc + .apply(0x1_200, 0x5_700, &mut mem, 0x400) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0x400)), 0x700); +} + +// ───────────────────────────────────────────────────────────────────── +// Sanity: page-aligned place — both formulas agree, bug is invisible +// ───────────────────────────────────────────────────────────────────── + +#[test] +fn sanity_page_aligned_place_formulas_agree() { + // place = 0x2_000 (page-aligned) → P & 0xFFF = 0 + // Both formulas give the same result; existing tests all look like this. + let mut mem = ADD.to_le_bytes(); + Rel64Tag::AddAbsLo12Nc + .apply(0x2_000, 0x4_ABC, &mut mem, 0) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC); +} + +#[test] +fn sanity_zero_base_zero_offset_formulas_agree() { + // place = 0 → both formulas identical; this is what the original tests use. + let mut mem = ADD.to_le_bytes(); + Rel64Tag::AddAbsLo12Nc + .apply(0, 0x4_ABC, &mut mem, 0) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC); +} // ═══════════════════════════════════════════════════════════════════════ // tst_br14 (base, target, mem, offset) From b0692e08a4f8972b981fa679cfcf667c33e132cb Mon Sep 17 00:00:00 2001 From: Ivan Boldyrev Date: Mon, 9 Mar 2026 08:47:19 +0100 Subject: [PATCH 10/13] ldst_* relocations --- harm/src/instructions/ldst.rs | 1 + harm/src/reloc.rs | 60 ++++++- harm/src/reloc/addr.rs | 114 +++++++++++++ harm/src/reloc/claude_tests.rs | 289 +++++++++++++++++++++++++++++++++ 4 files changed, 463 insertions(+), 1 deletion(-) diff --git a/harm/src/instructions/ldst.rs b/harm/src/instructions/ldst.rs index 23e71aa..3a1e2c1 100644 --- a/harm/src/instructions/ldst.rs +++ b/harm/src/instructions/ldst.rs @@ -67,6 +67,7 @@ pub type ScaledOffset8 = UBitValue<12, 0>; pub type ScaledOffset16 = UBitValue<12, 1>; pub type ScaledOffset32 = UBitValue<12, 2>; pub type ScaledOffset64 = UBitValue<12, 3>; +pub type ScaledOffset128 = UBitValue<12, 4>; pub type UnscaledOffset = SBitValue<9>; diff --git a/harm/src/reloc.rs b/harm/src/reloc.rs index 54b7977..880bed0 100644 --- a/harm/src/reloc.rs +++ b/harm/src/reloc.rs @@ -55,6 +55,51 @@ impl Rel64 { } } + #[inline] + pub const fn adr_prel_lo21(label: LabelRef) -> Self { + Self::new(Rel64Tag::AdrPrelLo21, label) + } + + #[inline] + pub const fn adr_prel_pg_hi21(label: LabelRef) -> Self { + Self::new(Rel64Tag::AdrPrelPgHi21, label) + } + + #[inline] + pub const fn adr_prel_pg_hi21nc(label: LabelRef) -> Self { + Self::new(Rel64Tag::AdrPrelPgHi21Nc, label) + } + + #[inline] + pub const fn add_abs_lo12nc(label: LabelRef) -> Self { + Self::new(Rel64Tag::AddAbsLo12Nc, label) + } + + #[inline] + pub const fn ld_st8abs_lo12nc(label: LabelRef) -> Self { + Self::new(Rel64Tag::LdSt8AbsLo12Nc, label) + } + + #[inline] + pub const fn ld_st16abs_lo12nc(label: LabelRef) -> Self { + Self::new(Rel64Tag::LdSt16AbsLo12Nc, label) + } + + #[inline] + pub const fn ld_st32abs_lo12nc(label: LabelRef) -> Self { + Self::new(Rel64Tag::LdSt32AbsLo12Nc, label) + } + + #[inline] + pub const fn ld_st64abs_lo12nc(label: LabelRef) -> Self { + Self::new(Rel64Tag::LdSt64AbsLo12Nc, label) + } + + #[inline] + pub const fn ld_st128abs_lo12nc(label: LabelRef) -> Self { + Self::new(Rel64Tag::LdSt128AbsLo12Nc, label) + } + #[inline] pub const fn cond_br19(label: LabelRef) -> Self { Self { @@ -217,12 +262,19 @@ pub enum Rel64Tag { PRel32, PRel16, - // Static AArch64 relocations + // Static AArch64 address relocations LdPrelLo19, AdrPrelLo21, AdrPrelPgHi21, AdrPrelPgHi21Nc, AddAbsLo12Nc, + + LdSt8AbsLo12Nc, + LdSt16AbsLo12Nc, + LdSt32AbsLo12Nc, + LdSt64AbsLo12Nc, + LdSt128AbsLo12Nc, + // Static control flow relocations TstBr14, CondBr19, @@ -272,6 +324,12 @@ impl Rel64Tag { Rel64Tag::AdrPrelPgHi21Nc => adrp_prel_pg_hi21_nc_reloc(base, value, memory, offset), Rel64Tag::AddAbsLo12Nc => add_abs_lo_12_nc_reloc(value, memory, offset), + Rel64Tag::LdSt8AbsLo12Nc => ldst8_abs_lo12_nc_reloc(value, memory, offset), + Rel64Tag::LdSt16AbsLo12Nc => ldst16_abs_lo12_nc_reloc(value, memory, offset), + Rel64Tag::LdSt32AbsLo12Nc => ldst32_abs_lo12_nc_reloc(value, memory, offset), + Rel64Tag::LdSt64AbsLo12Nc => ldst64_abs_lo12_nc_reloc(value, memory, offset), + Rel64Tag::LdSt128AbsLo12Nc => ldst128_abs_lo12_nc_reloc(value, memory, offset), + Rel64Tag::TstBr14 => tst_br14_reloc(base, value, memory, offset), Rel64Tag::CondBr19 => cond_br19_reloc(base, value, memory, offset), Rel64Tag::Jump26 | Rel64Tag::Call26 => jump26_reloc(base, value, memory, offset), diff --git a/harm/src/reloc/addr.rs b/harm/src/reloc/addr.rs index d8a1524..2a7c3fd 100644 --- a/harm/src/reloc/addr.rs +++ b/harm/src/reloc/addr.rs @@ -7,6 +7,7 @@ use aarchmrs_types::InstructionCode; use super::{Addr, Rel64Error, cond_br19_reloc}; use crate::instructions::dpimm::{AdrOffset, AdrpOffset}; +use crate::instructions::ldst::{ScaledOffset16, ScaledOffset32, ScaledOffset64, ScaledOffset128}; use crate::reloc::get_bytes_mut; use crate::reloc::{calc_offset, calc_page_offset}; @@ -18,6 +19,30 @@ const ADR_ADRP_IMMLO_WIDTH: u32 = 2u32; const ADD_IMM12_OFFSET: u32 = 10u32; const ADD_IMM12_WIDTH: u32 = 12u32; +const LDST16_IMM12_OFFSET: u32 = 10u32; +// The value width is 12 bits to be compatible with ADRP, even though 13-bit values (12 bit value + 1 bit shift) can be +// encoded in the instruction. +const LDST16_IMM12_VALUE_WIDTH: u32 = 12u32; +const LDST16_IMM12_CLEAR_WIDTH: u32 = 12u32; + +const LDST32_IMM12_OFFSET: u32 = 10u32; +// The value width is 12 bits to be compatible with ADRP, even though 14-bit values (12 bit value + 2 bit shift) can be +// encoded in the instruction. +const LDST32_IMM12_VALUE_WIDTH: u32 = 12u32; +const LDST32_IMM12_CLEAR_WIDTH: u32 = 12u32; + +const LDST64_IMM12_OFFSET: u32 = 10u32; +// The value width is 12 bits to be compatible with ADRP, even though 15-bit values (12 bit value + 3 bit shift) can be +// encoded in the instruction. +const LDST64_IMM12_VALUE_WIDTH: u32 = 12u32; +const LDST64_IMM12_CLEAR_WIDTH: u32 = 12u32; + +const LDST128_IMM12_OFFSET: u32 = 10u32; +// The value width is 12 bits to be compatible with ADRP, even though 16-bit values (12 bit value + 4 bit shift) can be +// encoded in the instruction. +const LDST128_IMM12_VALUE_WIDTH: u32 = 12u32; +const LDST128_IMM12_CLEAR_WIDTH: u32 = 12u32; + #[inline] pub fn ld_prel_lo19_reloc( base: Addr, @@ -110,3 +135,92 @@ fn patch_adr_adrp(mem: &mut [u8; 4], checked_value: u32) { *mem = InstructionCode::from_u32(inst_code).0; } + +#[inline] +pub fn ldst8_abs_lo12_nc_reloc( + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + add_abs_lo_12_nc_reloc(symbol, mem, offset) +} + +pub fn ldst16_abs_lo12_nc_reloc( + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + const CLEAR_MASK: u32 = (1 << LDST16_IMM12_CLEAR_WIDTH) - 1; + const VALUE_MASK: u32 = (1 << LDST16_IMM12_VALUE_WIDTH) - 1; + + let bytes = get_bytes_mut(mem, offset)?; + // ... a linker should check that the value of X is aligned to a multiple of the datum size. + let symbol_bits = ScaledOffset16::new(symbol as u32 & VALUE_MASK)?; + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !(CLEAR_MASK << LDST16_IMM12_OFFSET); + inst_code |= symbol_bits.bits() << LDST16_IMM12_OFFSET; + + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) +} + +pub fn ldst32_abs_lo12_nc_reloc( + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + const CLEAR_MASK: u32 = (1 << LDST32_IMM12_CLEAR_WIDTH) - 1; + const VALUE_MASK: u32 = (1 << LDST32_IMM12_VALUE_WIDTH) - 1; + + let bytes = get_bytes_mut(mem, offset)?; + // ... a linker should check that the value of X is aligned to a multiple of the datum size. + let symbol_bits = ScaledOffset32::new(symbol as u32 & VALUE_MASK)?; + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !(CLEAR_MASK << LDST32_IMM12_OFFSET); + inst_code |= symbol_bits.bits() << LDST32_IMM12_OFFSET; + + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) +} + +pub fn ldst64_abs_lo12_nc_reloc( + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + const CLEAR_MASK: u32 = (1 << LDST64_IMM12_CLEAR_WIDTH) - 1; + const VALUE_MASK: u32 = (1 << LDST64_IMM12_VALUE_WIDTH) - 1; + + let bytes = get_bytes_mut(mem, offset)?; + // ... a linker should check that the value of X is aligned to a multiple of the datum size. + let symbol_bits = ScaledOffset64::new(symbol as u32 & VALUE_MASK)?; + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !(CLEAR_MASK << LDST64_IMM12_OFFSET); + inst_code |= symbol_bits.bits() << LDST64_IMM12_OFFSET; + + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) +} + +pub fn ldst128_abs_lo12_nc_reloc( + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + const CLEAR_MASK: u32 = (1 << LDST128_IMM12_CLEAR_WIDTH) - 1; + const VALUE_MASK: u32 = (1 << LDST128_IMM12_VALUE_WIDTH) - 1; + + let bytes = get_bytes_mut(mem, offset)?; + // ... a linker should check that the value of X is aligned to a multiple of the datum size. + let symbol_bits = ScaledOffset128::new(symbol as u32 & VALUE_MASK)?; + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !(CLEAR_MASK << LDST128_IMM12_OFFSET); + inst_code |= symbol_bits.bits() << LDST128_IMM12_OFFSET; + + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) +} diff --git a/harm/src/reloc/claude_tests.rs b/harm/src/reloc/claude_tests.rs index f868792..24d1e1c 100644 --- a/harm/src/reloc/claude_tests.rs +++ b/harm/src/reloc/claude_tests.rs @@ -1630,3 +1630,292 @@ fn tag_place_is_base_plus_offset() { Rel64Tag::Jump26.apply(0x0FFC, 0x1010, &mut mem, 4).unwrap(); assert_eq!(dec_imm26(u32_at(&mem, 4)), 4); } + +// ═══════════════════════════════════════════════════════════════════════ +// ldst{8,16,32,64,128}_abs_lo12_nc (target, mem, offset) +// +// imm12 = (target & 0xFFF) >> shift +// where shift = 0 / 1 / 2 / 3 / 4 for 8/16/32/64/128-bit access. +// UnalignedValue if the low `shift` bits of target are non-zero. +// No overflow check (NC): high bits above 12 are silently discarded. +// +// Instruction skeletons (LDR Rt, [Rn, #0]): +// LDRB W0,[X1] → 0x3940_0020 (8-bit) +// LDRH W0,[X1] → 0x7940_0020 (16-bit) +// LDR W0,[X1] → 0xB940_0020 (32-bit) +// LDR X0,[X1] → 0xF940_0020 (64-bit) +// LDR Q0,[X1] → 0x3DC0_0020 (128-bit) +// ═══════════════════════════════════════════════════════════════════════ + +// Skeleton instruction words for load/store unsigned-offset forms. +const LDRB: u32 = 0x3940_0020; // LDRB W0, [X1, #0] +const LDRH: u32 = 0x7940_0020; // LDRH W0, [X1, #0] +const LDR_W: u32 = 0xB940_0020; // LDR W0, [X1, #0] +const LDR_X: u32 = 0xF940_0020; // LDR X0, [X1, #0] +const LDR_Q: u32 = 0x3DC0_0020; // LDR Q0, [X1, #0] (128-bit) + +// ── ldst8 (shift = 0, no alignment requirement) ─────────────────────── + +#[test] +fn ldst8_basic() { + let mut mem = insn_buf(LDRB); + ldst8_abs_lo12_nc_reloc(0x1ABC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC); +} + +#[test] +fn ldst8_odd_target() { + // Byte access: odd addresses are valid. + let mut mem = insn_buf(LDRB); + ldst8_abs_lo12_nc_reloc(0x1, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x001); +} + +#[test] +fn ldst8_max_field() { + let mut mem = insn_buf(LDRB); + ldst8_abs_lo12_nc_reloc(0xFFF, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFFF); +} + +#[test] +fn ldst8_masks_high_bits() { + let mut mem = insn_buf(LDRB); + ldst8_abs_lo12_nc_reloc(0xDEAD_BEEF_CAFE_1234, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x234); +} + +// ── ldst16 (shift = 1, must be 2-byte aligned) ──────────────────────── + +#[test] +fn ldst16_basic() { + // target = 0x1_ABC (even) → imm12 = 0xABC >> 1 = 0x55E + let mut mem = insn_buf(LDRH); + ldst16_abs_lo12_nc_reloc(0x1_ABC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC >> 1); +} + +#[test] +fn ldst16_max_field() { + // Max aligned lo12 = 0xFFE → imm12 = 0x7FF + let mut mem = insn_buf(LDRH); + ldst16_abs_lo12_nc_reloc(0xFFE, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x7FF); +} + +#[test] +fn ldst16_masks_high_bits() { + // Only lo12 matters; high bits discarded before shift. + let mut mem = insn_buf(LDRH); + ldst16_abs_lo12_nc_reloc(0xDEAD_BEEF_CAFE_1234, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x234 >> 1); +} + +#[test] +fn ldst16_unaligned() { + let mut mem = [0u8; 4]; + assert!(matches!( + ldst16_abs_lo12_nc_reloc(0x1, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 1 })) + )); +} + +// ── ldst32 (shift = 2, must be 4-byte aligned) ──────────────────────── + +#[test] +fn ldst32_basic() { + // target = 0x1_ABC, but 0xABC & 3 = 0 → only multiples of 4 are valid. + // Use 0x1_ABC & !3 = 0x1_AB8 → wait, 0xABC = 0b1010_1011_1100, & 3 = 0 ✓ + // Actually 0xABC = 2748, 2748 % 4 = 0 ✓ + let mut mem = insn_buf(LDR_W); + ldst32_abs_lo12_nc_reloc(0x1_ABC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC >> 2); +} + +#[test] +fn ldst32_max_field() { + // Max aligned lo12 = 0xFFC → imm12 = 0x3FF + let mut mem = insn_buf(LDR_W); + ldst32_abs_lo12_nc_reloc(0xFFC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x3FF); +} + +#[test] +fn ldst32_masks_high_bits() { + let mut mem = insn_buf(LDR_W); + ldst32_abs_lo12_nc_reloc(0xDEAD_BEEF_CAFE_1234, &mut mem, 0).unwrap(); + // 0x234 & !3 = 0x234 (already aligned); 0x234 >> 2 = 0x8D + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x234 >> 2); +} + +#[test] +fn ldst32_unaligned_by_1() { + let mut mem = [0u8; 4]; + assert!(matches!( + ldst32_abs_lo12_nc_reloc(0x1, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 2 })) + )); +} + +#[test] +fn ldst32_unaligned_by_2() { + let mut mem = [0u8; 4]; + assert!(matches!( + ldst32_abs_lo12_nc_reloc(0x2, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 2 })) + )); +} + +// ── ldst64 (shift = 3, must be 8-byte aligned) ──────────────────────── + +#[test] +fn ldst64_basic() { + // 0xFF8 & 7 = 0 ✓ → imm12 = 0xFF8 >> 3 = 0x1FF + let mut mem = insn_buf(LDR_X); + ldst64_abs_lo12_nc_reloc(0xFF8, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFF8 >> 3); +} + +#[test] +fn ldst64_max_field() { + // Max aligned lo12 = 0xFF8 → imm12 = 0x1FF + let mut mem = insn_buf(LDR_X); + ldst64_abs_lo12_nc_reloc(0xFF8, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x1FF); +} + +#[test] +fn ldst64_masks_high_bits() { + // 0x1_000_000_FF8: lo12 = 0xFF8, already 8-aligned; imm12 = 0x1FF + let mut mem = insn_buf(LDR_X); + ldst64_abs_lo12_nc_reloc(0x1_0000_0000_0FF8, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFF8 >> 3); +} + +#[test] +fn ldst64_unaligned() { + let mut mem = [0u8; 4]; + assert!(matches!( + ldst64_abs_lo12_nc_reloc(0x4, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 3 })) + )); +} + +// ── ldst128 (shift = 4, must be 16-byte aligned) ────────────────────── + +#[test] +fn ldst128_basic() { + // 0xFF0 & 15 = 0 ✓ → imm12 = 0xFF0 >> 4 = 0xFF + let mut mem = insn_buf(LDR_Q); + ldst128_abs_lo12_nc_reloc(0xFF0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFF); +} + +#[test] +fn ldst128_max_field() { + // Max aligned lo12 = 0xFF0 → imm12 = 0xFF + let mut mem = insn_buf(LDR_Q); + ldst128_abs_lo12_nc_reloc(0xFF0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFF); +} + +#[test] +fn ldst128_masks_high_bits() { + let mut mem = insn_buf(LDR_Q); + ldst128_abs_lo12_nc_reloc(0xDEAD_BEEF_0001_0FF0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFF0 >> 4); +} + +#[test] +fn ldst128_unaligned() { + let mut mem = [0u8; 4]; + assert!(matches!( + ldst128_abs_lo12_nc_reloc(0x8, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 4 })) + )); +} + +// ── Cross-cutting: non-immediate bits are preserved ─────────────────── + +#[test] +fn ldst_patch_preserves_non_imm12_fields() { + // Use LDR X5, [X3, #0]: size=11, V=0, opc=01, Rn=3, Rt=5 → 0xF940_0065 + let ldr_x5_x3: u32 = 0xF940_0065; + let mut mem = ldr_x5_x3.to_le_bytes(); + ldst64_abs_lo12_nc_reloc(0x1_0000_0000_0FF8, &mut mem, 0).unwrap(); + let insn = u32_at(&mem, 0); + assert_eq!(dec_imm12(insn), 0xFF8 >> 3); + assert_eq!(insn & 0xFC00_03FF, ldr_x5_x3 & 0xFC00_03FF); // size/opc/Rn/Rt preserved +} + +// ── Tag dispatch ────────────────────────────────────────────────────── + +#[test] +fn tag_ldst8_via_apply() { + let mut mem = insn_buf(LDRB); + Rel64Tag::LdSt8AbsLo12Nc + .apply(0, 0xABC, &mut mem, 0) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC); +} + +#[test] +fn tag_ldst16_via_apply() { + let mut mem = insn_buf(LDRH); + Rel64Tag::LdSt16AbsLo12Nc + .apply(0, 0xABC, &mut mem, 0) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC >> 1); +} + +#[test] +fn tag_ldst32_via_apply() { + let mut mem = insn_buf(LDR_W); + Rel64Tag::LdSt32AbsLo12Nc + .apply(0, 0xABC, &mut mem, 0) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC >> 2); +} + +#[test] +fn tag_ldst64_via_apply() { + let mut mem = insn_buf(LDR_X); + Rel64Tag::LdSt64AbsLo12Nc + .apply(0, 0xFF8, &mut mem, 0) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFF8 >> 3); +} + +#[test] +fn tag_ldst128_via_apply() { + let mut mem = insn_buf(LDR_Q); + Rel64Tag::LdSt128AbsLo12Nc + .apply(0, 0xFF0, &mut mem, 0) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFF0 >> 4); +} + +#[test] +fn tag_ldst_unaligned_via_apply() { + // Alignment is enforced through the tag dispatcher too. + let mut mem = [0u8; 4]; + assert!(matches!( + Rel64Tag::LdSt64AbsLo12Nc.apply(0, 0x4, &mut mem, 0), + Err(Rel64Error::InvalidBits(_)) + )); +} + +// ── Contrast with AddAbsLo12Nc (same imm12 field, shift=0) ─────────── + +#[test] +fn ldst8_and_add_produce_identical_imm12() { + // LdSt8 is shift=0, identical formula to AddAbsLo12Nc. + let mut mem_ldst = insn_buf(LDRB); + let mut mem_add = insn_buf(ADD); + ldst8_abs_lo12_nc_reloc(0x1_ABC, &mut mem_ldst, 0).unwrap(); + add_abs_lo_12_nc_reloc(0x1_ABC, &mut mem_add, 0).unwrap(); + assert_eq!( + dec_imm12(u32_at(&mem_ldst, 0)), + dec_imm12(u32_at(&mem_add, 0)) + ); +} From 84471d1c82d97855bd855179c7e55d683b5000e5 Mon Sep 17 00:00:00 2001 From: Ivan Boldyrev Date: Tue, 10 Mar 2026 22:35:22 +0100 Subject: [PATCH 11/13] Harmonize function names --- harm/src/reloc.rs | 2 +- harm/src/reloc/addr.rs | 4 ++-- harm/src/reloc/claude_tests.rs | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/harm/src/reloc.rs b/harm/src/reloc.rs index 880bed0..b109c43 100644 --- a/harm/src/reloc.rs +++ b/harm/src/reloc.rs @@ -322,7 +322,7 @@ impl Rel64Tag { Rel64Tag::AdrPrelLo21 => adr_prel_lo21_reloc(base, value, memory, offset), Rel64Tag::AdrPrelPgHi21 => adrp_prel_pg_hi21_reloc(base, value, memory, offset), Rel64Tag::AdrPrelPgHi21Nc => adrp_prel_pg_hi21_nc_reloc(base, value, memory, offset), - Rel64Tag::AddAbsLo12Nc => add_abs_lo_12_nc_reloc(value, memory, offset), + Rel64Tag::AddAbsLo12Nc => add_abs_lo12_nc_reloc(value, memory, offset), Rel64Tag::LdSt8AbsLo12Nc => ldst8_abs_lo12_nc_reloc(value, memory, offset), Rel64Tag::LdSt16AbsLo12Nc => ldst16_abs_lo12_nc_reloc(value, memory, offset), diff --git a/harm/src/reloc/addr.rs b/harm/src/reloc/addr.rs index 2a7c3fd..d57c174 100644 --- a/harm/src/reloc/addr.rs +++ b/harm/src/reloc/addr.rs @@ -104,7 +104,7 @@ pub fn adrp_prel_pg_hi21_nc_reloc( } #[inline] -pub fn add_abs_lo_12_nc_reloc( +pub fn add_abs_lo12_nc_reloc( symbol: Addr, mem: &mut [u8], offset: usize, @@ -142,7 +142,7 @@ pub fn ldst8_abs_lo12_nc_reloc( mem: &mut [u8], offset: usize, ) -> Result<(), Rel64Error> { - add_abs_lo_12_nc_reloc(symbol, mem, offset) + add_abs_lo12_nc_reloc(symbol, mem, offset) } pub fn ldst16_abs_lo12_nc_reloc( diff --git a/harm/src/reloc/claude_tests.rs b/harm/src/reloc/claude_tests.rs index 24d1e1c..91cb70c 100644 --- a/harm/src/reloc/claude_tests.rs +++ b/harm/src/reloc/claude_tests.rs @@ -778,28 +778,28 @@ fn sanity_page_aligned_place_formulas_always_agree() { #[test] fn add_abs_lo12_nc_basic() { let mut mem = insn_buf(ADD); - add_abs_lo_12_nc_reloc(0x1ABC, &mut mem, 0).unwrap(); + add_abs_lo12_nc_reloc(0x1ABC, &mut mem, 0).unwrap(); assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC); } #[test] fn add_abs_lo12_nc_zero() { let mut mem = insn_buf(ADD); - add_abs_lo_12_nc_reloc(0x0, &mut mem, 0).unwrap(); + add_abs_lo12_nc_reloc(0x0, &mut mem, 0).unwrap(); assert_eq!(dec_imm12(u32_at(&mem, 0)), 0); } #[test] fn add_abs_lo12_nc_max_field() { let mut mem = insn_buf(ADD); - add_abs_lo_12_nc_reloc(0xFFF, &mut mem, 0).unwrap(); + add_abs_lo12_nc_reloc(0xFFF, &mut mem, 0).unwrap(); assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFFF); } #[test] fn add_abs_lo12_nc_masks_high_bits() { let mut mem = insn_buf(ADD); - add_abs_lo_12_nc_reloc(0xDEAD_BEEF_FFFF_1234, &mut mem, 0).unwrap(); + add_abs_lo12_nc_reloc(0xDEAD_BEEF_FFFF_1234, &mut mem, 0).unwrap(); assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x234); } // ───────────────────────────────────────────────────────────────────── @@ -1913,7 +1913,7 @@ fn ldst8_and_add_produce_identical_imm12() { let mut mem_ldst = insn_buf(LDRB); let mut mem_add = insn_buf(ADD); ldst8_abs_lo12_nc_reloc(0x1_ABC, &mut mem_ldst, 0).unwrap(); - add_abs_lo_12_nc_reloc(0x1_ABC, &mut mem_add, 0).unwrap(); + add_abs_lo12_nc_reloc(0x1_ABC, &mut mem_add, 0).unwrap(); assert_eq!( dec_imm12(u32_at(&mem_ldst, 0)), dec_imm12(u32_at(&mem_add, 0)) From e549c5db9e136e5b0f87882448a7e69d279e6461 Mon Sep 17 00:00:00 2001 From: Ivan Boldyrev Date: Sat, 14 Mar 2026 18:06:54 +0100 Subject: [PATCH 12/13] Misc --- harm/src/reloc.rs | 29 +++++++--- harm/src/reloc/movs.rs | 122 ++++++++++------------------------------- 2 files changed, 49 insertions(+), 102 deletions(-) diff --git a/harm/src/reloc.rs b/harm/src/reloc.rs index b109c43..53b34e7 100644 --- a/harm/src/reloc.rs +++ b/harm/src/reloc.rs @@ -1,8 +1,24 @@ -/* Copyright (C) 2025 Ivan Boldyrev +/* Copyright (C) 2026 Ivan Boldyrev * * This document is licensed under the BSD 3-clause license. */ +//! AArch64 instruction relocations. +//! +//! This module defines relocation types used by the `harm` project, and code to apply them to instructions. +//! +//! There relocations follow the static AArch64 ELF relocation types. It is important to note that while the spec +//! uses `S+A` for destination address, this module assumes that `A` is already added to the symbol address. +//! +//! The functions do not require the memory to be in place. The functions' parameters are: +//! - `base`: the base (starting) address of the memory. This can be different from the real memory location for +//! flexibility: the memory can be moved to final location later, even to another host. +//! - `value`: the real target address ('S+A'). +//! - `memory`: the memory mutable slice to apply the relocation to. +//! - `offset`: the offset in the memory to apply the relocation at. +//! +//! So, `P` in the spec is `base + offset`, and the memory to be modified starts from `&memory[offset]`. + mod addr; mod control; mod data; @@ -28,7 +44,6 @@ pub type Offset = i64; pub type Addr = u64; -// b_cond(Cond, LabelRef) #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct LabelRef { pub id: LabelId, @@ -213,7 +228,7 @@ impl Rel64 { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum Rel64Error { NotEnoughMemory { offset: usize }, InvalidOffset { offset: usize }, @@ -250,7 +265,7 @@ impl From for Rel64Error { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Rel64Tag { None, // Static data relocations @@ -279,9 +294,8 @@ pub enum Rel64Tag { TstBr14, CondBr19, Jump26, - Call26, // same as Jump26 actually? + Call26, // same as Jump26 actually - // TODO `MOVW` and some `add`/`ldst`-related relocations MovWAbsG0, MovWAbsG0Nc, MovWAbsG0S, @@ -297,7 +311,7 @@ pub enum Rel64Tag { impl Rel64Tag { /// Applies the relocation in the `memory` at the `offset`, presuming the real target address ('S+A') is `value`, /// presuming that base (starting) address of the `memory` is `base` (the can be different from real `memory` - /// location for flexibility: the memory can be translated on another host). + /// location for flexibility: the memory can be moved to real destination later). pub fn apply( self, base: Addr, @@ -454,7 +468,6 @@ mod tests { Rel64Tag::PRel64 .apply(0x1000, 0x123456789abcdef0, &mut mem, 0) .unwrap(); - // TODO is it correct? assert_eq!( mem, 0x123456789abcdef0u64.wrapping_sub(0x1000).to_le_bytes() diff --git a/harm/src/reloc/movs.rs b/harm/src/reloc/movs.rs index d56e6ea..3e49a7b 100644 --- a/harm/src/reloc/movs.rs +++ b/harm/src/reloc/movs.rs @@ -17,138 +17,88 @@ const MOV_IMM16_OFFSET: u32 = 5; const MOV_IMM16_WIDTH: u32 = 16; pub fn mov_w_abs_g0_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { - const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; - let target: u16 = value.try_into()?; let bytes = get_bytes_mut(mem, offset)?; - - let mut inst_code = InstructionCode(*bytes).unpack(); - inst_code &= !INST_MASK; - inst_code |= (target as u32) << MOV_IMM16_OFFSET; - *bytes = InstructionCode::from_u32(inst_code).0; - + patch_mov(target, bytes); Ok(()) } pub fn mov_w_abs_g0nc_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { - const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; - let target = value as u16; let bytes = get_bytes_mut(mem, offset)?; - - let mut inst_code = InstructionCode(*bytes).unpack(); - inst_code &= !INST_MASK; - inst_code |= (target as u32) << MOV_IMM16_OFFSET; - *bytes = InstructionCode::from_u32(inst_code).0; - + patch_mov(target, bytes); Ok(()) } pub fn mov_w_abs_g0s_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { - const INST_MASK: u32 = (((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET) - | (((1 << MOV_OPCODE_WIDTH) - 1) << MOV_OPCODE_OFFSET); - let target: i16 = value.try_into()?; let bytes = get_bytes_mut(mem, offset)?; - - let mut inst_code = InstructionCode(*bytes).unpack(); - inst_code &= !INST_MASK; - if target < 0 { - inst_code |= ((!target as u16) as u32) << MOV_IMM16_OFFSET; - inst_code |= MOV_OPCODE_MOVN << MOV_OPCODE_OFFSET; - } else { - inst_code |= ((target as u16) as u32) << MOV_IMM16_OFFSET; - inst_code |= MOV_OPCODE_MOVZ << MOV_OPCODE_OFFSET; - } - *bytes = InstructionCode::from_u32(inst_code).0; - + patch_mov_signed(target, bytes); Ok(()) } pub fn mov_w_abs_g1_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { - const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; - let target: u16 = (value >> 16).try_into()?; let bytes = get_bytes_mut(mem, offset)?; - - let mut inst_code = InstructionCode(*bytes).unpack(); - inst_code &= !INST_MASK; - inst_code |= (target as u32) << MOV_IMM16_OFFSET; - *bytes = InstructionCode::from_u32(inst_code).0; - + patch_mov(target, bytes); Ok(()) } pub fn mov_w_abs_g1nc_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { - const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; - let target = (value >> 16) as u16; let bytes = get_bytes_mut(mem, offset)?; - - let mut inst_code = InstructionCode(*bytes).unpack(); - inst_code &= !INST_MASK; - inst_code |= (target as u32) << MOV_IMM16_OFFSET; - *bytes = InstructionCode::from_u32(inst_code).0; - + patch_mov(target, bytes); Ok(()) } pub fn mov_w_abs_g1s_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { - const INST_MASK: u32 = (((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET) - | (((1 << MOV_OPCODE_WIDTH) - 1) << MOV_OPCODE_OFFSET); - let target: i16 = (value >> 16).try_into()?; let bytes = get_bytes_mut(mem, offset)?; - - let mut inst_code = InstructionCode(*bytes).unpack(); - inst_code &= !INST_MASK; - if target < 0 { - inst_code |= ((!target as u16) as u32) << MOV_IMM16_OFFSET; - inst_code |= MOV_OPCODE_MOVN << MOV_OPCODE_OFFSET; - } else { - inst_code |= ((target as u16) as u32) << MOV_IMM16_OFFSET; - inst_code |= MOV_OPCODE_MOVZ << MOV_OPCODE_OFFSET; - } - *bytes = InstructionCode::from_u32(inst_code).0; - + patch_mov_signed(target, bytes); Ok(()) } pub fn mov_w_abs_g2_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { - const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; - let target: u16 = (value >> 32).try_into()?; let bytes = get_bytes_mut(mem, offset)?; - - let mut inst_code = InstructionCode(*bytes).unpack(); - inst_code &= !INST_MASK; - inst_code |= (target as u32) << MOV_IMM16_OFFSET; - *bytes = InstructionCode::from_u32(inst_code).0; - + patch_mov(target, bytes); Ok(()) } pub fn mov_w_abs_g2nc_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { - const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; - let target = (value >> 32) as u16; let bytes = get_bytes_mut(mem, offset)?; + patch_mov(target, bytes); + Ok(()) +} + +pub fn mov_w_abs_g2s_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target: i16 = (value >> 32).try_into()?; + let bytes = get_bytes_mut(mem, offset)?; + patch_mov_signed(target, bytes); + Ok(()) +} + +pub fn mov_w_abs_g3_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target = (value >> 48) as u16; + let bytes = get_bytes_mut(mem, offset)?; + patch_mov(target, bytes); + Ok(()) +} + +fn patch_mov(target: u16, bytes: &mut [u8; 4]) { + const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; let mut inst_code = InstructionCode(*bytes).unpack(); inst_code &= !INST_MASK; inst_code |= (target as u32) << MOV_IMM16_OFFSET; *bytes = InstructionCode::from_u32(inst_code).0; - - Ok(()) } -pub fn mov_w_abs_g2s_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { +fn patch_mov_signed(target: i16, bytes: &mut [u8; 4]) { const INST_MASK: u32 = (((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET) | (((1 << MOV_OPCODE_WIDTH) - 1) << MOV_OPCODE_OFFSET); - let target: i16 = (value >> 32).try_into()?; - let bytes = get_bytes_mut(mem, offset)?; - let mut inst_code = InstructionCode(*bytes).unpack(); inst_code &= !INST_MASK; if target < 0 { @@ -159,20 +109,4 @@ pub fn mov_w_abs_g2s_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result< inst_code |= MOV_OPCODE_MOVZ << MOV_OPCODE_OFFSET; } *bytes = InstructionCode::from_u32(inst_code).0; - - Ok(()) -} - -pub fn mov_w_abs_g3_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { - const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; - - let target = (value >> 48) as u32; - let bytes = get_bytes_mut(mem, offset)?; - - let mut inst_code = InstructionCode(*bytes).unpack(); - inst_code &= !INST_MASK; - inst_code |= target << MOV_IMM16_OFFSET; - *bytes = InstructionCode::from_u32(inst_code).0; - - Ok(()) } From 7f449cebe730b20584a8d54a8a611a85db0636e3 Mon Sep 17 00:00:00 2001 From: Ivan Boldyrev Date: Sun, 15 Mar 2026 19:05:24 +0100 Subject: [PATCH 13/13] Fix ABS32/ABS16/PREL32/PREL16 range check Thanks to @coderabbitai. --- aarchmrs-instructions/src/lib.rs | 2 +- harm/src/reloc.rs | 4 +- harm/src/reloc/claude_tests.rs | 79 ++++++++++++++++++++++++++++++-- harm/src/reloc/data.rs | 37 +++++++++++++-- 4 files changed, 110 insertions(+), 12 deletions(-) diff --git a/aarchmrs-instructions/src/lib.rs b/aarchmrs-instructions/src/lib.rs index 59138da..e3ed4a7 100644 --- a/aarchmrs-instructions/src/lib.rs +++ b/aarchmrs-instructions/src/lib.rs @@ -15,7 +15,7 @@ instruction variant described in the dataset, a corresponding Rust function is generated. The source code of this crate is generated by tools at the same repository at -https://github.com/monoid/harm. +. As with the original dataset, this code is licensed under BSD-3-Clause license. diff --git a/harm/src/reloc.rs b/harm/src/reloc.rs index 53b34e7..467fd42 100644 --- a/harm/src/reloc.rs +++ b/harm/src/reloc.rs @@ -13,10 +13,10 @@ //! The functions do not require the memory to be in place. The functions' parameters are: //! - `base`: the base (starting) address of the memory. This can be different from the real memory location for //! flexibility: the memory can be moved to final location later, even to another host. -//! - `value`: the real target address ('S+A'). +//! - `value`: the real target address (`S+A`). //! - `memory`: the memory mutable slice to apply the relocation to. //! - `offset`: the offset in the memory to apply the relocation at. -//! +//! //! So, `P` in the spec is `base + offset`, and the memory to be modified starts from `&memory[offset]`. mod addr; diff --git a/harm/src/reloc/claude_tests.rs b/harm/src/reloc/claude_tests.rs index 91cb70c..7fb071f 100644 --- a/harm/src/reloc/claude_tests.rs +++ b/harm/src/reloc/claude_tests.rs @@ -193,6 +193,23 @@ fn abs32_overflow() { )); } +#[test] +fn abs32_unsigned_boundary() { + // 0x8000_0000 = i32::MAX + 1: valid as an unsigned 32-bit value, + // but a buggy impl that checks fits_signed(v, 32) rejects it. + let mut mem = [0u8; 4]; + abs32_reloc(0x8000_0000, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0), 0x8000_0000); +} + +#[test] +fn abs32_unsigned_mid() { + // Mid-point of the unsigned-only half [2^31, 2^32). + let mut mem = [0u8; 4]; + abs32_reloc(0xC000_0000, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0), 0xC000_0000); +} + // ═══════════════════════════════════════════════════════════════════════ // abs16 (target, mem, offset) // ═══════════════════════════════════════════════════════════════════════ @@ -227,6 +244,23 @@ fn abs16_overflow() { )); } +#[test] +fn abs16_unsigned_boundary() { + // 0x8000 = i16::MAX + 1: valid as an unsigned 16-bit value, + // but a buggy impl that checks fits_signed(v, 16) rejects it. + let mut mem = [0u8; 2]; + abs16_reloc(0x8000, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0), 0x8000); +} + +#[test] +fn abs16_unsigned_mid() { + // Mid-point of the unsigned-only half [2^15, 2^16). + let mut mem = [0u8; 2]; + abs16_reloc(0xC000, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0), 0xC000); +} + // ═══════════════════════════════════════════════════════════════════════ // prel64 (base, target, mem, offset) // @@ -268,7 +302,7 @@ fn prel64_nonzero_offset() { // ═══════════════════════════════════════════════════════════════════════ // prel32 (base, target, mem, offset) // -// diff = target − place; must fit in [i32::MIN, i32::MAX] +// diff = target − place; must fit in [i32::MIN, u32::MAX] // ═══════════════════════════════════════════════════════════════════════ #[test] @@ -302,12 +336,29 @@ fn prel32_max_negative() { assert_eq!(u32_at(&mem, 0) as i32, i32::MIN); } +#[test] +fn prel32_unsigned_boundary() { + // diff = 0x8000_0000: valid as an unsigned 32-bit value; + // a buggy fits_signed(diff, 32) check incorrectly rejects it. + let mut mem = [0u8; 4]; + prel32_reloc(0x0, 0x8000_0000, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0), 0x8000_0000); +} + +#[test] +fn prel32_unsigned_max() { + // diff = 0xFFFF_FFFF = u32::MAX: the largest valid unsigned 32-bit value. + let mut mem = [0u8; 4]; + prel32_reloc(0x0, 0xFFFF_FFFF, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0), 0xFFFF_FFFF); +} + #[test] fn prel32_overflow_positive() { - // diff = 0x8000_0000 = i32::MAX + 1 + // diff = 0x1_0000_0000 = u32::MAX + 1: truly out of range. let mut mem = [0u8; 4]; assert!(matches!( - prel32_reloc(0x0, 0x8000_0000, &mut mem, 0), + prel32_reloc(0x0, 0x1_0000_0000, &mut mem, 0), Err(Rel64Error::InvalidValue(_)) )); } @@ -333,7 +384,7 @@ fn prel32_nonzero_offset() { // ═══════════════════════════════════════════════════════════════════════ // prel16 (base, target, mem, offset) // -// diff must fit in [i16::MIN, i16::MAX] +// diff must fit in [i16::MIN, u16::MAX] // ═══════════════════════════════════════════════════════════════════════ #[test] @@ -367,11 +418,29 @@ fn prel16_max_negative() { assert_eq!(u16_at(&mem, 0) as i16, i16::MIN); } +#[test] +fn prel16_unsigned_boundary() { + // diff = 0x8000: valid as an unsigned 16-bit value; + // a buggy fits_signed(diff, 16) check incorrectly rejects it. + let mut mem = [0u8; 2]; + prel16_reloc(0x0, 0x8000, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0), 0x8000); +} + +#[test] +fn prel16_unsigned_max() { + // diff = 0xFFFF = u16::MAX: the largest valid unsigned 16-bit value. + let mut mem = [0u8; 2]; + prel16_reloc(0x0, 0xFFFF, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0), 0xFFFF); +} + #[test] fn prel16_overflow_positive() { + // diff = 0x1_0000 = u16::MAX + 1: truly out of range. let mut mem = [0u8; 2]; assert!(matches!( - prel16_reloc(0x0, 0x8000, &mut mem, 0), + prel16_reloc(0x0, 0x1_0000, &mut mem, 0), Err(Rel64Error::InvalidValue(_)) )); } diff --git a/harm/src/reloc/data.rs b/harm/src/reloc/data.rs index 0e9795d..2e24c2b 100644 --- a/harm/src/reloc/data.rs +++ b/harm/src/reloc/data.rs @@ -12,14 +12,14 @@ pub fn abs64_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel6 } pub fn abs32_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { - let target: i32 = value.try_into()?; + let target: u32 = try_unsigned_and_signed::(value)?; let bytes = get_bytes_mut(mem, offset)?; *bytes = target.to_le_bytes(); Ok(()) } pub fn abs16_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { - let target: i16 = value.try_into()?; + let target: u16 = try_unsigned_and_signed::(value)?; let bytes = get_bytes_mut(mem, offset)?; *bytes = target.to_le_bytes(); Ok(()) @@ -45,7 +45,8 @@ pub fn prel32_reloc( ) -> Result<(), Rel64Error> { let bytes = get_bytes_mut(mem, offset)?; - let value: i32 = calc_offset(base, symbol, offset)?.try_into()?; + let delta = calc_offset(base, symbol, offset)?; + let value = try_unsigned_and_signed::(delta)?; *bytes = value.to_le_bytes(); Ok(()) } @@ -58,7 +59,35 @@ pub fn prel16_reloc( ) -> Result<(), Rel64Error> { let bytes = get_bytes_mut(mem, offset)?; - let value: i16 = calc_offset(base, symbol, offset)?.try_into()?; + let delta = calc_offset(base, symbol, offset)?; + let value = try_unsigned_and_signed::(delta)?; *bytes = value.to_le_bytes(); Ok(()) } + +fn try_unsigned_and_signed(value: i64) -> Result +where + T1: TryFrom, + T2: TryFrom, + Rel64Error: From<>::Error>, + T2: AsPrimitive, +{ + Ok(T1::try_from(value).or_else(|_| T2::try_from(value).map(AsPrimitive::as_primitive))?) +} + +// It repeats partially the num-traits crate, but I want the crate to be leaner. +trait AsPrimitive { + fn as_primitive(self) -> Other; +} + +impl AsPrimitive for i32 { + fn as_primitive(self) -> u32 { + self as u32 + } +} + +impl AsPrimitive for i16 { + fn as_primitive(self) -> u16 { + self as u16 + } +}