1use crate::stencil_model::RelocKind;
7
8#[derive(Debug, PartialEq, Eq)]
10pub enum PatchError {
11 OutOfRange {
13 kind: &'static str,
15 site: u64,
17 target: u64,
19 },
20 Misaligned {
22 kind: &'static str,
24 target: u64,
26 },
27 BadScale {
29 kind: &'static str,
31 target: u64,
33 scale: u8,
35 },
36}
37
38impl std::fmt::Display for PatchError {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 match self {
41 PatchError::OutOfRange { kind, site, target } => {
42 write!(f, "{kind}: target {target:#x} out of range of site {site:#x}")
43 }
44 PatchError::Misaligned { kind, target } => {
45 write!(f, "{kind}: target {target:#x} is not 4-byte aligned")
46 }
47 PatchError::BadScale { kind, target, scale } => {
48 write!(f, "{kind}: target {target:#x} not aligned for scale {scale}")
49 }
50 }
51 }
52}
53
54impl std::error::Error for PatchError {}
55
56fn read_u32(buf: &[u8], off: usize) -> u32 {
57 u32::from_le_bytes([buf[off], buf[off + 1], buf[off + 2], buf[off + 3]])
58}
59
60fn write_u32(buf: &mut [u8], off: usize, v: u32) {
61 buf[off..off + 4].copy_from_slice(&v.to_le_bytes());
62}
63
64pub fn patch_aarch64_branch26(
67 buf: &mut [u8],
68 off: usize,
69 site_addr: u64,
70 target: u64,
71) -> Result<(), PatchError> {
72 if target % 4 != 0 {
73 return Err(PatchError::Misaligned { kind: "branch26", target });
74 }
75 let delta = (target as i64).wrapping_sub(site_addr as i64);
76 if !(-(1 << 27)..(1 << 27)).contains(&delta) {
77 return Err(PatchError::OutOfRange { kind: "branch26", site: site_addr, target });
78 }
79 let imm26 = ((delta >> 2) as u32) & 0x03FF_FFFF;
80 let insn = read_u32(buf, off);
81 write_u32(buf, off, (insn & 0xFC00_0000) | imm26);
82 Ok(())
83}
84
85pub fn patch_aarch64_page21(
88 buf: &mut [u8],
89 off: usize,
90 site_addr: u64,
91 target: u64,
92) -> Result<(), PatchError> {
93 let page_delta = ((target as i64) >> 12).wrapping_sub((site_addr as i64) >> 12);
94 if !(-(1 << 20)..(1 << 20)).contains(&page_delta) {
95 return Err(PatchError::OutOfRange { kind: "page21", site: site_addr, target });
96 }
97 let imm = page_delta as u32;
98 let immlo = imm & 0b11;
99 let immhi = (imm >> 2) & 0x7FFFF;
100 let insn = read_u32(buf, off);
101 let cleared = insn & !((0b11 << 29) | (0x7FFFF << 5));
102 write_u32(buf, off, cleared | (immlo << 29) | (immhi << 5));
103 Ok(())
104}
105
106pub fn patch_aarch64_pageoff12(
109 buf: &mut [u8],
110 off: usize,
111 target: u64,
112 scale: u8,
113) -> Result<(), PatchError> {
114 let lo12 = (target & 0xFFF) as u32;
115 if scale > 0 && lo12 & ((1 << scale) - 1) != 0 {
116 return Err(PatchError::BadScale { kind: "pageoff12", target, scale });
117 }
118 let imm12 = lo12 >> scale;
119 let insn = read_u32(buf, off);
120 let cleared = insn & !(0xFFF << 10);
121 write_u32(buf, off, cleared | (imm12 << 10));
122 Ok(())
123}
124
125pub fn patch_x64_rel32(
130 buf: &mut [u8],
131 off: usize,
132 site_addr: u64,
133 target: u64,
134 addend: i64,
135) -> Result<(), PatchError> {
136 let delta = (target as i64).wrapping_add(addend).wrapping_sub(site_addr as i64);
137 let disp = i32::try_from(delta)
138 .map_err(|_| PatchError::OutOfRange { kind: "rel32", site: site_addr, target })?;
139 buf[off..off + 4].copy_from_slice(&disp.to_le_bytes());
140 Ok(())
141}
142
143pub fn patch_abs64(buf: &mut [u8], off: usize, target: u64, addend: i64) {
145 let v = (target as i64).wrapping_add(addend) as u64;
146 buf[off..off + 8].copy_from_slice(&v.to_le_bytes());
147}
148
149pub fn is_indirect(kind: RelocKind) -> bool {
152 matches!(kind, RelocKind::GotPage21 | RelocKind::GotPageOff12 | RelocKind::GotRel32)
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158
159 #[test]
162 fn b26_forward_8_bytes_encodes_0x14000002() {
163 let mut buf = vec![0x00, 0x00, 0x00, 0x14];
165 patch_aarch64_branch26(&mut buf, 0, 0, 8).unwrap();
166 assert_eq!(buf, vec![0x02, 0x00, 0x00, 0x14]);
167 }
168
169 #[test]
170 fn b26_backward_4_bytes_encodes_0x17ffffff() {
171 let mut buf = vec![0x00, 0x00, 0x00, 0x14];
173 patch_aarch64_branch26(&mut buf, 0, 4, 0).unwrap();
174 assert_eq!(buf, vec![0xFF, 0xFF, 0xFF, 0x17]);
175 }
176
177 #[test]
178 fn b26_preserves_opcode_bits_including_bl() {
179 let mut buf = vec![0x00, 0x00, 0x00, 0x94];
181 patch_aarch64_branch26(&mut buf, 0, 0x1000, 0x1010).unwrap();
182 assert_eq!(buf, vec![0x04, 0x00, 0x00, 0x94]);
183 }
184
185 #[test]
186 fn b26_range_and_alignment_errors() {
187 let mut buf = vec![0u8; 4];
188 assert!(matches!(
190 patch_aarch64_branch26(&mut buf, 0, 0, 1 << 28),
191 Err(PatchError::OutOfRange { .. })
192 ));
193 assert!(patch_aarch64_branch26(&mut buf, 0, 0, (1 << 27) - 4).is_ok());
195 assert!(matches!(
197 patch_aarch64_branch26(&mut buf, 0, 0, 6),
198 Err(PatchError::Misaligned { .. })
199 ));
200 }
201
202 #[test]
205 fn adrp_same_page_encodes_zero_imm() {
206 let mut buf = vec![0x00, 0x00, 0x00, 0x90];
208 patch_aarch64_page21(&mut buf, 0, 0x4000, 0x4FFF).unwrap();
209 assert_eq!(read_u32(&buf, 0), 0x9000_0000);
210 }
211
212 #[test]
213 fn adrp_immhi_immlo_split_matches_hand_encoding() {
214 let mut buf = vec![0x00, 0x00, 0x00, 0x90];
217 patch_aarch64_page21(&mut buf, 0, 0, 5 << 12).unwrap();
218 assert_eq!(read_u32(&buf, 0), 0x9000_0000 | (1 << 29) | (1 << 5));
219 }
220
221 #[test]
222 fn adrp_beyond_4gb_is_err() {
223 let mut buf = vec![0x00, 0x00, 0x00, 0x90];
224 assert!(matches!(
225 patch_aarch64_page21(&mut buf, 0, 0, 1 << 33),
226 Err(PatchError::OutOfRange { .. })
227 ));
228 }
229
230 #[test]
233 fn pageoff12_on_add_is_unscaled() {
234 let mut buf = vec![0x00, 0x00, 0x00, 0x91];
236 patch_aarch64_pageoff12(&mut buf, 0, 0x123, 0).unwrap();
237 assert_eq!(read_u32(&buf, 0), 0x9100_0000 | (0x123 << 10));
238 }
239
240 #[test]
241 fn pageoff12_on_ldr64_scales_offset_by_8() {
242 let mut buf = vec![0x00, 0x00, 0x40, 0xF9];
244 patch_aarch64_pageoff12(&mut buf, 0, 0x18, 3).unwrap();
245 assert_eq!(read_u32(&buf, 0), 0xF940_0000 | (3 << 10));
246 }
247
248 #[test]
249 fn pageoff12_value_unaligned_for_scale_is_err() {
250 let mut buf = vec![0x00, 0x00, 0x40, 0xF9];
251 assert!(matches!(
252 patch_aarch64_pageoff12(&mut buf, 0, 0x1C, 3),
253 Err(PatchError::BadScale { .. })
254 ));
255 }
256
257 #[test]
260 fn rel32_jmp_to_next_instruction_encodes_zero() {
261 let mut buf = vec![0xE9, 0xAA, 0xAA, 0xAA, 0xAA];
264 patch_x64_rel32(&mut buf, 1, 0x1001, 0x1005, -4).unwrap();
265 assert_eq!(&buf[1..], &[0, 0, 0, 0]);
266 }
267
268 #[test]
269 fn rel32_backward_with_normalized_addend() {
270 let mut buf = vec![0u8; 4];
273 patch_x64_rel32(&mut buf, 0, 0x2000, 0x1000, -4).unwrap();
274 assert_eq!(i32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]), -0x1004);
275 patch_x64_rel32(&mut buf, 0, 0x2000, 0x1000, -5).unwrap();
277 assert_eq!(i32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]), -0x1005);
278 }
279
280 #[test]
281 fn rel32_beyond_2gb_is_err() {
282 let mut buf = vec![0u8; 4];
283 assert!(matches!(
284 patch_x64_rel32(&mut buf, 0, 0, 1 << 32, 0),
285 Err(PatchError::OutOfRange { .. })
286 ));
287 }
288
289 #[test]
292 fn abs64_writes_little_endian_with_addend() {
293 let mut buf = vec![0u8; 8];
294 patch_abs64(&mut buf, 0, 0x1122_3344_5566_7788, 0x10);
295 assert_eq!(buf, 0x1122_3344_5566_7798u64.to_le_bytes());
296 }
297
298 fn reference_branch26(insn: u32, pc: u64, target: u64) -> u32 {
303 let delta = target.wrapping_sub(pc) as i64;
304 (insn & 0xFC00_0000) | (((delta as u64 >> 2) as u32) & 0x03FF_FFFF)
305 }
306
307 #[test]
308 fn branch26_matches_reference_encoder_across_grid() {
309 let base: u64 = 0x10_0000;
310 for k in 0..10_000u64 {
311 let delta: i64 = ((k as i64) - 5_000) * 25_036;
313 let target = (base as i64 + delta) as u64 & !3;
314 let mut buf = vec![0x00, 0x00, 0x00, 0x14];
315 patch_aarch64_branch26(&mut buf, 0, base, target).unwrap();
316 assert_eq!(
317 read_u32(&buf, 0),
318 reference_branch26(0x1400_0000, base, target),
319 "delta {delta}"
320 );
321 }
322 }
323}