fortrangoingonforty/afs-ld / 20d5047

Browse files

Capture linker layout and formatting updates

Authored by espadonne
SHA
20d5047b0a9875d243f48f48b5ecfd70cc5d046a
Parents
497b944
Tree
2f13a94

31 changed files

StatusFile+-
M src/archive.rs 57 41
M src/args.rs 18 14
M src/atom.rs 37 21
M src/dump.rs 20 37
M src/layout.rs 136 37
M src/lib.rs 7 2
M src/macho/dylib.rs 6 12
M src/macho/exports.rs 6 1
M src/macho/reader.rs 82 35
M src/macho/tbd.rs 15 13
M src/macho/tbd_yaml.rs 10 16
M src/macho/writer.rs 4 1
M src/reloc/arm64.rs 16 2
M src/reloc/mod.rs 24 12
M src/resolve.rs 29 45
M src/section.rs 14 10
M src/string_table.rs 23 16
M src/symbol.rs 7 1
M src/synth/code_sig.rs 30 21
M src/synth/dyld_info.rs 33 16
M src/synth/mod.rs 124 0
M src/synth/unwind.rs 384 62
M tests/archive_runtime.rs 14 6
M tests/atom_integration.rs 10 9
M tests/dylib_integration.rs 1 9
M tests/linker_run.rs 115 0
M tests/minimal_output.rs 4 1
M tests/reader_corpus_round_trip.rs 28 19
M tests/resolve_integration.rs 4 10
M tests/tbd_integration.rs 5 2
M tests/tbd_smoke.rs 5 1
src/archive.rsmodified
@@ -37,13 +37,20 @@ pub const AR_HDR_SIZE: usize = 60;
3737
 #[derive(Debug)]
3838
 pub enum ArchiveError {
3939
     /// The input buffer is shorter than the next structure we need to read.
40
-    Truncated { need: usize, have: usize, context: &'static str },
40
+    Truncated {
41
+        need: usize,
42
+        have: usize,
43
+        context: &'static str,
44
+    },
4145
     /// The 8-byte magic is neither `!<arch>\n` nor `!<thin>\n`.
4246
     BadMagic { got: [u8; 8] },
4347
     /// A header footer (`fmag`) wasn't `` `\n ``.
4448
     BadEntryFooter { at_offset: usize },
4549
     /// An ASCII decimal field (size/date/uid/gid/mode) didn't parse.
46
-    BadAsciiField { at_offset: usize, field: &'static str },
50
+    BadAsciiField {
51
+        at_offset: usize,
52
+        field: &'static str,
53
+    },
4754
     /// A member's claimed size would overrun the archive.
4855
     MemberOverrun { at_offset: usize, size: u64 },
4956
     /// A `/NNN` long-name offset didn't land inside the `//` table.
@@ -229,7 +236,9 @@ impl<'a> Archive<'a> {
229236
     /// Return every non-special member (skips symbol indexes and long-name
230237
     /// tables).
231238
     pub fn object_members(&self) -> impl Iterator<Item = &Member<'a>> {
232
-        self.members.iter().filter(|m| m.special == SpecialMember::None)
239
+        self.members
240
+            .iter()
241
+            .filter(|m| m.special == SpecialMember::None)
233242
     }
234243
 
235244
     /// Find the first member whose `ar_hdr` begins at `header_offset`. The
@@ -266,10 +275,7 @@ impl<'a> Archive<'a> {
266275
     /// Resolve `name` to its defining member, then parse that member as an
267276
     /// `ObjectFile`. Returns `None` when the symbol is absent; `Some(Err(_))`
268277
     /// when the member exists but fails to parse.
269
-    pub fn fetch_object_defining(
270
-        &self,
271
-        name: &str,
272
-    ) -> Option<Result<ObjectFile, FetchError>> {
278
+    pub fn fetch_object_defining(&self, name: &str) -> Option<Result<ObjectFile, FetchError>> {
273279
         let member = self.first_member_defining(name)?;
274280
         Some(self.parse_member_object(member))
275281
     }
@@ -349,15 +355,8 @@ fn parse_members<'a>(
349355
             });
350356
         }
351357
         let raw_name = hdr.raw_name_str();
352
-        let (real_name, body_offset, body, special) = decode_member(
353
-            raw_name,
354
-            data,
355
-            body_start,
356
-            size,
357
-            flavor,
358
-            long_names,
359
-            cursor,
360
-        )?;
358
+        let (real_name, body_offset, body, special) =
359
+            decode_member(raw_name, data, body_start, size, flavor, long_names, cursor)?;
361360
 
362361
         // Opportunistic flavor refinement. The sole unambiguous signal for
363362
         // Sysv is the presence of `/` or `//` members — BSD never emits
@@ -383,10 +382,12 @@ fn parse_members<'a>(
383382
         // Advance past body + 1-byte alignment pad for odd sizes (GNU-thin
384383
         // members have zero-byte bodies so this collapses to a no-op).
385384
         let advance = AR_HDR_SIZE + size + (size & 1);
386
-        cursor = cursor.checked_add(advance).ok_or(ArchiveError::MemberOverrun {
387
-            at_offset: cursor,
388
-            size: hdr.size,
389
-        })?;
385
+        cursor = cursor
386
+            .checked_add(advance)
387
+            .ok_or(ArchiveError::MemberOverrun {
388
+                at_offset: cursor,
389
+                size: hdr.size,
390
+            })?;
390391
     }
391392
 
392393
     Ok((out, flavor))
@@ -405,7 +406,12 @@ fn decode_member<'a>(
405406
     // GNU-thin: body is zero bytes; name field is the external path.
406407
     if flavor == Flavor::GnuThin {
407408
         let name = raw_name.trim_end_matches('/').to_string();
408
-        return Ok((name, body_start, &data[body_start..body_start], SpecialMember::None));
409
+        return Ok((
410
+            name,
411
+            body_start,
412
+            &data[body_start..body_start],
413
+            SpecialMember::None,
414
+        ));
409415
     }
410416
 
411417
     // BSD extended: "#1/<N>" — first N bytes of the body are the real name.
@@ -576,9 +582,11 @@ fn parse_bsd_symbol_index(body: &[u8]) -> Result<SymbolIndex, ArchiveError> {
576582
             reason: "__.SYMDEF ranlib byte count not a multiple of 8",
577583
         });
578584
     }
