Rust · 37714 bytes Raw Blame History
1 //! ARM64 Mach-O relocations — reader side.
2 //!
3 //! Sprint 3 decodes every `ARM64_RELOC_*` kind afs-as emits, normalizes
4 //! paired relocations (ADDEND + primary, SUBTRACTOR + UNSIGNED) into a
5 //! linker-friendly form, and round-trips the lot. Sprint 11 consumes this
6 //! model when it applies relocations against final output addresses.
7 //!
8 //! Wire format (Mach-O `relocation_info`, 8 bytes, little-endian):
9 //! ```text
10 //! int32 r_address; byte offset into the containing section
11 //! uint32 r_info = [r_symbolnum:24 | r_pcrel:1 | r_length:2 | r_extern:1 | r_type:4]
12 //! ```
13 //!
14 //! `afs-ld` parses the raw form below, then `parse_relocs` lifts it into the
15 //! fused `Reloc` form. Later passes (Sprint 11, Sprint 23's dead-strip) reason
16 //! about `Reloc` and never touch `RawRelocation` again.
17
18 pub mod arm64;
19
20 use std::collections::HashMap;
21
22 use crate::macho::constants::*;
23 use crate::macho::reader::{u32_le, ReadError};
24 use crate::resolve::InputId;
25
26 /// Size of one `relocation_info` on the wire.
27 pub const RAW_RELOC_SIZE: usize = 8;
28
29 /// Exact wire representation — pre-fusion, post-decoding of the bit fields.
30 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
31 pub struct RawRelocation {
32 pub r_address: i32,
33 pub r_symbolnum: u32,
34 pub r_pcrel: bool,
35 pub r_length: u8,
36 pub r_extern: bool,
37 pub r_type: u8,
38 }
39
40 impl RawRelocation {
41 pub fn parse(bytes: &[u8]) -> Result<Self, ReadError> {
42 if bytes.len() < RAW_RELOC_SIZE {
43 return Err(ReadError::Truncated {
44 need: RAW_RELOC_SIZE,
45 have: bytes.len(),
46 context: "relocation_info",
47 });
48 }
49 let r_address = i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
50 let info = u32_le(&bytes[4..8]);
51 Ok(RawRelocation {
52 r_address,
53 r_symbolnum: info & 0x00FF_FFFF,
54 r_pcrel: (info >> 24) & 1 != 0,
55 r_length: ((info >> 25) & 0b11) as u8,
56 r_extern: (info >> 27) & 1 != 0,
57 r_type: ((info >> 28) & 0x0f) as u8,
58 })
59 }
60
61 pub fn write(&self, out: &mut Vec<u8>) {
62 out.extend_from_slice(&self.r_address.to_le_bytes());
63 let info = (self.r_symbolnum & 0x00FF_FFFF)
64 | ((self.r_pcrel as u32) << 24)
65 | (((self.r_length as u32) & 0b11) << 25)
66 | ((self.r_extern as u32) << 27)
67 | (((self.r_type as u32) & 0x0f) << 28);
68 out.extend_from_slice(&info.to_le_bytes());
69 }
70 }
71
72 /// Parse `nreloc` raw relocation_info entries starting at `reloff` bytes into
73 /// the file image. Each entry is 8 bytes.
74 pub fn parse_raw_relocs(
75 file_bytes: &[u8],
76 reloff: u32,
77 nreloc: u32,
78 ) -> Result<Vec<RawRelocation>, ReadError> {
79 let start = reloff as usize;
80 let total = (nreloc as usize)
81 .checked_mul(RAW_RELOC_SIZE)
82 .ok_or(ReadError::Truncated {
83 need: usize::MAX,
84 have: file_bytes.len(),
85 context: "reloc table (nreloc × 8 overflows)",
86 })?;
87 let end = start.checked_add(total).ok_or(ReadError::Truncated {
88 need: usize::MAX,
89 have: file_bytes.len(),
90 context: "reloc table (reloff + size overflows)",
91 })?;
92 if end > file_bytes.len() {
93 return Err(ReadError::Truncated {
94 need: end,
95 have: file_bytes.len(),
96 context: "reloc table",
97 });
98 }
99 let mut out = Vec::with_capacity(nreloc as usize);
100 for i in 0..nreloc as usize {
101 let off = start + i * RAW_RELOC_SIZE;
102 out.push(RawRelocation::parse(
103 &file_bytes[off..off + RAW_RELOC_SIZE],
104 )?);
105 }
106 Ok(out)
107 }
108
109 /// Serialize raw relocs back to wire form.
110 pub fn write_raw_relocs(relocs: &[RawRelocation], out: &mut Vec<u8>) {
111 for r in relocs {
112 r.write(out);
113 }
114 }
115
116 // ---------------------------------------------------------------------------
117 // Fused Reloc form. Sprint 11's reloc-application pass consumes this.
118 // ---------------------------------------------------------------------------
119
120 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
121 pub enum RelocKind {
122 Unsigned,
123 Branch26,
124 Page21,
125 PageOff12,
126 GotLoadPage21,
127 GotLoadPageOff12,
128 PointerToGot,
129 TlvpLoadPage21,
130 TlvpLoadPageOff12,
131 /// Fused `ARM64_RELOC_SUBTRACTOR` + `ARM64_RELOC_UNSIGNED` pair — the
132 /// value stored is `minuend - subtrahend + addend`. `referent` carries
133 /// the minuend; `subtrahend` (set on the fused form only) carries the
134 /// other half.
135 Subtractor,
136 }
137
138 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
139 #[repr(u8)]
140 pub enum RelocLength {
141 Byte = 0,
142 Half = 1,
143 Word = 2,
144 Quad = 3,
145 }
146
147 impl RelocLength {
148 pub fn from_bits(v: u8) -> Option<Self> {
149 match v {
150 0 => Some(RelocLength::Byte),
151 1 => Some(RelocLength::Half),
152 2 => Some(RelocLength::Word),
153 3 => Some(RelocLength::Quad),
154 _ => None,
155 }
156 }
157
158 pub fn as_bits(self) -> u8 {
159 self as u8
160 }
161
162 pub fn byte_width(self) -> usize {
163 1 << (self as u8)
164 }
165 }
166
167 /// What a relocation references.
168 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
169 pub enum Referent {
170 /// Set when the raw `r_extern` flag is `1`. Index into the nlist table.
171 Symbol(u32),
172 /// Set when `r_extern = 0`. 1-based section index.
173 Section(u8),
174 }
175
176 /// Fused linker-facing relocation. One `Reloc` may correspond to 1-3 raw
177 /// `relocation_info` entries on the wire (ADDEND prefix, SUBTRACTOR + UNSIGNED
178 /// pair, or the combination of both).
179 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
180 pub struct Reloc {
181 pub offset: u32,
182 pub kind: RelocKind,
183 pub length: RelocLength,
184 pub pcrel: bool,
185 pub referent: Referent,
186 pub addend: i64,
187 /// Only set when `kind == Subtractor`.
188 pub subtrahend: Option<Referent>,
189 }
190
191 pub type ParsedRelocCache = HashMap<(InputId, u8), Vec<Reloc>>;
192
193 fn referent_from(raw: &RawRelocation) -> Result<Referent, ReadError> {
194 if raw.r_extern {
195 Ok(Referent::Symbol(raw.r_symbolnum))
196 } else {
197 if raw.r_symbolnum == 0 || raw.r_symbolnum > u8::MAX as u32 {
198 return Err(ReadError::BadRelocation {
199 at_offset: raw.r_address as u32,
200 reason: "non-extern reloc with out-of-range section index",
201 });
202 }
203 Ok(Referent::Section(raw.r_symbolnum as u8))
204 }
205 }
206
207 /// Sign-extend a 24-bit value (the ADDEND reloc's `r_symbolnum` field) into
208 /// a full `i32`.
209 fn sign_extend_24(v: u32) -> i32 {
210 if v & 0x0080_0000 != 0 {
211 (v | 0xFF00_0000) as i32
212 } else {
213 v as i32
214 }
215 }
216
217 fn primary_kind_from_type(t: u8) -> Option<RelocKind> {
218 match t {
219 ARM64_RELOC_UNSIGNED => Some(RelocKind::Unsigned),
220 ARM64_RELOC_BRANCH26 => Some(RelocKind::Branch26),
221 ARM64_RELOC_PAGE21 => Some(RelocKind::Page21),
222 ARM64_RELOC_PAGEOFF12 => Some(RelocKind::PageOff12),
223 ARM64_RELOC_GOT_LOAD_PAGE21 => Some(RelocKind::GotLoadPage21),
224 ARM64_RELOC_GOT_LOAD_PAGEOFF12 => Some(RelocKind::GotLoadPageOff12),
225 ARM64_RELOC_POINTER_TO_GOT => Some(RelocKind::PointerToGot),
226 ARM64_RELOC_TLVP_LOAD_PAGE21 => Some(RelocKind::TlvpLoadPage21),
227 ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => Some(RelocKind::TlvpLoadPageOff12),
228 _ => None,
229 }
230 }
231
232 /// Lift a vector of raw relocation entries into the fused `Reloc` form.
233 /// ADDEND prefixes fold into their following primary; SUBTRACTOR + UNSIGNED
234 /// pairs fold into a single `RelocKind::Subtractor`.
235 pub fn parse_relocs(raws: &[RawRelocation]) -> Result<Vec<Reloc>, ReadError> {
236 let mut out: Vec<Reloc> = Vec::with_capacity(raws.len());
237 let mut pending_addend: Option<i32> = None;
238 let mut pending_subtractor: Option<RawRelocation> = None;
239
240 for raw in raws {
241 match raw.r_type {
242 ARM64_RELOC_ADDEND => {
243 if pending_addend.is_some() {
244 return Err(ReadError::BadRelocation {
245 at_offset: raw.r_address as u32,
246 reason: "two ARM64_RELOC_ADDEND in a row",
247 });
248 }
249 if raw.r_extern {
250 return Err(ReadError::BadRelocation {
251 at_offset: raw.r_address as u32,
252 reason: "ARM64_RELOC_ADDEND must not have r_extern set",
253 });
254 }
255 pending_addend = Some(sign_extend_24(raw.r_symbolnum));
256 }
257 ARM64_RELOC_SUBTRACTOR => {
258 if pending_subtractor.is_some() {
259 return Err(ReadError::BadRelocation {
260 at_offset: raw.r_address as u32,
261 reason: "two ARM64_RELOC_SUBTRACTOR in a row",
262 });
263 }
264 pending_subtractor = Some(*raw);
265 }
266 t => {
267 let kind = primary_kind_from_type(t).ok_or(ReadError::BadRelocation {
268 at_offset: raw.r_address as u32,
269 reason: "unknown ARM64_RELOC_* type",
270 })?;
271 let length =
272 RelocLength::from_bits(raw.r_length).ok_or(ReadError::BadRelocation {
273 at_offset: raw.r_address as u32,
274 reason: "invalid r_length (must be 0..=3)",
275 })?;
276 let addend = pending_addend.take().map(|v| v as i64).unwrap_or(0);
277 let referent = referent_from(raw)?;
278
279 let (kind, subtrahend) = match pending_subtractor.take() {
280 Some(sub) => {
281 if t != ARM64_RELOC_UNSIGNED {
282 return Err(ReadError::BadRelocation {
283 at_offset: raw.r_address as u32,
284 reason: "SUBTRACTOR must be followed by UNSIGNED",
285 });
286 }
287 if sub.r_address != raw.r_address {
288 return Err(ReadError::BadRelocation {
289 at_offset: raw.r_address as u32,
290 reason: "SUBTRACTOR/UNSIGNED pair must share r_address",
291 });
292 }
293 if sub.r_length != raw.r_length {
294 return Err(ReadError::BadRelocation {
295 at_offset: raw.r_address as u32,
296 reason: "SUBTRACTOR/UNSIGNED pair must share r_length",
297 });
298 }
299 (RelocKind::Subtractor, Some(referent_from(&sub)?))
300 }
301 None => (kind, None),
302 };
303
304 out.push(Reloc {
305 offset: raw.r_address as u32,
306 kind,
307 length,
308 pcrel: raw.r_pcrel,
309 referent,
310 addend,
311 subtrahend,
312 });
313 }
314 }
315 }
316
317 if pending_addend.is_some() {
318 return Err(ReadError::BadRelocation {
319 at_offset: 0,
320 reason: "trailing ARM64_RELOC_ADDEND with no following primary",
321 });
322 }
323 if pending_subtractor.is_some() {
324 return Err(ReadError::BadRelocation {
325 at_offset: 0,
326 reason: "trailing ARM64_RELOC_SUBTRACTOR with no following UNSIGNED",
327 });
328 }
329
330 Ok(out)
331 }
332
333 // ---------------------------------------------------------------------------
334 // Round-trip encoder: fused Reloc -> raw wire entries.
335 // ---------------------------------------------------------------------------
336
337 fn ref_to_raw_parts(r: Referent) -> (u32, bool) {
338 match r {
339 Referent::Symbol(idx) => (idx & 0x00FF_FFFF, true),
340 Referent::Section(idx) => (idx as u32, false),
341 }
342 }
343
344 fn reloc_type_byte(k: RelocKind) -> u8 {
345 match k {
346 RelocKind::Unsigned => ARM64_RELOC_UNSIGNED,
347 RelocKind::Branch26 => ARM64_RELOC_BRANCH26,
348 RelocKind::Page21 => ARM64_RELOC_PAGE21,
349 RelocKind::PageOff12 => ARM64_RELOC_PAGEOFF12,
350 RelocKind::GotLoadPage21 => ARM64_RELOC_GOT_LOAD_PAGE21,
351 RelocKind::GotLoadPageOff12 => ARM64_RELOC_GOT_LOAD_PAGEOFF12,
352 RelocKind::PointerToGot => ARM64_RELOC_POINTER_TO_GOT,
353 RelocKind::TlvpLoadPage21 => ARM64_RELOC_TLVP_LOAD_PAGE21,
354 RelocKind::TlvpLoadPageOff12 => ARM64_RELOC_TLVP_LOAD_PAGEOFF12,
355 RelocKind::Subtractor => ARM64_RELOC_UNSIGNED, // the minuend half of the pair
356 }
357 }
358
359 fn encode_addend_prefix(offset: i32, addend: i64, length: u8) -> Result<RawRelocation, ReadError> {
360 const ADDEND_MIN: i64 = -0x0080_0000;
361 const ADDEND_MAX: i64 = 0x007F_FFFF;
362 if !(ADDEND_MIN..=ADDEND_MAX).contains(&addend) {
363 return Err(ReadError::BadRelocation {
364 at_offset: offset as u32,
365 reason: "addend outside 24-bit signed range (use ARM64_RELOC_ADDEND)",
366 });
367 }
368 let sym = (addend as i32) as u32 & 0x00FF_FFFF;
369 Ok(RawRelocation {
370 r_address: offset,
371 r_symbolnum: sym,
372 r_pcrel: false,
373 r_length: length,
374 r_extern: false,
375 r_type: ARM64_RELOC_ADDEND,
376 })
377 }
378
379 /// Per-kind expected `r_length` (in bit-width form). Instruction-patching
380 /// kinds are always `Word` (one 4-byte ARM64 instruction). Data kinds
381 /// (`Unsigned`, `Subtractor`, `PointerToGot`) validly carry `Word` or `Quad`.
382 fn kind_expects_word_only(k: RelocKind) -> bool {
383 !matches!(
384 k,
385 RelocKind::Unsigned | RelocKind::Subtractor | RelocKind::PointerToGot
386 )
387 }
388
389 /// Per-kind expected `r_pcrel`. `None` means the flag is unconstrained.
390 /// `PointerToGot` can be either PC-relative (32-bit delta inside code) or
391 /// absolute (64-bit pointer inside data), so we leave it unconstrained.
392 fn kind_expects_pcrel(k: RelocKind) -> Option<bool> {
393 match k {
394 RelocKind::Branch26
395 | RelocKind::Page21
396 | RelocKind::GotLoadPage21
397 | RelocKind::TlvpLoadPage21 => Some(true),
398 RelocKind::PageOff12 | RelocKind::GotLoadPageOff12 | RelocKind::TlvpLoadPageOff12 => {
399 Some(false)
400 }
401 RelocKind::Unsigned | RelocKind::Subtractor | RelocKind::PointerToGot => None,
402 }
403 }
404
405 /// Validate one fused relocation against the sizes of its containing section,
406 /// the symbol table, and the section table. Sprint 11 will additionally check
407 /// semantic constraints (range of BRANCH26 targets, etc.) when it has final
408 /// addresses.
409 pub fn validate_reloc(
410 r: &Reloc,
411 section_size: u64,
412 nsyms: u32,
413 nsects: u8,
414 ) -> Result<(), ReadError> {
415 // Offset + width must fit inside the section.
416 let width = r.length.byte_width() as u64;
417 let end = (r.offset as u64)
418 .checked_add(width)
419 .ok_or(ReadError::BadRelocation {
420 at_offset: r.offset,
421 reason: "reloc offset + width overflows u64",
422 })?;
423 if end > section_size {
424 return Err(ReadError::BadRelocation {
425 at_offset: r.offset,
426 reason: "reloc offset + width exceeds section size",
427 });
428 }
429
430 // Referent must be in range.
431 validate_referent(r.offset, r.referent, nsyms, nsects)?;
432 if let Some(sub) = r.subtrahend {
433 validate_referent(r.offset, sub, nsyms, nsects)?;
434 }
435
436 // Kind-specific length constraint.
437 if kind_expects_word_only(r.kind) && r.length != RelocLength::Word {
438 return Err(ReadError::BadRelocation {
439 at_offset: r.offset,
440 reason: "reloc kind must use Word length (4 bytes)",
441 });
442 }
443
444 // Kind-specific pcrel constraint.
445 if let Some(expected) = kind_expects_pcrel(r.kind) {
446 if r.pcrel != expected {
447 return Err(ReadError::BadRelocation {
448 at_offset: r.offset,
449 reason: "reloc kind has fixed r_pcrel value that this entry violates",
450 });
451 }
452 }
453
454 // Subtractor invariants: must have a subtrahend.
455 if r.kind == RelocKind::Subtractor && r.subtrahend.is_none() {
456 return Err(ReadError::BadRelocation {
457 at_offset: r.offset,
458 reason: "Subtractor kind requires a subtrahend",
459 });
460 }
461
462 Ok(())
463 }
464
465 fn validate_referent(
466 offset: u32,
467 referent: Referent,
468 nsyms: u32,
469 nsects: u8,
470 ) -> Result<(), ReadError> {
471 match referent {
472 Referent::Symbol(idx) => {
473 if idx >= nsyms {
474 return Err(ReadError::BadRelocation {
475 at_offset: offset,
476 reason: "r_symbolnum past end of symbol table",
477 });
478 }
479 }
480 Referent::Section(idx) => {
481 if idx == 0 || idx > nsects {
482 return Err(ReadError::BadRelocation {
483 at_offset: offset,
484 reason: "section-relative reloc with out-of-range section index",
485 });
486 }
487 }
488 }
489 Ok(())
490 }
491
492 /// Validate every entry in a section's reloc list. Short-circuits on the first
493 /// error so the diagnostic stream stays stable across re-runs.
494 pub fn validate_relocs(
495 relocs: &[Reloc],
496 section_size: u64,
497 nsyms: u32,
498 nsects: u8,
499 ) -> Result<(), ReadError> {
500 for r in relocs {
501 validate_reloc(r, section_size, nsyms, nsects)?;
502 }
503 Ok(())
504 }
505
506 /// Lower the fused `Reloc` stream back to raw wire entries.
507 /// `parse_relocs(write_relocs(r))` must produce `r` for any `r` that came out
508 /// of `parse_relocs`; `write_raw_relocs(write_relocs(r))` produces bytes that
509 /// re-read through `parse_raw_relocs` into the same raw stream.
510 pub fn write_relocs(relocs: &[Reloc]) -> Result<Vec<RawRelocation>, ReadError> {
511 let mut out = Vec::with_capacity(relocs.len() * 2);
512 for r in relocs {
513 match r.kind {
514 RelocKind::Subtractor => {
515 let sub = r.subtrahend.ok_or(ReadError::BadRelocation {
516 at_offset: r.offset,
517 reason: "Subtractor kind requires a subtrahend",
518 })?;
519 let (sub_sym, sub_ext) = ref_to_raw_parts(sub);
520 out.push(RawRelocation {
521 r_address: r.offset as i32,
522 r_symbolnum: sub_sym,
523 r_pcrel: false,
524 r_length: r.length.as_bits(),
525 r_extern: sub_ext,
526 r_type: ARM64_RELOC_SUBTRACTOR,
527 });
528 if r.addend != 0 {
529 out.push(encode_addend_prefix(
530 r.offset as i32,
531 r.addend,
532 r.length.as_bits(),
533 )?);
534 }
535 let (min_sym, min_ext) = ref_to_raw_parts(r.referent);
536 out.push(RawRelocation {
537 r_address: r.offset as i32,
538 r_symbolnum: min_sym,
539 r_pcrel: false,
540 r_length: r.length.as_bits(),
541 r_extern: min_ext,
542 r_type: ARM64_RELOC_UNSIGNED,
543 });
544 }
545 kind => {
546 if r.addend != 0 {
547 out.push(encode_addend_prefix(
548 r.offset as i32,
549 r.addend,
550 r.length.as_bits(),
551 )?);
552 }
553 let (sym, ext) = ref_to_raw_parts(r.referent);
554 out.push(RawRelocation {
555 r_address: r.offset as i32,
556 r_symbolnum: sym,
557 r_pcrel: r.pcrel,
558 r_length: r.length.as_bits(),
559 r_extern: ext,
560 r_type: reloc_type_byte(kind),
561 });
562 }
563 }
564 }
565 Ok(out)
566 }
567
568 #[cfg(test)]
569 mod tests {
570 use super::*;
571
572 #[test]
573 fn raw_reloc_round_trip_byte_equal() {
574 let raw = RawRelocation {
575 r_address: 0x24,
576 r_symbolnum: 5,
577 r_pcrel: true,
578 r_length: 2,
579 r_extern: true,
580 r_type: ARM64_RELOC_BRANCH26,
581 };
582 let mut buf = Vec::new();
583 raw.write(&mut buf);
584 assert_eq!(buf.len(), RAW_RELOC_SIZE);
585 let back = RawRelocation::parse(&buf).unwrap();
586 assert_eq!(back, raw);
587 }
588
589 #[test]
590 fn raw_reloc_preserves_all_bit_fields() {
591 // Cover every extreme value to catch shift/mask bugs.
592 let raw = RawRelocation {
593 r_address: -1, // signed negative round-trips
594 r_symbolnum: 0x00FF_FFFF, // max 24-bit value
595 r_pcrel: true,
596 r_length: 0b11,
597 r_extern: true,
598 r_type: 0x0f,
599 };
600 let mut buf = Vec::new();
601 raw.write(&mut buf);
602 let back = RawRelocation::parse(&buf).unwrap();
603 assert_eq!(back, raw);
604 }
605
606 #[test]
607 fn raw_reloc_parses_known_branch26_pattern() {
608 // BRANCH26, external, length=2, pcrel, symnum=3.
609 // r_info = 3 | (1<<24) | (2<<25) | (1<<27) | (2<<28)
610 // = 0x0000_0003 | 0x0100_0000 | 0x0400_0000 | 0x0800_0000 | 0x2000_0000
611 // = 0x2D00_0003 → little-endian bytes [03, 00, 00, 2D]
612 let bytes = [
613 0x10, 0x00, 0x00, 0x00, // r_address = 0x10
614 0x03, 0x00, 0x00, 0x2D, // r_info
615 ];
616 let raw = RawRelocation::parse(&bytes).unwrap();
617 assert_eq!(raw.r_address, 0x10);
618 assert_eq!(raw.r_symbolnum, 3);
619 assert!(raw.r_pcrel);
620 assert_eq!(raw.r_length, 2);
621 assert!(raw.r_extern);
622 assert_eq!(raw.r_type, ARM64_RELOC_BRANCH26);
623 }
624
625 #[test]
626 fn raw_reloc_truncated_errors() {
627 let err = RawRelocation::parse(&[0u8; 4]).unwrap_err();
628 assert!(matches!(
629 err,
630 ReadError::Truncated {
631 need: RAW_RELOC_SIZE,
632 have: 4,
633 ..
634 }
635 ));
636 }
637
638 #[test]
639 fn parse_raw_relocs_reads_consecutive_entries() {
640 let r1 = RawRelocation {
641 r_address: 0x4,
642 r_symbolnum: 1,
643 r_pcrel: true,
644 r_length: 2,
645 r_extern: true,
646 r_type: ARM64_RELOC_BRANCH26,
647 };
648 let r2 = RawRelocation {
649 r_address: 0x10,
650 r_symbolnum: 2,
651 r_pcrel: false,
652 r_length: 3,
653 r_extern: true,
654 r_type: ARM64_RELOC_UNSIGNED,
655 };
656 let mut file = vec![0u8; 32]; // plant at offset 16
657 let mut plant = Vec::new();
658 r1.write(&mut plant);
659 r2.write(&mut plant);
660 file[16..16 + plant.len()].copy_from_slice(&plant);
661
662 let parsed = parse_raw_relocs(&file, 16, 2).unwrap();
663 assert_eq!(parsed, vec![r1, r2]);
664
665 let mut reemit = Vec::new();
666 write_raw_relocs(&parsed, &mut reemit);
667 assert_eq!(reemit, plant);
668 }
669
670 #[test]
671 fn parse_raw_relocs_oob_errors() {
672 let file = vec![0u8; 8];
673 // Ask for 2 × 8 = 16 bytes starting at 0; we only have 8.
674 let err = parse_raw_relocs(&file, 0, 2).unwrap_err();
675 assert!(matches!(err, ReadError::Truncated { .. }));
676 }
677
678 // ---------- fused Reloc tests ----------
679
680 fn rel(ty: u8, addr: i32, symnum: u32, ext: bool, pcrel: bool, len: u8) -> RawRelocation {
681 RawRelocation {
682 r_address: addr,
683 r_symbolnum: symnum,
684 r_pcrel: pcrel,
685 r_length: len,
686 r_extern: ext,
687 r_type: ty,
688 }
689 }
690
691 #[test]
692 fn parse_branch26_simple() {
693 let raws = vec![rel(ARM64_RELOC_BRANCH26, 0x10, 3, true, true, 2)];
694 let parsed = parse_relocs(&raws).unwrap();
695 assert_eq!(parsed.len(), 1);
696 let r = parsed[0];
697 assert_eq!(r.kind, RelocKind::Branch26);
698 assert_eq!(r.length, RelocLength::Word);
699 assert!(r.pcrel);
700 assert_eq!(r.referent, Referent::Symbol(3));
701 assert_eq!(r.addend, 0);
702 assert_eq!(r.subtrahend, None);
703 }
704
705 #[test]
706 fn parse_page_pageoff_pair() {
707 let raws = vec![
708 rel(ARM64_RELOC_PAGE21, 0x10, 5, true, true, 2),
709 rel(ARM64_RELOC_PAGEOFF12, 0x14, 5, true, false, 2),
710 ];
711 let parsed = parse_relocs(&raws).unwrap();
712 assert_eq!(parsed.len(), 2);
713 assert_eq!(parsed[0].kind, RelocKind::Page21);
714 assert_eq!(parsed[1].kind, RelocKind::PageOff12);
715 assert!(parsed[0].pcrel);
716 assert!(!parsed[1].pcrel);
717 }
718
719 #[test]
720 fn parse_addend_prefix_folds_positive() {
721 let raws = vec![
722 rel(ARM64_RELOC_ADDEND, 0x10, 0x1000, false, false, 2),
723 rel(ARM64_RELOC_PAGEOFF12, 0x10, 7, true, false, 2),
724 ];
725 let parsed = parse_relocs(&raws).unwrap();
726 assert_eq!(parsed.len(), 1);
727 assert_eq!(parsed[0].kind, RelocKind::PageOff12);
728 assert_eq!(parsed[0].addend, 0x1000);
729 assert_eq!(parsed[0].referent, Referent::Symbol(7));
730 }
731
732 #[test]
733 fn parse_addend_prefix_folds_negative() {
734 // r_symbolnum = 0xFFFFFF → 24-bit signed = -1.
735 let raws = vec![
736 rel(ARM64_RELOC_ADDEND, 0x20, 0x00FF_FFFF, false, false, 2),
737 rel(ARM64_RELOC_UNSIGNED, 0x20, 9, true, false, 3),
738 ];
739 let parsed = parse_relocs(&raws).unwrap();
740 assert_eq!(parsed.len(), 1);
741 assert_eq!(parsed[0].addend, -1);
742 assert_eq!(parsed[0].kind, RelocKind::Unsigned);
743 assert_eq!(parsed[0].length, RelocLength::Quad);
744 }
745
746 #[test]
747 fn parse_subtractor_unsigned_pair() {
748 let raws = vec![
749 rel(ARM64_RELOC_SUBTRACTOR, 0x30, 11, true, false, 3),
750 rel(ARM64_RELOC_UNSIGNED, 0x30, 12, true, false, 3),
751 ];
752 let parsed = parse_relocs(&raws).unwrap();
753 assert_eq!(parsed.len(), 1);
754 let r = parsed[0];
755 assert_eq!(r.kind, RelocKind::Subtractor);
756 assert_eq!(r.referent, Referent::Symbol(12)); // minuend
757 assert_eq!(r.subtrahend, Some(Referent::Symbol(11))); // subtrahend
758 assert_eq!(r.length, RelocLength::Quad);
759 }
760
761 #[test]
762 fn parse_subtractor_with_addend() {
763 // SUBTRACTOR, ADDEND, UNSIGNED — addend applies to the final value.
764 let raws = vec![
765 rel(ARM64_RELOC_SUBTRACTOR, 0x40, 20, true, false, 3),
766 rel(ARM64_RELOC_ADDEND, 0x40, 0x100, false, false, 3),
767 rel(ARM64_RELOC_UNSIGNED, 0x40, 21, true, false, 3),
768 ];
769 let parsed = parse_relocs(&raws).unwrap();
770 assert_eq!(parsed.len(), 1);
771 assert_eq!(parsed[0].kind, RelocKind::Subtractor);
772 assert_eq!(parsed[0].referent, Referent::Symbol(21));
773 assert_eq!(parsed[0].subtrahend, Some(Referent::Symbol(20)));
774 assert_eq!(parsed[0].addend, 0x100);
775 }
776
777 #[test]
778 fn parse_section_relative_referent() {
779 // r_extern = false → section referent (1-based).
780 let raws = vec![rel(ARM64_RELOC_UNSIGNED, 0x0, 2, false, false, 3)];
781 let parsed = parse_relocs(&raws).unwrap();
782 assert_eq!(parsed[0].referent, Referent::Section(2));
783 }
784
785 #[test]
786 fn parse_trailing_addend_errors() {
787 let raws = vec![rel(ARM64_RELOC_ADDEND, 0x0, 1, false, false, 2)];
788 let err = parse_relocs(&raws).unwrap_err();
789 assert!(matches!(
790 err,
791 ReadError::BadRelocation { reason, .. } if reason.contains("trailing")
792 ));
793 }
794
795 #[test]
796 fn parse_trailing_subtractor_errors() {
797 let raws = vec![rel(ARM64_RELOC_SUBTRACTOR, 0x0, 1, true, false, 3)];
798 let err = parse_relocs(&raws).unwrap_err();
799 assert!(matches!(
800 err,
801 ReadError::BadRelocation { reason, .. } if reason.contains("trailing")
802 ));
803 }
804
805 #[test]
806 fn parse_subtractor_not_followed_by_unsigned_errors() {
807 let raws = vec![
808 rel(ARM64_RELOC_SUBTRACTOR, 0x0, 1, true, false, 3),
809 rel(ARM64_RELOC_BRANCH26, 0x0, 2, true, true, 2),
810 ];
811 let err = parse_relocs(&raws).unwrap_err();
812 assert!(matches!(
813 err,
814 ReadError::BadRelocation { reason, .. } if reason.contains("followed by UNSIGNED")
815 ));
816 }
817
818 #[test]
819 fn parse_subtractor_address_mismatch_errors() {
820 let raws = vec![
821 rel(ARM64_RELOC_SUBTRACTOR, 0x0, 1, true, false, 3),
822 rel(ARM64_RELOC_UNSIGNED, 0x8, 2, true, false, 3),
823 ];
824 let err = parse_relocs(&raws).unwrap_err();
825 assert!(matches!(
826 err,
827 ReadError::BadRelocation { reason, .. } if reason.contains("share r_address")
828 ));
829 }
830
831 #[test]
832 fn parse_subtractor_length_mismatch_errors() {
833 let raws = vec![
834 rel(ARM64_RELOC_SUBTRACTOR, 0x0, 1, true, false, 2),
835 rel(ARM64_RELOC_UNSIGNED, 0x0, 2, true, false, 3),
836 ];
837 let err = parse_relocs(&raws).unwrap_err();
838 assert!(matches!(
839 err,
840 ReadError::BadRelocation { reason, .. } if reason.contains("share r_length")
841 ));
842 }
843
844 #[test]
845 fn parse_unknown_reloc_type_errors() {
846 let raws = vec![rel(0x0F, 0x0, 1, true, false, 2)];
847 let err = parse_relocs(&raws).unwrap_err();
848 assert!(matches!(
849 err,
850 ReadError::BadRelocation { reason, .. } if reason.contains("unknown")
851 ));
852 }
853
854 // ---------- round-trip write_relocs tests ----------
855
856 #[test]
857 fn write_then_parse_round_trip_simple() {
858 let input = vec![Reloc {
859 offset: 0x10,
860 kind: RelocKind::Branch26,
861 length: RelocLength::Word,
862 pcrel: true,
863 referent: Referent::Symbol(5),
864 addend: 0,
865 subtrahend: None,
866 }];
867 let raws = write_relocs(&input).unwrap();
868 assert_eq!(raws.len(), 1); // no ADDEND prefix when addend=0
869 let back = parse_relocs(&raws).unwrap();
870 assert_eq!(back, input);
871 }
872
873 #[test]
874 fn write_then_parse_round_trip_with_addend() {
875 let input = vec![Reloc {
876 offset: 0x20,
877 kind: RelocKind::PageOff12,
878 length: RelocLength::Word,
879 pcrel: false,
880 referent: Referent::Symbol(7),
881 addend: 0x1000,
882 subtrahend: None,
883 }];
884 let raws = write_relocs(&input).unwrap();
885 assert_eq!(raws.len(), 2);
886 assert_eq!(raws[0].r_type, ARM64_RELOC_ADDEND);
887 assert_eq!(raws[1].r_type, ARM64_RELOC_PAGEOFF12);
888 let back = parse_relocs(&raws).unwrap();
889 assert_eq!(back, input);
890 }
891
892 #[test]
893 fn write_then_parse_round_trip_subtractor_pair() {
894 let input = vec![Reloc {
895 offset: 0x30,
896 kind: RelocKind::Subtractor,
897 length: RelocLength::Quad,
898 pcrel: false,
899 referent: Referent::Symbol(12),
900 addend: 0,
901 subtrahend: Some(Referent::Symbol(11)),
902 }];
903 let raws = write_relocs(&input).unwrap();
904 assert_eq!(raws.len(), 2);
905 assert_eq!(raws[0].r_type, ARM64_RELOC_SUBTRACTOR);
906 assert_eq!(raws[0].r_symbolnum, 11);
907 assert_eq!(raws[1].r_type, ARM64_RELOC_UNSIGNED);
908 assert_eq!(raws[1].r_symbolnum, 12);
909 let back = parse_relocs(&raws).unwrap();
910 assert_eq!(back, input);
911 }
912
913 #[test]
914 fn write_then_parse_round_trip_subtractor_with_addend() {
915 let input = vec![Reloc {
916 offset: 0x40,
917 kind: RelocKind::Subtractor,
918 length: RelocLength::Quad,
919 pcrel: false,
920 referent: Referent::Symbol(21),
921 addend: 0x100,
922 subtrahend: Some(Referent::Symbol(20)),
923 }];
924 let raws = write_relocs(&input).unwrap();
925 assert_eq!(raws.len(), 3);
926 assert_eq!(raws[0].r_type, ARM64_RELOC_SUBTRACTOR);
927 assert_eq!(raws[1].r_type, ARM64_RELOC_ADDEND);
928 assert_eq!(raws[2].r_type, ARM64_RELOC_UNSIGNED);
929 let back = parse_relocs(&raws).unwrap();
930 assert_eq!(back, input);
931 }
932
933 #[test]
934 fn write_then_parse_round_trip_negative_addend() {
935 let input = vec![Reloc {
936 offset: 0x50,
937 kind: RelocKind::Unsigned,
938 length: RelocLength::Quad,
939 pcrel: false,
940 referent: Referent::Symbol(3),
941 addend: -1,
942 subtrahend: None,
943 }];
944 let raws = write_relocs(&input).unwrap();
945 let back = parse_relocs(&raws).unwrap();
946 assert_eq!(back, input);
947 }
948
949 #[test]
950 fn write_subtractor_without_subtrahend_errors() {
951 let bad = vec![Reloc {
952 offset: 0,
953 kind: RelocKind::Subtractor,
954 length: RelocLength::Quad,
955 pcrel: false,
956 referent: Referent::Symbol(1),
957 addend: 0,
958 subtrahend: None,
959 }];
960 let err = write_relocs(&bad).unwrap_err();
961 assert!(matches!(
962 err,
963 ReadError::BadRelocation { reason, .. } if reason.contains("subtrahend")
964 ));
965 }
966
967 #[test]
968 fn write_addend_overflow_errors() {
969 let bad = vec![Reloc {
970 offset: 0,
971 kind: RelocKind::Unsigned,
972 length: RelocLength::Quad,
973 pcrel: false,
974 referent: Referent::Symbol(1),
975 addend: 0x0100_0000, // outside 24-bit signed range
976 subtrahend: None,
977 }];
978 let err = write_relocs(&bad).unwrap_err();
979 assert!(matches!(
980 err,
981 ReadError::BadRelocation { reason, .. } if reason.contains("24-bit")
982 ));
983 }
984
985 // ---------- validate_reloc tests ----------
986
987 fn good() -> Reloc {
988 Reloc {
989 offset: 0x10,
990 kind: RelocKind::Branch26,
991 length: RelocLength::Word,
992 pcrel: true,
993 referent: Referent::Symbol(0),
994 addend: 0,
995 subtrahend: None,
996 }
997 }
998
999 #[test]
1000 fn validate_accepts_good_reloc() {
1001 assert!(validate_reloc(&good(), 0x100, 1, 1).is_ok());
1002 }
1003
1004 #[test]
1005 fn validate_rejects_out_of_bounds_offset() {
1006 let mut r = good();
1007 r.offset = 0xFC; // offset+4 = 0x100, at boundary → ok
1008 assert!(validate_reloc(&r, 0x100, 1, 1).is_ok());
1009 r.offset = 0xFD; // offset+4 = 0x101, past end
1010 assert!(matches!(
1011 validate_reloc(&r, 0x100, 1, 1).unwrap_err(),
1012 ReadError::BadRelocation { reason, .. } if reason.contains("exceeds section")
1013 ));
1014 }
1015
1016 #[test]
1017 fn validate_rejects_symbol_oob() {
1018 let mut r = good();
1019 r.referent = Referent::Symbol(5);
1020 assert!(matches!(
1021 validate_reloc(&r, 0x100, 3, 1).unwrap_err(),
1022 ReadError::BadRelocation { reason, .. } if reason.contains("past end of symbol")
1023 ));
1024 }
1025
1026 #[test]
1027 fn validate_rejects_section_oob() {
1028 let mut r = good();
1029 r.referent = Referent::Section(4);
1030 assert!(matches!(
1031 validate_reloc(&r, 0x100, 0, 2).unwrap_err(),
1032 ReadError::BadRelocation { reason, .. } if reason.contains("out-of-range section")
1033 ));
1034 }
1035
1036 #[test]
1037 fn validate_rejects_branch26_with_wrong_length() {
1038 let mut r = good();
1039 r.length = RelocLength::Quad; // BRANCH26 must be Word
1040 assert!(matches!(
1041 validate_reloc(&r, 0x100, 1, 1).unwrap_err(),
1042 ReadError::BadRelocation { reason, .. } if reason.contains("Word length")
1043 ));
1044 }
1045
1046 #[test]
1047 fn validate_rejects_page21_without_pcrel() {
1048 let r = Reloc {
1049 offset: 0x10,
1050 kind: RelocKind::Page21,
1051 length: RelocLength::Word,
1052 pcrel: false, // Page21 requires pcrel=true
1053 referent: Referent::Symbol(0),
1054 addend: 0,
1055 subtrahend: None,
1056 };
1057 assert!(matches!(
1058 validate_reloc(&r, 0x100, 1, 1).unwrap_err(),
1059 ReadError::BadRelocation { reason, .. } if reason.contains("pcrel")
1060 ));
1061 }
1062
1063 #[test]
1064 fn validate_accepts_unsigned_quad() {
1065 let r = Reloc {
1066 offset: 0,
1067 kind: RelocKind::Unsigned,
1068 length: RelocLength::Quad,
1069 pcrel: false,
1070 referent: Referent::Symbol(0),
1071 addend: 0,
1072 subtrahend: None,
1073 };
1074 assert!(validate_reloc(&r, 0x100, 1, 1).is_ok());
1075 }
1076
1077 #[test]
1078 fn validate_rejects_subtractor_missing_subtrahend() {
1079 let r = Reloc {
1080 offset: 0,
1081 kind: RelocKind::Subtractor,
1082 length: RelocLength::Quad,
1083 pcrel: false,
1084 referent: Referent::Symbol(0),
1085 addend: 0,
1086 subtrahend: None,
1087 };
1088 assert!(matches!(
1089 validate_reloc(&r, 0x100, 1, 1).unwrap_err(),
1090 ReadError::BadRelocation { reason, .. } if reason.contains("subtrahend")
1091 ));
1092 }
1093
1094 #[test]
1095 fn write_then_parse_round_trip_section_referent() {
1096 let input = vec![Reloc {
1097 offset: 0x60,
1098 kind: RelocKind::Unsigned,
1099 length: RelocLength::Quad,
1100 pcrel: false,
1101 referent: Referent::Section(2),
1102 addend: 0,
1103 subtrahend: None,
1104 }];
1105 let raws = write_relocs(&input).unwrap();
1106 assert!(!raws[0].r_extern);
1107 assert_eq!(raws[0].r_symbolnum, 2);
1108 let back = parse_relocs(&raws).unwrap();
1109 assert_eq!(back, input);
1110 }
1111 }
1112