Capture linker layout and formatting updates
- SHA
20d5047b0a9875d243f48f48b5ecfd70cc5d046a- Parents
-
497b944 - Tree
2f13a94
20d5047
20d5047b0a9875d243f48f48b5ecfd70cc5d046a497b944
2f13a94| Status | File | + | - |
|---|---|---|---|
| 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; | ||
| 37 | 37 | #[derive(Debug)] |
| 38 | 38 | pub enum ArchiveError { |
| 39 | 39 | /// 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 | + }, | |
| 41 | 45 | /// The 8-byte magic is neither `!<arch>\n` nor `!<thin>\n`. |
| 42 | 46 | BadMagic { got: [u8; 8] }, |
| 43 | 47 | /// A header footer (`fmag`) wasn't `` `\n ``. |
| 44 | 48 | BadEntryFooter { at_offset: usize }, |
| 45 | 49 | /// 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 | + }, | |
| 47 | 54 | /// A member's claimed size would overrun the archive. |
| 48 | 55 | MemberOverrun { at_offset: usize, size: u64 }, |
| 49 | 56 | /// A `/NNN` long-name offset didn't land inside the `//` table. |
@@ -229,7 +236,9 @@ impl<'a> Archive<'a> { | ||
| 229 | 236 | /// Return every non-special member (skips symbol indexes and long-name |
| 230 | 237 | /// tables). |
| 231 | 238 | 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) | |
| 233 | 242 | } |
| 234 | 243 | |
| 235 | 244 | /// Find the first member whose `ar_hdr` begins at `header_offset`. The |
@@ -266,10 +275,7 @@ impl<'a> Archive<'a> { | ||
| 266 | 275 | /// Resolve `name` to its defining member, then parse that member as an |
| 267 | 276 | /// `ObjectFile`. Returns `None` when the symbol is absent; `Some(Err(_))` |
| 268 | 277 | /// 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>> { | |
| 273 | 279 | let member = self.first_member_defining(name)?; |
| 274 | 280 | Some(self.parse_member_object(member)) |
| 275 | 281 | } |
@@ -349,15 +355,8 @@ fn parse_members<'a>( | ||
| 349 | 355 | }); |
| 350 | 356 | } |
| 351 | 357 | 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)?; | |
| 361 | 360 | |
| 362 | 361 | // Opportunistic flavor refinement. The sole unambiguous signal for |
| 363 | 362 | // Sysv is the presence of `/` or `//` members — BSD never emits |
@@ -383,10 +382,12 @@ fn parse_members<'a>( | ||
| 383 | 382 | // Advance past body + 1-byte alignment pad for odd sizes (GNU-thin |
| 384 | 383 | // members have zero-byte bodies so this collapses to a no-op). |
| 385 | 384 | 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 | + })?; | |
| 390 | 391 | } |
| 391 | 392 | |
| 392 | 393 | Ok((out, flavor)) |
@@ -405,7 +406,12 @@ fn decode_member<'a>( | ||
| 405 | 406 | // GNU-thin: body is zero bytes; name field is the external path. |
| 406 | 407 | if flavor == Flavor::GnuThin { |
| 407 | 408 | 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 | + )); | |
| 409 | 415 | } |
| 410 | 416 | |
| 411 | 417 | // 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> { | ||
| 576 | 582 | reason: "__.SYMDEF ranlib byte count not a multiple of 8", |
| 577 | 583 | }); |
| 578 | 584 | } |
| 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 | + })?; | |
| 582 | 590 | if ranlib_end + 4 > body.len() { |
| 583 | 591 | return Err(ArchiveError::BadSymbolIndex { |
| 584 | 592 | reason: "__.SYMDEF ranlib + stringsize region overruns member", |
@@ -587,11 +595,12 @@ fn parse_bsd_symbol_index(body: &[u8]) -> Result<SymbolIndex, ArchiveError> { | ||
| 587 | 595 | let stringsize = |
| 588 | 596 | u32::from_le_bytes(body[ranlib_end..ranlib_end + 4].try_into().unwrap()) as usize; |
| 589 | 597 | 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 | + })?; | |
| 595 | 604 | if strings_end > body.len() { |
| 596 | 605 | return Err(ArchiveError::BadSymbolIndex { |
| 597 | 606 | reason: "__.SYMDEF strings region overruns member", |
@@ -686,11 +695,7 @@ fn parse_sysv_symbol_index(body: &[u8]) -> Result<SymbolIndex, ArchiveError> { | ||
| 686 | 695 | Ok(SymbolIndex { entries }) |
| 687 | 696 | } |
| 688 | 697 | |
| 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> { | |
| 694 | 699 | let start = strx as usize; |
| 695 | 700 | if start >= table.len() { |
| 696 | 701 | return Err(ArchiveError::LongNameOob { at_offset, strx }); |
@@ -703,7 +708,11 @@ fn decode_long_name( | ||
| 703 | 708 | .map(|i| start + i) |
| 704 | 709 | .unwrap_or(table.len()); |
| 705 | 710 | // 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 | + }; | |
| 707 | 716 | str::from_utf8(&table[start..trimmed_end]) |
| 708 | 717 | .map(|s| s.to_string()) |
| 709 | 718 | .map_err(|_| ArchiveError::BadName { at_offset }) |
@@ -792,12 +801,13 @@ mod tests { | ||
| 792 | 801 | let mut buf = Vec::with_capacity(AR_HDR_SIZE); |
| 793 | 802 | let name_bytes = name.as_bytes(); |
| 794 | 803 | 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)]); | |
| 796 | 806 | buf.extend_from_slice(&name_field); |
| 797 | 807 | 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 | |
| 801 | 811 | let mut size_field = [b' '; 10]; |
| 802 | 812 | let size_str = size.to_string(); |
| 803 | 813 | let bytes = size_str.as_bytes(); |
@@ -840,7 +850,10 @@ mod tests { | ||
| 840 | 850 | let short = vec![0u8; 30]; |
| 841 | 851 | assert!(matches!( |
| 842 | 852 | ArHeader::parse(&short, 0).unwrap_err(), |
| 843 | - ArchiveError::Truncated { need: AR_HDR_SIZE, .. } | |
| 853 | + ArchiveError::Truncated { | |
| 854 | + need: AR_HDR_SIZE, | |
| 855 | + .. | |
| 856 | + } | |
| 844 | 857 | )); |
| 845 | 858 | } |
| 846 | 859 | |
@@ -901,7 +914,10 @@ mod tests { | ||
| 901 | 914 | fn bsd_extended_name_splits_body() { |
| 902 | 915 | let mut buf = Vec::new(); |
| 903 | 916 | 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 | + )); | |
| 905 | 921 | let ar = Archive::open("/tmp/bsd_ext.a", &buf).unwrap(); |
| 906 | 922 | assert_eq!(ar.members()[0].name, "long_filename_with_many_chars.o"); |
| 907 | 923 | assert_eq!(ar.members()[0].body, b"CONT"); |
src/args.rsmodified@@ -23,7 +23,10 @@ impl std::fmt::Display for ArgsError { | ||
| 23 | 23 | write!(f, "flag `{flag}` requires a value") |
| 24 | 24 | } |
| 25 | 25 | 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 | + ) | |
| 27 | 30 | } |
| 28 | 31 | } |
| 29 | 32 | } |
@@ -36,7 +39,8 @@ pub fn parse(argv: &[String]) -> Result<LinkOptions, ArgsError> { | ||
| 36 | 39 | match arg.as_str() { |
| 37 | 40 | "-o" => { |
| 38 | 41 | 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()))?, | |
| 40 | 44 | )); |
| 41 | 45 | } |
| 42 | 46 | "-e" => { |
@@ -66,22 +70,22 @@ pub fn parse(argv: &[String]) -> Result<LinkOptions, ArgsError> { | ||
| 66 | 70 | )); |
| 67 | 71 | } |
| 68 | 72 | "--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 | + })?)); | |
| 73 | 77 | } |
| 74 | 78 | "--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 | + })?)); | |
| 79 | 83 | } |
| 80 | 84 | "--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 | + })?)); | |
| 85 | 89 | } |
| 86 | 90 | s if s.starts_with('-') => { |
| 87 | 91 | return Err(ArgsError::UnknownFlag(s.to_string())); |
src/atom.rsmodified@@ -22,7 +22,7 @@ use std::collections::HashMap; | ||
| 22 | 22 | use crate::input::ObjectFile; |
| 23 | 23 | use crate::macho::constants::MH_SUBSECTIONS_VIA_SYMBOLS; |
| 24 | 24 | 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}; | |
| 26 | 26 | use crate::section::{InputSection, SectionKind}; |
| 27 | 27 | use crate::symbol::{InputSymbol, SymKind}; |
| 28 | 28 | |
@@ -81,10 +81,7 @@ impl AtomSection { | ||
| 81 | 81 | } |
| 82 | 82 | |
| 83 | 83 | pub fn is_zerofill(self) -> bool { |
| 84 | - matches!( | |
| 85 | - self, | |
| 86 | - AtomSection::ZeroFill | AtomSection::ThreadLocalBss | |
| 87 | - ) | |
| 84 | + matches!(self, AtomSection::ZeroFill | AtomSection::ThreadLocalBss) | |
| 88 | 85 | } |
| 89 | 86 | |
| 90 | 87 | pub fn is_literal(self) -> bool { |
@@ -224,7 +221,9 @@ impl AtomTable { | ||
| 224 | 221 | pub fn by_input_section(&self) -> HashMap<(InputId, u8), Vec<AtomId>> { |
| 225 | 222 | let mut out: HashMap<(InputId, u8), Vec<AtomId>> = HashMap::new(); |
| 226 | 223 | 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); | |
| 228 | 227 | } |
| 229 | 228 | out |
| 230 | 229 | } |
@@ -582,15 +581,36 @@ fn atomize_literal_section( | ||
| 582 | 581 | AtomSection::CStringLiterals => { |
| 583 | 582 | atomize_cstring(input_id, section_idx, sect, syms, atom_section, table, out) |
| 584 | 583 | } |
| 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 | + ), | |
| 594 | 614 | _ => unreachable!("atomize_literal_section called with non-literal kind"), |
| 595 | 615 | } |
| 596 | 616 | } |
@@ -615,9 +635,7 @@ fn atomize_cstring( | ||
| 615 | 635 | let data = sect.data[offset..end].to_vec(); |
| 616 | 636 | let size = (end - offset) as u32; |
| 617 | 637 | |
| 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); | |
| 621 | 639 | let owner_idx = owner_entry.map(|(i, _, _)| *i); |
| 622 | 640 | |
| 623 | 641 | let mut flags = AtomFlags::default().with(AtomFlags::LITERAL); |
@@ -671,9 +689,7 @@ fn atomize_fixed_literal( | ||
| 671 | 689 | }; |
| 672 | 690 | let size = (end - offset) as u32; |
| 673 | 691 | |
| 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); | |
| 677 | 693 | let owner_idx = owner_entry.map(|(i, _, _)| *i); |
| 678 | 694 | |
| 679 | 695 | let mut flags = AtomFlags::default().with(AtomFlags::LITERAL); |
src/dump.rsmodified@@ -9,14 +9,14 @@ use std::path::Path; | ||
| 9 | 9 | |
| 10 | 10 | use crate::archive::{Archive, Flavor, SpecialMember}; |
| 11 | 11 | use crate::input::ObjectFile; |
| 12 | +use crate::macho::constants::*; | |
| 12 | 13 | use crate::macho::dylib::{DylibFile, DylibLoadKind}; |
| 13 | 14 | use crate::macho::exports::ExportKind; |
| 14 | -use crate::macho::tbd::parse_tbd; | |
| 15 | -use crate::macho::constants::*; | |
| 16 | 15 | use crate::macho::reader::{ |
| 17 | 16 | BuildVersionCmd, DyldInfoCmd, DylibCmd, DysymtabCmd, LinkEditDataCmd, LoadCommand, |
| 18 | 17 | MachHeader64, RpathCmd, Section64Header, Segment64, SymtabCmd, |
| 19 | 18 | }; |
| 19 | +use crate::macho::tbd::parse_tbd; | |
| 20 | 20 | use crate::reloc::{parse_raw_relocs, parse_relocs, Referent, Reloc, RelocKind}; |
| 21 | 21 | use crate::section::InputSection; |
| 22 | 22 | use crate::symbol::{InputSymbol, SymKind}; |
@@ -58,7 +58,11 @@ pub fn dump_archive_file(path: &Path) -> io::Result<()> { | ||
| 58 | 58 | if let Some(idx) = ar.symbol_index() { |
| 59 | 59 | writeln!(h, "Symbols ({}):", idx.len())?; |
| 60 | 60 | 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 | + )?; | |
| 62 | 66 | } |
| 63 | 67 | if idx.len() > 16 { |
| 64 | 68 | writeln!(h, " ... ({} more)", idx.len() - 16)?; |
@@ -69,8 +73,8 @@ pub fn dump_archive_file(path: &Path) -> io::Result<()> { | ||
| 69 | 73 | |
| 70 | 74 | pub fn dump_tbd_file(path: &Path) -> io::Result<()> { |
| 71 | 75 | 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()))?; | |
| 74 | 78 | let out = io::stdout(); |
| 75 | 79 | let mut h = out.lock(); |
| 76 | 80 | writeln!(h, "{}:", path.display())?; |
@@ -86,18 +90,10 @@ pub fn dump_tbd_file(path: &Path) -> io::Result<()> { | ||
| 86 | 90 | tbd.compatibility_version.as_deref().unwrap_or("-") |
| 87 | 91 | )?; |
| 88 | 92 | 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(); | |
| 94 | 94 | writeln!(h, " reexported-libraries: {total}")?; |
| 95 | 95 | } |
| 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(); | |
| 101 | 97 | if export_syms > 0 { |
| 102 | 98 | writeln!(h, " exports: {export_syms} symbols total")?; |
| 103 | 99 | } |
@@ -222,12 +218,7 @@ fn write_command(w: &mut impl Write, idx: usize, cmd: &LoadCommand) -> io::Resul | ||
| 222 | 218 | LoadCommand::DyldExportsTrie(l) => write_linkedit_data(w, l, "EXPORTS_TRIE"), |
| 223 | 219 | LoadCommand::DyldChainedFixups(l) => write_linkedit_data(w, l, "CHAINED_FIXUPS"), |
| 224 | 220 | 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()) | |
| 231 | 222 | } |
| 232 | 223 | } |
| 233 | 224 | } |
@@ -340,11 +331,7 @@ fn write_build_version(w: &mut impl Write, b: &BuildVersionCmd) -> io::Result<() | ||
| 340 | 331 | } |
| 341 | 332 | |
| 342 | 333 | 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) | |
| 348 | 335 | } |
| 349 | 336 | |
| 350 | 337 | 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<()> { | ||
| 356 | 343 | writeln!( |
| 357 | 344 | w, |
| 358 | 345 | " [{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 | |
| 367 | 347 | )?; |
| 368 | 348 | if !s.data.is_empty() { |
| 369 | 349 | writeln!(w, " data: {}", hex_preview(&s.data, 16))?; |
| 370 | 350 | } |
| 371 | 351 | if s.nreloc > 0 { |
| 372 | 352 | 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)) | |
| 375 | 354 | { |
| 376 | 355 | Ok(fused) => { |
| 377 | 356 | for (ri, r) in fused.iter().enumerate() { |
@@ -399,7 +378,11 @@ fn write_symbols(w: &mut impl Write, obj: &ObjectFile) -> io::Result<()> { | ||
| 399 | 378 | |
| 400 | 379 | fn describe_symbol(sym: &InputSymbol) -> String { |
| 401 | 380 | 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 | + ); | |
| 403 | 386 | } |
| 404 | 387 | let mut parts: Vec<String> = Vec::new(); |
| 405 | 388 | parts.push( |
src/layout.rsmodified@@ -9,14 +9,22 @@ use crate::atom::AtomTable; | ||
| 9 | 9 | use crate::input::ObjectFile; |
| 10 | 10 | use crate::macho::constants::SG_READ_ONLY; |
| 11 | 11 | 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 | +}; | |
| 13 | 15 | use crate::synth::SyntheticPlan; |
| 14 | 16 | use crate::OutputKind; |
| 15 | 17 | |
| 16 | 18 | pub const PAGE_SIZE: u64 = 0x4000; |
| 17 | 19 | pub const EXECUTABLE_TEXT_BASE: u64 = 0x1_0000_0000; |
| 18 | 20 | |
| 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 | +]; | |
| 20 | 28 | const DYLIB_SEGMENTS: [&str; 4] = ["__TEXT", "__DATA_CONST", "__DATA", "__LINKEDIT"]; |
| 21 | 29 | |
| 22 | 30 | #[derive(Debug, Clone, Copy)] |
@@ -59,8 +67,10 @@ impl Layout { | ||
| 59 | 67 | header_size: u64, |
| 60 | 68 | synthetic_plan: Option<&SyntheticPlan>, |
| 61 | 69 | ) -> 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(); | |
| 64 | 74 | |
| 65 | 75 | let mut sections: Vec<OutputSection> = Vec::new(); |
| 66 | 76 | let mut section_index: HashMap<SectionKey, usize> = HashMap::new(); |
@@ -122,10 +132,9 @@ impl Layout { | ||
| 122 | 132 | |
| 123 | 133 | if let Some(plan) = synthetic_plan { |
| 124 | 134 | 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 | + }) { | |
| 129 | 138 | merge_synthetic_section(existing, synthetic); |
| 130 | 139 | } else { |
| 131 | 140 | sections.push(synthetic); |
@@ -136,7 +145,9 @@ impl Layout { | ||
| 136 | 145 | sections.sort_by(|a, b| { |
| 137 | 146 | segment_rank(kind, &a.segment) |
| 138 | 147 | .cmp(&segment_rank(kind, &b.segment)) |
| 139 | - .then_with(|| section_rank(&a.segment, &a.name).cmp(§ion_rank(&b.segment, &b.name))) | |
| 148 | + .then_with(|| { | |
| 149 | + section_rank(&a.segment, &a.name).cmp(§ion_rank(&b.segment, &b.name)) | |
| 150 | + }) | |
| 140 | 151 | .then_with(|| a.segment.cmp(&b.segment)) |
| 141 | 152 | .then_with(|| a.name.cmp(&b.name)) |
| 142 | 153 | }); |
@@ -159,12 +170,13 @@ impl Layout { | ||
| 159 | 170 | placed.offset = size; |
| 160 | 171 | size += placed.size; |
| 161 | 172 | } |
| 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 | + }; | |
| 168 | 180 | section.size = if section.synthetic_data.is_empty() { |
| 169 | 181 | size |
| 170 | 182 | } else { |
@@ -278,11 +290,7 @@ impl Layout { | ||
| 278 | 290 | } else { |
| 279 | 291 | seg_start_vm |
| 280 | 292 | }; |
| 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 }; | |
| 286 | 294 | let mut seg_vm_end = if is_text { |
| 287 | 295 | seg_start_vm + header_size |
| 288 | 296 | } else { |
@@ -323,13 +331,25 @@ impl Layout { | ||
| 323 | 331 | align_up(raw_vm_size, PAGE_SIZE) |
| 324 | 332 | }; |
| 325 | 333 | 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 | + } | |
| 327 | 339 | } else { |
| 328 | 340 | align_up(raw_file_size, PAGE_SIZE) |
| 329 | 341 | }; |
| 330 | 342 | |
| 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 | + }; | |
| 333 | 353 | } |
| 334 | 354 | } |
| 335 | 355 | } |
@@ -343,7 +363,9 @@ fn merge_synthetic_section(existing: &mut OutputSection, synthetic: OutputSectio | ||
| 343 | 363 | existing.reserved2 = synthetic.reserved2; |
| 344 | 364 | existing.reserved3 = synthetic.reserved3; |
| 345 | 365 | 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); | |
| 347 | 369 | } |
| 348 | 370 | } |
| 349 | 371 | |
@@ -412,7 +434,10 @@ fn segment_rank(kind: OutputKind, segment: &str) -> usize { | ||
| 412 | 434 | OutputKind::Executable => &EXEC_SEGMENTS, |
| 413 | 435 | OutputKind::Dylib => &DYLIB_SEGMENTS, |
| 414 | 436 | }; |
| 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()) | |
| 416 | 441 | } |
| 417 | 442 | |
| 418 | 443 | fn section_rank(segment: &str, section: &str) -> usize { |
@@ -440,7 +465,10 @@ fn section_rank(segment: &str, section: &str) -> usize { | ||
| 440 | 465 | "__LINKEDIT" => &[], |
| 441 | 466 | _ => &[], |
| 442 | 467 | }; |
| 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()) | |
| 444 | 472 | } |
| 445 | 473 | |
| 446 | 474 | fn segment_init_prot(name: &str) -> Prot { |
@@ -529,11 +557,18 @@ mod tests { | ||
| 529 | 557 | |
| 530 | 558 | use crate::atom::{Atom, AtomFlags, AtomSection, AtomTable}; |
| 531 | 559 | 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 | + }; | |
| 533 | 564 | use crate::macho::reader::MachHeader64; |
| 534 | 565 | use crate::resolve::{DylibId, InputId, SymbolId}; |
| 535 | 566 | 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 | + }; | |
| 537 | 572 | |
| 538 | 573 | use super::*; |
| 539 | 574 | |
@@ -553,7 +588,13 @@ mod tests { | ||
| 553 | 588 | }, |
| 554 | 589 | commands: Vec::new(), |
| 555 | 590 | 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 | + ), | |
| 557 | 598 | input_section( |
| 558 | 599 | "__TEXT", |
| 559 | 600 | "__text", |
@@ -582,8 +623,24 @@ mod tests { | ||
| 582 | 623 | 0, |
| 583 | 624 | b"hello\0".to_vec(), |
| 584 | 625 | )); |
| 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 | + )); | |
| 587 | 644 | |
| 588 | 645 | let layout = Layout::build( |
| 589 | 646 | OutputKind::Executable, |
@@ -626,7 +683,13 @@ mod tests { | ||
| 626 | 683 | reserved: 0, |
| 627 | 684 | }, |
| 628 | 685 | 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 | + )], | |
| 630 | 693 | symbols: Vec::new(), |
| 631 | 694 | strings: crate::string_table::StringTable::from_bytes(vec![0]), |
| 632 | 695 | symtab: None, |
@@ -635,7 +698,15 @@ mod tests { | ||
| 635 | 698 | }; |
| 636 | 699 | |
| 637 | 700 | 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 | + )); | |
| 639 | 710 | |
| 640 | 711 | let layout = Layout::build( |
| 641 | 712 | OutputKind::Executable, |
@@ -689,7 +760,15 @@ mod tests { | ||
| 689 | 760 | }; |
| 690 | 761 | |
| 691 | 762 | 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 | + )); | |
| 693 | 772 | atoms.push(atom(InputId(0), 2, AtomSection::Data, 0, 8, 3, vec![0; 8])); |
| 694 | 773 | |
| 695 | 774 | let layout = Layout::build( |
@@ -858,7 +937,13 @@ mod tests { | ||
| 858 | 937 | reserved: 0, |
| 859 | 938 | }, |
| 860 | 939 | 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 | + )], | |
| 862 | 947 | symbols: Vec::new(), |
| 863 | 948 | strings: crate::string_table::StringTable::from_bytes(vec![0]), |
| 864 | 949 | symtab: None, |
@@ -867,7 +952,15 @@ mod tests { | ||
| 867 | 952 | }; |
| 868 | 953 | |
| 869 | 954 | 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 | + )); | |
| 871 | 964 | |
| 872 | 965 | let plan = SyntheticPlan { |
| 873 | 966 | got: GotSection { |
@@ -929,7 +1022,13 @@ mod tests { | ||
| 929 | 1022 | reserved: 0, |
| 930 | 1023 | }, |
| 931 | 1024 | 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 | + )], | |
| 933 | 1032 | symbols: Vec::new(), |
| 934 | 1033 | strings: crate::string_table::StringTable::from_bytes(vec![0]), |
| 935 | 1034 | symtab: None, |
src/lib.rsmodified@@ -305,8 +305,13 @@ impl Linker { | ||
| 305 | 305 | )?; |
| 306 | 306 | layout = next_layout; |
| 307 | 307 | 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 | + )?; | |
| 310 | 315 | if !changed { |
| 311 | 316 | break; |
| 312 | 317 | } |
src/macho/dylib.rsmodified@@ -11,7 +11,9 @@ use std::path::PathBuf; | ||
| 11 | 11 | |
| 12 | 12 | use super::constants::*; |
| 13 | 13 | 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 | +}; | |
| 15 | 17 | use super::tbd::{parse_version, SymbolLists, Target, Tbd}; |
| 16 | 18 | |
| 17 | 19 | /// How a consumer loaded this dylib. The filetype of the dylib itself is |
@@ -132,10 +134,7 @@ impl DylibFile { | ||
| 132 | 134 | /// `LC_DYLD_EXPORTS_TRIE` (chained-fixups era). Dylibs built by older |
| 133 | 135 | /// toolchains may have no export trie; in that case return an empty |
| 134 | 136 | /// `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> { | |
| 139 | 138 | for cmd in commands { |
| 140 | 139 | match cmd { |
| 141 | 140 | LoadCommand::DyldInfoOnly(d) if d.export_size != 0 => { |
@@ -315,9 +314,7 @@ pub fn dependency_ordinal(deps: &[DylibDependency], install_name: &str) -> Optio | ||
| 315 | 314 | #[cfg(test)] |
| 316 | 315 | mod tests { |
| 317 | 316 | 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}; | |
| 321 | 318 | |
| 322 | 319 | fn make_dylib_image(commands: Vec<LoadCommand>) -> Vec<u8> { |
| 323 | 320 | let sizeofcmds: u32 = commands.iter().map(|c| c.cmdsize()).sum(); |
@@ -404,10 +401,7 @@ mod tests { | ||
| 404 | 401 | }), |
| 405 | 402 | ]); |
| 406 | 403 | 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"]); | |
| 411 | 405 | } |
| 412 | 406 | |
| 413 | 407 | // ----- DylibFile::from_tbd tests ----- |
src/macho/exports.rsmodified@@ -275,7 +275,12 @@ fn lookup( | ||
| 275 | 275 | child_cursor += off_len; |
| 276 | 276 | let edge_bytes = edge.as_bytes(); |
| 277 | 277 | 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 | + ); | |
| 279 | 284 | } |
| 280 | 285 | } |
| 281 | 286 | Ok(None) |
src/macho/reader.rsmodified@@ -17,15 +17,27 @@ use super::constants::*; | ||
| 17 | 17 | #[derive(Debug)] |
| 18 | 18 | pub enum ReadError { |
| 19 | 19 | /// 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 | + }, | |
| 21 | 25 | /// Magic number is not `MH_MAGIC_64`. |
| 22 | 26 | BadMagic { got: u32 }, |
| 23 | 27 | /// CPU type is not `CPU_TYPE_ARM64`. |
| 24 | 28 | UnsupportedCpu { got: u32 }, |
| 25 | 29 | /// 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 | + }, | |
| 27 | 36 | /// 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 | + }, | |
| 29 | 41 | } |
| 30 | 42 | |
| 31 | 43 | impl fmt::Display for ReadError { |
@@ -144,7 +156,11 @@ pub enum LoadCommand { | ||
| 144 | 156 | DyldChainedFixups(LinkEditDataCmd), |
| 145 | 157 | /// A load command whose payload we haven't decoded yet. Preserves bytes |
| 146 | 158 | /// 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 | + }, | |
| 148 | 164 | } |
| 149 | 165 | |
| 150 | 166 | impl LoadCommand { |
@@ -357,17 +373,15 @@ impl Section64Header { | ||
| 357 | 373 | /// Parse the `header.ncmds` load commands that follow a `mach_header_64`. |
| 358 | 374 | /// The slice must cover the full file (or at least through `sizeofcmds`); |
| 359 | 375 | /// 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 | + })?; | |
| 371 | 385 | if bytes.len() < cmds_end { |
| 372 | 386 | return Err(ReadError::Truncated { |
| 373 | 387 | need: cmds_end, |
@@ -439,13 +453,12 @@ fn decode_command(cmd: u32, cmdsize: u32, payload: &[u8]) -> Result<LoadCommand, | ||
| 439 | 453 | LC_LINKER_OPTIMIZATION_HINT => Ok(LoadCommand::LinkerOptimizationHint( |
| 440 | 454 | LinkEditDataCmd::parse(LC_LINKER_OPTIMIZATION_HINT, cmdsize, payload)?, |
| 441 | 455 | )), |
| 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 | |
| 446 | 457 | | LC_LOAD_UPWARD_DYLIB => Ok(LoadCommand::Dylib(DylibCmd::parse(cmd, cmdsize, payload)?)), |
| 447 | 458 | 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 | + )?)), | |
| 449 | 462 | LC_DYLD_EXPORTS_TRIE => Ok(LoadCommand::DyldExportsTrie(LinkEditDataCmd::parse( |
| 450 | 463 | LC_DYLD_EXPORTS_TRIE, |
| 451 | 464 | cmdsize, |
@@ -855,12 +868,15 @@ impl RpathCmd { | ||
| 855 | 868 | } |
| 856 | 869 | let start = off_in_cmd - 8; |
| 857 | 870 | 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 | + })?; | |
| 864 | 880 | let path = std::str::from_utf8(&bytes[..nul]) |
| 865 | 881 | .map_err(|_| ReadError::BadCmdsize { |
| 866 | 882 | cmd: LC_RPATH, |
@@ -1074,7 +1090,14 @@ mod tests { | ||
| 1074 | 1090 | fn truncated_header_errors_cleanly() { |
| 1075 | 1091 | let err = parse_header(&[0u8; 10]).unwrap_err(); |
| 1076 | 1092 | 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 | + ), | |
| 1078 | 1101 | "unexpected: {err:?}" |
| 1079 | 1102 | ); |
| 1080 | 1103 | } |
@@ -1093,7 +1116,10 @@ mod tests { | ||
| 1093 | 1116 | // Overwrite cputype with x86_64 (0x01000007). |
| 1094 | 1117 | bytes[4..8].copy_from_slice(&0x0100_0007u32.to_le_bytes()); |
| 1095 | 1118 | 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 | + )); | |
| 1097 | 1123 | } |
| 1098 | 1124 | |
| 1099 | 1125 | /// Synthesize a mach-o image with `n` load commands, each of size |
@@ -1350,8 +1376,14 @@ mod tests { | ||
| 1350 | 1376 | minos: (11 << 16) | (3 << 8), |
| 1351 | 1377 | sdk: (14 << 16) | (2 << 8), |
| 1352 | 1378 | 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 | + }, | |
| 1355 | 1387 | ], |
| 1356 | 1388 | }; |
| 1357 | 1389 | let mut wire2 = Vec::new(); |
@@ -1367,7 +1399,10 @@ mod tests { | ||
| 1367 | 1399 | platform: PLATFORM_MACOS, |
| 1368 | 1400 | minos: (11 << 16), |
| 1369 | 1401 | sdk: (14 << 16), |
| 1370 | - tools: vec![BuildTool { tool: 3, version: 1 }], | |
| 1402 | + tools: vec![BuildTool { | |
| 1403 | + tool: 3, | |
| 1404 | + version: 1, | |
| 1405 | + }], | |
| 1371 | 1406 | }; |
| 1372 | 1407 | let mut wire = Vec::new(); |
| 1373 | 1408 | cmd.write(&mut wire); |
@@ -1491,7 +1526,9 @@ mod tests { | ||
| 1491 | 1526 | payload.extend_from_slice(&0u32.to_le_bytes()); |
| 1492 | 1527 | payload.extend_from_slice(&0u32.to_le_bytes()); |
| 1493 | 1528 | 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 | + ); | |
| 1495 | 1532 | } |
| 1496 | 1533 | |
| 1497 | 1534 | #[test] |
@@ -1504,8 +1541,12 @@ mod tests { | ||
| 1504 | 1541 | cmd.write(LC_LINKER_OPTIMIZATION_HINT, &mut wire); |
| 1505 | 1542 | assert_eq!(wire.len(), LinkEditDataCmd::WIRE_SIZE as usize); |
| 1506 | 1543 | |
| 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(); | |
| 1509 | 1550 | assert_eq!(decoded, cmd); |
| 1510 | 1551 | |
| 1511 | 1552 | let hdr = MachHeader64 { |
@@ -1563,7 +1604,13 @@ mod tests { | ||
| 1563 | 1604 | let parsed_hdr = parse_header(&image).unwrap(); |
| 1564 | 1605 | let cmds = parse_commands(&parsed_hdr, &image).unwrap(); |
| 1565 | 1606 | 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 | + )); | |
| 1567 | 1614 | |
| 1568 | 1615 | let mut out = Vec::new(); |
| 1569 | 1616 | write_header(&parsed_hdr, &mut out); |
src/macho/tbd.rsmodified@@ -152,11 +152,7 @@ fn decode_document(doc: &Document) -> Result<Tbd, TbdError> { | ||
| 152 | 152 | "exports" => tbd.exports = decode_scoped_symbols(v)?, |
| 153 | 153 | "reexports" => tbd.reexports = decode_scoped_symbols(v)?, |
| 154 | 154 | // 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" | |
| 160 | 156 | | "parent-libraries" => {} |
| 161 | 157 | _ => { |
| 162 | 158 | // Silently accept unknown keys — TAPI can add new ones in |
@@ -180,7 +176,9 @@ fn decode_target_list(v: &Value) -> Result<Vec<Target>, TbdError> { | ||
| 180 | 176 | .ok_or_else(|| schema("'targets' must be a sequence"))?; |
| 181 | 177 | let mut out = Vec::with_capacity(seq.len()); |
| 182 | 178 | 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"))?; | |
| 184 | 182 | out.push(parse_target(s)?); |
| 185 | 183 | } |
| 186 | 184 | Ok(out) |
@@ -189,9 +187,9 @@ fn decode_target_list(v: &Value) -> Result<Vec<Target>, TbdError> { | ||
| 189 | 187 | fn parse_target(s: &str) -> Result<Target, TbdError> { |
| 190 | 188 | // `arch-platform`. Arch may contain a hyphen (none today, but armv7k |
| 191 | 189 | // 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`")))?; | |
| 195 | 193 | let arch = match &s[..hyphen] { |
| 196 | 194 | "arm64" => Arch::Arm64, |
| 197 | 195 | "arm64e" => Arch::Arm64e, |
@@ -220,8 +218,7 @@ fn decode_scoped_umbrella(v: &Value) -> Result<Vec<Scoped<String>>, TbdError> { | ||
| 220 | 218 | .as_mapping() |
| 221 | 219 | .ok_or_else(|| schema("parent-umbrella entry must be a mapping"))?; |
| 222 | 220 | 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"))?; | |
| 225 | 222 | out.push(Scoped { |
| 226 | 223 | targets, |
| 227 | 224 | value: umbrella, |
@@ -322,7 +319,9 @@ fn scalar_string(v: &Value, context: &str) -> Result<String, TbdError> { | ||
| 322 | 319 | } |
| 323 | 320 | |
| 324 | 321 | fn schema(msg: &str) -> TbdError { |
| 325 | - TbdError::Schema { msg: msg.to_string() } | |
| 322 | + TbdError::Schema { | |
| 323 | + msg: msg.to_string(), | |
| 324 | + } | |
| 326 | 325 | } |
| 327 | 326 | |
| 328 | 327 | /// Pack a `"X.Y.Z"` / `"X.Y"` / `"X"` / `"1351"` version string to |
@@ -435,7 +434,10 @@ mod tests { | ||
| 435 | 434 | assert_eq!(tbd.reexported_libraries.len(), 1); |
| 436 | 435 | assert_eq!( |
| 437 | 436 | 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 | + ] | |
| 439 | 441 | ); |
| 440 | 442 | } |
| 441 | 443 | |
src/macho/tbd_yaml.rsmodified@@ -67,7 +67,11 @@ pub struct YamlError { | ||
| 67 | 67 | |
| 68 | 68 | impl fmt::Display for YamlError { |
| 69 | 69 | 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 | + ) | |
| 71 | 75 | } |
| 72 | 76 | } |
| 73 | 77 | |
@@ -237,10 +241,7 @@ fn flow_unbalanced(s: &str) -> bool { | ||
| 237 | 241 | fn parse_tag(rest: &str) -> Option<String> { |
| 238 | 242 | let rest = rest.trim_start(); |
| 239 | 243 | 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(); | |
| 244 | 245 | Some(format!("!{}", &tag[..end])) |
| 245 | 246 | } else { |
| 246 | 247 | None |
@@ -438,11 +439,7 @@ fn find_top_level_mapping_colon(s: &str) -> Option<usize> { | ||
| 438 | 439 | None |
| 439 | 440 | } |
| 440 | 441 | |
| 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> { | |
| 446 | 443 | let colon = find_top_level_mapping_colon(content).ok_or(YamlError { |
| 447 | 444 | line: line_no, |
| 448 | 445 | col: col + 1, |
@@ -626,9 +623,7 @@ mod tests { | ||
| 626 | 623 | |
| 627 | 624 | #[test] |
| 628 | 625 | 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"); | |
| 632 | 627 | let a = doc.root.get("a").unwrap(); |
| 633 | 628 | let m = a.as_mapping().unwrap(); |
| 634 | 629 | assert_eq!(m[0], ("b".into(), Value::Scalar("1".into()))); |
@@ -684,9 +679,8 @@ mod tests { | ||
| 684 | 679 | |
| 685 | 680 | #[test] |
| 686 | 681 | 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"); | |
| 690 | 684 | let e = doc.root.get("exports").unwrap().as_sequence().unwrap(); |
| 691 | 685 | let entry = e[0].as_mapping().unwrap(); |
| 692 | 686 | assert_eq!( |
src/macho/writer.rsmodified@@ -2217,6 +2217,9 @@ mod tests { | ||
| 2217 | 2217 | }; |
| 2218 | 2218 | |
| 2219 | 2219 | 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 | + ); | |
| 2221 | 2224 | } |
| 2222 | 2225 | } |
src/reloc/arm64.rsmodified@@ -957,8 +957,22 @@ fn read_implicit_addend( | ||
| 957 | 957 | referent: Referent, |
| 958 | 958 | ) -> Result<i64, RelocError> { |
| 959 | 959 | 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), | |
| 962 | 976 | other => Err(reloc_error( |
| 963 | 977 | atom, |
| 964 | 978 | &obj.path, |
src/reloc/mod.rsmodified@@ -74,11 +74,13 @@ pub fn parse_raw_relocs( | ||
| 74 | 74 | nreloc: u32, |
| 75 | 75 | ) -> Result<Vec<RawRelocation>, ReadError> { |
| 76 | 76 | 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 | + })?; | |
| 82 | 84 | let end = start.checked_add(total).ok_or(ReadError::Truncated { |
| 83 | 85 | need: usize::MAX, |
| 84 | 86 | have: file_bytes.len(), |
@@ -94,7 +96,9 @@ pub fn parse_raw_relocs( | ||
| 94 | 96 | let mut out = Vec::with_capacity(nreloc as usize); |
| 95 | 97 | for i in 0..nreloc as usize { |
| 96 | 98 | 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 | + )?); | |
| 98 | 102 | } |
| 99 | 103 | Ok(out) |
| 100 | 104 | } |
@@ -259,10 +263,11 @@ pub fn parse_relocs(raws: &[RawRelocation]) -> Result<Vec<Reloc>, ReadError> { | ||
| 259 | 263 | at_offset: raw.r_address as u32, |
| 260 | 264 | reason: "unknown ARM64_RELOC_* type", |
| 261 | 265 | })?; |
| 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 | + })?; | |
| 266 | 271 | let addend = pending_addend.take().map(|v| v as i64).unwrap_or(0); |
| 267 | 272 | let referent = referent_from(raw)?; |
| 268 | 273 | |
@@ -580,7 +585,7 @@ mod tests { | ||
| 580 | 585 | fn raw_reloc_preserves_all_bit_fields() { |
| 581 | 586 | // Cover every extreme value to catch shift/mask bugs. |
| 582 | 587 | let raw = RawRelocation { |
| 583 | - r_address: -1, // signed negative round-trips | |
| 588 | + r_address: -1, // signed negative round-trips | |
| 584 | 589 | r_symbolnum: 0x00FF_FFFF, // max 24-bit value |
| 585 | 590 | r_pcrel: true, |
| 586 | 591 | r_length: 0b11, |
@@ -615,7 +620,14 @@ mod tests { | ||
| 615 | 620 | #[test] |
| 616 | 621 | fn raw_reloc_truncated_errors() { |
| 617 | 622 | 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 | + )); | |
| 619 | 631 | } |
| 620 | 632 | |
| 621 | 633 | #[test] |
src/resolve.rsmodified@@ -277,11 +277,7 @@ impl Inputs { | ||
| 277 | 277 | compatibility_version: file.compatibility_version, |
| 278 | 278 | ordinal, |
| 279 | 279 | }; |
| 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) | |
| 285 | 281 | } |
| 286 | 282 | |
| 287 | 283 | pub fn add_dylib_from_file_with_meta( |
@@ -303,7 +299,12 @@ impl Inputs { | ||
| 303 | 299 | } |
| 304 | 300 | |
| 305 | 301 | 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 | |
| 307 | 308 | } |
| 308 | 309 | |
| 309 | 310 | // ---- accessors ---- |
@@ -671,9 +672,9 @@ impl SymbolTable { | ||
| 671 | 672 | first: existing_id, |
| 672 | 673 | second: Box::new(new.clone()), |
| 673 | 674 | }), |
| 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 | |
| 677 | 678 | } |
| 678 | 679 | } |
| 679 | 680 | |
@@ -708,7 +709,9 @@ impl SymbolTable { | ||
| 708 | 709 | InsertOutcome::CommonCoalesced { id } |
| 709 | 710 | } |
| 710 | 711 | 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] | |
| 712 | 715 | else { |
| 713 | 716 | unreachable!("PendingArchiveFetch requires LazyArchive in slot") |
| 714 | 717 | }; |
@@ -719,8 +722,7 @@ impl SymbolTable { | ||
| 719 | 722 | } |
| 720 | 723 | } |
| 721 | 724 | 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 { | |
| 724 | 726 | unreachable!("PendingObjectLoad requires LazyObject in slot") |
| 725 | 727 | }; |
| 726 | 728 | InsertOutcome::PendingObjectLoad { id, origin } |
@@ -746,9 +748,7 @@ impl SymbolTable { | ||
| 746 | 748 | unreachable!("coalesce_common requires two Common entries"); |
| 747 | 749 | }; |
| 748 | 750 | if let Symbol::Common { |
| 749 | - size, | |
| 750 | - align_pow2, | |
| 751 | - .. | |
| 751 | + size, align_pow2, .. | |
| 752 | 752 | } = slot |
| 753 | 753 | { |
| 754 | 754 | *size = a_size.max(b_size); |
@@ -892,10 +892,7 @@ impl ReferrerLog { | ||
| 892 | 892 | } |
| 893 | 893 | |
| 894 | 894 | 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(&[]) | |
| 899 | 896 | } |
| 900 | 897 | |
| 901 | 898 | pub fn extend_from(&mut self, other: &ReferrerLog) { |
@@ -1037,11 +1034,7 @@ pub fn seed_dylib( | ||
| 1037 | 1034 | report: &mut SeedReport, |
| 1038 | 1035 | ) -> Result<(), SeedError> { |
| 1039 | 1036 | 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)?; | |
| 1045 | 1038 | for entry in entries { |
| 1046 | 1039 | let name = table.intern(&entry.name); |
| 1047 | 1040 | let sym = Symbol::DylibImport { |
@@ -1149,13 +1142,12 @@ fn ingest_member_bytes( | ||
| 1149 | 1142 | // Extract owned data before mutating the registry. |
| 1150 | 1143 | let (logical_path, member_bytes) = { |
| 1151 | 1144 | 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 | + })?; | |
| 1159 | 1151 | let logical = format!("{}({})", ai.path.display(), member.name); |
| 1160 | 1152 | (logical, member.body.to_vec()) |
| 1161 | 1153 | }; |
@@ -1212,13 +1204,7 @@ pub fn force_load_archive( | ||
| 1212 | 1204 | }; |
| 1213 | 1205 | let mut queue: Vec<PendingFetch> = Vec::new(); |
| 1214 | 1206 | 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)?; | |
| 1222 | 1208 | queue.extend(new); |
| 1223 | 1209 | } |
| 1224 | 1210 | while let Some(p) = queue.pop() { |
@@ -1325,9 +1311,7 @@ pub fn levenshtein(a: &str, b: &str) -> usize { | ||
| 1325 | 1311 | row[0] = i + 1; |
| 1326 | 1312 | for (j, cb) in b.iter().enumerate() { |
| 1327 | 1313 | 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); | |
| 1331 | 1315 | prev = row[j + 1]; |
| 1332 | 1316 | row[j + 1] = new_val; |
| 1333 | 1317 | } |
@@ -1440,9 +1424,7 @@ pub fn classify_unresolved( | ||
| 1440 | 1424 | let undefs: Vec<(SymbolId, Istr, bool)> = table |
| 1441 | 1425 | .iter() |
| 1442 | 1426 | .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)), | |
| 1446 | 1428 | _ => None, |
| 1447 | 1429 | }) |
| 1448 | 1430 | .collect(); |
@@ -2018,7 +2000,9 @@ mod tests { | ||
| 2018 | 2000 | t.insert(lazy).unwrap(); |
| 2019 | 2001 | let want = undef(&mut t, "_hidden"); |
| 2020 | 2002 | match t.insert(want).unwrap() { |
| 2021 | - InsertOutcome::PendingArchiveFetch { archive, member, .. } => { | |
| 2003 | + InsertOutcome::PendingArchiveFetch { | |
| 2004 | + archive, member, .. | |
| 2005 | + } => { | |
| 2022 | 2006 | assert_eq!(archive, ArchiveId(7)); |
| 2023 | 2007 | assert_eq!(member, MemberId(42)); |
| 2024 | 2008 | } |
src/section.rsmodified@@ -158,11 +158,13 @@ impl InputSection { | ||
| 158 | 158 | Vec::new() |
| 159 | 159 | } else { |
| 160 | 160 | 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 | + })?; | |
| 166 | 168 | if end > file_bytes.len() { |
| 167 | 169 | return Err(ReadError::Truncated { |
| 168 | 170 | need: end, |
@@ -177,11 +179,13 @@ impl InputSection { | ||
| 177 | 179 | Vec::new() |
| 178 | 180 | } else { |
| 179 | 181 | 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 | + })?; | |
| 185 | 189 | let end = start.checked_add(total).ok_or(ReadError::Truncated { |
| 186 | 190 | need: usize::MAX, |
| 187 | 191 | have: file_bytes.len(), |
src/string_table.rsmodified@@ -24,11 +24,13 @@ impl StringTable { | ||
| 24 | 24 | /// from `LC_SYMTAB`. |
| 25 | 25 | pub fn from_file(file_bytes: &[u8], stroff: u32, strsize: u32) -> Result<Self, ReadError> { |
| 26 | 26 | 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 | + })?; | |
| 32 | 34 | if end > file_bytes.len() { |
| 33 | 35 | return Err(ReadError::Truncated { |
| 34 | 36 | need: end, |
@@ -75,15 +77,16 @@ impl StringTable { | ||
| 75 | 77 | reason: "strx out of bounds", |
| 76 | 78 | }); |
| 77 | 79 | } |
| 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 | + })?; | |
| 87 | 90 | std::str::from_utf8(&self.raw[start..end]).map_err(|_| ReadError::BadCmdsize { |
| 88 | 91 | cmd: 0, |
| 89 | 92 | cmdsize: 0, |
@@ -196,14 +199,18 @@ mod tests { | ||
| 196 | 199 | fn out_of_bounds_strx_errors() { |
| 197 | 200 | let t = tbl(b"\0a\0"); |
| 198 | 201 | 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 | + ); | |
| 200 | 205 | } |
| 201 | 206 | |
| 202 | 207 | #[test] |
| 203 | 208 | fn unterminated_string_errors() { |
| 204 | 209 | let t = tbl(b"\0abcdef"); // no trailing null |
| 205 | 210 | 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 | + ); | |
| 207 | 214 | } |
| 208 | 215 | |
| 209 | 216 | #[test] |
src/symbol.rsmodified@@ -224,7 +224,13 @@ mod tests { | ||
| 224 | 224 | use super::*; |
| 225 | 225 | |
| 226 | 226 | 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 | + } | |
| 228 | 234 | } |
| 229 | 235 | |
| 230 | 236 | #[test] |
src/synth/code_sig.rsmodified@@ -34,13 +34,15 @@ impl CodeSignaturePlan { | ||
| 34 | 34 | code_limit: u64, |
| 35 | 35 | executable: bool, |
| 36 | 36 | ) -> 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")?; | |
| 38 | 39 | let identifier = output_identifier(opts); |
| 39 | 40 | let (exec_seg_base, exec_seg_limit, exec_seg_flags) = exec_segment_info(layout, executable); |
| 40 | 41 | let blob_len = blob_len(code_limit as usize, &identifier); |
| 41 | 42 | Ok(Self { |
| 42 | 43 | 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")?, | |
| 44 | 46 | code_limit, |
| 45 | 47 | identifier, |
| 46 | 48 | exec_seg_base, |
@@ -114,9 +116,9 @@ fn exec_segment_info(layout: &Layout, executable: bool) -> (u64, u64, u64) { | ||
| 114 | 116 | if !is_executable(section.kind) || section.is_zerofill() { |
| 115 | 117 | continue; |
| 116 | 118 | } |
| 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 | + })); | |
| 120 | 122 | max_end = max_end.max(section.file_off + section.size); |
| 121 | 123 | } |
| 122 | 124 | 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) { | ||
| 132 | 134 | } |
| 133 | 135 | |
| 134 | 136 | 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; | |
| 136 | 141 | align_up((SUPERBLOB_HEADER_SIZE + cd_len) as u64, 8) as usize |
| 137 | 142 | } |
| 138 | 143 | |
@@ -162,13 +167,7 @@ fn align_up(value: u64, align: u64) -> u64 { | ||
| 162 | 167 | |
| 163 | 168 | fn sha256(data: &[u8]) -> [u8; 32] { |
| 164 | 169 | 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, | |
| 172 | 171 | 0x5be0cd19, |
| 173 | 172 | ]; |
| 174 | 173 | const K: [u32; 64] = [ |
@@ -188,7 +187,11 @@ fn sha256(data: &[u8]) -> [u8; 32] { | ||
| 188 | 187 | let mut block = [0u8; 128]; |
| 189 | 188 | let full_blocks = data.len() / 64; |
| 190 | 189 | 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 | + ); | |
| 192 | 195 | } |
| 193 | 196 | |
| 194 | 197 | let rem = &data[full_blocks * 64..]; |
@@ -288,17 +291,17 @@ mod tests { | ||
| 288 | 291 | assert_eq!( |
| 289 | 292 | sha256(b""), |
| 290 | 293 | [ |
| 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, | |
| 294 | 297 | ] |
| 295 | 298 | ); |
| 296 | 299 | assert_eq!( |
| 297 | 300 | sha256(b"abc"), |
| 298 | 301 | [ |
| 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, | |
| 302 | 305 | ] |
| 303 | 306 | ); |
| 304 | 307 | } |
@@ -309,7 +312,13 @@ mod tests { | ||
| 309 | 312 | output: Some("apple".into()), |
| 310 | 313 | ..LinkOptions::default() |
| 311 | 314 | }; |
| 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(); | |
| 313 | 322 | let blob = plan.build(&vec![0; 16_512]); |
| 314 | 323 | |
| 315 | 324 | assert_eq!(plan.dataoff, 16_512); |
src/synth/dyld_info.rsmodified@@ -2,12 +2,12 @@ use std::collections::BTreeMap; | ||
| 2 | 2 | |
| 3 | 3 | use crate::leb::{write_sleb, write_uleb}; |
| 4 | 4 | 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, | |
| 11 | 11 | }; |
| 12 | 12 | use crate::macho::exports::{ExportEntry, ExportKind}; |
| 13 | 13 | |
@@ -195,7 +195,8 @@ fn emit_trie_node(node: &FlatTrieNode, offsets: &[usize], out: &mut Vec<u8>) { | ||
| 195 | 195 | let mut stream = OpcodeStream::new(); |
| 196 | 196 | stream.uleb(terminal.len() as u64); |
| 197 | 197 | 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")); | |
| 199 | 200 | for (edge, child) in &node.children { |
| 200 | 201 | stream.string(edge); |
| 201 | 202 | stream.uleb(offsets[*child] as u64); |
@@ -258,8 +259,12 @@ pub fn emit_bind_records(specs: &[BindRecordSpec<'_>]) -> Vec<u8> { | ||
| 258 | 259 | state.ordinal = Some(spec.ordinal); |
| 259 | 260 | } |
| 260 | 261 | |
| 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 | + ); | |
| 263 | 268 | out.string(spec.name); |
| 264 | 269 | current_symbol = Some(spec.name.to_string()); |
| 265 | 270 | state.weak_import = Some(spec.weak_import); |
@@ -278,9 +283,11 @@ pub fn emit_bind_records(specs: &[BindRecordSpec<'_>]) -> Vec<u8> { | ||
| 278 | 283 | |
| 279 | 284 | match (state.segment_index, state.next_segment_offset) { |
| 280 | 285 | (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 => {} | |
| 282 | 288 | (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 => | |
| 284 | 291 | { |
| 285 | 292 | out.byte(BIND_OPCODE_ADD_ADDR_ULEB); |
| 286 | 293 | out.uleb(spec.segment_offset - next_segment_offset); |
@@ -350,9 +357,8 @@ mod tests { | ||
| 350 | 357 | use crate::macho::constants::{ |
| 351 | 358 | BIND_OPCODE_DO_BIND, BIND_OPCODE_SET_ADDEND_SLEB, BIND_OPCODE_SET_DYLIB_ORDINAL_IMM, |
| 352 | 359 | 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, | |
| 356 | 362 | }; |
| 357 | 363 | use crate::macho::exports::Exports; |
| 358 | 364 | |
@@ -469,13 +475,24 @@ mod tests { | ||
| 469 | 475 | vec![ |
| 470 | 476 | BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | 1, |
| 471 | 477 | 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, | |
| 473 | 485 | BIND_OPCODE_SET_TYPE_IMM | BIND_TYPE_POINTER, |
| 474 | 486 | BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | 2, |
| 475 | 487 | 0, |
| 476 | 488 | BIND_OPCODE_DO_BIND, |
| 477 | 489 | 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, | |
| 479 | 496 | BIND_OPCODE_DO_BIND, |
| 480 | 497 | 0, |
| 481 | 498 | ] |
src/synth/mod.rsmodified@@ -29,6 +29,8 @@ use self::stubs::{ | ||
| 29 | 29 | }; |
| 30 | 30 | use self::tlv::{ThreadPointerSection, THREAD_POINTER_SIZE}; |
| 31 | 31 | |
| 32 | +const COMPACT_UNWIND_PERSONALITY_FIELD_OFFSET: u32 = 16; | |
| 33 | + | |
| 32 | 34 | #[derive(Debug, Clone, PartialEq, Eq)] |
| 33 | 35 | pub struct SyntheticPlan { |
| 34 | 36 | pub got: GotSection, |
@@ -131,6 +133,17 @@ impl SyntheticPlan { | ||
| 131 | 133 | .map(Vec::as_slice) |
| 132 | 134 | .unwrap_or(&[]); |
| 133 | 135 | 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 | + } | |
| 134 | 147 | match reloc.kind { |
| 135 | 148 | RelocKind::Unsigned => { |
| 136 | 149 | // `__thread_vars` descriptors carry their own dedicated |
@@ -992,6 +1005,68 @@ mod tests { | ||
| 992 | 1005 | assert_eq!(plan.direct_binds[0].symbol, import); |
| 993 | 1006 | } |
| 994 | 1007 | |
| 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 | + | |
| 995 | 1070 | fn libsystem_input() -> DylibInput { |
| 996 | 1071 | DylibInput { |
| 997 | 1072 | path: PathBuf::from("/tmp/libSystem.tbd"), |
@@ -1163,4 +1238,53 @@ mod tests { | ||
| 1163 | 1238 | data_in_code: Vec::new(), |
| 1164 | 1239 | } |
| 1165 | 1240 | } |
| 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 | + } | |
| 1166 | 1290 | } |
src/synth/unwind.rsmodified@@ -8,6 +8,7 @@ use crate::macho::constants::S_REGULAR; | ||
| 8 | 8 | use crate::reloc::{parse_raw_relocs, parse_relocs, Referent, Reloc}; |
| 9 | 9 | use crate::resolve::{AtomId, InputId, Symbol, SymbolTable}; |
| 10 | 10 | use crate::section::{OutputSection, SectionKind}; |
| 11 | +use crate::synth::SyntheticPlan; | |
| 11 | 12 | |
| 12 | 13 | const PAGE_SIZE: usize = 4096; |
| 13 | 14 | const UNWIND_INFO_VERSION: u32 = 1; |
@@ -15,6 +16,15 @@ const UNWIND_SECOND_LEVEL_REGULAR: u32 = 2; | ||
| 15 | 16 | const UNWIND_SECOND_LEVEL_COMPRESSED: u32 = 3; |
| 16 | 17 | const FIRST_LEVEL_ENTRY_SIZE: usize = 12; |
| 17 | 18 | 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; | |
| 18 | 28 | |
| 19 | 29 | #[derive(Debug, Clone, PartialEq, Eq)] |
| 20 | 30 | pub struct UnwindError { |
@@ -44,6 +54,7 @@ pub enum UnwindReadError { | ||
| 44 | 54 | UnsupportedSecondLevelPageKind(u32), |
| 45 | 55 | BadFirstLevelIndexOrder { previous: u32, next: u32 }, |
| 46 | 56 | BadEncodingIndex { index: u32, max: u32 }, |
| 57 | + TooManyPersonalities(usize), | |
| 47 | 58 | } |
| 48 | 59 | |
| 49 | 60 | impl fmt::Display for UnwindReadError { |
@@ -66,6 +77,9 @@ impl fmt::Display for UnwindReadError { | ||
| 66 | 77 | "encoding index {index} exceeds decoded encoding table size {max}" |
| 67 | 78 | ) |
| 68 | 79 | } |
| 80 | + UnwindReadError::TooManyPersonalities(count) => { | |
| 81 | + write!(f, "unwind info needs {count} personalities but only 3 are encodable") | |
| 82 | + } | |
| 69 | 83 | } |
| 70 | 84 | } |
| 71 | 85 | } |
@@ -81,6 +95,8 @@ pub struct DecodedUnwindRecord { | ||
| 81 | 95 | #[derive(Debug, Clone, PartialEq, Eq)] |
| 82 | 96 | pub struct DecodedUnwindInfo { |
| 83 | 97 | pub version: u32, |
| 98 | + pub personalities: Vec<u32>, | |
| 99 | + pub lsdas: Vec<DecodedLsdaRecord>, | |
| 84 | 100 | pub records: Vec<DecodedUnwindRecord>, |
| 85 | 101 | } |
| 86 | 102 | |
@@ -89,6 +105,20 @@ struct UnwindRecord { | ||
| 89 | 105 | function_offset: u32, |
| 90 | 106 | code_len: u32, |
| 91 | 107 | 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, | |
| 92 | 122 | } |
| 93 | 123 | |
| 94 | 124 | #[derive(Debug, Clone, PartialEq, Eq)] |
@@ -103,9 +133,10 @@ pub fn synthesize( | ||
| 103 | 133 | inputs: &[LayoutInput<'_>], |
| 104 | 134 | atoms: &AtomTable, |
| 105 | 135 | sym_table: &SymbolTable, |
| 136 | + synthetic_plan: &SyntheticPlan, | |
| 106 | 137 | ) -> Result<bool, UnwindError> { |
| 107 | 138 | 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)?; | |
| 109 | 140 | if records.is_empty() { |
| 110 | 141 | changed |= remove_unwind_info_section(layout); |
| 111 | 142 | if changed { |
@@ -114,7 +145,11 @@ pub fn synthesize( | ||
| 114 | 145 | return Ok(changed); |
| 115 | 146 | } |
| 116 | 147 | |
| 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 | + })?; | |
| 118 | 153 | validate_serialized_unwind_info(&bytes, &records).map_err(|err| UnwindError { |
| 119 | 154 | input: PathBuf::from("<synthetic unwind>"), |
| 120 | 155 | atom: AtomId(0), |
@@ -132,6 +167,7 @@ fn collect_records( | ||
| 132 | 167 | inputs: &[LayoutInput<'_>], |
| 133 | 168 | atoms: &AtomTable, |
| 134 | 169 | sym_table: &SymbolTable, |
| 170 | + synthetic_plan: &SyntheticPlan, | |
| 135 | 171 | ) -> Result<Vec<UnwindRecord>, UnwindError> { |
| 136 | 172 | let text_base = layout |
| 137 | 173 | .segment("__TEXT") |
@@ -191,20 +227,48 @@ fn collect_records( | ||
| 191 | 227 | .unwrap_or(&[]); |
| 192 | 228 | let function_addr = |
| 193 | 229 | 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 { | |
| 196 | 245 | input: obj.path.clone(), |
| 197 | 246 | 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 { | |
| 203 | 266 | input: obj.path.clone(), |
| 204 | 267 | 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()?; | |
| 208 | 272 | |
| 209 | 273 | let function_offset = |
| 210 | 274 | u32::try_from(function_addr.saturating_sub(text_base)).map_err(|_| UnwindError { |
@@ -216,6 +280,8 @@ fn collect_records( | ||
| 216 | 280 | function_offset, |
| 217 | 281 | code_len: u32::from_le_bytes(atom.data[8..12].try_into().unwrap()), |
| 218 | 282 | encoding: u32::from_le_bytes(atom.data[12..16].try_into().unwrap()), |
| 283 | + personality_offset, | |
| 284 | + lsda_offset, | |
| 219 | 285 | }); |
| 220 | 286 | } |
| 221 | 287 | |
@@ -249,20 +315,100 @@ fn resolve_function_address( | ||
| 249 | 315 | detail: "function_start reloc is missing".to_string(), |
| 250 | 316 | }); |
| 251 | 317 | }; |
| 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 { | |
| 253 | 391 | 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 | + })?; | |
| 255 | 400 | let Some((candidate_id, candidate)) = atoms.iter().find(|(_, candidate)| { |
| 256 | 401 | candidate.origin == atom.origin |
| 257 | 402 | && 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 | |
| 260 | 406 | }) else { |
| 261 | 407 | return Err(UnwindError { |
| 262 | 408 | input: obj.path.clone(), |
| 263 | 409 | atom: atom_id, |
| 264 | 410 | 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}", | |
| 266 | 412 | section_idx, target_offset |
| 267 | 413 | ), |
| 268 | 414 | }); |
@@ -271,10 +417,11 @@ fn resolve_function_address( | ||
| 271 | 417 | return Err(UnwindError { |
| 272 | 418 | input: obj.path.clone(), |
| 273 | 419 | 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), | |
| 275 | 421 | }); |
| 276 | 422 | }; |
| 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)) | |
| 278 | 425 | } |
| 279 | 426 | Referent::Symbol(sym_idx) => { |
| 280 | 427 | let input_symbol = obj |
@@ -283,39 +430,49 @@ fn resolve_function_address( | ||
| 283 | 430 | .ok_or_else(|| UnwindError { |
| 284 | 431 | input: obj.path.clone(), |
| 285 | 432 | 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), | |
| 287 | 434 | })?; |
| 288 | 435 | let name = obj.symbol_name(input_symbol).map_err(|err| UnwindError { |
| 289 | 436 | input: obj.path.clone(), |
| 290 | 437 | atom: atom_id, |
| 291 | 438 | detail: err.to_string(), |
| 292 | 439 | })?; |
| 293 | - let Some((_, symbol)) = sym_table | |
| 440 | + let Some((symbol_id, symbol)) = sym_table | |
| 294 | 441 | .iter() |
| 295 | 442 | .find(|(_, symbol)| sym_table.interner.resolve(symbol.name()) == name) |
| 296 | 443 | else { |
| 297 | 444 | return Err(UnwindError { |
| 298 | 445 | input: obj.path.clone(), |
| 299 | 446 | atom: atom_id, |
| 300 | - detail: format!("function_start symbol `{name}` was not resolved"), | |
| 447 | + detail: format!("{label} symbol `{name}` was not resolved"), | |
| 301 | 448 | }); |
| 302 | 449 | }; |
| 303 | 450 | 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 { | |
| 306 | 457 | return Err(UnwindError { |
| 307 | 458 | input: obj.path.clone(), |
| 308 | 459 | 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 | + ), | |
| 310 | 464 | }); |
| 311 | 465 | }; |
| 312 | 466 | Ok(base_addr + *value) |
| 313 | 467 | } |
| 468 | + Symbol::DylibImport { .. } if allow_import_got => { | |
| 469 | + personality_got_addr(layout, synthetic_plan, symbol_id, atom_id, obj, label) | |
| 470 | + } | |
| 314 | 471 | other => Err(UnwindError { |
| 315 | 472 | input: obj.path.clone(), |
| 316 | 473 | atom: atom_id, |
| 317 | 474 | detail: format!( |
| 318 | - "function_start symbol `{name}` resolved to unsupported kind {:?}", | |
| 475 | + "{label} symbol `{name}` resolved to unsupported kind {:?}", | |
| 319 | 476 | other.kind() |
| 320 | 477 | ), |
| 321 | 478 | }), |
@@ -324,6 +481,42 @@ fn resolve_function_address( | ||
| 324 | 481 | } |
| 325 | 482 | } |
| 326 | 483 | |
| 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 | + | |
| 327 | 520 | fn read_u64(atom: &Atom, offset: usize) -> Result<u64, UnwindError> { |
| 328 | 521 | let end = offset + 8; |
| 329 | 522 | if end > atom.data.len() { |
@@ -338,26 +531,17 @@ fn read_u64(atom: &Atom, offset: usize) -> Result<u64, UnwindError> { | ||
| 338 | 531 | )) |
| 339 | 532 | } |
| 340 | 533 | |
| 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; | |
| 354 | 541 | 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; | |
| 359 | 544 | let page_blobs: Vec<Vec<u8>> = pages.iter().map(serialize_compressed_page).collect(); |
| 360 | - let lsda_offset = second_level_start as u32; | |
| 361 | 545 | let sentinel = records |
| 362 | 546 | .last() |
| 363 | 547 | .map(|record| record.function_offset + record.code_len) |
@@ -365,23 +549,49 @@ fn serialize_unwind_info(records: &[UnwindRecord]) -> Vec<u8> { | ||
| 365 | 549 | |
| 366 | 550 | let mut out = Vec::new(); |
| 367 | 551 | 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()); | |
| 372 | 556 | out.extend_from_slice(&(indices_offset as u32).to_le_bytes()); |
| 373 | 557 | out.extend_from_slice(&indices_count.to_le_bytes()); |
| 374 | 558 | |
| 559 | + for personality in &personalities { | |
| 560 | + out.extend_from_slice(&personality.to_le_bytes()); | |
| 561 | + } | |
| 562 | + | |
| 563 | + let mut page_lsda_index = 0usize; | |
| 375 | 564 | 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; | |
| 377 | 576 | out.extend_from_slice(&page.start_function_offset.to_le_bytes()); |
| 378 | 577 | 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 | + } | |
| 380 | 584 | page_offset += serialize_compressed_page(page).len() as u32; |
| 381 | 585 | } |
| 382 | 586 | out.extend_from_slice(&sentinel.to_le_bytes()); |
| 383 | 587 | 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 | + | |
| 385 | 595 | while out.len() < second_level_start { |
| 386 | 596 | out.push(0); |
| 387 | 597 | } |
@@ -393,7 +603,7 @@ fn serialize_unwind_info(records: &[UnwindRecord]) -> Vec<u8> { | ||
| 393 | 603 | while !out.len().is_multiple_of(8) { |
| 394 | 604 | out.push(0); |
| 395 | 605 | } |
| 396 | - out | |
| 606 | + Ok(out) | |
| 397 | 607 | } |
| 398 | 608 | |
| 399 | 609 | pub fn decode_unwind_info(bytes: &[u8]) -> Result<DecodedUnwindInfo, UnwindReadError> { |
@@ -406,8 +616,8 @@ pub fn decode_unwind_info(bytes: &[u8]) -> Result<DecodedUnwindInfo, UnwindReadE | ||
| 406 | 616 | } |
| 407 | 617 | let common_encodings_offset = read_u32(bytes, 4, "common encodings offset")? as usize; |
| 408 | 618 | 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; | |
| 411 | 621 | let indices_offset = read_u32(bytes, 20, "indices offset")? as usize; |
| 412 | 622 | let indices_count = read_u32(bytes, 24, "indices count")? as usize; |
| 413 | 623 | |
@@ -417,13 +627,21 @@ pub fn decode_unwind_info(bytes: &[u8]) -> Result<DecodedUnwindInfo, UnwindReadE | ||
| 417 | 627 | common_encodings_count, |
| 418 | 628 | "common encodings", |
| 419 | 629 | )?; |
| 630 | + let personalities = read_u32_array( | |
| 631 | + bytes, | |
| 632 | + personalities_offset, | |
| 633 | + personalities_count, | |
| 634 | + "personality array", | |
| 635 | + )?; | |
| 420 | 636 | let mut index_starts = Vec::new(); |
| 637 | + let mut index_lsda_offsets = Vec::new(); | |
| 421 | 638 | for idx in 0..indices_count { |
| 422 | 639 | let entry_off = indices_offset + idx * FIRST_LEVEL_ENTRY_SIZE; |
| 423 | 640 | if entry_off + FIRST_LEVEL_ENTRY_SIZE > bytes.len() { |
| 424 | 641 | return Err(UnwindReadError::Truncated("first-level index")); |
| 425 | 642 | } |
| 426 | 643 | 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")?); | |
| 427 | 645 | } |
| 428 | 646 | for pair in index_starts.windows(2) { |
| 429 | 647 | if pair[0] > pair[1] { |
@@ -434,6 +652,27 @@ pub fn decode_unwind_info(bytes: &[u8]) -> Result<DecodedUnwindInfo, UnwindReadE | ||
| 434 | 652 | } |
| 435 | 653 | } |
| 436 | 654 | |
| 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 | + | |
| 437 | 676 | let mut records = Vec::new(); |
| 438 | 677 | for idx in 0..indices_count.saturating_sub(1) { |
| 439 | 678 | let entry_off = indices_offset + idx * FIRST_LEVEL_ENTRY_SIZE; |
@@ -481,7 +720,12 @@ pub fn decode_unwind_info(bytes: &[u8]) -> Result<DecodedUnwindInfo, UnwindReadE | ||
| 481 | 720 | } |
| 482 | 721 | } |
| 483 | 722 | |
| 484 | - Ok(DecodedUnwindInfo { version, records }) | |
| 723 | + Ok(DecodedUnwindInfo { | |
| 724 | + version, | |
| 725 | + personalities, | |
| 726 | + lsdas, | |
| 727 | + records, | |
| 728 | + }) | |
| 485 | 729 | } |
| 486 | 730 | |
| 487 | 731 | fn validate_serialized_unwind_info( |
@@ -489,6 +733,7 @@ fn validate_serialized_unwind_info( | ||
| 489 | 733 | records: &[UnwindRecord], |
| 490 | 734 | ) -> Result<(), UnwindReadError> { |
| 491 | 735 | let decoded = decode_unwind_info(bytes)?; |
| 736 | + let (records, personalities, lsdas) = finalize_unwind_records(records)?; | |
| 492 | 737 | let expected: Vec<DecodedUnwindRecord> = records |
| 493 | 738 | .iter() |
| 494 | 739 | .map(|record| DecodedUnwindRecord { |
@@ -501,9 +746,71 @@ fn validate_serialized_unwind_info( | ||
| 501 | 746 | "decoded unwind records do not round-trip", |
| 502 | 747 | )); |
| 503 | 748 | } |
| 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 | + } | |
| 504 | 766 | Ok(()) |
| 505 | 767 | } |
| 506 | 768 | |
| 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 | + | |
| 507 | 814 | fn build_pages(records: &[UnwindRecord]) -> Vec<CompressedPage> { |
| 508 | 815 | let mut pages = Vec::new(); |
| 509 | 816 | let mut current: Option<CompressedPage> = None; |
@@ -722,7 +1029,10 @@ mod tests { | ||
| 722 | 1029 | function_offset: 0x348, |
| 723 | 1030 | code_len: 0x14, |
| 724 | 1031 | encoding: 0x0200_1000, |
| 725 | - }]); | |
| 1032 | + personality_offset: None, | |
| 1033 | + lsda_offset: None, | |
| 1034 | + }]) | |
| 1035 | + .unwrap(); | |
| 726 | 1036 | let words: Vec<u32> = bytes |
| 727 | 1037 | .chunks_exact(4) |
| 728 | 1038 | .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap())) |
@@ -739,10 +1049,10 @@ mod tests { | ||
| 739 | 1049 | 2, |
| 740 | 1050 | 0x348, |
| 741 | 1051 | 0x40, |
| 742 | - 0x40, | |
| 1052 | + 0x34, | |
| 743 | 1053 | 0x35c, |
| 744 | 1054 | 0, |
| 745 | - 0x40, | |
| 1055 | + 0x34, | |
| 746 | 1056 | 0, |
| 747 | 1057 | 0, |
| 748 | 1058 | 0, |
@@ -755,6 +1065,8 @@ mod tests { | ||
| 755 | 1065 | ] |
| 756 | 1066 | ); |
| 757 | 1067 | let decoded = decode_unwind_info(&bytes).unwrap(); |
| 1068 | + assert!(decoded.personalities.is_empty()); | |
| 1069 | + assert!(decoded.lsdas.is_empty()); | |
| 758 | 1070 | assert_eq!( |
| 759 | 1071 | decoded.records, |
| 760 | 1072 | vec![DecodedUnwindRecord { |
@@ -771,13 +1083,18 @@ mod tests { | ||
| 771 | 1083 | function_offset: 0x348, |
| 772 | 1084 | code_len: 0x8, |
| 773 | 1085 | encoding: 0x0200_0000, |
| 1086 | + personality_offset: None, | |
| 1087 | + lsda_offset: None, | |
| 774 | 1088 | }, |
| 775 | 1089 | UnwindRecord { |
| 776 | 1090 | function_offset: 0x350, |
| 777 | 1091 | code_len: 0x20, |
| 778 | 1092 | encoding: 0x0400_0000, |
| 1093 | + personality_offset: None, | |
| 1094 | + lsda_offset: None, | |
| 779 | 1095 | }, |
| 780 | - ]); | |
| 1096 | + ]) | |
| 1097 | + .unwrap(); | |
| 781 | 1098 | let words: Vec<u32> = bytes |
| 782 | 1099 | .chunks_exact(4) |
| 783 | 1100 | .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap())) |
@@ -794,10 +1111,10 @@ mod tests { | ||
| 794 | 1111 | 2, |
| 795 | 1112 | 0x348, |
| 796 | 1113 | 0x40, |
| 797 | - 0x40, | |
| 1114 | + 0x34, | |
| 798 | 1115 | 0x370, |
| 799 | 1116 | 0, |
| 800 | - 0x40, | |
| 1117 | + 0x34, | |
| 801 | 1118 | 0, |
| 802 | 1119 | 0, |
| 803 | 1120 | 0, |
@@ -812,6 +1129,8 @@ mod tests { | ||
| 812 | 1129 | ] |
| 813 | 1130 | ); |
| 814 | 1131 | let decoded = decode_unwind_info(&bytes).unwrap(); |
| 1132 | + assert!(decoded.personalities.is_empty()); | |
| 1133 | + assert!(decoded.lsdas.is_empty()); | |
| 815 | 1134 | assert_eq!( |
| 816 | 1135 | decoded.records, |
| 817 | 1136 | vec![ |
@@ -833,7 +1152,10 @@ mod tests { | ||
| 833 | 1152 | function_offset: 0x348, |
| 834 | 1153 | code_len: 0x14, |
| 835 | 1154 | encoding: 0x0200_1000, |
| 836 | - }]); | |
| 1155 | + personality_offset: None, | |
| 1156 | + lsda_offset: None, | |
| 1157 | + }]) | |
| 1158 | + .unwrap(); | |
| 837 | 1159 | let second_level_offset = |
| 838 | 1160 | u32::from_le_bytes(bytes[28 + 4..28 + 8].try_into().unwrap()) as usize; |
| 839 | 1161 | let entries_offset = second_level_offset |
tests/archive_runtime.rsmodified@@ -16,7 +16,10 @@ fn find_runtime_archive() -> Option<PathBuf> { | ||
| 16 | 16 | // .../armfortas/target/. |
| 17 | 17 | let workspace = Path::new(env!("CARGO_MANIFEST_DIR")).join(".."); |
| 18 | 18 | 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"); | |
| 20 | 23 | if candidate.is_file() { |
| 21 | 24 | return Some(candidate); |
| 22 | 25 | } |
@@ -38,7 +41,9 @@ fn libarmfortas_rt_archive_walks_cleanly() { | ||
| 38 | 41 | let members: Vec<_> = ar.object_members().collect(); |
| 39 | 42 | assert!(!members.is_empty(), "runtime archive has no object members"); |
| 40 | 43 | |
| 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"); | |
| 42 | 47 | assert!(!idx.is_empty(), "runtime archive symbol index is empty"); |
| 43 | 48 | |
| 44 | 49 | // Pick a symbol we're confident the runtime exports. `_afs_program_init` |
@@ -63,9 +68,7 @@ fn libarmfortas_rt_archive_walks_cleanly() { | ||
| 63 | 68 | .symbols |
| 64 | 69 | .iter() |
| 65 | 70 | .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) | |
| 69 | 72 | && s.kind() == SymKind::Sect |
| 70 | 73 | && s.is_ext() |
| 71 | 74 | }) |
@@ -84,6 +87,11 @@ fn libarmfortas_rt_archive_walks_cleanly() { | ||
| 84 | 87 | } |
| 85 | 88 | |
| 86 | 89 | 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(); | |
| 88 | 96 | panic!("{msg}\nfirst 10 symbols: {preview:?}"); |
| 89 | 97 | } |
tests/atom_integration.rsmodified@@ -89,10 +89,7 @@ fn atomize_splits_text_at_symbol_boundaries_and_backpatches_symbols() { | ||
| 89 | 89 | .subsections_via_symbols |
| 90 | 90 | "#; |
| 91 | 91 | |
| 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())); | |
| 96 | 93 | if let Err(e) = assemble(src, &obj_path) { |
| 97 | 94 | eprintln!("skipping: assemble failed: {e}"); |
| 98 | 95 | return; |
@@ -111,7 +108,13 @@ fn atomize_splits_text_at_symbol_boundaries_and_backpatches_symbols() { | ||
| 111 | 108 | let obj = inputs.object_file(input_id).unwrap(); |
| 112 | 109 | let mut atom_table = AtomTable::new(); |
| 113 | 110 | 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 | + ); | |
| 115 | 118 | |
| 116 | 119 | // At least one atom per defined function plus one for data_global. |
| 117 | 120 | assert!( |
@@ -192,10 +195,8 @@ fn atomize_cstring_splits_at_null_terminators() { | ||
| 192 | 195 | .subsections_via_symbols |
| 193 | 196 | "#; |
| 194 | 197 | |
| 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())); | |
| 199 | 200 | if let Err(e) = assemble(src, &obj_path) { |
| 200 | 201 | eprintln!("skipping: assemble failed: {e}"); |
| 201 | 202 | return; |
tests/dylib_integration.rsmodified@@ -17,15 +17,7 @@ use afs_ld::macho::exports::ExportKind; | ||
| 17 | 17 | fn build_test_dylib(src: &str, out: &PathBuf) -> Result<(), String> { |
| 18 | 18 | let mut child = Command::new("xcrun") |
| 19 | 19 | .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", | |
| 29 | 21 | ]) |
| 30 | 22 | .arg(out) |
| 31 | 23 | .arg("-install_name") |
tests/linker_run.rsmodified@@ -128,6 +128,30 @@ fn compile_c(src: &str, out: &PathBuf) -> Result<(), String> { | ||
| 128 | 128 | Ok(()) |
| 129 | 129 | } |
| 130 | 130 | |
| 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 | + | |
| 131 | 155 | fn compile_dylib_c(src: &str, out: &PathBuf) -> Result<(), String> { |
| 132 | 156 | let tmp = std::env::temp_dir().join(format!( |
| 133 | 157 | "afs-ld-linker-run-{}-{}.c", |
@@ -701,6 +725,30 @@ fn apple_link_dylib_classic( | ||
| 701 | 725 | Ok(()) |
| 702 | 726 | } |
| 703 | 727 | |
| 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 | + | |
| 704 | 752 | #[derive(Debug, Clone, PartialEq, Eq)] |
| 705 | 753 | struct RebaseRecord { |
| 706 | 754 | segment: String, |
@@ -3744,6 +3792,73 @@ fn linker_run_preserves_eh_frame_like_ld() { | ||
| 3744 | 3792 | let _ = fs::remove_file(apple_out); |
| 3745 | 3793 | } |
| 3746 | 3794 | |
| 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 | + | |
| 3747 | 3862 | #[test] |
| 3748 | 3863 | fn linker_run_emits_function_starts_like_ld() { |
| 3749 | 3864 | if !have_xcrun() { |
tests/minimal_output.rsmodified@@ -55,7 +55,10 @@ fn minimal_outputs_pass_otool_and_file() { | ||
| 55 | 55 | |
| 56 | 56 | for (path, expected) in [ |
| 57 | 57 | (&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 | + ), | |
| 59 | 62 | ] { |
| 60 | 63 | let otool = Command::new("xcrun") |
| 61 | 64 | .args(["otool", "-lV"]) |
tests/reader_corpus_round_trip.rsmodified@@ -13,10 +13,11 @@ use std::path::{Path, PathBuf}; | ||
| 13 | 13 | use std::process::Command; |
| 14 | 14 | |
| 15 | 15 | 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 | +}; | |
| 17 | 19 | 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, | |
| 20 | 21 | }; |
| 21 | 22 | use afs_ld::symbol::{write_nlist_table, NLIST_SIZE}; |
| 22 | 23 | |
@@ -79,7 +80,9 @@ fn every_afs_as_corpus_s_round_trips() { | ||
| 79 | 80 | fixture_count += 1; |
| 80 | 81 | let obj_name = format!( |
| 81 | 82 | "{}.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") | |
| 83 | 86 | ); |
| 84 | 87 | let obj = scratch.join(&obj_name); |
| 85 | 88 | |
@@ -126,7 +129,11 @@ fn every_afs_as_corpus_s_round_trips() { | ||
| 126 | 129 | } |
| 127 | 130 | } |
| 128 | 131 | |
| 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 | + ); | |
| 130 | 137 | assert!( |
| 131 | 138 | failures.is_empty(), |
| 132 | 139 | "{} of {} corpus fixtures failed:\n{}", |
@@ -166,7 +173,9 @@ fn every_afs_as_corpus_object_parses_fully() { | ||
| 166 | 173 | fixture_count += 1; |
| 167 | 174 | let obj_path = scratch.join(format!( |
| 168 | 175 | "{}.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") | |
| 170 | 179 | )); |
| 171 | 180 | |
| 172 | 181 | if let Err(e) = assemble(&src, &obj_path) { |
@@ -208,8 +217,7 @@ fn every_afs_as_corpus_object_parses_fully() { | ||
| 208 | 217 | if sym.stab_kind().is_some() { |
| 209 | 218 | continue; // stab n_sect has a different meaning |
| 210 | 219 | } |
| 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() | |
| 213 | 221 | { |
| 214 | 222 | failures.push(format!( |
| 215 | 223 | "{}: 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() { | ||
| 225 | 233 | if let Some(symtab) = obj.symtab { |
| 226 | 234 | let mut reemitted = Vec::with_capacity(obj.symbols.len() * NLIST_SIZE); |
| 227 | 235 | 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]; | |
| 230 | 238 | if reemitted != want { |
| 231 | 239 | failures.push(format!( |
| 232 | 240 | "{}: nlist region re-emit mismatch (symoff=0x{:x} nsyms={})", |
@@ -238,8 +246,8 @@ fn every_afs_as_corpus_object_parses_fully() { | ||
| 238 | 246 | } |
| 239 | 247 | |
| 240 | 248 | // 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]; | |
| 243 | 251 | if obj.strings.as_bytes() != strtab_want { |
| 244 | 252 | failures.push(format!( |
| 245 | 253 | "{}: strtab byte mismatch (stroff=0x{:x} strsize={})", |
@@ -293,7 +301,9 @@ fn every_afs_as_corpus_section_relocs_round_trip() { | ||
| 293 | 301 | fixture_count += 1; |
| 294 | 302 | let obj_path = scratch.join(format!( |
| 295 | 303 | "{}.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") | |
| 297 | 307 | )); |
| 298 | 308 | if let Err(e) = assemble(&src, &obj_path) { |
| 299 | 309 | failures.push(format!("{}: assemble: {e}", src.display())); |
@@ -401,7 +411,10 @@ fn every_afs_as_corpus_section_relocs_round_trip() { | ||
| 401 | 411 | } |
| 402 | 412 | |
| 403 | 413 | 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())) | |
| 405 | 418 | } |
| 406 | 419 | |
| 407 | 420 | fn tempdir() -> PathBuf { |
@@ -410,11 +423,7 @@ fn tempdir() -> PathBuf { | ||
| 410 | 423 | // Each caller gets a unique dir so cargo's parallel tests don't step on |
| 411 | 424 | // one another's .o files. |
| 412 | 425 | 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)); | |
| 418 | 427 | let _ = fs::remove_dir_all(&base); |
| 419 | 428 | fs::create_dir_all(&base).expect("create scratch dir"); |
| 420 | 429 | base |
tests/resolve_integration.rsmodified@@ -53,9 +53,7 @@ fn assemble(src_text: &str, out: &PathBuf) -> Result<(), String> { | ||
| 53 | 53 | let tmp = std::env::temp_dir().join(format!( |
| 54 | 54 | "afs-ld-resolve-{}-{}.s", |
| 55 | 55 | 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") | |
| 59 | 57 | )); |
| 60 | 58 | fs::write(&tmp, src_text).map_err(|e| format!("write .s: {e}"))?; |
| 61 | 59 | let status = Command::new("xcrun") |
@@ -93,11 +91,7 @@ fn pack_archive(members: &[&PathBuf], out: &PathBuf) -> Result<(), String> { | ||
| 93 | 91 | } |
| 94 | 92 | |
| 95 | 93 | 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)) | |
| 101 | 95 | } |
| 102 | 96 | |
| 103 | 97 | #[test] |
@@ -170,8 +164,8 @@ fn resolve_pipeline_pulls_archive_member_and_flags_missing() { | ||
| 170 | 164 | "unexpected duplicates in seeding: {:?}", |
| 171 | 165 | seed_report.duplicates |
| 172 | 166 | ); |
| 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"); | |
| 175 | 169 | assert!( |
| 176 | 170 | drain_report.fetched_members >= 1, |
| 177 | 171 | "expected at least one archive member fetched; got {}", |
tests/tbd_integration.rsmodified@@ -118,8 +118,11 @@ fn libsystem_tbd_materializes_into_dylib_file() { | ||
| 118 | 118 | |
| 119 | 119 | // Common expected sub-dylibs we re-export — matches what `otool -L |
| 120 | 120 | // 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(); | |
| 123 | 126 | for sub in [ |
| 124 | 127 | "/usr/lib/system/libsystem_c.dylib", |
| 125 | 128 | "/usr/lib/system/libsystem_kernel.dylib", |
tests/tbd_smoke.rsmodified@@ -19,7 +19,11 @@ fn libsystem_tbd_parses_to_many_documents() { | ||
| 19 | 19 | let docs = parse_documents(&bytes).unwrap_or_else(|e| { |
| 20 | 20 | panic!("libSystem.tbd failed to parse: {e}"); |
| 21 | 21 | }); |
| 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 | + ); | |
| 23 | 27 | // Every doc should carry the !tapi-tbd tag. |
| 24 | 28 | for (i, d) in docs.iter().enumerate() { |
| 25 | 29 | assert_eq!(d.tag.as_deref(), Some("!tapi-tbd"), "doc[{i}] tag"); |