579
-    let ranlib_end = 4usize.checked_add(ranlib_bytes).ok_or(ArchiveError::BadSymbolIndex {
580
-        reason: "__.SYMDEF ranlib region overflows",
581
-    })?;
585
+    let ranlib_end = 4usize
586
+        .checked_add(ranlib_bytes)
587
+        .ok_or(ArchiveError::BadSymbolIndex {
588
+            reason: "__.SYMDEF ranlib region overflows",
589
+        })?;
582590
     if ranlib_end + 4 > body.len() {
583591
         return Err(ArchiveError::BadSymbolIndex {
584592
             reason: "__.SYMDEF ranlib + stringsize region overruns member",
@@ -587,11 +595,12 @@ fn parse_bsd_symbol_index(body: &[u8]) -> Result<SymbolIndex, ArchiveError> {
587595
     let stringsize =
588596
         u32::from_le_bytes(body[ranlib_end..ranlib_end + 4].try_into().unwrap()) as usize;
589597
     let strings_start = ranlib_end + 4;
590
-    let strings_end = strings_start
591
-        .checked_add(stringsize)
592
-        .ok_or(ArchiveError::BadSymbolIndex {
593
-            reason: "__.SYMDEF strings region overflows",
594
-        })?;
598
+    let strings_end =
599
+        strings_start
600
+            .checked_add(stringsize)
601
+            .ok_or(ArchiveError::BadSymbolIndex {
602
+                reason: "__.SYMDEF strings region overflows",
603
+            })?;
595604
     if strings_end > body.len() {
596605
         return Err(ArchiveError::BadSymbolIndex {
597606
             reason: "__.SYMDEF strings region overruns member",
@@ -686,11 +695,7 @@ fn parse_sysv_symbol_index(body: &[u8]) -> Result<SymbolIndex, ArchiveError> {
686695
     Ok(SymbolIndex { entries })
687696
 }
688697
 
689
-fn decode_long_name(
690
-    table: &[u8],
691
-    strx: u32,
692
-    at_offset: usize,
693
-) -> Result<String, ArchiveError> {
698
+fn decode_long_name(table: &[u8], strx: u32, at_offset: usize) -> Result<String, ArchiveError> {
694699
     let start = strx as usize;
695700
     if start >= table.len() {
696701
         return Err(ArchiveError::LongNameOob { at_offset, strx });
@@ -703,7 +708,11 @@ fn decode_long_name(
703708
         .map(|i| start + i)
704709
         .unwrap_or(table.len());
705710
     // Strip a trailing slash that GNU appends before the newline.
706
-    let trimmed_end = if end > 0 && table[end - 1] == b'/' { end - 1 } else { end };
711
+    let trimmed_end = if end > 0 && table[end - 1] == b'/' {
712
+        end - 1
713
+    } else {
714
+        end
715
+    };
707716
     str::from_utf8(&table[start..trimmed_end])
708717
         .map(|s| s.to_string())
709718
         .map_err(|_| ArchiveError::BadName { at_offset })
@@ -792,12 +801,13 @@ mod tests {
792801
         let mut buf = Vec::with_capacity(AR_HDR_SIZE);
793802
         let name_bytes = name.as_bytes();
794803
         let mut name_field = [b' '; 16];
795
-        name_field[..name_bytes.len().min(16)].copy_from_slice(&name_bytes[..name_bytes.len().min(16)]);
804
+        name_field[..name_bytes.len().min(16)]
805
+            .copy_from_slice(&name_bytes[..name_bytes.len().min(16)]);
796806
         buf.extend_from_slice(&name_field);
797807
         buf.extend_from_slice(&[b' '; 12]); // date
798
-        buf.extend_from_slice(&[b' '; 6]);  // uid
799
-        buf.extend_from_slice(&[b' '; 6]);  // gid
800
-        buf.extend_from_slice(&[b' '; 8]);  // mode
808
+        buf.extend_from_slice(&[b' '; 6]); // uid
809
+        buf.extend_from_slice(&[b' '; 6]); // gid
810
+        buf.extend_from_slice(&[b' '; 8]); // mode
801811
         let mut size_field = [b' '; 10];
802812
         let size_str = size.to_string();
803813
         let bytes = size_str.as_bytes();
@@ -840,7 +850,10 @@ mod tests {
840850
         let short = vec![0u8; 30];
841851
         assert!(matches!(
842852
             ArHeader::parse(&short, 0).unwrap_err(),
843
-            ArchiveError::Truncated { need: AR_HDR_SIZE, .. }
853
+            ArchiveError::Truncated {
854
+                need: AR_HDR_SIZE,
855
+                ..
856
+            }
844857
         ));
845858
     }
846859
 
@@ -901,7 +914,10 @@ mod tests {
901914
     fn bsd_extended_name_splits_body() {
902915
         let mut buf = Vec::new();
903916
         buf.extend_from_slice(AR_MAGIC);
904
-        buf.extend_from_slice(&encode_bsd_extended("long_filename_with_many_chars.o", b"CONT"));
917
+        buf.extend_from_slice(&encode_bsd_extended(
918
+            "long_filename_with_many_chars.o",
919
+            b"CONT",
920
+        ));
905921
         let ar = Archive::open("/tmp/bsd_ext.a", &buf).unwrap();
906922
         assert_eq!(ar.members()[0].name, "long_filename_with_many_chars.o");
907923
         assert_eq!(ar.members()[0].body, b"CONT");
src/args.rsmodified
@@ -23,7 +23,10 @@ impl std::fmt::Display for ArgsError {
2323
                 write!(f, "flag `{flag}` requires a value")
2424
             }
2525
             ArgsError::UnknownFlag(flag) => {
26
-                write!(f, "unknown flag `{flag}` (Sprint 19 adds the full `ld` surface)")
26
+                write!(
27
+                    f,
28
+                    "unknown flag `{flag}` (Sprint 19 adds the full `ld` surface)"
29
+                )
2730
             }
2831
         }
2932
     }
@@ -36,7 +39,8 @@ pub fn parse(argv: &[String]) -> Result<LinkOptions, ArgsError> {
3639
         match arg.as_str() {
3740
             "-o" => {
3841
                 opts.output = Some(PathBuf::from(
39
-                    it.next().ok_or_else(|| ArgsError::MissingValue("-o".into()))?,
42
+                    it.next()
43
+                        .ok_or_else(|| ArgsError::MissingValue("-o".into()))?,
4044
                 ));
4145
             }
4246
             "-e" => {
@@ -66,22 +70,22 @@ pub fn parse(argv: &[String]) -> Result<LinkOptions, ArgsError> {
6670
                 ));
6771
             }
6872
             "--dump-archive" => {
69
-                opts.dump_archive = Some(PathBuf::from(
70
-                    it.next()
71
-                        .ok_or_else(|| ArgsError::MissingValue("--dump-archive".into()))?,
72
-                ));
73
+                opts.dump_archive =
74
+                    Some(PathBuf::from(it.next().ok_or_else(|| {
75
+                        ArgsError::MissingValue("--dump-archive".into())
76
+                    })?));
7377
             }
7478
             "--dump-dylib" => {
75
-                opts.dump_dylib = Some(PathBuf::from(
76
-                    it.next()
77
-                        .ok_or_else(|| ArgsError::MissingValue("--dump-dylib".into()))?,
78
-                ));
79
+                opts.dump_dylib =
80
+                    Some(PathBuf::from(it.next().ok_or_else(|| {
81
+                        ArgsError::MissingValue("--dump-dylib".into())
82
+                    })?));
7983
             }
8084
             "--dump-tbd" => {
81
-                opts.dump_tbd = Some(PathBuf::from(
82
-                    it.next()
83
-                        .ok_or_else(|| ArgsError::MissingValue("--dump-tbd".into()))?,
84
-                ));
85
+                opts.dump_tbd =
86
+                    Some(PathBuf::from(it.next().ok_or_else(|| {
87
+                        ArgsError::MissingValue("--dump-tbd".into())
88
+                    })?));
8589
             }
8690
             s if s.starts_with('-') => {
8791
                 return Err(ArgsError::UnknownFlag(s.to_string()));
src/atom.rsmodified
@@ -22,7 +22,7 @@ use std::collections::HashMap;
2222
 use crate::input::ObjectFile;
2323
 use crate::macho::constants::MH_SUBSECTIONS_VIA_SYMBOLS;
2424
 use crate::reloc::{parse_raw_relocs, parse_relocs, Referent};
25
-use crate::resolve::{AtomId, InputId, SymbolTable, SymbolId};
25
+use crate::resolve::{AtomId, InputId, SymbolId, SymbolTable};
2626
 use crate::section::{InputSection, SectionKind};
2727
 use crate::symbol::{InputSymbol, SymKind};
2828
 
@@ -81,10 +81,7 @@ impl AtomSection {
8181
     }
8282
 
8383
     pub fn is_zerofill(self) -> bool {
84
-        matches!(
85
-            self,
86
-            AtomSection::ZeroFill | AtomSection::ThreadLocalBss
87
-        )
84
+        matches!(self, AtomSection::ZeroFill | AtomSection::ThreadLocalBss)
8885
     }
8986
 
9087
     pub fn is_literal(self) -> bool {
@@ -224,7 +221,9 @@ impl AtomTable {
224221
     pub fn by_input_section(&self) -> HashMap<(InputId, u8), Vec<AtomId>> {
225222
         let mut out: HashMap<(InputId, u8), Vec<AtomId>> = HashMap::new();
226223
         for (id, atom) in self.iter() {
227
-            out.entry((atom.origin, atom.input_section)).or_default().push(id);
224
+            out.entry((atom.origin, atom.input_section))
225
+                .or_default()
226
+                .push(id);
228227
         }
229228
         out
230229
     }
@@ -582,15 +581,36 @@ fn atomize_literal_section(
582581
         AtomSection::CStringLiterals => {
583582
             atomize_cstring(input_id, section_idx, sect, syms, atom_section, table, out)
584583
         }
585
-        AtomSection::Literal4 => {
586
-            atomize_fixed_literal(input_id, section_idx, sect, syms, 4, atom_section, table, out)
587
-        }
588
-        AtomSection::Literal8 => {
589
-            atomize_fixed_literal(input_id, section_idx, sect, syms, 8, atom_section, table, out)
590
-        }
591
-        AtomSection::Literal16 => {
592
-            atomize_fixed_literal(input_id, section_idx, sect, syms, 16, atom_section, table, out)
593
-        }
584
+        AtomSection::Literal4 => atomize_fixed_literal(
585
+            input_id,
586
+            section_idx,
587
+            sect,
588
+            syms,
589
+            4,
590
+            atom_section,
591
+            table,
592
+            out,
593
+        ),
594
+        AtomSection::Literal8 => atomize_fixed_literal(
595
+            input_id,
596
+            section_idx,
597
+            sect,
598
+            syms,
599
+            8,
600
+            atom_section,
601
+            table,
602
+            out,
603
+        ),
604
+        AtomSection::Literal16 => atomize_fixed_literal(
605
+            input_id,
606
+            section_idx,
607
+            sect,
608
+            syms,
609
+            16,
610
+            atom_section,
611
+            table,
612
+            out,
613
+        ),
594614
         _ => unreachable!("atomize_literal_section called with non-literal kind"),
595615
     }
596616
 }
@@ -615,9 +635,7 @@ fn atomize_cstring(
615635
         let data = sect.data[offset..end].to_vec();
616636
         let size = (end - offset) as u32;
617637
 
618
-        let owner_entry = syms
619
-            .iter()
620
-            .find(|(_, _, off)| *off as usize == offset);
638
+        let owner_entry = syms.iter().find(|(_, _, off)| *off as usize == offset);
621639
         let owner_idx = owner_entry.map(|(i, _, _)| *i);
622640
 
623641
         let mut flags = AtomFlags::default().with(AtomFlags::LITERAL);
@@ -671,9 +689,7 @@ fn atomize_fixed_literal(
671689
         };
672690
         let size = (end - offset) as u32;
673691
 
674
-        let owner_entry = syms
675
-            .iter()
676
-            .find(|(_, _, off)| *off as usize == offset);
692
+        let owner_entry = syms.iter().find(|(_, _, off)| *off as usize == offset);
677693
         let owner_idx = owner_entry.map(|(i, _, _)| *i);
678694
 
679695
         let mut flags = AtomFlags::default().with(AtomFlags::LITERAL);
src/dump.rsmodified
@@ -9,14 +9,14 @@ use std::path::Path;
99
 
1010
 use crate::archive::{Archive, Flavor, SpecialMember};
1111
 use crate::input::ObjectFile;
12
+use crate::macho::constants::*;
1213
 use crate::macho::dylib::{DylibFile, DylibLoadKind};
1314
 use crate::macho::exports::ExportKind;
14
-use crate::macho::tbd::parse_tbd;
15
-use crate::macho::constants::*;
1615
 use crate::macho::reader::{
1716
     BuildVersionCmd, DyldInfoCmd, DylibCmd, DysymtabCmd, LinkEditDataCmd, LoadCommand,
1817
     MachHeader64, RpathCmd, Section64Header, Segment64, SymtabCmd,
1918
 };
19
+use crate::macho::tbd::parse_tbd;
2020
 use crate::reloc::{parse_raw_relocs, parse_relocs, Referent, Reloc, RelocKind};
2121
 use crate::section::InputSection;
2222
 use crate::symbol::{InputSymbol, SymKind};
@@ -58,7 +58,11 @@ pub fn dump_archive_file(path: &Path) -> io::Result<()> {
5858
     if let Some(idx) = ar.symbol_index() {
5959
         writeln!(h, "Symbols ({}):", idx.len())?;
6060
         for (i, e) in idx.entries.iter().enumerate().take(16) {
61
-            writeln!(h, "  [{i}] {} -> member@0x{:x}", e.name, e.member_header_offset)?;
61
+            writeln!(
62
+                h,
63
+                "  [{i}] {} -> member@0x{:x}",
64
+                e.name, e.member_header_offset
65
+            )?;
6266
         }
6367
         if idx.len() > 16 {
6468
             writeln!(h, "  ... ({} more)", idx.len() - 16)?;
@@ -69,8 +73,8 @@ pub fn dump_archive_file(path: &Path) -> io::Result<()> {
6973
 
7074
 pub fn dump_tbd_file(path: &Path) -> io::Result<()> {
7175
     let src = std::fs::read_to_string(path)?;
72
-    let docs = parse_tbd(&src)
73
-        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
76
+    let docs =
77
+        parse_tbd(&src).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?;
7478
     let out = io::stdout();
7579
     let mut h = out.lock();
7680
     writeln!(h, "{}:", path.display())?;
@@ -86,18 +90,10 @@ pub fn dump_tbd_file(path: &Path) -> io::Result<()> {
8690
             tbd.compatibility_version.as_deref().unwrap_or("-")
8791
         )?;
8892
         if !tbd.reexported_libraries.is_empty() {
89
-            let total: usize = tbd
90
-                .reexported_libraries
91
-                .iter()
92
-                .map(|s| s.value.len())
93
-                .sum();
93
+            let total: usize = tbd.reexported_libraries.iter().map(|s| s.value.len()).sum();
9494
             writeln!(h, "      reexported-libraries: {total}")?;
9595
         }
96
-        let export_syms: usize = tbd
97
-            .exports
98
-            .iter()
99
-            .map(|s| s.value.total())
100
-            .sum();
96
+        let export_syms: usize = tbd.exports.iter().map(|s| s.value.total()).sum();
10197
         if export_syms > 0 {
10298
             writeln!(h, "      exports: {export_syms} symbols total")?;
10399
         }
@@ -222,12 +218,7 @@ fn write_command(w: &mut impl Write, idx: usize, cmd: &LoadCommand) -> io::Resul
222218
         LoadCommand::DyldExportsTrie(l) => write_linkedit_data(w, l, "EXPORTS_TRIE"),
223219
         LoadCommand::DyldChainedFixups(l) => write_linkedit_data(w, l, "CHAINED_FIXUPS"),
224220
         LoadCommand::Raw { cmd, data, .. } => {
225
-            writeln!(
226
-                w,
227
-                "  (raw — cmd=0x{:x}, payload {} bytes)",
228
-                cmd,
229
-                data.len()
230
-            )
221
+            writeln!(w, "  (raw — cmd=0x{:x}, payload {} bytes)", cmd, data.len())
231222
         }
232223
     }
233224
 }
@@ -340,11 +331,7 @@ fn write_build_version(w: &mut impl Write, b: &BuildVersionCmd) -> io::Result<()
340331
 }
341332
 
342333
 fn write_linkedit_data(w: &mut impl Write, l: &LinkEditDataCmd, kind: &str) -> io::Result<()> {
343
-    writeln!(
344
-        w,
345
-        "  {kind} dataoff={} datasize={}",
346
-        l.dataoff, l.datasize
347
-    )
334
+    writeln!(w, "  {kind} dataoff={} datasize={}", l.dataoff, l.datasize)
348335
 }
349336
 
350337
 fn write_sections(w: &mut impl Write, secs: &[InputSection]) -> io::Result<()> {
@@ -356,22 +343,14 @@ fn write_sections(w: &mut impl Write, secs: &[InputSection]) -> io::Result<()> {
356343
         writeln!(
357344
             w,
358345
             "  [{i}] {},{:<16} {:?} addr=0x{:x} size=0x{:x} align=2^{} offset={} flags=0x{:08x}",
359
-            s.segname,
360
-            s.sectname,
361
-            s.kind,
362
-            s.addr,
363
-            s.size,
364
-            s.align_pow2,
365
-            s.offset,
366
-            s.flags
346
+            s.segname, s.sectname, s.kind, s.addr, s.size, s.align_pow2, s.offset, s.flags
367347
         )?;
368348
         if !s.data.is_empty() {
369349
             writeln!(w, "      data: {}", hex_preview(&s.data, 16))?;
370350
         }
371351
         if s.nreloc > 0 {
372352
             writeln!(w, "      relocs ({}):", s.nreloc)?;
373
-            match parse_raw_relocs(&s.raw_relocs, 0, s.nreloc)
374
-                .and_then(|raws| parse_relocs(&raws))
353
+            match parse_raw_relocs(&s.raw_relocs, 0, s.nreloc).and_then(|raws| parse_relocs(&raws))
375354
             {
376355
                 Ok(fused) => {
377356
                     for (ri, r) in fused.iter().enumerate() {
@@ -399,7 +378,11 @@ fn write_symbols(w: &mut impl Write, obj: &ObjectFile) -> io::Result<()> {
399378
 
400379
 fn describe_symbol(sym: &InputSymbol) -> String {
401380
     if let Some(stab) = sym.stab_kind() {
402
-        return format!("STAB kind=0x{stab:02x} sect={} value=0x{:x}", sym.sect_idx(), sym.value());
381
+        return format!(
382
+            "STAB kind=0x{stab:02x} sect={} value=0x{:x}",
383
+            sym.sect_idx(),
384
+            sym.value()
385
+        );
403386
     }
404387
     let mut parts: Vec<String> = Vec::new();
405388
     parts.push(
src/layout.rsmodified
@@ -9,14 +9,22 @@ use crate::atom::AtomTable;
99
 use crate::input::ObjectFile;
1010
 use crate::macho::constants::SG_READ_ONLY;
1111
 use crate::resolve::InputId;
12
-use crate::section::{is_zerofill, OutputAtom, OutputSection, OutputSectionId, OutputSegment, Prot};
12
+use crate::section::{
13
+    is_zerofill, OutputAtom, OutputSection, OutputSectionId, OutputSegment, Prot,
14
+};
1315
 use crate::synth::SyntheticPlan;
1416
 use crate::OutputKind;
1517
 
1618
 pub const PAGE_SIZE: u64 = 0x4000;
1719
 pub const EXECUTABLE_TEXT_BASE: u64 = 0x1_0000_0000;
1820
 
19
-const EXEC_SEGMENTS: [&str; 5] = ["__PAGEZERO", "__TEXT", "__DATA_CONST", "__DATA", "__LINKEDIT"];
21
+const EXEC_SEGMENTS: [&str; 5] = [
22
+    "__PAGEZERO",
23
+    "__TEXT",
24
+    "__DATA_CONST",
25
+    "__DATA",
26
+    "__LINKEDIT",
27
+];
2028
 const DYLIB_SEGMENTS: [&str; 4] = ["__TEXT", "__DATA_CONST", "__DATA", "__LINKEDIT"];
2129
 
2230
 #[derive(Debug, Clone, Copy)]
@@ -59,8 +67,10 @@ impl Layout {
5967
         header_size: u64,
6068
         synthetic_plan: Option<&SyntheticPlan>,
6169
     ) -> Self {
62
-        let input_map: HashMap<InputId, &ObjectFile> =
63
-            inputs.iter().map(|input| (input.id, input.object)).collect();
70
+        let input_map: HashMap<InputId, &ObjectFile> = inputs
71
+            .iter()
72
+            .map(|input| (input.id, input.object))
73
+            .collect();
6474
 
6575
         let mut sections: Vec<OutputSection> = Vec::new();
6676
         let mut section_index: HashMap<SectionKey, usize> = HashMap::new();
@@ -122,10 +132,9 @@ impl Layout {
122132
 
123133
         if let Some(plan) = synthetic_plan {
124134
             for synthetic in plan.output_sections() {
125
-                if let Some(existing) = sections
126
-                    .iter_mut()
127
-                    .find(|section| section.segment == synthetic.segment && section.name == synthetic.name)
128
-                {
135
+                if let Some(existing) = sections.iter_mut().find(|section| {
136
+                    section.segment == synthetic.segment && section.name == synthetic.name
137
+                }) {
129138
                     merge_synthetic_section(existing, synthetic);
130139
                 } else {
131140
                     sections.push(synthetic);
@@ -136,7 +145,9 @@ impl Layout {
136145
         sections.sort_by(|a, b| {
137146
             segment_rank(kind, &a.segment)
138147
                 .cmp(&segment_rank(kind, &b.segment))
139
-                .then_with(|| section_rank(&a.segment, &a.name).cmp(&section_rank(&b.segment, &b.name)))
148
+                .then_with(|| {
149
+                    section_rank(&a.segment, &a.name).cmp(&section_rank(&b.segment, &b.name))
150
+                })
140151
                 .then_with(|| a.segment.cmp(&b.segment))
141152
                 .then_with(|| a.name.cmp(&b.name))
142153
         });
@@ -159,12 +170,13 @@ impl Layout {
159170
                 placed.offset = size;
160171
                 size += placed.size;
161172
             }
162
-            section.synthetic_offset = if section.synthetic_data.is_empty() || section.atoms.is_empty() {
163
-                0
164
-            } else {
165
-                let align = 1u64 << section.align_pow2.min(63);
166
-                align_up(size, align)
167
-            };
173
+            section.synthetic_offset =
174
+                if section.synthetic_data.is_empty() || section.atoms.is_empty() {
175
+                    0
176
+                } else {
177
+                    let align = 1u64 << section.align_pow2.min(63);
178
+                    align_up(size, align)
179
+                };
168180
             section.size = if section.synthetic_data.is_empty() {
169181
                 size
170182
             } else {
@@ -278,11 +290,7 @@ impl Layout {
278290
             } else {
279291
                 seg_start_vm
280292
             };
281
-            let mut file_cursor = if is_text {
282
-                header_size
283
-            } else {
284
-                seg_start_file
285
-            };
293
+            let mut file_cursor = if is_text { header_size } else { seg_start_file };
286294
             let mut seg_vm_end = if is_text {
287295
                 seg_start_vm + header_size
288296
             } else {
@@ -323,13 +331,25 @@ impl Layout {
323331
                 align_up(raw_vm_size, PAGE_SIZE)
324332
             };
325333
             segment.file_size = if is_linkedit || raw_file_size == 0 {
326
-                if is_linkedit { 0 } else { raw_file_size }
334
+                if is_linkedit {
335
+                    0
336
+                } else {
337
+                    raw_file_size
338
+                }
327339
             } else {
328340
                 align_up(raw_file_size, PAGE_SIZE)
329341
             };
330342
 
331
-            next_vm = if is_linkedit { seg_start_vm } else { seg_vm_end };
332
-            next_file = if is_linkedit { seg_start_file } else { seg_file_end };
343
+            next_vm = if is_linkedit {
344
+                seg_start_vm
345
+            } else {
346
+                seg_vm_end
347
+            };
348
+            next_file = if is_linkedit {
349
+                seg_start_file
350
+            } else {
351
+                seg_file_end
352
+            };
333353
         }
334354
     }
335355
 }
@@ -343,7 +363,9 @@ fn merge_synthetic_section(existing: &mut OutputSection, synthetic: OutputSectio
343363
     existing.reserved2 = synthetic.reserved2;
344364
     existing.reserved3 = synthetic.reserved3;
345365
     if !synthetic.synthetic_data.is_empty() {
346
-        existing.synthetic_data.extend_from_slice(&synthetic.synthetic_data);
366
+        existing
367
+            .synthetic_data
368
+            .extend_from_slice(&synthetic.synthetic_data);
347369
     }
348370
 }
349371
 
@@ -412,7 +434,10 @@ fn segment_rank(kind: OutputKind, segment: &str) -> usize {
412434
         OutputKind::Executable => &EXEC_SEGMENTS,
413435
         OutputKind::Dylib => &DYLIB_SEGMENTS,
414436
     };
415
-    order.iter().position(|name| *name == segment).unwrap_or(order.len())
437
+    order
438
+        .iter()
439
+        .position(|name| *name == segment)
440
+        .unwrap_or(order.len())
416441
 }
417442
 
418443
 fn section_rank(segment: &str, section: &str) -> usize {
@@ -440,7 +465,10 @@ fn section_rank(segment: &str, section: &str) -> usize {
440465
         "__LINKEDIT" => &[],
441466
         _ => &[],
442467
     };
443
-    order.iter().position(|name| *name == section).unwrap_or(order.len())
468
+    order
469
+        .iter()
470
+        .position(|name| *name == section)
471
+        .unwrap_or(order.len())
444472
 }
445473
 
446474
 fn segment_init_prot(name: &str) -> Prot {
@@ -529,11 +557,18 @@ mod tests {
529557
 
530558
     use crate::atom::{Atom, AtomFlags, AtomSection, AtomTable};
531559
     use crate::input::ObjectFile;
532
-    use crate::macho::constants::{CPU_SUBTYPE_ARM64_ALL, CPU_TYPE_ARM64, MH_MAGIC_64, MH_OBJECT, S_ATTR_PURE_INSTRUCTIONS, S_ATTR_SOME_INSTRUCTIONS, S_CSTRING_LITERALS, S_REGULAR, S_ZEROFILL};
560
+    use crate::macho::constants::{
561
+        CPU_SUBTYPE_ARM64_ALL, CPU_TYPE_ARM64, MH_MAGIC_64, MH_OBJECT, S_ATTR_PURE_INSTRUCTIONS,
562
+        S_ATTR_SOME_INSTRUCTIONS, S_CSTRING_LITERALS, S_REGULAR, S_ZEROFILL,
563
+    };
533564
     use crate::macho::reader::MachHeader64;
534565
     use crate::resolve::{DylibId, InputId, SymbolId};
535566
     use crate::section::{InputSection, SectionKind};
536
-    use crate::synth::{got::GotSection, stubs::{LazyPointerSection, StubsSection, StubEntry, LazyPointerEntry}, SyntheticPlan};
567
+    use crate::synth::{
568
+        got::GotSection,
569
+        stubs::{LazyPointerEntry, LazyPointerSection, StubEntry, StubsSection},
570
+        SyntheticPlan,
571
+    };
537572
 
538573
     use super::*;
539574
 
@@ -553,7 +588,13 @@ mod tests {
553588
             },
554589
             commands: Vec::new(),
555590
             sections: vec![
556
-                input_section("__TEXT", "__cstring", SectionKind::CStringLiterals, 0, S_CSTRING_LITERALS),
591
+                input_section(
592
+                    "__TEXT",
593
+                    "__cstring",
594
+                    SectionKind::CStringLiterals,
595
+                    0,
596
+                    S_CSTRING_LITERALS,
597
+                ),
557598
                 input_section(
558599
                     "__TEXT",
559600
                     "__text",
@@ -582,8 +623,24 @@ mod tests {
582623
             0,
583624
             b"hello\0".to_vec(),
584625
         ));
585
-        atoms.push(atom(InputId(0), 3, AtomSection::ConstData, 0, 16, 3, vec![1; 16]));
586
-        atoms.push(atom(InputId(0), 4, AtomSection::ZeroFill, 0, 32, 3, Vec::new()));
626
+        atoms.push(atom(
627
+            InputId(0),
628
+            3,
629
+            AtomSection::ConstData,
630
+            0,
631
+            16,
632
+            3,
633
+            vec![1; 16],
634
+        ));
635
+        atoms.push(atom(
636
+            InputId(0),
637
+            4,
638
+            AtomSection::ZeroFill,
639
+            0,
640
+            32,
641
+            3,
642
+            Vec::new(),
643
+        ));
587644
 
588645
         let layout = Layout::build(
589646
             OutputKind::Executable,
@@ -626,7 +683,13 @@ mod tests {
626683
                 reserved: 0,
627684
             },
628685
             commands: Vec::new(),
629
-            sections: vec![input_section("__DATA", "__bss", SectionKind::ZeroFill, 4, S_ZEROFILL)],
686
+            sections: vec![input_section(
687
+                "__DATA",
688
+                "__bss",
689
+                SectionKind::ZeroFill,
690
+                4,
691
+                S_ZEROFILL,
692
+            )],
630693
             symbols: Vec::new(),
631694
             strings: crate::string_table::StringTable::from_bytes(vec![0]),
632695
             symtab: None,
@@ -635,7 +698,15 @@ mod tests {
635698
         };
636699
 
637700
         let mut atoms = AtomTable::new();
638
-        atoms.push(atom(InputId(0), 1, AtomSection::ZeroFill, 0, 64, 4, Vec::new()));
701
+        atoms.push(atom(
702
+            InputId(0),
703
+            1,
704
+            AtomSection::ZeroFill,
705
+            0,
706
+            64,
707
+            4,
708
+            Vec::new(),
709
+        ));
639710
 
640711
         let layout = Layout::build(
641712
             OutputKind::Executable,
@@ -689,7 +760,15 @@ mod tests {
689760
         };
690761
 
691762
         let mut atoms = AtomTable::new();
692
-        atoms.push(atom(InputId(0), 1, AtomSection::Text, 0, 16, 2, vec![0; 16]));
763
+        atoms.push(atom(
764
+            InputId(0),
765
+            1,
766
+            AtomSection::Text,
767
+            0,
768
+            16,
769
+            2,
770
+            vec![0; 16],
771
+        ));
693772
         atoms.push(atom(InputId(0), 2, AtomSection::Data, 0, 8, 3, vec![0; 8]));
694773
 
695774
         let layout = Layout::build(
@@ -858,7 +937,13 @@ mod tests {
858937
                 reserved: 0,
859938
             },
860939
             commands: Vec::new(),
861
-            sections: vec![input_section("__DATA", "__data", SectionKind::Data, 3, S_REGULAR)],
940
+            sections: vec![input_section(
941
+                "__DATA",
942
+                "__data",
943
+                SectionKind::Data,
944
+                3,
945
+                S_REGULAR,
946
+            )],
862947
             symbols: Vec::new(),
863948
             strings: crate::string_table::StringTable::from_bytes(vec![0]),
864949
             symtab: None,
@@ -867,7 +952,15 @@ mod tests {
867952
         };
868953
 
869954
         let mut atoms = AtomTable::new();
870
-        atoms.push(atom(InputId(0), 1, AtomSection::Data, 0, 16, 3, vec![0xaa; 16]));
955
+        atoms.push(atom(
956
+            InputId(0),
957
+            1,
958
+            AtomSection::Data,
959
+            0,
960
+            16,
961
+            3,
962
+            vec![0xaa; 16],
963
+        ));
871964
 
872965
         let plan = SyntheticPlan {
873966
             got: GotSection {
@@ -929,7 +1022,13 @@ mod tests {
9291022
                 reserved: 0,
9301023
             },
9311024
             commands: Vec::new(),
932
-            sections: vec![input_section("__FOO", "__bar", SectionKind::Data, 3, S_REGULAR)],
1025
+            sections: vec![input_section(
1026
+                "__FOO",
1027
+                "__bar",
1028
+                SectionKind::Data,
1029
+                3,
1030
+                S_REGULAR,
1031
+            )],
9331032
             symbols: Vec::new(),
9341033
             strings: crate::string_table::StringTable::from_bytes(vec![0]),
9351034
             symtab: None,
src/lib.rsmodified
@@ -305,8 +305,13 @@ impl Linker {
305305
             )?;
306306
             layout = next_layout;
307307
             linkedit = Some(next_linkedit);
308
-            let changed =
309
-                synth::unwind::synthesize(&mut layout, &layout_inputs, &atom_table, &sym_table)?;
308
+            let changed = synth::unwind::synthesize(
309
+                &mut layout,
310
+                &layout_inputs,
311
+                &atom_table,
312
+                &sym_table,
313
+                &synthetic_plan,
314
+            )?;
310315
             if !changed {
311316
                 break;
312317
             }
src/macho/dylib.rsmodified
@@ -11,7 +11,9 @@ use std::path::PathBuf;
1111
 
1212
 use super::constants::*;
1313
 use super::exports::{ExportEntry, ExportKind, Exports};
14
-use super::reader::{parse_commands, parse_header, LoadCommand, MachHeader64, ReadError, SymtabCmd};
14
+use super::reader::{
15
+    parse_commands, parse_header, LoadCommand, MachHeader64, ReadError, SymtabCmd,
16
+};
1517
 use super::tbd::{parse_version, SymbolLists, Target, Tbd};
1618
 
1719
 /// How a consumer loaded this dylib. The filetype of the dylib itself is
@@ -132,10 +134,7 @@ impl DylibFile {
132134
 /// `LC_DYLD_EXPORTS_TRIE` (chained-fixups era). Dylibs built by older
133135
 /// toolchains may have no export trie; in that case return an empty
134136
 /// `Exports::Flat(vec![])` so downstream `entries()` works uniformly.
135
-fn locate_exports(
136
-    commands: &[LoadCommand],
137
-    file_bytes: &[u8],
138
-) -> Result<Exports, ReadError> {
137
+fn locate_exports(commands: &[LoadCommand], file_bytes: &[u8]) -> Result<Exports, ReadError> {
139138
     for cmd in commands {
140139
         match cmd {
141140
             LoadCommand::DyldInfoOnly(d) if d.export_size != 0 => {
@@ -315,9 +314,7 @@ pub fn dependency_ordinal(deps: &[DylibDependency], install_name: &str) -> Optio
315314
 #[cfg(test)]
316315
 mod tests {
317316
     use super::*;
318
-    use crate::macho::reader::{
319
-        write_commands, write_header, DylibCmd, RpathCmd,
320
-    };
317
+    use crate::macho::reader::{write_commands, write_header, DylibCmd, RpathCmd};
321318
 
322319
     fn make_dylib_image(commands: Vec<LoadCommand>) -> Vec<u8> {
323320
         let sizeofcmds: u32 = commands.iter().map(|c| c.cmdsize()).sum();
@@ -404,10 +401,7 @@ mod tests {
404401
             }),
405402
         ]);
406403
         let dy = DylibFile::parse("/tmp/x.dylib", &image).unwrap();
407
-        assert_eq!(
408
-            dy.rpaths,
409
-            vec!["@executable_path/../lib", "/opt/local/lib"]
410
-        );
404
+        assert_eq!(dy.rpaths, vec!["@executable_path/../lib", "/opt/local/lib"]);
411405
     }
412406
 
413407
     // ----- DylibFile::from_tbd tests -----
src/macho/exports.rsmodified
@@ -275,7 +275,12 @@ fn lookup(
275275
         child_cursor += off_len;
276276
         let edge_bytes = edge.as_bytes();
277277
         if remaining.len() >= edge_bytes.len() && &remaining[..edge_bytes.len()] == edge_bytes {
278
-            return lookup(trie, child_off as usize, &remaining[edge_bytes.len()..], depth + 1);
278
+            return lookup(
279
+                trie,
280
+                child_off as usize,
281
+                &remaining[edge_bytes.len()..],
282
+                depth + 1,
283
+            );
279284
         }
280285
     }
281286
     Ok(None)
src/macho/reader.rsmodified
@@ -17,15 +17,27 @@ use super::constants::*;
1717
 #[derive(Debug)]
1818
 pub enum ReadError {
1919
     /// Not enough bytes to decode the next field.
20
-    Truncated { need: usize, have: usize, context: &'static str },
20
+    Truncated {
21
+        need: usize,
22
+        have: usize,
23
+        context: &'static str,
24
+    },
2125
     /// Magic number is not `MH_MAGIC_64`.
2226
     BadMagic { got: u32 },
2327
     /// CPU type is not `CPU_TYPE_ARM64`.
2428
     UnsupportedCpu { got: u32 },
2529
     /// A load command's `cmdsize` field is malformed.
26
-    BadCmdsize { cmd: u32, cmdsize: u32, at_offset: usize, reason: &'static str },
30
+    BadCmdsize {
31
+        cmd: u32,
32
+        cmdsize: u32,
33
+        at_offset: usize,
34
+        reason: &'static str,
35
+    },
2736
     /// A relocation entry or pairing is structurally invalid.
28
-    BadRelocation { at_offset: u32, reason: &'static str },
37
+    BadRelocation {
38
+        at_offset: u32,
39
+        reason: &'static str,
40
+    },
2941
 }
3042
 
3143
 impl fmt::Display for ReadError {
@@ -144,7 +156,11 @@ pub enum LoadCommand {
144156
     DyldChainedFixups(LinkEditDataCmd),
145157
     /// A load command whose payload we haven't decoded yet. Preserves bytes
146158
     /// verbatim for byte-level round-trip.
147
-    Raw { cmd: u32, cmdsize: u32, data: Vec<u8> },
159
+    Raw {
160
+        cmd: u32,
161
+        cmdsize: u32,
162
+        data: Vec<u8>,
163
+    },
148164
 }
149165
 
150166
 impl LoadCommand {
@@ -357,17 +373,15 @@ impl Section64Header {
357373
 /// Parse the `header.ncmds` load commands that follow a `mach_header_64`.
358374
 /// The slice must cover the full file (or at least through `sizeofcmds`);
359375
 /// offsets are always relative to the start of the mach-o image.
360
-pub fn parse_commands(
361
-    header: &MachHeader64,
362
-    bytes: &[u8],
363
-) -> Result<Vec<LoadCommand>, ReadError> {
364
-    let cmds_end = HEADER_SIZE
365
-        .checked_add(header.sizeofcmds as usize)
366
-        .ok_or(ReadError::Truncated {
367
-            need: usize::MAX,
368
-            have: bytes.len(),
369
-            context: "load-command region (sizeofcmds overflows)",
370
-        })?;
376
+pub fn parse_commands(header: &MachHeader64, bytes: &[u8]) -> Result<Vec<LoadCommand>, ReadError> {
377
+    let cmds_end =
378
+        HEADER_SIZE
379
+            .checked_add(header.sizeofcmds as usize)
380
+            .ok_or(ReadError::Truncated {
381
+                need: usize::MAX,
382
+                have: bytes.len(),
383
+                context: "load-command region (sizeofcmds overflows)",
384
+            })?;
371385
     if bytes.len() < cmds_end {
372386
         return Err(ReadError::Truncated {
373387
             need: cmds_end,
@@ -439,13 +453,12 @@ fn decode_command(cmd: u32, cmdsize: u32, payload: &[u8]) -> Result<LoadCommand,
439453
         LC_LINKER_OPTIMIZATION_HINT => Ok(LoadCommand::LinkerOptimizationHint(
440454
             LinkEditDataCmd::parse(LC_LINKER_OPTIMIZATION_HINT, cmdsize, payload)?,
441455
         )),
442
-        LC_ID_DYLIB
443
-        | LC_LOAD_DYLIB
444
-        | LC_LOAD_WEAK_DYLIB
445
-        | LC_REEXPORT_DYLIB
456
+        LC_ID_DYLIB | LC_LOAD_DYLIB | LC_LOAD_WEAK_DYLIB | LC_REEXPORT_DYLIB
446457
         | LC_LOAD_UPWARD_DYLIB => Ok(LoadCommand::Dylib(DylibCmd::parse(cmd, cmdsize, payload)?)),
447458
         LC_RPATH => Ok(LoadCommand::Rpath(RpathCmd::parse(cmdsize, payload)?)),
448
-        LC_DYLD_INFO_ONLY => Ok(LoadCommand::DyldInfoOnly(DyldInfoCmd::parse(cmdsize, payload)?)),
459
+        LC_DYLD_INFO_ONLY => Ok(LoadCommand::DyldInfoOnly(DyldInfoCmd::parse(
460
+            cmdsize, payload,
461
+        )?)),
449462
         LC_DYLD_EXPORTS_TRIE => Ok(LoadCommand::DyldExportsTrie(LinkEditDataCmd::parse(
450463
             LC_DYLD_EXPORTS_TRIE,
451464
             cmdsize,
@@ -855,12 +868,15 @@ impl RpathCmd {
855868
         }
856869
         let start = off_in_cmd - 8;
857870
         let bytes = &payload[start..];
858
-        let nul = bytes.iter().position(|&b| b == 0).ok_or(ReadError::BadCmdsize {
859
-            cmd: LC_RPATH,
860
-            cmdsize,
861
-            at_offset: 0,
862
-            reason: "rpath_command path is not null-terminated",
863
-        })?;
871
+        let nul = bytes
872
+            .iter()
873
+            .position(|&b| b == 0)
874
+            .ok_or(ReadError::BadCmdsize {
875
+                cmd: LC_RPATH,
876
+                cmdsize,
877
+                at_offset: 0,
878
+                reason: "rpath_command path is not null-terminated",
879
+            })?;
864880
         let path = std::str::from_utf8(&bytes[..nul])
865881
             .map_err(|_| ReadError::BadCmdsize {
866882
                 cmd: LC_RPATH,
@@ -1074,7 +1090,14 @@ mod tests {
10741090
     fn truncated_header_errors_cleanly() {
10751091
         let err = parse_header(&[0u8; 10]).unwrap_err();
10761092
         assert!(
1077
-            matches!(err, ReadError::Truncated { need: HEADER_SIZE, have: 10, .. }),
1093
+            matches!(
1094
+                err,
1095
+                ReadError::Truncated {
1096
+                    need: HEADER_SIZE,
1097
+                    have: 10,
1098
+                    ..
1099
+                }
1100
+            ),
10781101
             "unexpected: {err:?}"
10791102
         );
10801103
     }
@@ -1093,7 +1116,10 @@ mod tests {
10931116
         // Overwrite cputype with x86_64 (0x01000007).
10941117
         bytes[4..8].copy_from_slice(&0x0100_0007u32.to_le_bytes());
10951118
         let err = parse_header(&bytes).unwrap_err();
1096
-        assert!(matches!(err, ReadError::UnsupportedCpu { got: 0x0100_0007 }));
1119
+        assert!(matches!(
1120
+            err,
1121
+            ReadError::UnsupportedCpu { got: 0x0100_0007 }
1122
+        ));
10971123
     }
10981124
 
10991125
     /// Synthesize a mach-o image with `n` load commands, each of size
@@ -1350,8 +1376,14 @@ mod tests {
13501376
             minos: (11 << 16) | (3 << 8),
13511377
             sdk: (14 << 16) | (2 << 8),
13521378
             tools: vec![
1353
-                BuildTool { tool: 3, version: 0x0001_0002 },
1354
-                BuildTool { tool: 4, version: 0x0002_0003 },
1379
+                BuildTool {
1380
+                    tool: 3,
1381
+                    version: 0x0001_0002,
1382
+                },
1383
+                BuildTool {
1384
+                    tool: 4,
1385
+                    version: 0x0002_0003,
1386
+                },
13551387
             ],
13561388
         };
13571389
         let mut wire2 = Vec::new();
@@ -1367,7 +1399,10 @@ mod tests {
13671399
             platform: PLATFORM_MACOS,
13681400
             minos: (11 << 16),
13691401
             sdk: (14 << 16),
1370
-            tools: vec![BuildTool { tool: 3, version: 1 }],
1402
+            tools: vec![BuildTool {
1403
+                tool: 3,
1404
+                version: 1,
1405
+            }],
13711406
         };
13721407
         let mut wire = Vec::new();
13731408
         cmd.write(&mut wire);
@@ -1491,7 +1526,9 @@ mod tests {
14911526
         payload.extend_from_slice(&0u32.to_le_bytes());
14921527
         payload.extend_from_slice(&0u32.to_le_bytes());
14931528
         let err = DylibCmd::parse(LC_LOAD_DYLIB, 32, &payload).unwrap_err();
1494
-        assert!(matches!(err, ReadError::BadCmdsize { reason, .. } if reason.contains("name offset")));
1529
+        assert!(
1530
+            matches!(err, ReadError::BadCmdsize { reason, .. } if reason.contains("name offset"))
1531
+        );
14951532
     }
14961533
 
14971534
     #[test]
@@ -1504,8 +1541,12 @@ mod tests {
15041541
         cmd.write(LC_LINKER_OPTIMIZATION_HINT, &mut wire);
15051542
         assert_eq!(wire.len(), LinkEditDataCmd::WIRE_SIZE as usize);
15061543
 
1507
-        let decoded =
1508
-            LinkEditDataCmd::parse(LC_LINKER_OPTIMIZATION_HINT, LinkEditDataCmd::WIRE_SIZE, &wire[8..]).unwrap();
1544
+        let decoded = LinkEditDataCmd::parse(
1545
+            LC_LINKER_OPTIMIZATION_HINT,
1546
+            LinkEditDataCmd::WIRE_SIZE,
1547
+            &wire[8..],
1548
+        )
1549
+        .unwrap();
15091550
         assert_eq!(decoded, cmd);
15101551
 
15111552
         let hdr = MachHeader64 {
@@ -1563,7 +1604,13 @@ mod tests {
15631604
         let parsed_hdr = parse_header(&image).unwrap();
15641605
         let cmds = parse_commands(&parsed_hdr, &image).unwrap();
15651606
         assert!(matches!(cmds[0], LoadCommand::Segment64(_)));
1566
-        assert!(matches!(cmds[1], LoadCommand::Raw { cmd: 0xCAFE_F00D, .. }));
1607
+        assert!(matches!(
1608
+            cmds[1],
1609
+            LoadCommand::Raw {
1610
+                cmd: 0xCAFE_F00D,
1611
+                ..
1612
+            }
1613
+        ));
15671614
 
15681615
         let mut out = Vec::new();
15691616
         write_header(&parsed_hdr, &mut out);
src/macho/tbd.rsmodified
@@ -152,11 +152,7 @@ fn decode_document(doc: &Document) -> Result<Tbd, TbdError> {
152152
             "exports" => tbd.exports = decode_scoped_symbols(v)?,
153153
             "reexports" => tbd.reexports = decode_scoped_symbols(v)?,
154154
             // Known-but-ignored keys (grow this list as TAPI adds them).
155
-            "uuids"
156
-            | "flags"
157
-            | "swift-abi-version"
158
-            | "rpaths"
159
-            | "objc-constraint"
155
+            "uuids" | "flags" | "swift-abi-version" | "rpaths" | "objc-constraint"
160156
             | "parent-libraries" => {}
161157
             _ => {
162158
                 // Silently accept unknown keys — TAPI can add new ones in
@@ -180,7 +176,9 @@ fn decode_target_list(v: &Value) -> Result<Vec<Target>, TbdError> {
180176
         .ok_or_else(|| schema("'targets' must be a sequence"))?;
181177
     let mut out = Vec::with_capacity(seq.len());
182178
     for item in seq {
183
-        let s = item.as_str().ok_or_else(|| schema("target must be a scalar"))?;
179
+        let s = item
180
+            .as_str()
181
+            .ok_or_else(|| schema("target must be a scalar"))?;
184182
         out.push(parse_target(s)?);
185183
     }
186184
     Ok(out)
@@ -189,9 +187,9 @@ fn decode_target_list(v: &Value) -> Result<Vec<Target>, TbdError> {
189187
 fn parse_target(s: &str) -> Result<Target, TbdError> {
190188
     // `arch-platform`. Arch may contain a hyphen (none today, but armv7k
191189
     // in the wild) — split on the *last* `-`.
192
-    let hyphen = s.rfind('-').ok_or_else(|| schema(&format!(
193
-        "target {s:?} is not `arch-platform`"
194
-    )))?;
190
+    let hyphen = s
191
+        .rfind('-')
192
+        .ok_or_else(|| schema(&format!("target {s:?} is not `arch-platform`")))?;
195193
     let arch = match &s[..hyphen] {
196194
         "arm64" => Arch::Arm64,
197195
         "arm64e" => Arch::Arm64e,
@@ -220,8 +218,7 @@ fn decode_scoped_umbrella(v: &Value) -> Result<Vec<Scoped<String>>, TbdError> {
220218
             .as_mapping()
221219
             .ok_or_else(|| schema("parent-umbrella entry must be a mapping"))?;
222220
         let targets = lookup_required(m, "targets").and_then(decode_target_list)?;
223
-        let umbrella = lookup_required(m, "umbrella")
224
-            .and_then(|v| scalar_string(v, "umbrella"))?;
221
+        let umbrella = lookup_required(m, "umbrella").and_then(|v| scalar_string(v, "umbrella"))?;
225222
         out.push(Scoped {
226223
             targets,
227224
             value: umbrella,
@@ -322,7 +319,9 @@ fn scalar_string(v: &Value, context: &str) -> Result<String, TbdError> {
322319
 }
323320
 
324321
 fn schema(msg: &str) -> TbdError {
325
-    TbdError::Schema { msg: msg.to_string() }
322
+    TbdError::Schema {
323
+        msg: msg.to_string(),
324
+    }
326325
 }
327326
 
328327
 /// Pack a `"X.Y.Z"` / `"X.Y"` / `"X"` / `"1351"` version string to
@@ -435,7 +434,10 @@ mod tests {
435434
         assert_eq!(tbd.reexported_libraries.len(), 1);
436435
         assert_eq!(
437436
             tbd.reexported_libraries[0].value,
438
-            vec!["/usr/lib/system/libcache.dylib", "/usr/lib/system/libxpc.dylib"]
437
+            vec![
438
+                "/usr/lib/system/libcache.dylib",
439
+                "/usr/lib/system/libxpc.dylib"
440
+            ]
439441
         );
440442
     }
441443
 
src/macho/tbd_yaml.rsmodified
@@ -67,7 +67,11 @@ pub struct YamlError {
6767
 
6868
 impl fmt::Display for YamlError {
6969
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70
-        write!(f, "YAML error at line {}, col {}: {}", self.line, self.col, self.msg)
70
+        write!(
71
+            f,
72
+            "YAML error at line {}, col {}: {}",
73
+            self.line, self.col, self.msg
74
+        )
7175
     }
7276
 }
7377
 
@@ -237,10 +241,7 @@ fn flow_unbalanced(s: &str) -> bool {
237241
 fn parse_tag(rest: &str) -> Option<String> {
238242
     let rest = rest.trim_start();
239243
     if let Some(tag) = rest.strip_prefix('!') {
240
-        let end = tag
241
-            .chars()
242
-            .take_while(|c| !c.is_whitespace())
243
-            .count();
244
+        let end = tag.chars().take_while(|c| !c.is_whitespace()).count();
244245
         Some(format!("!{}", &tag[..end]))
245246
     } else {
246247
         None
@@ -438,11 +439,7 @@ fn find_top_level_mapping_colon(s: &str) -> Option<usize> {
438439
     None
439440
 }
440441
 
441
-fn split_key(
442
-    content: &str,
443
-    line_no: usize,
444
-    col: usize,
445
-) -> Result<(String, String), YamlError> {
442
+fn split_key(content: &str, line_no: usize, col: usize) -> Result<(String, String), YamlError> {
446443
     let colon = find_top_level_mapping_colon(content).ok_or(YamlError {
447444
         line: line_no,
448445
         col: col + 1,
@@ -626,9 +623,7 @@ mod tests {
626623
 
627624
     #[test]
628625
     fn nested_mapping_via_indentation() {
629
-        let doc = parse_one(
630
-            "a:\n  b: 1\n  c: 2\n",
631
-        );
626
+        let doc = parse_one("a:\n  b: 1\n  c: 2\n");
632627
         let a = doc.root.get("a").unwrap();
633628
         let m = a.as_mapping().unwrap();
634629
         assert_eq!(m[0], ("b".into(), Value::Scalar("1".into())));
@@ -684,9 +679,8 @@ mod tests {
684679
 
685680
     #[test]
686681
     fn block_sequence_entries_with_flow_values() {
687
-        let doc = parse_one(
688
-            "exports:\n  - targets: [ arm64-macos ]\n    symbols: [ _foo, _bar ]\n",
689
-        );
682
+        let doc =
683
+            parse_one("exports:\n  - targets: [ arm64-macos ]\n    symbols: [ _foo, _bar ]\n");
690684
         let e = doc.root.get("exports").unwrap().as_sequence().unwrap();
691685
         let entry = e[0].as_mapping().unwrap();
692686
         assert_eq!(
src/macho/writer.rsmodified
@@ -2217,6 +2217,9 @@ mod tests {
22172217
         };
22182218
 
22192219
         let blob = build_function_starts(&layout, &atoms).unwrap();
2220
-        assert_eq!(decode_function_starts_blob(&blob), vec![0x1000, 0x1008, 0x1040]);
2220
+        assert_eq!(
2221
+            decode_function_starts_blob(&blob),
2222
+            vec![0x1000, 0x1008, 0x1040]
2223
+        );
22212224
     }
22222225
 }
src/reloc/arm64.rsmodified
@@ -957,8 +957,22 @@ fn read_implicit_addend(
957957
     referent: Referent,
958958
 ) -> Result<i64, RelocError> {
959959
     match length {
960
-        RelocLength::Word => Ok(read_u32(bytes, local_offset, atom, obj, kind, &describe_referent(obj, referent))? as i32 as i64),
961
-        RelocLength::Quad => Ok(read_u64(bytes, local_offset, atom, obj, kind, &describe_referent(obj, referent))? as i64),
960
+        RelocLength::Word => Ok(read_u32(
961
+            bytes,
962
+            local_offset,
963
+            atom,
964
+            obj,
965
+            kind,
966
+            &describe_referent(obj, referent),
967
+        )? as i32 as i64),
968
+        RelocLength::Quad => Ok(read_u64(
969
+            bytes,
970
+            local_offset,
971
+            atom,
972
+            obj,
973
+            kind,
974
+            &describe_referent(obj, referent),
975
+        )? as i64),
962976
         other => Err(reloc_error(
963977
             atom,
964978
             &obj.path,
src/reloc/mod.rsmodified
@@ -74,11 +74,13 @@ pub fn parse_raw_relocs(
7474
     nreloc: u32,
7575
 ) -> Result<Vec<RawRelocation>, ReadError> {
7676
     let start = reloff as usize;
77
-    let total = (nreloc as usize).checked_mul(RAW_RELOC_SIZE).ok_or(ReadError::Truncated {
78
-        need: usize::MAX,
79
-        have: file_bytes.len(),
80
-        context: "reloc table (nreloc × 8 overflows)",
81
-    })?;
77
+    let total = (nreloc as usize)
78
+        .checked_mul(RAW_RELOC_SIZE)
79
+        .ok_or(ReadError::Truncated {
80
+            need: usize::MAX,
81
+            have: file_bytes.len(),
82
+            context: "reloc table (nreloc × 8 overflows)",
83
+        })?;
8284
     let end = start.checked_add(total).ok_or(ReadError::Truncated {
8385
         need: usize::MAX,
8486
         have: file_bytes.len(),
@@ -94,7 +96,9 @@ pub fn parse_raw_relocs(
9496
     let mut out = Vec::with_capacity(nreloc as usize);
9597
     for i in 0..nreloc as usize {
9698
         let off = start + i * RAW_RELOC_SIZE;
97
-        out.push(RawRelocation::parse(&file_bytes[off..off + RAW_RELOC_SIZE])?);
99
+        out.push(RawRelocation::parse(
100
+            &file_bytes[off..off + RAW_RELOC_SIZE],
101
+        )?);
98102
     }
99103
     Ok(out)
100104
 }
@@ -259,10 +263,11 @@ pub fn parse_relocs(raws: &[RawRelocation]) -> Result<Vec<Reloc>, ReadError> {
259263
                     at_offset: raw.r_address as u32,
260264
                     reason: "unknown ARM64_RELOC_* type",
261265
                 })?;
262
-                let length = RelocLength::from_bits(raw.r_length).ok_or(ReadError::BadRelocation {
263
-                    at_offset: raw.r_address as u32,
264
-                    reason: "invalid r_length (must be 0..=3)",
265
-                })?;
266
+                let length =
267
+                    RelocLength::from_bits(raw.r_length).ok_or(ReadError::BadRelocation {
268
+                        at_offset: raw.r_address as u32,
269
+                        reason: "invalid r_length (must be 0..=3)",
270
+                    })?;
266271
                 let addend = pending_addend.take().map(|v| v as i64).unwrap_or(0);
267272
                 let referent = referent_from(raw)?;
268273
 
@@ -580,7 +585,7 @@ mod tests {
580585
     fn raw_reloc_preserves_all_bit_fields() {
581586
         // Cover every extreme value to catch shift/mask bugs.
582587
         let raw = RawRelocation {
583
-            r_address: -1, // signed negative round-trips
588
+            r_address: -1,            // signed negative round-trips
584589
             r_symbolnum: 0x00FF_FFFF, // max 24-bit value
585590
             r_pcrel: true,
586591
             r_length: 0b11,
@@ -615,7 +620,14 @@ mod tests {
615620
     #[test]
616621
     fn raw_reloc_truncated_errors() {
617622
         let err = RawRelocation::parse(&[0u8; 4]).unwrap_err();
618
-        assert!(matches!(err, ReadError::Truncated { need: RAW_RELOC_SIZE, have: 4, .. }));
623
+        assert!(matches!(
624
+            err,
625
+            ReadError::Truncated {
626
+                need: RAW_RELOC_SIZE,
627
+                have: 4,
628
+                ..
629
+            }
630
+        ));
619631
     }
620632
 
621633
     #[test]
src/resolve.rsmodified
@@ -277,11 +277,7 @@ impl Inputs {
277277
             compatibility_version: file.compatibility_version,
278278
             ordinal,
279279
         };
280
-        self.add_dylib_from_file_with_meta(
281
-            path,
282
-            file,
283
-            load,
284
-        )
280
+        self.add_dylib_from_file_with_meta(path, file, load)
285281
     }
286282
 
287283
     pub fn add_dylib_from_file_with_meta(
@@ -303,7 +299,12 @@ impl Inputs {
303299
     }
304300
 
305301
     pub fn next_dylib_ordinal(&self) -> u16 {
306
-        self.dylibs.iter().map(|dylib| dylib.ordinal).max().unwrap_or(0) + 1
302
+        self.dylibs
303
+            .iter()
304
+            .map(|dylib| dylib.ordinal)
305
+            .max()
306
+            .unwrap_or(0)
307
+            + 1
307308
     }
308309
 
309310
     // ---- accessors ----
@@ -671,9 +672,9 @@ impl SymbolTable {
671672
                 first: existing_id,
672673
                 second: Box::new(new.clone()),
673674
             }),
674
-            (false, true) => Ok(Action::Replace),     // strong over weak
675
-            (true, false) => Ok(Action::Keep),        // strong keeps its seat
676
-            (false, false) => Ok(Action::Keep),       // first weak wins
675
+            (false, true) => Ok(Action::Replace), // strong over weak
676
+            (true, false) => Ok(Action::Keep),    // strong keeps its seat
677
+            (false, false) => Ok(Action::Keep),   // first weak wins
677678
         }
678679
     }
679680
 
@@ -708,7 +709,9 @@ impl SymbolTable {
708709
                 InsertOutcome::CommonCoalesced { id }
709710
             }
710711
             Action::PendingArchiveFetch => {
711
-                let Symbol::LazyArchive { archive, member, .. } = self.symbols[id.0 as usize]
712
+                let Symbol::LazyArchive {
713
+                    archive, member, ..
714
+                } = self.symbols[id.0 as usize]
712715
                 else {
713716
                     unreachable!("PendingArchiveFetch requires LazyArchive in slot")
714717
                 };
@@ -719,8 +722,7 @@ impl SymbolTable {
719722
                 }
720723
             }
721724
             Action::PendingObjectLoad => {
722
-                let Symbol::LazyObject { origin, .. } = self.symbols[id.0 as usize]
723
-                else {
725
+                let Symbol::LazyObject { origin, .. } = self.symbols[id.0 as usize] else {
724726
                     unreachable!("PendingObjectLoad requires LazyObject in slot")
725727
                 };
726728
                 InsertOutcome::PendingObjectLoad { id, origin }
@@ -746,9 +748,7 @@ impl SymbolTable {
746748
             unreachable!("coalesce_common requires two Common entries");
747749
         };
748750
         if let Symbol::Common {
749
-            size,
750
-            align_pow2,
751
-            ..
751
+            size, align_pow2, ..
752752
         } = slot
753753
         {
754754
             *size = a_size.max(b_size);
@@ -892,10 +892,7 @@ impl ReferrerLog {
892892
     }
893893
 
894894
     pub fn get(&self, name: Istr) -> &[InputId] {
895
-        self.entries
896
-            .get(&name)
897
-            .map(|v| v.as_slice())
898
-            .unwrap_or(&[])
895
+        self.entries.get(&name).map(|v| v.as_slice()).unwrap_or(&[])
899896
     }
900897
 
901898
     pub fn extend_from(&mut self, other: &ReferrerLog) {
@@ -1037,11 +1034,7 @@ pub fn seed_dylib(
10371034
     report: &mut SeedReport,
10381035
 ) -> Result<(), SeedError> {
10391036
     let di = inputs.dylib(dylib_id);
1040
-    let entries = di
1041
-        .file
1042
-        .exports
1043
-        .entries()
1044
-        .map_err(SeedError::Read)?;
1037
+    let entries = di.file.exports.entries().map_err(SeedError::Read)?;
10451038
     for entry in entries {
10461039
         let name = table.intern(&entry.name);
10471040
         let sym = Symbol::DylibImport {
@@ -1149,13 +1142,12 @@ fn ingest_member_bytes(
11491142
     // Extract owned data before mutating the registry.
11501143
     let (logical_path, member_bytes) = {
11511144
         let archive = Archive::open(&ai.path, &ai.bytes)?;
1152
-        let member =
1153
-            archive
1154
-                .member_at_offset(member_id.0)
1155
-                .ok_or(FetchError::MemberNotFound {
1156
-                    archive: archive_id,
1157
-                    member: member_id,
1158
-                })?;
1145
+        let member = archive
1146
+            .member_at_offset(member_id.0)
1147
+            .ok_or(FetchError::MemberNotFound {
1148
+                archive: archive_id,
1149
+                member: member_id,
1150
+            })?;
11591151
         let logical = format!("{}({})", ai.path.display(), member.name);
11601152
         (logical, member.body.to_vec())
11611153
     };
@@ -1212,13 +1204,7 @@ pub fn force_load_archive(
12121204
     };
12131205
     let mut queue: Vec<PendingFetch> = Vec::new();
12141206
     for offset in member_offsets {
1215
-        let new = ingest_member_bytes(
1216
-            inputs,
1217
-            table,
1218
-            archive_id,
1219
-            MemberId(offset),
1220
-            report,
1221
-        )?;
1207
+        let new = ingest_member_bytes(inputs, table, archive_id, MemberId(offset), report)?;
12221208
         queue.extend(new);
12231209
     }
12241210
     while let Some(p) = queue.pop() {
@@ -1325,9 +1311,7 @@ pub fn levenshtein(a: &str, b: &str) -> usize {
13251311
         row[0] = i + 1;
13261312
         for (j, cb) in b.iter().enumerate() {
13271313
             let cost = if ca == cb { 0 } else { 1 };
1328
-            let new_val = (row[j + 1] + 1)
1329
-                .min(row[j] + 1)
1330
-                .min(prev + cost);
1314
+            let new_val = (row[j + 1] + 1).min(row[j] + 1).min(prev + cost);
13311315
             prev = row[j + 1];
13321316
             row[j + 1] = new_val;
13331317
         }
@@ -1440,9 +1424,7 @@ pub fn classify_unresolved(
14401424
     let undefs: Vec<(SymbolId, Istr, bool)> = table
14411425
         .iter()
14421426
         .filter_map(|(id, s)| match s {
1443
-            Symbol::Undefined {
1444
-                name, weak_ref, ..
1445
-            } => Some((id, *name, *weak_ref)),
1427
+            Symbol::Undefined { name, weak_ref, .. } => Some((id, *name, *weak_ref)),
14461428
             _ => None,
14471429
         })
14481430
         .collect();
@@ -2018,7 +2000,9 @@ mod tests {
20182000
         t.insert(lazy).unwrap();
20192001
         let want = undef(&mut t, "_hidden");
20202002
         match t.insert(want).unwrap() {
2021
-            InsertOutcome::PendingArchiveFetch { archive, member, .. } => {
2003
+            InsertOutcome::PendingArchiveFetch {
2004
+                archive, member, ..
2005
+            } => {
20222006
                 assert_eq!(archive, ArchiveId(7));
20232007
                 assert_eq!(member, MemberId(42));
20242008
             }
src/section.rsmodified
@@ -158,11 +158,13 @@ impl InputSection {
158158
             Vec::new()
159159
         } else {
160160
             let start = hdr.offset as usize;
161
-            let end = start.checked_add(hdr.size as usize).ok_or(ReadError::Truncated {
162
-                need: usize::MAX,
163
-                have: file_bytes.len(),
164
-                context: "section content (offset + size overflows)",
165
-            })?;
161
+            let end = start
162
+                .checked_add(hdr.size as usize)
163
+                .ok_or(ReadError::Truncated {
164
+                    need: usize::MAX,
165
+                    have: file_bytes.len(),
166
+                    context: "section content (offset + size overflows)",
167
+                })?;
166168
             if end > file_bytes.len() {
167169
                 return Err(ReadError::Truncated {
168170
                     need: end,
@@ -177,11 +179,13 @@ impl InputSection {
177179
             Vec::new()
178180
         } else {
179181
             let start = hdr.reloff as usize;
180
-            let total = (hdr.nreloc as usize).checked_mul(8).ok_or(ReadError::Truncated {
181
-                need: usize::MAX,
182
-                have: file_bytes.len(),
183
-                context: "section relocs (nreloc × 8 overflows)",
184
-            })?;
182
+            let total = (hdr.nreloc as usize)
183
+                .checked_mul(8)
184
+                .ok_or(ReadError::Truncated {
185
+                    need: usize::MAX,
186
+                    have: file_bytes.len(),
187
+                    context: "section relocs (nreloc × 8 overflows)",
188
+                })?;
185189
             let end = start.checked_add(total).ok_or(ReadError::Truncated {
186190
                 need: usize::MAX,
187191
                 have: file_bytes.len(),
src/string_table.rsmodified
@@ -24,11 +24,13 @@ impl StringTable {
2424
     /// from `LC_SYMTAB`.
2525
     pub fn from_file(file_bytes: &[u8], stroff: u32, strsize: u32) -> Result<Self, ReadError> {
2626
         let start = stroff as usize;
27
-        let end = start.checked_add(strsize as usize).ok_or(ReadError::Truncated {
28
-            need: usize::MAX,
29
-            have: file_bytes.len(),
30
-            context: "string table (stroff + strsize overflows)",
31
-        })?;
27
+        let end = start
28
+            .checked_add(strsize as usize)
29
+            .ok_or(ReadError::Truncated {
30
+                need: usize::MAX,
31
+                have: file_bytes.len(),
32
+                context: "string table (stroff + strsize overflows)",
33
+            })?;
3234
         if end > file_bytes.len() {
3335
             return Err(ReadError::Truncated {
3436
                 need: end,
@@ -75,15 +77,16 @@ impl StringTable {
7577
                 reason: "strx out of bounds",
7678
             });
7779
         }
78
-        let end = start + self.raw[start..]
79
-            .iter()
80
-            .position(|&b| b == 0)
81
-            .ok_or(ReadError::BadCmdsize {
82
-                cmd: 0,
83
-                cmdsize: 0,
84
-                at_offset: start,
85
-                reason: "unterminated string (no null byte before end)",
86
-            })?;
80
+        let end = start
81
+            + self.raw[start..]
82
+                .iter()
83
+                .position(|&b| b == 0)
84
+                .ok_or(ReadError::BadCmdsize {
85
+                    cmd: 0,
86
+                    cmdsize: 0,
87
+                    at_offset: start,
88
+                    reason: "unterminated string (no null byte before end)",
89
+                })?;
8790
         std::str::from_utf8(&self.raw[start..end]).map_err(|_| ReadError::BadCmdsize {
8891
             cmd: 0,
8992
             cmdsize: 0,
@@ -196,14 +199,18 @@ mod tests {
196199
     fn out_of_bounds_strx_errors() {
197200
         let t = tbl(b"\0a\0");
198201
         let err = t.get(100).unwrap_err();
199
-        assert!(matches!(err, ReadError::BadCmdsize { reason, .. } if reason.contains("out of bounds")));
202
+        assert!(
203
+            matches!(err, ReadError::BadCmdsize { reason, .. } if reason.contains("out of bounds"))
204
+        );
200205
     }
201206
 
202207
     #[test]
203208
     fn unterminated_string_errors() {
204209
         let t = tbl(b"\0abcdef"); // no trailing null
205210
         let err = t.get(1).unwrap_err();
206
-        assert!(matches!(err, ReadError::BadCmdsize { reason, .. } if reason.contains("unterminated")));
211
+        assert!(
212
+            matches!(err, ReadError::BadCmdsize { reason, .. } if reason.contains("unterminated"))
213
+        );
207214
     }
208215
 
209216
     #[test]
src/symbol.rsmodified
@@ -224,7 +224,13 @@ mod tests {
224224
     use super::*;
225225
 
226226
     fn nlist(strx: u32, n_type: u8, n_sect: u8, n_desc: u16, n_value: u64) -> RawNlist {
227
-        RawNlist { strx, n_type, n_sect, n_desc, n_value }
227
+        RawNlist {
228
+            strx,
229
+            n_type,
230
+            n_sect,
231
+            n_desc,
232
+            n_value,
233
+        }
228234
     }
229235
 
230236
     #[test]
src/synth/code_sig.rsmodified
@@ -34,13 +34,15 @@ impl CodeSignaturePlan {
3434
         code_limit: u64,
3535
         executable: bool,
3636
     ) -> Result<Self, &'static str> {
37
-        let code_limit = u32::try_from(code_limit).map_err(|_| "code-signature offset exceeds 32-bit Mach-O field width")?;
37
+        let code_limit = u32::try_from(code_limit)
38
+            .map_err(|_| "code-signature offset exceeds 32-bit Mach-O field width")?;
3839
         let identifier = output_identifier(opts);
3940
         let (exec_seg_base, exec_seg_limit, exec_seg_flags) = exec_segment_info(layout, executable);
4041
         let blob_len = blob_len(code_limit as usize, &identifier);
4142
         Ok(Self {
4243
             dataoff: code_limit,
43
-            datasize: u32::try_from(blob_len).map_err(|_| "code-signature blob exceeds 32-bit Mach-O field width")?,
44
+            datasize: u32::try_from(blob_len)
45
+                .map_err(|_| "code-signature blob exceeds 32-bit Mach-O field width")?,
4446
             code_limit,
4547
             identifier,
4648
             exec_seg_base,
@@ -114,9 +116,9 @@ fn exec_segment_info(layout: &Layout, executable: bool) -> (u64, u64, u64) {
114116
         if !is_executable(section.kind) || section.is_zerofill() {
115117
             continue;
116118
         }
117
-        min_off = Some(
118
-            min_off.map_or(section.file_off, |min_off: u64| min_off.min(section.file_off)),
119
-        );
119
+        min_off = Some(min_off.map_or(section.file_off, |min_off: u64| {
120
+            min_off.min(section.file_off)
121
+        }));
120122
         max_end = max_end.max(section.file_off + section.size);
121123
     }
122124
     let exec_seg_limit = min_off.map_or(0, |min_off| max_end.saturating_sub(min_off));
@@ -132,7 +134,10 @@ fn exec_segment_info(layout: &Layout, executable: bool) -> (u64, u64, u64) {
132134
 }
133135
 
134136
 fn blob_len(code_limit: usize, identifier: &str) -> usize {
135
-    let cd_len = CODEDIRECTORY_HEADER_SIZE + identifier.len() + 1 + code_slots(code_limit) * CS_SHA256_LEN as usize;
137
+    let cd_len = CODEDIRECTORY_HEADER_SIZE
138
+        + identifier.len()
139
+        + 1
140
+        + code_slots(code_limit) * CS_SHA256_LEN as usize;
136141
     align_up((SUPERBLOB_HEADER_SIZE + cd_len) as u64, 8) as usize
137142
 }
138143
 
@@ -162,13 +167,7 @@ fn align_up(value: u64, align: u64) -> u64 {
162167
 
163168
 fn sha256(data: &[u8]) -> [u8; 32] {
164169
     const INIT: [u32; 8] = [
165
-        0x6a09e667,
166
-        0xbb67ae85,
167
-        0x3c6ef372,
168
-        0xa54ff53a,
169
-        0x510e527f,
170
-        0x9b05688c,
171
-        0x1f83d9ab,
170
+        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
172171
         0x5be0cd19,
173172
     ];
174173
     const K: [u32; 64] = [
@@ -188,7 +187,11 @@ fn sha256(data: &[u8]) -> [u8; 32] {
188187
     let mut block = [0u8; 128];
189188
     let full_blocks = data.len() / 64;
190189
     for idx in 0..full_blocks {
191
-        compress(&mut state, (&data[idx * 64..idx * 64 + 64]).try_into().unwrap(), &K);
190
+        compress(
191
+            &mut state,
192
+            (&data[idx * 64..idx * 64 + 64]).try_into().unwrap(),
193
+            &K,
194
+        );
192195
     }
193196
 
194197
     let rem = &data[full_blocks * 64..];
@@ -288,17 +291,17 @@ mod tests {
288291
         assert_eq!(
289292
             sha256(b""),
290293
             [
291
-                0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99,
292
-                0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95,
293
-                0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55,
294
+                0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f,
295
+                0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b,
296
+                0x78, 0x52, 0xb8, 0x55,
294297
             ]
295298
         );
296299
         assert_eq!(
297300
             sha256(b"abc"),
298301
             [
299
-                0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d,
300
-                0xae, 0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10,
301
-                0xff, 0x61, 0xf2, 0x00, 0x15, 0xad,
302
+                0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae,
303
+                0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61,
304
+                0xf2, 0x00, 0x15, 0xad,
302305
             ]
303306
         );
304307
     }
@@ -309,7 +312,13 @@ mod tests {
309312
             output: Some("apple".into()),
310313
             ..LinkOptions::default()
311314
         };
312
-        let plan = CodeSignaturePlan::new(&Layout::empty(crate::OutputKind::Executable, 0), &opts, 16_512, true).unwrap();
315
+        let plan = CodeSignaturePlan::new(
316
+            &Layout::empty(crate::OutputKind::Executable, 0),
317
+            &opts,
318
+            16_512,
319
+            true,
320
+        )
321
+        .unwrap();
313322
         let blob = plan.build(&vec![0; 16_512]);
314323
 
315324
         assert_eq!(plan.dataoff, 16_512);
src/synth/dyld_info.rsmodified
@@ -2,12 +2,12 @@ use std::collections::BTreeMap;
22
 
33
 use crate::leb::{write_sleb, write_uleb};
44
 use crate::macho::constants::{
5
-    BIND_IMMEDIATE_MASK, BIND_OPCODE_ADD_ADDR_ULEB, BIND_OPCODE_DO_BIND, BIND_OPCODE_SET_ADDEND_SLEB,
6
-    BIND_OPCODE_SET_DYLIB_ORDINAL_IMM, BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB,
7
-    BIND_OPCODE_SET_DYLIB_SPECIAL_IMM, BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB,
8
-    BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM, BIND_OPCODE_SET_TYPE_IMM,
9
-    BIND_SYMBOL_FLAGS_WEAK_IMPORT, BIND_TYPE_POINTER, REBASE_IMMEDIATE_MASK,
10
-    REBASE_OPCODE_DO_REBASE_IMM_TIMES, REBASE_OPCODE_DO_REBASE_ULEB_TIMES,
5
+    BIND_IMMEDIATE_MASK, BIND_OPCODE_ADD_ADDR_ULEB, BIND_OPCODE_DO_BIND,
6
+    BIND_OPCODE_SET_ADDEND_SLEB, BIND_OPCODE_SET_DYLIB_ORDINAL_IMM,
7
+    BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB, BIND_OPCODE_SET_DYLIB_SPECIAL_IMM,
8
+    BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB, BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM,
9
+    BIND_OPCODE_SET_TYPE_IMM, BIND_SYMBOL_FLAGS_WEAK_IMPORT, BIND_TYPE_POINTER,
10
+    REBASE_IMMEDIATE_MASK, REBASE_OPCODE_DO_REBASE_IMM_TIMES, REBASE_OPCODE_DO_REBASE_ULEB_TIMES,
1111
 };
1212
 use crate::macho::exports::{ExportEntry, ExportKind};
1313
 
@@ -195,7 +195,8 @@ fn emit_trie_node(node: &FlatTrieNode, offsets: &[usize], out: &mut Vec<u8>) {
195195
     let mut stream = OpcodeStream::new();
196196
     stream.uleb(terminal.len() as u64);
197197
     stream.bytes(&terminal);
198
-    stream.byte(u8::try_from(node.children.len()).expect("export trie node fanout should fit in u8"));
198
+    stream
199
+        .byte(u8::try_from(node.children.len()).expect("export trie node fanout should fit in u8"));
199200
     for (edge, child) in &node.children {
200201
         stream.string(edge);
201202
         stream.uleb(offsets[*child] as u64);
@@ -258,8 +259,12 @@ pub fn emit_bind_records(specs: &[BindRecordSpec<'_>]) -> Vec<u8> {
258259
             state.ordinal = Some(spec.ordinal);
259260
         }
260261
 
261
-        if current_symbol.as_deref() != Some(spec.name) || state.weak_import != Some(spec.weak_import) {
262
-            out.byte(BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | bind_symbol_flags(spec.weak_import));
262
+        if current_symbol.as_deref() != Some(spec.name)
263
+            || state.weak_import != Some(spec.weak_import)
264
+        {
265
+            out.byte(
266
+                BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | bind_symbol_flags(spec.weak_import),
267
+            );
263268
             out.string(spec.name);
264269
             current_symbol = Some(spec.name.to_string());
265270
             state.weak_import = Some(spec.weak_import);
@@ -278,9 +283,11 @@ pub fn emit_bind_records(specs: &[BindRecordSpec<'_>]) -> Vec<u8> {
278283
 
279284
         match (state.segment_index, state.next_segment_offset) {
280285
             (Some(segment_index), Some(next_segment_offset))
281
-                if segment_index == spec.segment_index && next_segment_offset == spec.segment_offset => {}
286
+                if segment_index == spec.segment_index
287
+                    && next_segment_offset == spec.segment_offset => {}
282288
             (Some(segment_index), Some(next_segment_offset))
283
-                if segment_index == spec.segment_index && next_segment_offset < spec.segment_offset =>
289
+                if segment_index == spec.segment_index
290
+                    && next_segment_offset < spec.segment_offset =>
284291
             {
285292
                 out.byte(BIND_OPCODE_ADD_ADDR_ULEB);
286293
                 out.uleb(spec.segment_offset - next_segment_offset);
@@ -350,9 +357,8 @@ mod tests {
350357
     use crate::macho::constants::{
351358
         BIND_OPCODE_DO_BIND, BIND_OPCODE_SET_ADDEND_SLEB, BIND_OPCODE_SET_DYLIB_ORDINAL_IMM,
352359
         BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB, BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM,
353
-        BIND_OPCODE_SET_TYPE_IMM, BIND_TYPE_POINTER,
354
-        EXPORT_SYMBOL_FLAGS_KIND_REGULAR, EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL,
355
-        EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION,
360
+        BIND_OPCODE_SET_TYPE_IMM, BIND_TYPE_POINTER, EXPORT_SYMBOL_FLAGS_KIND_REGULAR,
361
+        EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL, EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION,
356362
     };
357363
     use crate::macho::exports::Exports;
358364
 
@@ -469,13 +475,24 @@ mod tests {
469475
             vec![
470476
                 BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1,
471477
                 BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM,
472
-                b'_', b'a', b'l', b'p', b'h', b'a', 0,
478
+                b'_',
479
+                b'a',
480
+                b'l',
481
+                b'p',
482
+                b'h',
483
+                b'a',
484
+                0,
473485
                 BIND_OPCODE_SET_TYPE_IMM | BIND_TYPE_POINTER,
474486
                 BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 2,
475487
                 0,
476488
                 BIND_OPCODE_DO_BIND,
477489
                 BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM,
478
-                b'_', b'b', b'e', b't', b'a', 0,
490
+                b'_',
491
+                b'b',
492
+                b'e',
493
+                b't',
494
+                b'a',
495
+                0,
479496
                 BIND_OPCODE_DO_BIND,
480497
                 0,
481498
             ]
src/synth/mod.rsmodified
@@ -29,6 +29,8 @@ use self::stubs::{
2929
 };
3030
 use self::tlv::{ThreadPointerSection, THREAD_POINTER_SIZE};
3131
 
32
+const COMPACT_UNWIND_PERSONALITY_FIELD_OFFSET: u32 = 16;
33
+
3234
 #[derive(Debug, Clone, PartialEq, Eq)]
3335
 pub struct SyntheticPlan {
3436
     pub got: GotSection,
@@ -131,6 +133,17 @@ impl SyntheticPlan {
131133
                 .map(Vec::as_slice)
132134
                 .unwrap_or(&[]);
133135
             for reloc in relocs_for_atom(relocs, atom) {
136
+                if atom.section == AtomSection::CompactUnwind
137
+                    && reloc.kind == RelocKind::Unsigned
138
+                    && reloc.offset
139
+                        == atom.input_offset + COMPACT_UNWIND_PERSONALITY_FIELD_OFFSET
140
+                {
141
+                    if let Some(symbol_id) = dylib_import_referent(obj, reloc.referent, sym_table)
142
+                    {
143
+                        got.intern(symbol_id, dylib_import_is_weak(sym_table, symbol_id));
144
+                    }
145
+                    continue;
146
+                }
134147
                 match reloc.kind {
135148
                     RelocKind::Unsigned => {
136149
                         // `__thread_vars` descriptors carry their own dedicated
@@ -992,6 +1005,68 @@ mod tests {
9921005
         assert_eq!(plan.direct_binds[0].symbol, import);
9931006
     }
9941007
 
1008
+    #[test]
1009
+    fn synthetic_plan_collects_got_for_compact_unwind_personality_import() {
1010
+        let mut sym_table = SymbolTable::new();
1011
+        let name = sym_table.intern("___gxx_personality_v0");
1012
+        let input_id = InputId(0);
1013
+        let import = match sym_table
1014
+            .insert(Symbol::DylibImport {
1015
+                name,
1016
+                dylib: DylibId(0),
1017
+                ordinal: 2,
1018
+                weak_import: false,
1019
+            })
1020
+            .unwrap()
1021
+        {
1022
+            crate::resolve::InsertOutcome::Inserted(id) => id,
1023
+            other => panic!("unexpected insert outcome: {other:?}"),
1024
+        };
1025
+
1026
+        let relocs = vec![Reloc {
1027
+            offset: 16,
1028
+            kind: RelocKind::Unsigned,
1029
+            length: RelocLength::Quad,
1030
+            pcrel: false,
1031
+            referent: Referent::Symbol(0),
1032
+            addend: 0,
1033
+            subtrahend: None,
1034
+        }];
1035
+        let object = compact_unwind_object("___gxx_personality_v0", encode_raw_relocs(&relocs));
1036
+
1037
+        let mut atoms = AtomTable::new();
1038
+        atoms.push(Atom {
1039
+            id: crate::resolve::AtomId(0),
1040
+            origin: input_id,
1041
+            input_section: 1,
1042
+            section: AtomSection::CompactUnwind,
1043
+            input_offset: 0,
1044
+            size: 32,
1045
+            align_pow2: 3,
1046
+            owner: None,
1047
+            alt_entries: Vec::new(),
1048
+            data: vec![0; 32],
1049
+            flags: AtomFlags::default(),
1050
+            parent_of: None,
1051
+        });
1052
+
1053
+        let plan = SyntheticPlan::build(
1054
+            &[LayoutInput {
1055
+                id: input_id,
1056
+                object: &object,
1057
+            }],
1058
+            &atoms,
1059
+            &mut sym_table,
1060
+            &[libsystem_input()],
1061
+        )
1062
+        .unwrap();
1063
+
1064
+        assert_eq!(plan.got.entries.len(), 1);
1065
+        assert_eq!(plan.got.entries[0].symbol, import);
1066
+        assert!(plan.stubs.entries.is_empty());
1067
+        assert!(plan.lazy_pointers.entries.is_empty());
1068
+    }
1069
+
9951070
     fn libsystem_input() -> DylibInput {
9961071
         DylibInput {
9971072
             path: PathBuf::from("/tmp/libSystem.tbd"),
@@ -1163,4 +1238,53 @@ mod tests {
11631238
             data_in_code: Vec::new(),
11641239
         }
11651240
     }
1241
+
1242
+    fn compact_unwind_object(symbol_name: &str, raw_relocs: Vec<u8>) -> ObjectFile {
1243
+        let mut strings = vec![0];
1244
+        let strx = strings.len() as u32;
1245
+        strings.extend_from_slice(symbol_name.as_bytes());
1246
+        strings.push(0);
1247
+        ObjectFile {
1248
+            path: PathBuf::from("/tmp/synth-compact-unwind.o"),
1249
+            header: MachHeader64 {
1250
+                magic: MH_MAGIC_64,
1251
+                cputype: CPU_TYPE_ARM64,
1252
+                cpusubtype: CPU_SUBTYPE_ARM64_ALL,
1253
+                filetype: MH_OBJECT,
1254
+                ncmds: 0,
1255
+                sizeofcmds: 0,
1256
+                flags: 0,
1257
+                reserved: 0,
1258
+            },
1259
+            commands: Vec::new(),
1260
+            sections: vec![InputSection {
1261
+                segname: "__LD".into(),
1262
+                sectname: "__compact_unwind".into(),
1263
+                kind: SectionKind::CompactUnwind,
1264
+                addr: 0,
1265
+                size: 32,
1266
+                align_pow2: 3,
1267
+                flags: crate::macho::constants::S_REGULAR,
1268
+                offset: 0,
1269
+                reloff: 0,
1270
+                nreloc: (raw_relocs.len() / 8) as u32,
1271
+                reserved1: 0,
1272
+                reserved2: 0,
1273
+                reserved3: 0,
1274
+                data: vec![0; 32],
1275
+                raw_relocs,
1276
+            }],
1277
+            symbols: vec![InputSymbol::from_raw(RawNlist {
1278
+                strx,
1279
+                n_type: N_UNDF | N_EXT,
1280
+                n_sect: 0,
1281
+                n_desc: 0,
1282
+                n_value: 0,
1283
+            })],
1284
+            strings: StringTable::from_bytes(strings),
1285
+            symtab: None,
1286
+            dysymtab: None,
1287
+            data_in_code: Vec::new(),
1288
+        }
1289
+    }
11661290
 }
src/synth/unwind.rsmodified
@@ -8,6 +8,7 @@ use crate::macho::constants::S_REGULAR;
88
 use crate::reloc::{parse_raw_relocs, parse_relocs, Referent, Reloc};
99
 use crate::resolve::{AtomId, InputId, Symbol, SymbolTable};
1010
 use crate::section::{OutputSection, SectionKind};
11
+use crate::synth::SyntheticPlan;
1112
 
1213
 const PAGE_SIZE: usize = 4096;
1314
 const UNWIND_INFO_VERSION: u32 = 1;
@@ -15,6 +16,15 @@ const UNWIND_SECOND_LEVEL_REGULAR: u32 = 2;
1516
 const UNWIND_SECOND_LEVEL_COMPRESSED: u32 = 3;
1617
 const FIRST_LEVEL_ENTRY_SIZE: usize = 12;
1718
 const COMPRESSED_PAGE_HEADER_SIZE: usize = 12;
19
+const UNWIND_HAS_LSDA: u32 = 0x4000_0000;
20
+const UNWIND_PERSONALITY_MASK: u32 = 0x3000_0000;
21
+const UNWIND_PERSONALITY_SHIFT: u32 = 28;
22
+const UNWIND_ARM64_MODE_MASK: u32 = 0x0f00_0000;
23
+const UNWIND_ARM64_MODE_DWARF: u32 = 0x0300_0000;
24
+const UNWIND_ARM64_DWARF_SECTION_OFFSET_MASK: u32 = 0x00ff_ffff;
25
+const COMPACT_UNWIND_FUNCTION_OFFSET: usize = 0;
26
+const COMPACT_UNWIND_PERSONALITY_OFFSET: usize = 16;
27
+const COMPACT_UNWIND_LSDA_OFFSET: usize = 24;
1828
 
1929
 #[derive(Debug, Clone, PartialEq, Eq)]
2030
 pub struct UnwindError {
@@ -44,6 +54,7 @@ pub enum UnwindReadError {
4454
     UnsupportedSecondLevelPageKind(u32),
4555
     BadFirstLevelIndexOrder { previous: u32, next: u32 },
4656
     BadEncodingIndex { index: u32, max: u32 },
57
+    TooManyPersonalities(usize),
4758
 }
4859
 
4960
 impl fmt::Display for UnwindReadError {
@@ -66,6 +77,9 @@ impl fmt::Display for UnwindReadError {
6677
                     "encoding index {index} exceeds decoded encoding table size {max}"
6778
                 )
6879
             }
80
+            UnwindReadError::TooManyPersonalities(count) => {
81
+                write!(f, "unwind info needs {count} personalities but only 3 are encodable")
82
+            }
6983
         }
7084
     }
7185
 }
@@ -81,6 +95,8 @@ pub struct DecodedUnwindRecord {
8195
 #[derive(Debug, Clone, PartialEq, Eq)]
8296
 pub struct DecodedUnwindInfo {
8397
     pub version: u32,
98
+    pub personalities: Vec<u32>,
99
+    pub lsdas: Vec<DecodedLsdaRecord>,
84100
     pub records: Vec<DecodedUnwindRecord>,
85101
 }
86102
 
@@ -89,6 +105,20 @@ struct UnwindRecord {
89105
     function_offset: u32,
90106
     code_len: u32,
91107
     encoding: u32,
108
+    personality_offset: Option<u32>,
109
+    lsda_offset: Option<u32>,
110
+}
111
+
112
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113
+pub struct DecodedLsdaRecord {
114
+    pub function_offset: u32,
115
+    pub lsda_offset: u32,
116
+}
117
+
118
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119
+struct LsdaRecord {
120
+    function_offset: u32,
121
+    lsda_offset: u32,
92122
 }
93123
 
94124
 #[derive(Debug, Clone, PartialEq, Eq)]
@@ -103,9 +133,10 @@ pub fn synthesize(
103133
     inputs: &[LayoutInput<'_>],
104134
     atoms: &AtomTable,
105135
     sym_table: &SymbolTable,
136
+    synthetic_plan: &SyntheticPlan,
106137
 ) -> Result<bool, UnwindError> {
107138
     let mut changed = remove_compact_unwind_sections(layout);
108
-    let records = collect_records(layout, inputs, atoms, sym_table)?;
139
+    let records = collect_records(layout, inputs, atoms, sym_table, synthetic_plan)?;
109140
     if records.is_empty() {
110141
         changed |= remove_unwind_info_section(layout);
111142
         if changed {
@@ -114,7 +145,11 @@ pub fn synthesize(
114145
         return Ok(changed);
115146
     }
116147
 
117
-    let bytes = serialize_unwind_info(&records);
148
+    let bytes = serialize_unwind_info(&records).map_err(|err| UnwindError {
149
+        input: PathBuf::from("<synthetic unwind>"),
150
+        atom: AtomId(0),
151
+        detail: err.to_string(),
152
+    })?;
118153
     validate_serialized_unwind_info(&bytes, &records).map_err(|err| UnwindError {
119154
         input: PathBuf::from("<synthetic unwind>"),
120155
         atom: AtomId(0),
@@ -132,6 +167,7 @@ fn collect_records(
132167
     inputs: &[LayoutInput<'_>],
133168
     atoms: &AtomTable,
134169
     sym_table: &SymbolTable,
170
+    synthetic_plan: &SyntheticPlan,
135171
 ) -> Result<Vec<UnwindRecord>, UnwindError> {
136172
     let text_base = layout
137173
         .segment("__TEXT")
@@ -191,20 +227,48 @@ fn collect_records(
191227
             .unwrap_or(&[]);
192228
         let function_addr =
193229
             resolve_function_address(atom_id, atom, obj, relocs, atoms, sym_table, layout)?;
194
-        if has_nonzero_u64(atom, 16) || has_reloc_at(relocs, atom.input_offset + 16) {
195
-            return Err(UnwindError {
230
+        let personality_offset = resolve_metadata_offset(
231
+            atom_id,
232
+            atom,
233
+            obj,
234
+            relocs,
235
+            atoms,
236
+            sym_table,
237
+            layout,
238
+            synthetic_plan,
239
+            COMPACT_UNWIND_PERSONALITY_OFFSET,
240
+            true,
241
+            "personality",
242
+        )?
243
+        .map(|addr| {
244
+            u32::try_from(addr.saturating_sub(text_base)).map_err(|_| UnwindError {
196245
                 input: obj.path.clone(),
197246
                 atom: atom_id,
198
-                detail: "personality records are not implemented yet".to_string(),
199
-            });
200
-        }
201
-        if has_nonzero_u64(atom, 24) || has_reloc_at(relocs, atom.input_offset + 24) {
202
-            return Err(UnwindError {
247
+                detail: "personality target exceeds 32-bit unwind offset range".to_string(),
248
+            })
249
+        })
250
+        .transpose()?;
251
+        let lsda_offset = resolve_metadata_offset(
252
+            atom_id,
253
+            atom,
254
+            obj,
255
+            relocs,
256
+            atoms,
257
+            sym_table,
258
+            layout,
259
+            synthetic_plan,
260
+            COMPACT_UNWIND_LSDA_OFFSET,
261
+            false,
262
+            "LSDA",
263
+        )?
264
+        .map(|addr| {
265
+            u32::try_from(addr.saturating_sub(text_base)).map_err(|_| UnwindError {
203266
                 input: obj.path.clone(),
204267
                 atom: atom_id,
205
-                detail: "LSDA records are not implemented yet".to_string(),
206
-            });
207
-        }
268
+                detail: "LSDA target exceeds 32-bit unwind offset range".to_string(),
269
+            })
270
+        })
271
+        .transpose()?;
208272
 
209273
         let function_offset =
210274
             u32::try_from(function_addr.saturating_sub(text_base)).map_err(|_| UnwindError {
@@ -216,6 +280,8 @@ fn collect_records(
216280
             function_offset,
217281
             code_len: u32::from_le_bytes(atom.data[8..12].try_into().unwrap()),
218282
             encoding: u32::from_le_bytes(atom.data[12..16].try_into().unwrap()),
283
+            personality_offset,
284
+            lsda_offset,
219285
         });
220286
     }
221287
 
@@ -249,20 +315,100 @@ fn resolve_function_address(
249315
             detail: "function_start reloc is missing".to_string(),
250316
         });
251317
     };
252
-    match reloc.referent {
318
+    resolve_reference_address(
319
+        atom_id,
320
+        atom,
321
+        obj,
322
+        atoms,
323
+        sym_table,
324
+        layout,
325
+        None,
326
+        reloc.referent,
327
+        read_u64(atom, COMPACT_UNWIND_FUNCTION_OFFSET)? as u32,
328
+        "function_start",
329
+        false,
330
+    )
331
+}
332
+
333
+#[allow(clippy::too_many_arguments)]
334
+fn resolve_metadata_offset(
335
+    atom_id: AtomId,
336
+    atom: &Atom,
337
+    obj: &crate::input::ObjectFile,
338
+    relocs: &[Reloc],
339
+    atoms: &AtomTable,
340
+    sym_table: &SymbolTable,
341
+    layout: &Layout,
342
+    synthetic_plan: &SyntheticPlan,
343
+    field_offset: usize,
344
+    allow_import_got: bool,
345
+    label: &str,
346
+) -> Result<Option<u64>, UnwindError> {
347
+    let raw_value = read_u64(atom, field_offset)?;
348
+    let reloc = relocs
349
+        .iter()
350
+        .find(|reloc| reloc.offset == atom.input_offset + field_offset as u32);
351
+    if raw_value == 0 && reloc.is_none() {
352
+        return Ok(None);
353
+    }
354
+    let Some(reloc) = reloc else {
355
+        return Err(UnwindError {
356
+            input: obj.path.clone(),
357
+            atom: atom_id,
358
+            detail: format!("{label} field has inline value but no relocation"),
359
+        });
360
+    };
361
+    Ok(Some(resolve_reference_address(
362
+        atom_id,
363
+        atom,
364
+        obj,
365
+        atoms,
366
+        sym_table,
367
+        layout,
368
+        Some(synthetic_plan),
369
+        reloc.referent,
370
+        raw_value as u32,
371
+        label,
372
+        allow_import_got,
373
+    )?))
374
+}
375
+
376
+#[allow(clippy::too_many_arguments)]
377
+fn resolve_reference_address(
378
+    atom_id: AtomId,
379
+    atom: &Atom,
380
+    obj: &crate::input::ObjectFile,
381
+    atoms: &AtomTable,
382
+    sym_table: &SymbolTable,
383
+    layout: &Layout,
384
+    synthetic_plan: Option<&SyntheticPlan>,
385
+    referent: Referent,
386
+    target_offset: u32,
387
+    label: &str,
388
+    allow_import_got: bool,
389
+) -> Result<u64, UnwindError> {
390
+    match referent {
253391
         Referent::Section(section_idx) => {
254
-            let target_offset = read_u64(atom, 0)? as u32;
392
+            let input_section = obj
393
+                .sections
394
+                .get((section_idx as usize).saturating_sub(1))
395
+                .ok_or_else(|| UnwindError {
396
+                    input: obj.path.clone(),
397
+                    atom: atom_id,
398
+                    detail: format!("{label} section {} is out of range", section_idx),
399
+                })?;
255400
             let Some((candidate_id, candidate)) = atoms.iter().find(|(_, candidate)| {
256401
                 candidate.origin == atom.origin
257402
                     && candidate.input_section == section_idx
258
-                    && candidate.input_offset <= target_offset
259
-                    && target_offset < candidate.input_offset + candidate.size
403
+                    && input_section.addr + candidate.input_offset as u64 <= target_offset as u64
404
+                    && (target_offset as u64)
405
+                        < input_section.addr + candidate.input_offset as u64 + candidate.size as u64
260406
             }) else {
261407
                 return Err(UnwindError {
262408
                     input: obj.path.clone(),
263409
                     atom: atom_id,
264410
                     detail: format!(
265
-                        "function_start points at missing input atom section {} offset 0x{:x}",
411
+                        "{label} points at missing input atom section {} offset 0x{:x}",
266412
                         section_idx, target_offset
267413
                     ),
268414
                 });
@@ -271,10 +417,11 @@ fn resolve_function_address(
271417
                 return Err(UnwindError {
272418
                     input: obj.path.clone(),
273419
                     atom: atom_id,
274
-                    detail: format!("function atom {:?} missing from final layout", candidate_id),
420
+                    detail: format!("{label} atom {:?} missing from final layout", candidate_id),
275421
                 });
276422
             };
277
-            Ok(base_addr + (target_offset - candidate.input_offset) as u64)
423
+            let atom_input_addr = input_section.addr + candidate.input_offset as u64;
424
+            Ok(base_addr + (target_offset as u64 - atom_input_addr))
278425
         }
279426
         Referent::Symbol(sym_idx) => {
280427
             let input_symbol = obj
@@ -283,39 +430,49 @@ fn resolve_function_address(
283430
                 .ok_or_else(|| UnwindError {
284431
                     input: obj.path.clone(),
285432
                     atom: atom_id,
286
-                    detail: format!("function_start symbol {} is out of range", sym_idx),
433
+                    detail: format!("{label} symbol {} is out of range", sym_idx),
287434
                 })?;
288435
             let name = obj.symbol_name(input_symbol).map_err(|err| UnwindError {
289436
                 input: obj.path.clone(),
290437
                 atom: atom_id,
291438
                 detail: err.to_string(),
292439
             })?;
293
-            let Some((_, symbol)) = sym_table
440
+            let Some((symbol_id, symbol)) = sym_table
294441
                 .iter()
295442
                 .find(|(_, symbol)| sym_table.interner.resolve(symbol.name()) == name)
296443
             else {
297444
                 return Err(UnwindError {
298445
                     input: obj.path.clone(),
299446
                     atom: atom_id,
300
-                    detail: format!("function_start symbol `{name}` was not resolved"),
447
+                    detail: format!("{label} symbol `{name}` was not resolved"),
301448
                 });
302449
             };
303450
             match symbol {
304
-                Symbol::Defined { atom, value, .. } => {
305
-                    let Some(base_addr) = layout.atom_addr(*atom) else {
451
+                Symbol::Defined {
452
+                    atom: target_atom,
453
+                    value,
454
+                    ..
455
+                } => {
456
+                    let Some(base_addr) = layout.atom_addr(*target_atom) else {
306457
                         return Err(UnwindError {
307458
                             input: obj.path.clone(),
308459
                             atom: atom_id,
309
-                            detail: format!("function atom {:?} missing from final layout", atom),
460
+                            detail: format!(
461
+                                "{label} atom {:?} missing from final layout",
462
+                                target_atom
463
+                            ),
310464
                         });
311465
                     };
312466
                     Ok(base_addr + *value)
313467
                 }
468
+                Symbol::DylibImport { .. } if allow_import_got => {
469
+                    personality_got_addr(layout, synthetic_plan, symbol_id, atom_id, obj, label)
470
+                }
314471
                 other => Err(UnwindError {
315472
                     input: obj.path.clone(),
316473
                     atom: atom_id,
317474
                     detail: format!(
318
-                        "function_start symbol `{name}` resolved to unsupported kind {:?}",
475
+                        "{label} symbol `{name}` resolved to unsupported kind {:?}",
319476
                         other.kind()
320477
                     ),
321478
                 }),
@@ -324,6 +481,42 @@ fn resolve_function_address(
324481
     }
325482
 }
326483
 
484
+fn personality_got_addr(
485
+    layout: &Layout,
486
+    synthetic_plan: Option<&SyntheticPlan>,
487
+    symbol_id: crate::resolve::SymbolId,
488
+    atom_id: AtomId,
489
+    obj: &crate::input::ObjectFile,
490
+    label: &str,
491
+) -> Result<u64, UnwindError> {
492
+    let Some(plan) = synthetic_plan else {
493
+        return Err(UnwindError {
494
+            input: obj.path.clone(),
495
+            atom: atom_id,
496
+            detail: format!("{label} import needs a synthetic GOT slot"),
497
+        });
498
+    };
499
+    let Some((idx, _)) = plan.got.get(symbol_id) else {
500
+        return Err(UnwindError {
501
+            input: obj.path.clone(),
502
+            atom: atom_id,
503
+            detail: format!("{label} import is missing synthetic GOT planning"),
504
+        });
505
+    };
506
+    let Some(section) = layout
507
+        .sections
508
+        .iter()
509
+        .find(|section| section.segment == "__DATA_CONST" && section.name == "__got")
510
+    else {
511
+        return Err(UnwindError {
512
+            input: obj.path.clone(),
513
+            atom: atom_id,
514
+            detail: format!("{label} import is missing the output __got section"),
515
+        });
516
+    };
517
+    Ok(section.addr + (idx as u64) * 8)
518
+}
519
+
327520
 fn read_u64(atom: &Atom, offset: usize) -> Result<u64, UnwindError> {
328521
     let end = offset + 8;
329522
     if end > atom.data.len() {
@@ -338,26 +531,17 @@ fn read_u64(atom: &Atom, offset: usize) -> Result<u64, UnwindError> {
338531
     ))
339532
 }
340533
 
341
-fn has_nonzero_u64(atom: &Atom, offset: usize) -> bool {
342
-    read_u64(atom, offset)
343
-        .map(|value| value != 0)
344
-        .unwrap_or(false)
345
-}
346
-
347
-fn has_reloc_at(relocs: &[Reloc], offset: u32) -> bool {
348
-    relocs.iter().any(|reloc| reloc.offset == offset)
349
-}
350
-
351
-fn serialize_unwind_info(records: &[UnwindRecord]) -> Vec<u8> {
352
-    let pages = build_pages(records);
353
-    let indices_offset = 7 * 4;
534
+fn serialize_unwind_info(records: &[UnwindRecord]) -> Result<Vec<u8>, UnwindReadError> {
535
+    let (records, personalities, lsdas) = finalize_unwind_records(records)?;
536
+    let pages = build_pages(&records);
537
+    let common_encodings_offset = 7 * 4;
538
+    let common_encodings_count = 0u32;
539
+    let personalities_offset = common_encodings_offset + common_encodings_count as usize * 4;
540
+    let indices_offset = personalities_offset + personalities.len() * 4;
354541
     let indices_count = (pages.len() + 1) as u32;
355
-    let second_level_start = align_up(
356
-        (indices_offset + indices_count as usize * FIRST_LEVEL_ENTRY_SIZE) as u32,
357
-        16,
358
-    ) as usize;
542
+    let lsdas_offset = indices_offset + indices_count as usize * FIRST_LEVEL_ENTRY_SIZE;
543
+    let second_level_start = align_up((lsdas_offset + lsdas.len() * 8) as u32, 16) as usize;
359544
     let page_blobs: Vec<Vec<u8>> = pages.iter().map(serialize_compressed_page).collect();
360
-    let lsda_offset = second_level_start as u32;
361545
     let sentinel = records
362546
         .last()
363547
         .map(|record| record.function_offset + record.code_len)
@@ -365,23 +549,49 @@ fn serialize_unwind_info(records: &[UnwindRecord]) -> Vec<u8> {
365549
 
366550
     let mut out = Vec::new();
367551
     out.extend_from_slice(&UNWIND_INFO_VERSION.to_le_bytes());
368
-    out.extend_from_slice(&(indices_offset as u32).to_le_bytes());
369
-    out.extend_from_slice(&0u32.to_le_bytes());
370
-    out.extend_from_slice(&(indices_offset as u32).to_le_bytes());
371
-    out.extend_from_slice(&0u32.to_le_bytes());
552
+    out.extend_from_slice(&(common_encodings_offset as u32).to_le_bytes());
553
+    out.extend_from_slice(&common_encodings_count.to_le_bytes());
554
+    out.extend_from_slice(&(personalities_offset as u32).to_le_bytes());
555
+    out.extend_from_slice(&(personalities.len() as u32).to_le_bytes());
372556
     out.extend_from_slice(&(indices_offset as u32).to_le_bytes());
373557
     out.extend_from_slice(&indices_count.to_le_bytes());
374558
 
559
+    for personality in &personalities {
560
+        out.extend_from_slice(&personality.to_le_bytes());
561
+    }
562
+
563
+    let mut page_lsda_index = 0usize;
375564
     let mut page_offset = second_level_start as u32;
376
-    for page in &pages {
565
+    for (page_idx, page) in pages.iter().enumerate() {
566
+        let next_page_start = pages
567
+            .get(page_idx + 1)
568
+            .map(|page| page.start_function_offset)
569
+            .unwrap_or(sentinel);
570
+        while page_lsda_index < lsdas.len()
571
+            && lsdas[page_lsda_index].function_offset < page.start_function_offset
572
+        {
573
+            page_lsda_index += 1;
574
+        }
575
+        let lsda_index_offset = lsdas_offset as u32 + (page_lsda_index as u32) * 8;
377576
         out.extend_from_slice(&page.start_function_offset.to_le_bytes());
378577
         out.extend_from_slice(&page_offset.to_le_bytes());
379
-        out.extend_from_slice(&lsda_offset.to_le_bytes());
578
+        out.extend_from_slice(&lsda_index_offset.to_le_bytes());
579
+        while page_lsda_index < lsdas.len()
580
+            && lsdas[page_lsda_index].function_offset < next_page_start
581
+        {
582
+            page_lsda_index += 1;
583
+        }
380584
         page_offset += serialize_compressed_page(page).len() as u32;
381585
     }
382586
     out.extend_from_slice(&sentinel.to_le_bytes());
383587
     out.extend_from_slice(&0u32.to_le_bytes());
384
-    out.extend_from_slice(&lsda_offset.to_le_bytes());
588
+    out.extend_from_slice(&(lsdas_offset as u32 + (lsdas.len() as u32) * 8).to_le_bytes());
589
+
590
+    for lsda in &lsdas {
591
+        out.extend_from_slice(&lsda.function_offset.to_le_bytes());
592
+        out.extend_from_slice(&lsda.lsda_offset.to_le_bytes());
593
+    }
594
+
385595
     while out.len() < second_level_start {
386596
         out.push(0);
387597
     }
@@ -393,7 +603,7 @@ fn serialize_unwind_info(records: &[UnwindRecord]) -> Vec<u8> {
393603
     while !out.len().is_multiple_of(8) {
394604
         out.push(0);
395605
     }
396
-    out
606
+    Ok(out)
397607
 }
398608
 
399609
 pub fn decode_unwind_info(bytes: &[u8]) -> Result<DecodedUnwindInfo, UnwindReadError> {
@@ -406,8 +616,8 @@ pub fn decode_unwind_info(bytes: &[u8]) -> Result<DecodedUnwindInfo, UnwindReadE
406616
     }
407617
     let common_encodings_offset = read_u32(bytes, 4, "common encodings offset")? as usize;
408618
     let common_encodings_count = read_u32(bytes, 8, "common encodings count")? as usize;
409
-    let _personalities_offset = read_u32(bytes, 12, "personalities offset")? as usize;
410
-    let _personalities_count = read_u32(bytes, 16, "personalities count")? as usize;
619
+    let personalities_offset = read_u32(bytes, 12, "personalities offset")? as usize;
620
+    let personalities_count = read_u32(bytes, 16, "personalities count")? as usize;
411621
     let indices_offset = read_u32(bytes, 20, "indices offset")? as usize;
412622
     let indices_count = read_u32(bytes, 24, "indices count")? as usize;
413623
 
@@ -417,13 +627,21 @@ pub fn decode_unwind_info(bytes: &[u8]) -> Result<DecodedUnwindInfo, UnwindReadE
417627
         common_encodings_count,
418628
         "common encodings",
419629
     )?;
630
+    let personalities = read_u32_array(
631
+        bytes,
632
+        personalities_offset,
633
+        personalities_count,
634
+        "personality array",
635
+    )?;
420636
     let mut index_starts = Vec::new();
637
+    let mut index_lsda_offsets = Vec::new();
421638
     for idx in 0..indices_count {
422639
         let entry_off = indices_offset + idx * FIRST_LEVEL_ENTRY_SIZE;
423640
         if entry_off + FIRST_LEVEL_ENTRY_SIZE > bytes.len() {
424641
             return Err(UnwindReadError::Truncated("first-level index"));
425642
         }
426643
         index_starts.push(read_u32(bytes, entry_off, "first-level function offset")?);
644
+        index_lsda_offsets.push(read_u32(bytes, entry_off + 8, "first-level lsda offset")?);
427645
     }
428646
     for pair in index_starts.windows(2) {
429647
         if pair[0] > pair[1] {
@@ -434,6 +652,27 @@ pub fn decode_unwind_info(bytes: &[u8]) -> Result<DecodedUnwindInfo, UnwindReadE
434652
         }
435653
     }
436654
 
655
+    let mut lsdas = Vec::new();
656
+    if let (Some(&lsda_start), Some(&lsda_end)) = (index_lsda_offsets.first(), index_lsda_offsets.last())
657
+    {
658
+        let start = lsda_start as usize;
659
+        let end = lsda_end as usize;
660
+        if end < start {
661
+            return Err(UnwindReadError::Truncated("lsda index array"));
662
+        }
663
+        let mut entry_off = start;
664
+        while entry_off < end {
665
+            if entry_off + 8 > bytes.len() {
666
+                return Err(UnwindReadError::Truncated("lsda index entry"));
667
+            }
668
+            lsdas.push(DecodedLsdaRecord {
669
+                function_offset: read_u32(bytes, entry_off, "lsda function offset")?,
670
+                lsda_offset: read_u32(bytes, entry_off + 4, "lsda target offset")?,
671
+            });
672
+            entry_off += 8;
673
+        }
674
+    }
675
+
437676
     let mut records = Vec::new();
438677
     for idx in 0..indices_count.saturating_sub(1) {
439678
         let entry_off = indices_offset + idx * FIRST_LEVEL_ENTRY_SIZE;
@@ -481,7 +720,12 @@ pub fn decode_unwind_info(bytes: &[u8]) -> Result<DecodedUnwindInfo, UnwindReadE
481720
         }
482721
     }
483722
 
484
-    Ok(DecodedUnwindInfo { version, records })
723
+    Ok(DecodedUnwindInfo {
724
+        version,
725
+        personalities,
726
+        lsdas,
727
+        records,
728
+    })
485729
 }
486730
 
487731
 fn validate_serialized_unwind_info(
@@ -489,6 +733,7 @@ fn validate_serialized_unwind_info(
489733
     records: &[UnwindRecord],
490734
 ) -> Result<(), UnwindReadError> {
491735
     let decoded = decode_unwind_info(bytes)?;
736
+    let (records, personalities, lsdas) = finalize_unwind_records(records)?;
492737
     let expected: Vec<DecodedUnwindRecord> = records
493738
         .iter()
494739
         .map(|record| DecodedUnwindRecord {
@@ -501,9 +746,71 @@ fn validate_serialized_unwind_info(
501746
             "decoded unwind records do not round-trip",
502747
         ));
503748
     }
749
+    if decoded.personalities != personalities {
750
+        return Err(UnwindReadError::Truncated(
751
+            "decoded personality table does not round-trip",
752
+        ));
753
+    }
754
+    let expected_lsdas: Vec<DecodedLsdaRecord> = lsdas
755
+        .iter()
756
+        .map(|lsda| DecodedLsdaRecord {
757
+            function_offset: lsda.function_offset,
758
+            lsda_offset: lsda.lsda_offset,
759
+        })
760
+        .collect();
761
+    if decoded.lsdas != expected_lsdas {
762
+        return Err(UnwindReadError::Truncated(
763
+            "decoded lsda table does not round-trip",
764
+        ));
765
+    }
504766
     Ok(())
505767
 }
506768
 
769
+fn finalize_unwind_records(
770
+    records: &[UnwindRecord],
771
+) -> Result<(Vec<UnwindRecord>, Vec<u32>, Vec<LsdaRecord>), UnwindReadError> {
772
+    let mut personalities = Vec::new();
773
+    let mut finalized = Vec::with_capacity(records.len());
774
+    let mut lsdas = Vec::new();
775
+    let mut personality_index = HashMap::new();
776
+
777
+    for record in records {
778
+        let mut encoding = record.encoding & !UNWIND_PERSONALITY_MASK;
779
+        if let Some(personality_offset) = record.personality_offset {
780
+            let idx = if let Some(&idx) = personality_index.get(&personality_offset) {
781
+                idx
782
+            } else {
783
+                if personalities.len() == 3 {
784
+                    return Err(UnwindReadError::TooManyPersonalities(personalities.len() + 1));
785
+                }
786
+                personalities.push(personality_offset);
787
+                let idx = personalities.len() as u32;
788
+                personality_index.insert(personality_offset, idx);
789
+                idx
790
+            };
791
+            encoding |= idx << UNWIND_PERSONALITY_SHIFT;
792
+        }
793
+        if let Some(lsda_offset) = record.lsda_offset {
794
+            encoding |= UNWIND_HAS_LSDA;
795
+            lsdas.push(LsdaRecord {
796
+                function_offset: record.function_offset,
797
+                lsda_offset,
798
+            });
799
+        } else {
800
+            encoding &= !UNWIND_HAS_LSDA;
801
+        }
802
+        if encoding & UNWIND_ARM64_MODE_MASK == UNWIND_ARM64_MODE_DWARF {
803
+            encoding &= !UNWIND_ARM64_DWARF_SECTION_OFFSET_MASK;
804
+        }
805
+        finalized.push(UnwindRecord {
806
+            encoding,
807
+            ..*record
808
+        });
809
+    }
810
+
811
+    Ok((finalized, personalities, lsdas))
812
+}
813
+
507814
 fn build_pages(records: &[UnwindRecord]) -> Vec<CompressedPage> {
508815
     let mut pages = Vec::new();
509816
     let mut current: Option<CompressedPage> = None;
@@ -722,7 +1029,10 @@ mod tests {
7221029
             function_offset: 0x348,
7231030
             code_len: 0x14,
7241031
             encoding: 0x0200_1000,
725
-        }]);
1032
+            personality_offset: None,
1033
+            lsda_offset: None,
1034
+        }])
1035
+        .unwrap();
7261036
         let words: Vec<u32> = bytes
7271037
             .chunks_exact(4)
7281038
             .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap()))
@@ -739,10 +1049,10 @@ mod tests {
7391049
                 2,
7401050
                 0x348,
7411051
                 0x40,
742
-                0x40,
1052
+                0x34,
7431053
                 0x35c,
7441054
                 0,
745
-                0x40,
1055
+                0x34,
7461056
                 0,
7471057
                 0,
7481058
                 0,
@@ -755,6 +1065,8 @@ mod tests {
7551065
             ]
7561066
         );
7571067
         let decoded = decode_unwind_info(&bytes).unwrap();
1068
+        assert!(decoded.personalities.is_empty());
1069
+        assert!(decoded.lsdas.is_empty());
7581070
         assert_eq!(
7591071
             decoded.records,
7601072
             vec![DecodedUnwindRecord {
@@ -771,13 +1083,18 @@ mod tests {
7711083
                 function_offset: 0x348,
7721084
                 code_len: 0x8,
7731085
                 encoding: 0x0200_0000,
1086
+                personality_offset: None,
1087
+                lsda_offset: None,
7741088
             },
7751089
             UnwindRecord {
7761090
                 function_offset: 0x350,
7771091
                 code_len: 0x20,
7781092
                 encoding: 0x0400_0000,
1093
+                personality_offset: None,
1094
+                lsda_offset: None,
7791095
             },
780
-        ]);
1096
+        ])
1097
+        .unwrap();
7811098
         let words: Vec<u32> = bytes
7821099
             .chunks_exact(4)
7831100
             .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap()))
@@ -794,10 +1111,10 @@ mod tests {
7941111
                 2,
7951112
                 0x348,
7961113
                 0x40,
797
-                0x40,
1114
+                0x34,
7981115
                 0x370,
7991116
                 0,
800
-                0x40,
1117
+                0x34,
8011118
                 0,
8021119
                 0,
8031120
                 0,
@@ -812,6 +1129,8 @@ mod tests {
8121129
             ]
8131130
         );
8141131
         let decoded = decode_unwind_info(&bytes).unwrap();
1132
+        assert!(decoded.personalities.is_empty());
1133
+        assert!(decoded.lsdas.is_empty());
8151134
         assert_eq!(
8161135
             decoded.records,
8171136
             vec![
@@ -833,7 +1152,10 @@ mod tests {
8331152
             function_offset: 0x348,
8341153
             code_len: 0x14,
8351154
             encoding: 0x0200_1000,
836
-        }]);
1155
+            personality_offset: None,
1156
+            lsda_offset: None,
1157
+        }])
1158
+        .unwrap();
8371159
         let second_level_offset =
8381160
             u32::from_le_bytes(bytes[28 + 4..28 + 8].try_into().unwrap()) as usize;
8391161
         let entries_offset = second_level_offset
tests/archive_runtime.rsmodified
@@ -16,7 +16,10 @@ fn find_runtime_archive() -> Option<PathBuf> {
1616
     // .../armfortas/target/.
1717
     let workspace = Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
1818
     for profile in ["debug", "release"] {
19
-        let candidate = workspace.join("target").join(profile).join("libarmfortas_rt.a");
19
+        let candidate = workspace
20
+            .join("target")
21
+            .join(profile)
22
+            .join("libarmfortas_rt.a");
2023
         if candidate.is_file() {
2124
             return Some(candidate);
2225
         }
@@ -38,7 +41,9 @@ fn libarmfortas_rt_archive_walks_cleanly() {
3841
     let members: Vec<_> = ar.object_members().collect();
3942
     assert!(!members.is_empty(), "runtime archive has no object members");
4043
 
41
-    let idx = ar.symbol_index().expect("runtime archive has a BSD symbol index");
44
+    let idx = ar
45
+        .symbol_index()
46
+        .expect("runtime archive has a BSD symbol index");
4247
     assert!(!idx.is_empty(), "runtime archive symbol index is empty");
4348
 
4449
     // Pick a symbol we're confident the runtime exports. `_afs_program_init`
@@ -63,9 +68,7 @@ fn libarmfortas_rt_archive_walks_cleanly() {
6368
         .symbols
6469
         .iter()
6570
         .find(|s| {
66
-            obj.symbol_name(s)
67
-                .map(|n| n == target)
68
-                .unwrap_or(false)
71
+            obj.symbol_name(s).map(|n| n == target).unwrap_or(false)
6972
                 && s.kind() == SymKind::Sect
7073
                 && s.is_ext()
7174
         })
@@ -84,6 +87,11 @@ fn libarmfortas_rt_archive_walks_cleanly() {
8487
 }
8588
 
8689
 fn panic_with_index(msg: &str, idx: &SymbolIndex) -> ! {
87
-    let preview: Vec<&str> = idx.entries.iter().take(10).map(|e| e.name.as_str()).collect();
90
+    let preview: Vec<&str> = idx
91
+        .entries
92
+        .iter()
93
+        .take(10)
94
+        .map(|e| e.name.as_str())
95
+        .collect();
8896
     panic!("{msg}\nfirst 10 symbols: {preview:?}");
8997
 }
tests/atom_integration.rsmodified
@@ -89,10 +89,7 @@ fn atomize_splits_text_at_symbol_boundaries_and_backpatches_symbols() {
8989
         .subsections_via_symbols
9090
     "#;
9191
 
92
-    let obj_path = std::env::temp_dir().join(format!(
93
-        "afs-ld-atom-{}-test.o",
94
-        std::process::id()
95
-    ));
92
+    let obj_path = std::env::temp_dir().join(format!("afs-ld-atom-{}-test.o", std::process::id()));
9693
     if let Err(e) = assemble(src, &obj_path) {
9794
         eprintln!("skipping: assemble failed: {e}");
9895
         return;
@@ -111,7 +108,13 @@ fn atomize_splits_text_at_symbol_boundaries_and_backpatches_symbols() {
111108
     let obj = inputs.object_file(input_id).unwrap();
112109
     let mut atom_table = AtomTable::new();
113110
     let atomization = atomize_object(input_id, &obj, &mut atom_table);
114
-    backpatch_symbol_atoms(&atomization, input_id, &obj, &mut sym_table, &mut atom_table);
111
+    backpatch_symbol_atoms(
112
+        &atomization,
113
+        input_id,
114
+        &obj,
115
+        &mut sym_table,
116
+        &mut atom_table,
117
+    );
115118
 
116119
     // At least one atom per defined function plus one for data_global.
117120
     assert!(
@@ -192,10 +195,8 @@ fn atomize_cstring_splits_at_null_terminators() {
192195
         .subsections_via_symbols
193196
     "#;
194197
 
195
-    let obj_path = std::env::temp_dir().join(format!(
196
-        "afs-ld-atom-{}-cstrings.o",
197
-        std::process::id()
198
-    ));
198
+    let obj_path =
199
+        std::env::temp_dir().join(format!("afs-ld-atom-{}-cstrings.o", std::process::id()));
199200
     if let Err(e) = assemble(src, &obj_path) {
200201
         eprintln!("skipping: assemble failed: {e}");
201202
         return;
tests/dylib_integration.rsmodified
@@ -17,15 +17,7 @@ use afs_ld::macho::exports::ExportKind;
1717
 fn build_test_dylib(src: &str, out: &PathBuf) -> Result<(), String> {
1818
     let mut child = Command::new("xcrun")
1919
         .args([
20
-            "--sdk",
21
-            "macosx",
22
-            "clang",
23
-            "-x",
24
-            "c",
25
-            "-arch",
26
-            "arm64",
27
-            "-shared",
28
-            "-o",
20
+            "--sdk", "macosx", "clang", "-x", "c", "-arch", "arm64", "-shared", "-o",
2921
         ])
3022
         .arg(out)
3123
         .arg("-install_name")
tests/linker_run.rsmodified
@@ -128,6 +128,30 @@ fn compile_c(src: &str, out: &PathBuf) -> Result<(), String> {
128128
     Ok(())
129129
 }
130130
 
131
+fn compile_cxx(src: &str, out: &PathBuf) -> Result<(), String> {
132
+    let tmp = std::env::temp_dir().join(format!(
133
+        "afs-ld-linker-run-{}-{}.cc",
134
+        std::process::id(),
135
+        out.file_stem().and_then(|s| s.to_str()).unwrap_or("t")
136
+    ));
137
+    fs::write(&tmp, src).map_err(|e| format!("write: {e}"))?;
138
+    let output = Command::new("xcrun")
139
+        .args(["--sdk", "macosx", "clang++", "-arch", "arm64", "-c"])
140
+        .arg(&tmp)
141
+        .arg("-o")
142
+        .arg(out)
143
+        .output()
144
+        .map_err(|e| format!("spawn xcrun clang++: {e}"))?;
145
+    let _ = fs::remove_file(&tmp);
146
+    if !output.status.success() {
147
+        return Err(format!(
148
+            "xcrun clang++ failed: {}",
149
+            String::from_utf8_lossy(&output.stderr)
150
+        ));
151
+    }
152
+    Ok(())
153
+}
154
+
131155
 fn compile_dylib_c(src: &str, out: &PathBuf) -> Result<(), String> {
132156
     let tmp = std::env::temp_dir().join(format!(
133157
         "afs-ld-linker-run-{}-{}.c",
@@ -701,6 +725,30 @@ fn apple_link_dylib_classic(
701725
     Ok(())
702726
 }
703727
 
728
+fn apple_link_cxx_classic(obj: &PathBuf, out: &PathBuf) -> Result<(), String> {
729
+    let output = Command::new("xcrun")
730
+        .args([
731
+            "--sdk",
732
+            "macosx",
733
+            "clang++",
734
+            "-arch",
735
+            "arm64",
736
+            "-Wl,-no_fixup_chains",
737
+            "-o",
738
+        ])
739
+        .arg(out)
740
+        .arg(obj)
741
+        .output()
742
+        .map_err(|e| format!("spawn xcrun clang++ link: {e}"))?;
743
+    if !output.status.success() {
744
+        return Err(format!(
745
+            "xcrun clang++ link failed: {}",
746
+            String::from_utf8_lossy(&output.stderr)
747
+        ));
748
+    }
749
+    Ok(())
750
+}
751
+
704752
 #[derive(Debug, Clone, PartialEq, Eq)]
705753
 struct RebaseRecord {
706754
     segment: String,
@@ -3744,6 +3792,73 @@ fn linker_run_preserves_eh_frame_like_ld() {
37443792
     let _ = fs::remove_file(apple_out);
37453793
 }
37463794
 
3795
+#[test]
3796
+fn linker_run_preserves_exception_unwind_metadata_like_apple_ld() {
3797
+    if !have_xcrun() || !have_xcrun_tool("clang++") || !have_tool("codesign") {
3798
+        eprintln!("skipping: xcrun clang++ or codesign unavailable");
3799
+        return;
3800
+    }
3801
+
3802
+    let Some(sdk) = sdk_path() else {
3803
+        eprintln!("skipping: xcrun --show-sdk-path unavailable");
3804
+        return;
3805
+    };
3806
+    let libsystem = PathBuf::from(format!("{sdk}/usr/lib/libSystem.tbd"));
3807
+    let libcxx = PathBuf::from(format!("{sdk}/usr/lib/libc++.tbd"));
3808
+    if !libsystem.exists() {
3809
+        eprintln!("skipping: no libSystem.tbd at {}", libsystem.display());
3810
+        return;
3811
+    }
3812
+    if !libcxx.exists() {
3813
+        eprintln!("skipping: no libc++.tbd at {}", libcxx.display());
3814
+        return;
3815
+    }
3816
+
3817
+    let obj = scratch("cxx-exc.o");
3818
+    let our_out = scratch("cxx-exc-ours.out");
3819
+    let apple_out = scratch("cxx-exc-apple.out");
3820
+    let src = r#"
3821
+        int helper() { throw 7; }
3822
+        int main() {
3823
+            try { return helper(); }
3824
+            catch (...) { return 42; }
3825
+        }
3826
+    "#;
3827
+    if let Err(e) = compile_cxx(src, &obj) {
3828
+        eprintln!("skipping: clang++ compile failed: {e}");
3829
+        return;
3830
+    }
3831
+
3832
+    let opts = LinkOptions {
3833
+        inputs: vec![obj.clone(), libcxx.clone(), libsystem.clone()],
3834
+        output: Some(our_out.clone()),
3835
+        kind: OutputKind::Executable,
3836
+        ..LinkOptions::default()
3837
+    };
3838
+    Linker::run(&opts).unwrap();
3839
+    apple_link_cxx_classic(&obj, &apple_out).unwrap();
3840
+
3841
+    let our_bytes = fs::read(&our_out).unwrap();
3842
+    let apple_bytes = fs::read(&apple_out).unwrap();
3843
+    let (_, our_unwind) = output_section(&our_bytes, "__TEXT", "__unwind_info").unwrap();
3844
+    let (_, apple_unwind) = output_section(&apple_bytes, "__TEXT", "__unwind_info").unwrap();
3845
+    let our_decoded = decode_unwind_info(&our_unwind).unwrap();
3846
+    let apple_decoded = decode_unwind_info(&apple_unwind).unwrap();
3847
+    assert_eq!(our_decoded, apple_decoded);
3848
+    assert_eq!(our_decoded.personalities.len(), 1);
3849
+    assert_eq!(our_decoded.lsdas.len(), 1);
3850
+    assert!(output_section(&our_bytes, "__TEXT", "__gcc_except_tab").is_some());
3851
+
3852
+    let our_status = Command::new(&our_out).status().unwrap();
3853
+    let apple_status = Command::new(&apple_out).status().unwrap();
3854
+    assert_eq!(our_status.code(), Some(42));
3855
+    assert_eq!(apple_status.code(), Some(42));
3856
+
3857
+    let _ = fs::remove_file(obj);
3858
+    let _ = fs::remove_file(our_out);
3859
+    let _ = fs::remove_file(apple_out);
3860
+}
3861
+
37473862
 #[test]
37483863
 fn linker_run_emits_function_starts_like_ld() {
37493864
     if !have_xcrun() {
tests/minimal_output.rsmodified
@@ -55,7 +55,10 @@ fn minimal_outputs_pass_otool_and_file() {
5555
 
5656
     for (path, expected) in [
5757
         (&exe, "Mach-O 64-bit executable arm64"),
58
-        (&dylib, "Mach-O 64-bit dynamically linked shared library arm64"),
58
+        (
59
+            &dylib,
60
+            "Mach-O 64-bit dynamically linked shared library arm64",
61
+        ),
5962
     ] {
6063
         let otool = Command::new("xcrun")
6164
             .args(["otool", "-lV"])
tests/reader_corpus_round_trip.rsmodified
@@ -13,10 +13,11 @@ use std::path::{Path, PathBuf};
1313
 use std::process::Command;
1414
 
1515
 use afs_ld::input::ObjectFile;
16
-use afs_ld::macho::reader::{parse_commands, parse_header, write_commands, write_header, HEADER_SIZE};
16
+use afs_ld::macho::reader::{
17
+    parse_commands, parse_header, write_commands, write_header, HEADER_SIZE,
18
+};
1719
 use afs_ld::reloc::{
18
-    parse_raw_relocs, parse_relocs, validate_relocs, write_raw_relocs, write_relocs,
19
-    RAW_RELOC_SIZE,
20
+    parse_raw_relocs, parse_relocs, validate_relocs, write_raw_relocs, write_relocs, RAW_RELOC_SIZE,
2021
 };
2122
 use afs_ld::symbol::{write_nlist_table, NLIST_SIZE};
2223
 
@@ -79,7 +80,9 @@ fn every_afs_as_corpus_s_round_trips() {
7980
         fixture_count += 1;
8081
         let obj_name = format!(
8182
             "{}.o",
82
-            src.file_stem().and_then(|s| s.to_str()).unwrap_or("fixture")
83
+            src.file_stem()
84
+                .and_then(|s| s.to_str())
85
+                .unwrap_or("fixture")
8386
         );
8487
         let obj = scratch.join(&obj_name);
8588
 
@@ -126,7 +129,11 @@ fn every_afs_as_corpus_s_round_trips() {
126129
         }
127130
     }
128131
 
129
-    assert!(fixture_count > 0, "no .s fixtures found in {}", corpus.display());
132
+    assert!(
133
+        fixture_count > 0,
134
+        "no .s fixtures found in {}",
135
+        corpus.display()
136
+    );
130137
     assert!(
131138
         failures.is_empty(),
132139
         "{} of {} corpus fixtures failed:\n{}",
@@ -166,7 +173,9 @@ fn every_afs_as_corpus_object_parses_fully() {
166173
         fixture_count += 1;
167174
         let obj_path = scratch.join(format!(
168175
             "{}.o",
169
-            src.file_stem().and_then(|s| s.to_str()).unwrap_or("fixture")
176
+            src.file_stem()
177
+                .and_then(|s| s.to_str())
178
+                .unwrap_or("fixture")
170179
         ));
171180
 
172181
         if let Err(e) = assemble(&src, &obj_path) {
@@ -208,8 +217,7 @@ fn every_afs_as_corpus_object_parses_fully() {
208217
             if sym.stab_kind().is_some() {
209218
                 continue; // stab n_sect has a different meaning
210219
             }
211
-            if sym.kind() == afs_ld::symbol::SymKind::Sect
212
-                && obj.section_for_symbol(sym).is_none()
220
+            if sym.kind() == afs_ld::symbol::SymKind::Sect && obj.section_for_symbol(sym).is_none()
213221
             {
214222
                 failures.push(format!(
215223
                     "{}: symbol[{i}] has SECT kind but n_sect={} is out of range ({} sections)",
@@ -225,8 +233,8 @@ fn every_afs_as_corpus_object_parses_fully() {
225233
         if let Some(symtab) = obj.symtab {
226234
             let mut reemitted = Vec::with_capacity(obj.symbols.len() * NLIST_SIZE);
227235
             write_nlist_table(&obj.symbols, &mut reemitted);
228
-            let want = &bytes
229
-                [symtab.symoff as usize..symtab.symoff as usize + symtab.nsyms as usize * NLIST_SIZE];
236
+            let want = &bytes[symtab.symoff as usize
237
+                ..symtab.symoff as usize + symtab.nsyms as usize * NLIST_SIZE];
230238
             if reemitted != want {
231239
                 failures.push(format!(
232240
                     "{}: nlist region re-emit mismatch (symoff=0x{:x} nsyms={})",
@@ -238,8 +246,8 @@ fn every_afs_as_corpus_object_parses_fully() {
238246
             }
239247
 
240248
             // Byte-level equality of the string-table blob.
241
-            let strtab_want = &bytes
242
-                [symtab.stroff as usize..symtab.stroff as usize + symtab.strsize as usize];
249
+            let strtab_want =
250
+                &bytes[symtab.stroff as usize..symtab.stroff as usize + symtab.strsize as usize];
243251
             if obj.strings.as_bytes() != strtab_want {
244252
                 failures.push(format!(
245253
                     "{}: strtab byte mismatch (stroff=0x{:x} strsize={})",
@@ -293,7 +301,9 @@ fn every_afs_as_corpus_section_relocs_round_trip() {
293301
         fixture_count += 1;
294302
         let obj_path = scratch.join(format!(
295303
             "{}.o",
296
-            src.file_stem().and_then(|s| s.to_str()).unwrap_or("fixture")
304
+            src.file_stem()
305
+                .and_then(|s| s.to_str())
306
+                .unwrap_or("fixture")
297307
         ));
298308
         if let Err(e) = assemble(&src, &obj_path) {
299309
             failures.push(format!("{}: assemble: {e}", src.display()));
@@ -401,7 +411,10 @@ fn every_afs_as_corpus_section_relocs_round_trip() {
401411
 }
402412
 
403413
 fn first_diff(a: &[u8], b: &[u8]) -> usize {
404
-    a.iter().zip(b.iter()).position(|(x, y)| x != y).unwrap_or(a.len().min(b.len()))
414
+    a.iter()
415
+        .zip(b.iter())
416
+        .position(|(x, y)| x != y)
417
+        .unwrap_or(a.len().min(b.len()))
405418
 }
406419
 
407420
 fn tempdir() -> PathBuf {
@@ -410,11 +423,7 @@ fn tempdir() -> PathBuf {
410423
     // Each caller gets a unique dir so cargo's parallel tests don't step on
411424
     // one another's .o files.
412425
     let seq = COUNTER.fetch_add(1, Ordering::Relaxed);
413
-    let base = std::env::temp_dir().join(format!(
414
-        "afs-ld-corpus-{}-{}",
415
-        std::process::id(),
416
-        seq
417
-    ));
426
+    let base = std::env::temp_dir().join(format!("afs-ld-corpus-{}-{}", std::process::id(), seq));
418427
     let _ = fs::remove_dir_all(&base);
419428
     fs::create_dir_all(&base).expect("create scratch dir");
420429
     base
tests/resolve_integration.rsmodified
@@ -53,9 +53,7 @@ fn assemble(src_text: &str, out: &PathBuf) -> Result<(), String> {
5353
     let tmp = std::env::temp_dir().join(format!(
5454
         "afs-ld-resolve-{}-{}.s",
5555
         std::process::id(),
56
-        out.file_stem()
57
-            .and_then(|s| s.to_str())
58
-            .unwrap_or("t")
56
+        out.file_stem().and_then(|s| s.to_str()).unwrap_or("t")
5957
     ));
6058
     fs::write(&tmp, src_text).map_err(|e| format!("write .s: {e}"))?;
6159
     let status = Command::new("xcrun")
@@ -93,11 +91,7 @@ fn pack_archive(members: &[&PathBuf], out: &PathBuf) -> Result<(), String> {
9391
 }
9492
 
9593
 fn scratch(name: &str) -> PathBuf {
96
-    std::env::temp_dir().join(format!(
97
-        "afs-ld-resolve-{}-{}",
98
-        std::process::id(),
99
-        name
100
-    ))
94
+    std::env::temp_dir().join(format!("afs-ld-resolve-{}-{}", std::process::id(), name))
10195
 }
10296
 
10397
 #[test]
@@ -170,8 +164,8 @@ fn resolve_pipeline_pulls_archive_member_and_flags_missing() {
170164
         "unexpected duplicates in seeding: {:?}",
171165
         seed_report.duplicates
172166
     );
173
-    let drain_report = drain_fetches(&mut inputs, &mut table, seed_report.pending_fetches)
174
-        .expect("drain_fetches");
167
+    let drain_report =
168
+        drain_fetches(&mut inputs, &mut table, seed_report.pending_fetches).expect("drain_fetches");
175169
     assert!(
176170
         drain_report.fetched_members >= 1,
177171
         "expected at least one archive member fetched; got {}",
tests/tbd_integration.rsmodified
@@ -118,8 +118,11 @@ fn libsystem_tbd_materializes_into_dylib_file() {
118118
 
119119
     // Common expected sub-dylibs we re-export — matches what `otool -L
120120
     // libSystem.B.dylib` shows on a real macOS.
121
-    let install_names: Vec<&str> =
122
-        dy.dependencies.iter().map(|d| d.install_name.as_str()).collect();
121
+    let install_names: Vec<&str> = dy
122
+        .dependencies
123
+        .iter()
124
+        .map(|d| d.install_name.as_str())
125
+        .collect();
123126
     for sub in [
124127
         "/usr/lib/system/libsystem_c.dylib",
125128
         "/usr/lib/system/libsystem_kernel.dylib",
tests/tbd_smoke.rsmodified
@@ -19,7 +19,11 @@ fn libsystem_tbd_parses_to_many_documents() {
1919
     let docs = parse_documents(&bytes).unwrap_or_else(|e| {
2020
         panic!("libSystem.tbd failed to parse: {e}");
2121
     });
22
-    assert!(docs.len() >= 2, "expected >=2 documents, got {}", docs.len());
22
+    assert!(
23
+        docs.len() >= 2,
24
+        "expected >=2 documents, got {}",
25
+        docs.len()
26
+    );
2327
     // Every doc should carry the !tapi-tbd tag.
2428
     for (i, d) in docs.iter().enumerate() {
2529
         assert_eq!(d.tag.as_deref(), Some("!tapi-tbd"), "doc[{i}] tag");