Rust · 40902 bytes Raw Blame History
1 //! Sprint 10 output layout.
2 //!
3 //! Groups atoms into output sections, orders them deterministically, and
4 //! assigns segment VM/file ranges once the final Mach-O header size is known.
5
6 use std::collections::HashMap;
7
8 use crate::atom::AtomTable;
9 use crate::input::ObjectFile;
10 use crate::macho::constants::SG_READ_ONLY;
11 use crate::resolve::InputId;
12 use crate::section::{
13 is_zerofill, InputSection, OutputAtom, OutputSection, OutputSectionId, OutputSegment, Prot,
14 };
15 use crate::synth::SyntheticPlan;
16 use crate::OutputKind;
17
18 pub const PAGE_SIZE: u64 = 0x4000;
19 pub const EXECUTABLE_TEXT_BASE: u64 = 0x1_0000_0000;
20
21 const EXEC_SEGMENTS: [&str; 5] = [
22 "__PAGEZERO",
23 "__TEXT",
24 "__DATA_CONST",
25 "__DATA",
26 "__LINKEDIT",
27 ];
28 const DYLIB_SEGMENTS: [&str; 4] = ["__TEXT", "__DATA_CONST", "__DATA", "__LINKEDIT"];
29
30 #[derive(Debug, Clone, Copy)]
31 pub struct LayoutInput<'a> {
32 pub id: InputId,
33 pub object: &'a ObjectFile,
34 pub load_order: usize,
35 pub archive_member_offset: Option<u32>,
36 }
37
38 #[derive(Debug, Clone, PartialEq, Eq)]
39 pub struct Layout {
40 pub kind: OutputKind,
41 pub segments: Vec<OutputSegment>,
42 pub sections: Vec<OutputSection>,
43 }
44
45 #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
46 struct SectionKey {
47 segment: String,
48 name: String,
49 }
50
51 fn output_section_key(input_section: &InputSection) -> SectionKey {
52 match (
53 input_section.segname.as_str(),
54 input_section.sectname.as_str(),
55 ) {
56 ("__DATA", "__const") => SectionKey {
57 segment: "__DATA_CONST".to_string(),
58 name: "__const".to_string(),
59 },
60 _ => SectionKey {
61 segment: input_section.segname.clone(),
62 name: input_section.sectname.clone(),
63 },
64 }
65 }
66
67 impl Layout {
68 pub fn empty(kind: OutputKind, header_size: u64) -> Self {
69 Self::build(kind, &[], &AtomTable::new(), header_size)
70 }
71
72 pub fn build(
73 kind: OutputKind,
74 inputs: &[LayoutInput<'_>],
75 atoms: &AtomTable,
76 header_size: u64,
77 ) -> Self {
78 Self::build_with_synthetics(kind, inputs, atoms, header_size, None)
79 }
80
81 pub fn build_with_synthetics(
82 kind: OutputKind,
83 inputs: &[LayoutInput<'_>],
84 atoms: &AtomTable,
85 header_size: u64,
86 synthetic_plan: Option<&SyntheticPlan>,
87 ) -> Self {
88 let input_map: HashMap<InputId, LayoutInput<'_>> =
89 inputs.iter().map(|input| (input.id, *input)).collect();
90
91 let mut sections: Vec<OutputSection> = Vec::new();
92 let mut section_index: HashMap<SectionKey, usize> = HashMap::new();
93
94 for (atom_id, atom) in atoms.iter() {
95 let input = input_map
96 .get(&atom.origin)
97 .unwrap_or_else(|| panic!("missing object for input {:?}", atom.origin));
98 let input_section = input
99 .object
100 .sections
101 .get((atom.input_section as usize).saturating_sub(1))
102 .unwrap_or_else(|| {
103 panic!(
104 "input {} section {} missing for atom {:?}",
105 input.object.path.display(),
106 atom.input_section,
107 atom_id
108 )
109 });
110
111 let key = output_section_key(input_section);
112 let idx = match section_index.get(&key) {
113 Some(&idx) => idx,
114 None => {
115 let idx = sections.len();
116 sections.push(OutputSection {
117 segment: key.segment.clone(),
118 name: key.name.clone(),
119 kind: input_section.kind,
120 align_pow2: normalize_output_alignment(
121 input_section.kind,
122 input_section.align_pow2.min(u8::MAX as u32) as u8,
123 ),
124 flags: input_section.flags,
125 reserved1: input_section.reserved1,
126 reserved2: input_section.reserved2,
127 reserved3: input_section.reserved3,
128 atoms: Vec::new(),
129 synthetic_offset: 0,
130 synthetic_data: Vec::new(),
131 addr: 0,
132 size: 0,
133 file_off: 0,
134 });
135 section_index.insert(key, idx);
136 idx
137 }
138 };
139
140 let out = &mut sections[idx];
141 out.align_pow2 =
142 normalize_output_alignment(out.kind, out.align_pow2.max(atom.align_pow2));
143 out.atoms.push(OutputAtom {
144 atom: atom_id,
145 offset: 0,
146 size: atom.size as u64,
147 data: atom.data.clone(),
148 });
149 }
150
151 if let Some(plan) = synthetic_plan {
152 for synthetic in plan.output_sections() {
153 if let Some(existing) = sections.iter_mut().find(|section| {
154 section.segment == synthetic.segment && section.name == synthetic.name
155 }) {
156 merge_synthetic_section(existing, synthetic);
157 } else {
158 sections.push(synthetic);
159 }
160 }
161 }
162
163 sections.sort_by(|a, b| {
164 segment_rank(kind, &a.segment)
165 .cmp(&segment_rank(kind, &b.segment))
166 .then_with(|| {
167 section_rank(&a.segment, &a.name).cmp(&section_rank(&b.segment, &b.name))
168 })
169 .then_with(|| a.segment.cmp(&b.segment))
170 .then_with(|| a.name.cmp(&b.name))
171 });
172
173 for section in &mut sections {
174 section.atoms.sort_by(|a, b| {
175 let lhs = atoms.get(a.atom);
176 let rhs = atoms.get(b.atom);
177 let lhs_input = input_map
178 .get(&lhs.origin)
179 .unwrap_or_else(|| panic!("missing object for input {:?}", lhs.origin));
180 let rhs_input = input_map
181 .get(&rhs.origin)
182 .unwrap_or_else(|| panic!("missing object for input {:?}", rhs.origin));
183 lhs_input
184 .load_order
185 .cmp(&rhs_input.load_order)
186 .then_with(|| {
187 lhs_input
188 .archive_member_offset
189 .unwrap_or(0)
190 .cmp(&rhs_input.archive_member_offset.unwrap_or(0))
191 })
192 .then_with(|| lhs.origin.cmp(&rhs.origin))
193 .then_with(|| lhs.input_offset.cmp(&rhs.input_offset))
194 .then_with(|| a.atom.cmp(&b.atom))
195 });
196
197 let mut size = 0u64;
198 for placed in &mut section.atoms {
199 let atom = atoms.get(placed.atom);
200 let align = 1u64 << atom.align_pow2.min(63);
201 size = align_up(size, align);
202 placed.offset = size;
203 size += placed.size;
204 }
205 section.synthetic_offset =
206 if section.synthetic_data.is_empty() || section.atoms.is_empty() {
207 0
208 } else {
209 let align = 1u64 << section.align_pow2.min(63);
210 align_up(size, align)
211 };
212 section.size = if section.synthetic_data.is_empty() {
213 size
214 } else {
215 section.synthetic_offset + section.synthetic_data.len() as u64
216 };
217 }
218
219 let mut layout = Layout {
220 kind,
221 segments: build_segments(kind, &sections),
222 sections,
223 };
224 layout.assign_addresses(header_size);
225 layout
226 }
227
228 pub fn segment(&self, name: &str) -> Option<&OutputSegment> {
229 self.segments.iter().find(|seg| seg.name == name)
230 }
231
232 pub fn segment_mut(&mut self, name: &str) -> Option<&mut OutputSegment> {
233 self.segments.iter_mut().find(|seg| seg.name == name)
234 }
235
236 pub fn relayout(&mut self, header_size: u64) {
237 self.assign_addresses(header_size);
238 }
239
240 pub fn atom_file_offset(&self, atom_id: crate::resolve::AtomId) -> Option<u64> {
241 for section in &self.sections {
242 for placed in &section.atoms {
243 if placed.atom == atom_id {
244 return Some(section.file_off + placed.offset);
245 }
246 }
247 }
248 None
249 }
250
251 pub fn atom_addr(&self, atom_id: crate::resolve::AtomId) -> Option<u64> {
252 for section in &self.sections {
253 for placed in &section.atoms {
254 if placed.atom == atom_id {
255 return Some(section.addr + placed.offset);
256 }
257 }
258 }
259 None
260 }
261
262 fn assign_addresses(&mut self, header_size: u64) {
263 for seg in &mut self.segments {
264 seg.sections.clear();
265 seg.vm_addr = 0;
266 seg.vm_size = 0;
267 seg.file_off = 0;
268 seg.file_size = 0;
269 }
270
271 let section_membership: Vec<(usize, String)> = self
272 .sections
273 .iter()
274 .enumerate()
275 .map(|(idx, section)| (idx, section.segment.clone()))
276 .collect();
277 for (idx, segment_name) in section_membership {
278 let segment = self
279 .segment_mut(&segment_name)
280 .unwrap_or_else(|| panic!("no output segment named {segment_name}"));
281 segment.sections.push(OutputSectionId(idx as u32));
282 }
283
284 let text_base = match self.kind {
285 OutputKind::Executable => EXECUTABLE_TEXT_BASE,
286 OutputKind::Dylib => 0,
287 };
288
289 if self.kind == OutputKind::Executable {
290 let pagezero = self
291 .segment_mut("__PAGEZERO")
292 .expect("executable layout must include __PAGEZERO");
293 pagezero.vm_addr = 0;
294 pagezero.vm_size = EXECUTABLE_TEXT_BASE;
295 pagezero.file_off = 0;
296 pagezero.file_size = 0;
297 }
298
299 let mut next_vm = text_base;
300 let mut next_file = 0u64;
301 let segment_names: Vec<String> = self.segments.iter().map(|seg| seg.name.clone()).collect();
302 for seg_name in segment_names {
303 if seg_name == "__PAGEZERO" {
304 continue;
305 }
306
307 let is_text = seg_name == "__TEXT";
308 let is_linkedit = seg_name == "__LINKEDIT";
309 let seg_start_vm = if is_text {
310 text_base
311 } else {
312 align_up(next_vm, PAGE_SIZE)
313 };
314 let seg_start_file = if is_text {
315 0
316 } else {
317 align_up(next_file, PAGE_SIZE)
318 };
319
320 let mut vm_cursor = if is_text {
321 seg_start_vm + header_size
322 } else {
323 seg_start_vm
324 };
325 let mut file_cursor = if is_text { header_size } else { seg_start_file };
326 let mut seg_vm_end = if is_text {
327 seg_start_vm + header_size
328 } else {
329 seg_start_vm
330 };
331 let mut seg_file_end = if is_text { header_size } else { seg_start_file };
332
333 let section_ids = self
334 .segment(&seg_name)
335 .map(|seg| seg.sections.clone())
336 .unwrap_or_default();
337 for id in section_ids {
338 let section = &mut self.sections[id.0 as usize];
339 let align = 1u64 << section.align_pow2.min(63);
340 vm_cursor = align_up(vm_cursor, align);
341 section.addr = vm_cursor;
342 seg_vm_end = seg_vm_end.max(section.addr + section.size);
343 vm_cursor = section.addr + section.size;
344
345 if is_zerofill(section.kind) {
346 section.file_off = 0;
347 } else {
348 file_cursor = align_up(file_cursor, align);
349 section.file_off = file_cursor;
350 seg_file_end = seg_file_end.max(section.file_off + section.size);
351 file_cursor = section.file_off + section.size;
352 }
353 }
354
355 let segment = self.segment_mut(&seg_name).expect("segment disappeared");
356 segment.vm_addr = seg_start_vm;
357 segment.file_off = seg_start_file;
358 let raw_vm_size = seg_vm_end.saturating_sub(seg_start_vm);
359 let raw_file_size = seg_file_end.saturating_sub(seg_start_file);
360 segment.vm_size = if raw_vm_size == 0 || is_linkedit {
361 raw_vm_size
362 } else {
363 align_up(raw_vm_size, PAGE_SIZE)
364 };
365 segment.file_size = if is_linkedit || raw_file_size == 0 {
366 if is_linkedit {
367 0
368 } else {
369 raw_file_size
370 }
371 } else {
372 align_up(raw_file_size, PAGE_SIZE)
373 };
374
375 next_vm = if is_linkedit {
376 seg_start_vm
377 } else {
378 seg_vm_end
379 };
380 next_file = if is_linkedit {
381 seg_start_file
382 } else {
383 seg_file_end
384 };
385 }
386 }
387 }
388
389 fn merge_synthetic_section(existing: &mut OutputSection, synthetic: OutputSection) {
390 debug_assert_eq!(existing.segment, synthetic.segment);
391 debug_assert_eq!(existing.name, synthetic.name);
392 existing.align_pow2 =
393 normalize_output_alignment(existing.kind, existing.align_pow2.max(synthetic.align_pow2));
394 existing.flags = synthetic.flags;
395 existing.reserved1 = synthetic.reserved1;
396 existing.reserved2 = synthetic.reserved2;
397 existing.reserved3 = synthetic.reserved3;
398 if !synthetic.synthetic_data.is_empty() {
399 existing
400 .synthetic_data
401 .extend_from_slice(&synthetic.synthetic_data);
402 }
403 }
404
405 fn normalize_output_alignment(kind: crate::section::SectionKind, align_pow2: u8) -> u8 {
406 match kind {
407 crate::section::SectionKind::ThreadLocalRegular
408 | crate::section::SectionKind::ThreadLocalZeroFill
409 | crate::section::SectionKind::ThreadLocalVariables
410 | crate::section::SectionKind::ThreadLocalVariablePointers
411 | crate::section::SectionKind::ThreadLocalInitPointers => align_pow2.max(3),
412 _ => align_pow2,
413 }
414 }
415
416 fn build_segments(kind: OutputKind, sections: &[OutputSection]) -> Vec<OutputSegment> {
417 let names: &[&str] = match kind {
418 OutputKind::Executable => &EXEC_SEGMENTS,
419 OutputKind::Dylib => &DYLIB_SEGMENTS,
420 };
421 let mut segments: Vec<OutputSegment> = names
422 .iter()
423 .filter(|name| standard_segment_required(kind, name, sections))
424 .map(|name| OutputSegment {
425 name: (*name).to_string(),
426 sections: Vec::new(),
427 vm_addr: 0,
428 vm_size: 0,
429 file_off: 0,
430 file_size: 0,
431 init_prot: segment_init_prot(name),
432 max_prot: segment_max_prot(name),
433 flags: segment_flags(name),
434 })
435 .collect();
436
437 let mut custom_names: Vec<String> = sections
438 .iter()
439 .map(|section| section.segment.clone())
440 .filter(|name| !names.contains(&name.as_str()))
441 .collect();
442 custom_names.sort();
443 custom_names.dedup();
444
445 let linkedit_idx = segments
446 .iter()
447 .position(|segment| segment.name == "__LINKEDIT")
448 .unwrap_or(segments.len());
449 for name in custom_names.into_iter().rev() {
450 let prot = custom_segment_prot(&name, sections);
451 segments.insert(
452 linkedit_idx,
453 OutputSegment {
454 name,
455 sections: Vec::new(),
456 vm_addr: 0,
457 vm_size: 0,
458 file_off: 0,
459 file_size: 0,
460 init_prot: prot.0,
461 max_prot: prot.1,
462 flags: 0,
463 },
464 );
465 }
466 segments
467 }
468
469 fn standard_segment_required(kind: OutputKind, name: &&str, sections: &[OutputSection]) -> bool {
470 match (kind, *name) {
471 (OutputKind::Executable, "__PAGEZERO") | (_, "__TEXT") | (_, "__LINKEDIT") => true,
472 _ => sections.iter().any(|section| section.segment == *name),
473 }
474 }
475
476 fn segment_rank(kind: OutputKind, segment: &str) -> usize {
477 let order: &[&str] = match kind {
478 OutputKind::Executable => &EXEC_SEGMENTS,
479 OutputKind::Dylib => &DYLIB_SEGMENTS,
480 };
481 order
482 .iter()
483 .position(|name| *name == segment)
484 .unwrap_or(order.len())
485 }
486
487 fn section_rank(segment: &str, section: &str) -> usize {
488 let order: &[&str] = match segment {
489 "__TEXT" => &[
490 "__text",
491 "__stubs",
492 "__stub_helper",
493 "__cstring",
494 "__const",
495 "__literal16",
496 "__unwind_info",
497 "__eh_frame",
498 ],
499 "__DATA_CONST" => &["__got", "__const"],
500 "__DATA" => &[
501 "__la_symbol_ptr",
502 "__data",
503 "__thread_vars",
504 "__thread_ptrs",
505 "__thread_data",
506 "__thread_bss",
507 "__common",
508 "__bss",
509 ],
510 "__LINKEDIT" => &[],
511 _ => &[],
512 };
513 order
514 .iter()
515 .position(|name| *name == section)
516 .unwrap_or(order.len())
517 }
518
519 fn segment_init_prot(name: &str) -> Prot {
520 match name {
521 "__PAGEZERO" => Prot::NONE,
522 "__TEXT" => Prot::READ_EXECUTE,
523 "__DATA_CONST" => Prot::READ_WRITE,
524 "__DATA" => Prot::READ_WRITE,
525 "__LINKEDIT" => Prot::READ_ONLY,
526 _ => Prot::READ_WRITE,
527 }
528 }
529
530 fn segment_max_prot(name: &str) -> Prot {
531 match name {
532 "__PAGEZERO" => Prot::NONE,
533 "__TEXT" => Prot::READ_EXECUTE,
534 "__DATA_CONST" => Prot::READ_WRITE,
535 "__DATA" => Prot::READ_WRITE,
536 "__LINKEDIT" => Prot::READ_ONLY,
537 _ => Prot::READ_WRITE,
538 }
539 }
540
541 fn segment_flags(name: &str) -> u32 {
542 match name {
543 "__DATA_CONST" => SG_READ_ONLY,
544 _ => 0,
545 }
546 }
547
548 fn custom_segment_prot(name: &str, sections: &[OutputSection]) -> (Prot, Prot) {
549 let mut has_exec = false;
550 let mut all_read_only = true;
551 for section in sections.iter().filter(|section| section.segment == name) {
552 if is_executable_kind(section.kind) {
553 has_exec = true;
554 }
555 if !is_read_only_kind(section.kind) {
556 all_read_only = false;
557 }
558 }
559
560 if has_exec {
561 (Prot::READ_EXECUTE, Prot::READ_EXECUTE)
562 } else if all_read_only {
563 (Prot::READ_ONLY, Prot::READ_ONLY)
564 } else {
565 (Prot::READ_WRITE, Prot::READ_WRITE)
566 }
567 }
568
569 fn is_executable_kind(kind: crate::section::SectionKind) -> bool {
570 matches!(
571 kind,
572 crate::section::SectionKind::Text
573 | crate::section::SectionKind::SymbolStubs
574 | crate::section::SectionKind::Coalesced
575 )
576 }
577
578 fn is_read_only_kind(kind: crate::section::SectionKind) -> bool {
579 matches!(
580 kind,
581 crate::section::SectionKind::ConstData
582 | crate::section::SectionKind::CStringLiterals
583 | crate::section::SectionKind::Literal4
584 | crate::section::SectionKind::Literal8
585 | crate::section::SectionKind::Literal16
586 | crate::section::SectionKind::CompactUnwind
587 | crate::section::SectionKind::EhFrame
588 )
589 }
590
591 fn align_up(value: u64, align: u64) -> u64 {
592 if align <= 1 {
593 return value;
594 }
595 let mask = align - 1;
596 (value + mask) & !mask
597 }
598
599 #[cfg(test)]
600 mod tests {
601 use std::path::PathBuf;
602
603 use crate::atom::{Atom, AtomFlags, AtomSection, AtomTable};
604 use crate::input::ObjectFile;
605 use crate::macho::constants::{
606 CPU_SUBTYPE_ARM64_ALL, CPU_TYPE_ARM64, MH_MAGIC_64, MH_OBJECT, S_ATTR_PURE_INSTRUCTIONS,
607 S_ATTR_SOME_INSTRUCTIONS, S_CSTRING_LITERALS, S_REGULAR, S_ZEROFILL,
608 };
609 use crate::macho::reader::MachHeader64;
610 use crate::resolve::{DylibId, InputId, SymbolId};
611 use crate::section::{InputSection, SectionKind};
612 use crate::synth::{
613 got::GotSection,
614 stubs::{LazyPointerEntry, LazyPointerSection, StubEntry, StubsSection},
615 SyntheticPlan,
616 };
617
618 use super::*;
619
620 #[test]
621 fn layout_orders_text_sections_like_ld() {
622 let object = ObjectFile {
623 path: PathBuf::from("/tmp/layout.o"),
624 header: MachHeader64 {
625 magic: MH_MAGIC_64,
626 cputype: CPU_TYPE_ARM64,
627 cpusubtype: CPU_SUBTYPE_ARM64_ALL,
628 filetype: MH_OBJECT,
629 ncmds: 0,
630 sizeofcmds: 0,
631 flags: 0,
632 reserved: 0,
633 },
634 commands: Vec::new(),
635 sections: vec![
636 input_section(
637 "__TEXT",
638 "__cstring",
639 SectionKind::CStringLiterals,
640 0,
641 S_CSTRING_LITERALS,
642 ),
643 input_section(
644 "__TEXT",
645 "__text",
646 SectionKind::Text,
647 2,
648 S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS,
649 ),
650 input_section("__TEXT", "__const", SectionKind::ConstData, 3, S_REGULAR),
651 input_section("__DATA", "__bss", SectionKind::ZeroFill, 3, S_ZEROFILL),
652 ],
653 symbols: Vec::new(),
654 strings: crate::string_table::StringTable::from_bytes(vec![0]),
655 symtab: None,
656 dysymtab: None,
657 data_in_code: Vec::new(),
658 };
659
660 let mut atoms = AtomTable::new();
661 atoms.push(atom(InputId(0), 2, AtomSection::Text, 0, 8, 2, vec![0; 8]));
662 atoms.push(atom(
663 InputId(0),
664 1,
665 AtomSection::CStringLiterals,
666 0,
667 6,
668 0,
669 b"hello\0".to_vec(),
670 ));
671 atoms.push(atom(
672 InputId(0),
673 3,
674 AtomSection::ConstData,
675 0,
676 16,
677 3,
678 vec![1; 16],
679 ));
680 atoms.push(atom(
681 InputId(0),
682 4,
683 AtomSection::ZeroFill,
684 0,
685 32,
686 3,
687 Vec::new(),
688 ));
689
690 let layout = Layout::build(
691 OutputKind::Executable,
692 &[LayoutInput {
693 id: InputId(0),
694 object: &object,
695 load_order: 0,
696 archive_member_offset: None,
697 }],
698 &atoms,
699 0x200,
700 );
701
702 let names: Vec<(&str, &str)> = layout
703 .sections
704 .iter()
705 .map(|s| (s.segment.as_str(), s.name.as_str()))
706 .collect();
707 assert_eq!(
708 names,
709 vec![
710 ("__TEXT", "__text"),
711 ("__TEXT", "__cstring"),
712 ("__TEXT", "__const"),
713 ("__DATA", "__bss"),
714 ]
715 );
716 }
717
718 #[test]
719 fn layout_promotes_data_const_into_data_const_segment() {
720 let object = ObjectFile {
721 path: PathBuf::from("/tmp/layout-const.o"),
722 header: MachHeader64 {
723 magic: MH_MAGIC_64,
724 cputype: CPU_TYPE_ARM64,
725 cpusubtype: CPU_SUBTYPE_ARM64_ALL,
726 filetype: MH_OBJECT,
727 ncmds: 0,
728 sizeofcmds: 0,
729 flags: 0,
730 reserved: 0,
731 },
732 commands: Vec::new(),
733 sections: vec![input_section(
734 "__DATA",
735 "__const",
736 SectionKind::ConstData,
737 3,
738 S_REGULAR,
739 )],
740 symbols: Vec::new(),
741 strings: crate::string_table::StringTable::from_bytes(vec![0]),
742 symtab: None,
743 dysymtab: None,
744 data_in_code: Vec::new(),
745 };
746
747 let mut atoms = AtomTable::new();
748 atoms.push(atom(
749 InputId(0),
750 1,
751 AtomSection::ConstData,
752 0,
753 16,
754 3,
755 vec![1; 16],
756 ));
757
758 let layout = Layout::build(
759 OutputKind::Executable,
760 &[LayoutInput {
761 id: InputId(0),
762 object: &object,
763 load_order: 0,
764 archive_member_offset: None,
765 }],
766 &atoms,
767 0x200,
768 );
769
770 assert!(layout
771 .sections
772 .iter()
773 .any(|section| section.segment == "__DATA_CONST" && section.name == "__const"));
774 assert!(!layout
775 .sections
776 .iter()
777 .any(|section| section.segment == "__DATA" && section.name == "__const"));
778 }
779
780 #[test]
781 fn executable_layout_has_pagezero_and_zerofill_has_no_file_offset() {
782 let object = ObjectFile {
783 path: PathBuf::from("/tmp/layout-bss.o"),
784 header: MachHeader64 {
785 magic: MH_MAGIC_64,
786 cputype: CPU_TYPE_ARM64,
787 cpusubtype: CPU_SUBTYPE_ARM64_ALL,
788 filetype: MH_OBJECT,
789 ncmds: 0,
790 sizeofcmds: 0,
791 flags: 0,
792 reserved: 0,
793 },
794 commands: Vec::new(),
795 sections: vec![input_section(
796 "__DATA",
797 "__bss",
798 SectionKind::ZeroFill,
799 4,
800 S_ZEROFILL,
801 )],
802 symbols: Vec::new(),
803 strings: crate::string_table::StringTable::from_bytes(vec![0]),
804 symtab: None,
805 dysymtab: None,
806 data_in_code: Vec::new(),
807 };
808
809 let mut atoms = AtomTable::new();
810 atoms.push(atom(
811 InputId(0),
812 1,
813 AtomSection::ZeroFill,
814 0,
815 64,
816 4,
817 Vec::new(),
818 ));
819
820 let layout = Layout::build(
821 OutputKind::Executable,
822 &[LayoutInput {
823 id: InputId(0),
824 object: &object,
825 load_order: 0,
826 archive_member_offset: None,
827 }],
828 &atoms,
829 0x300,
830 );
831
832 let pagezero = layout.segment("__PAGEZERO").unwrap();
833 assert_eq!(pagezero.vm_addr, 0);
834 assert_eq!(pagezero.vm_size, EXECUTABLE_TEXT_BASE);
835
836 let bss = layout.sections.iter().find(|s| s.name == "__bss").unwrap();
837 assert_eq!(bss.file_off, 0);
838 assert!(bss.addr >= EXECUTABLE_TEXT_BASE + PAGE_SIZE);
839 }
840
841 #[test]
842 fn layout_orders_common_before_bss() {
843 let object = ObjectFile {
844 path: PathBuf::from("/tmp/layout-common-bss.o"),
845 header: MachHeader64 {
846 magic: MH_MAGIC_64,
847 cputype: CPU_TYPE_ARM64,
848 cpusubtype: CPU_SUBTYPE_ARM64_ALL,
849 filetype: MH_OBJECT,
850 ncmds: 0,
851 sizeofcmds: 0,
852 flags: 0,
853 reserved: 0,
854 },
855 commands: Vec::new(),
856 sections: vec![
857 input_section("__DATA", "__common", SectionKind::ZeroFill, 3, S_ZEROFILL),
858 input_section("__DATA", "__bss", SectionKind::ZeroFill, 3, S_ZEROFILL),
859 ],
860 symbols: Vec::new(),
861 strings: crate::string_table::StringTable::from_bytes(vec![0]),
862 symtab: None,
863 dysymtab: None,
864 data_in_code: Vec::new(),
865 };
866
867 let mut atoms = AtomTable::new();
868 atoms.push(atom(
869 InputId(0),
870 1,
871 AtomSection::ZeroFill,
872 0,
873 16,
874 3,
875 Vec::new(),
876 ));
877 atoms.push(atom(
878 InputId(0),
879 2,
880 AtomSection::ZeroFill,
881 0,
882 32,
883 3,
884 Vec::new(),
885 ));
886
887 let layout = Layout::build(
888 OutputKind::Executable,
889 &[LayoutInput {
890 id: InputId(0),
891 object: &object,
892 load_order: 0,
893 archive_member_offset: None,
894 }],
895 &atoms,
896 0x200,
897 );
898
899 let names: Vec<(&str, &str)> = layout
900 .sections
901 .iter()
902 .map(|section| (section.segment.as_str(), section.name.as_str()))
903 .collect();
904 assert_eq!(names, vec![("__DATA", "__common"), ("__DATA", "__bss")]);
905 }
906
907 #[test]
908 fn file_backed_segments_round_to_page_boundaries() {
909 let object = ObjectFile {
910 path: PathBuf::from("/tmp/layout-pages.o"),
911 header: MachHeader64 {
912 magic: MH_MAGIC_64,
913 cputype: CPU_TYPE_ARM64,
914 cpusubtype: CPU_SUBTYPE_ARM64_ALL,
915 filetype: MH_OBJECT,
916 ncmds: 0,
917 sizeofcmds: 0,
918 flags: 0,
919 reserved: 0,
920 },
921 commands: Vec::new(),
922 sections: vec![
923 input_section(
924 "__TEXT",
925 "__text",
926 SectionKind::Text,
927 2,
928 S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS,
929 ),
930 input_section("__DATA", "__data", SectionKind::Data, 3, S_REGULAR),
931 ],
932 symbols: Vec::new(),
933 strings: crate::string_table::StringTable::from_bytes(vec![0]),
934 symtab: None,
935 dysymtab: None,
936 data_in_code: Vec::new(),
937 };
938
939 let mut atoms = AtomTable::new();
940 atoms.push(atom(
941 InputId(0),
942 1,
943 AtomSection::Text,
944 0,
945 16,
946 2,
947 vec![0; 16],
948 ));
949 atoms.push(atom(InputId(0), 2, AtomSection::Data, 0, 8, 3, vec![0; 8]));
950
951 let layout = Layout::build(
952 OutputKind::Executable,
953 &[LayoutInput {
954 id: InputId(0),
955 object: &object,
956 load_order: 0,
957 archive_member_offset: None,
958 }],
959 &atoms,
960 0x200,
961 );
962
963 let text = layout.segment("__TEXT").unwrap();
964 let data = layout.segment("__DATA").unwrap();
965 assert_eq!(text.file_size % PAGE_SIZE, 0);
966 assert_eq!(text.vm_size % PAGE_SIZE, 0);
967 assert_eq!(data.file_size % PAGE_SIZE, 0);
968 assert_eq!(data.vm_size % PAGE_SIZE, 0);
969 }
970
971 #[test]
972 fn layout_places_synthetic_import_sections_in_expected_order() {
973 let object = ObjectFile {
974 path: PathBuf::from("/tmp/layout-synth.o"),
975 header: MachHeader64 {
976 magic: MH_MAGIC_64,
977 cputype: CPU_TYPE_ARM64,
978 cpusubtype: CPU_SUBTYPE_ARM64_ALL,
979 filetype: MH_OBJECT,
980 ncmds: 0,
981 sizeofcmds: 0,
982 flags: 0,
983 reserved: 0,
984 },
985 commands: Vec::new(),
986 sections: vec![input_section(
987 "__TEXT",
988 "__text",
989 SectionKind::Text,
990 2,
991 S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS,
992 )],
993 symbols: Vec::new(),
994 strings: crate::string_table::StringTable::from_bytes(vec![0]),
995 symtab: None,
996 dysymtab: None,
997 data_in_code: Vec::new(),
998 };
999
1000 let mut atoms = AtomTable::new();
1001 atoms.push(atom(InputId(0), 1, AtomSection::Text, 0, 8, 2, vec![0; 8]));
1002
1003 let plan = SyntheticPlan {
1004 got: GotSection {
1005 entries: vec![
1006 crate::synth::got::GotEntry {
1007 symbol: SymbolId(1),
1008 weak_import: false,
1009 },
1010 crate::synth::got::GotEntry {
1011 symbol: SymbolId(2),
1012 weak_import: false,
1013 },
1014 ],
1015 index: [(SymbolId(1), 0), (SymbolId(2), 1)].into_iter().collect(),
1016 },
1017 stubs: StubsSection {
1018 entries: vec![StubEntry {
1019 symbol: SymbolId(1),
1020 dylib: DylibId(0),
1021 weak_import: false,
1022 }],
1023 index: [(SymbolId(1), 0)].into_iter().collect(),
1024 },
1025 lazy_pointers: LazyPointerSection {
1026 entries: vec![LazyPointerEntry {
1027 symbol: SymbolId(1),
1028 dylib: DylibId(0),
1029 weak_import: false,
1030 }],
1031 index: [(SymbolId(1), 0)].into_iter().collect(),
1032 },
1033 thread_pointers: crate::synth::tlv::ThreadPointerSection {
1034 entries: Vec::new(),
1035 index: HashMap::new(),
1036 },
1037 direct_binds: Vec::new(),
1038 binder_symbol: Some(SymbolId(2)),
1039 tlv_bootstrap_symbol: None,
1040 needs_dyld_private: true,
1041 };
1042
1043 let layout = Layout::build_with_synthetics(
1044 OutputKind::Executable,
1045 &[LayoutInput {
1046 id: InputId(0),
1047 object: &object,
1048 load_order: 0,
1049 archive_member_offset: None,
1050 }],
1051 &atoms,
1052 0x200,
1053 Some(&plan),
1054 );
1055
1056 let names: Vec<(&str, &str)> = layout
1057 .sections
1058 .iter()
1059 .map(|section| (section.segment.as_str(), section.name.as_str()))
1060 .collect();
1061 assert_eq!(
1062 names,
1063 vec![
1064 ("__TEXT", "__text"),
1065 ("__TEXT", "__stubs"),
1066 ("__TEXT", "__stub_helper"),
1067 ("__DATA_CONST", "__got"),
1068 ("__DATA", "__la_symbol_ptr"),
1069 ("__DATA", "__data"),
1070 ]
1071 );
1072
1073 let stubs = layout
1074 .sections
1075 .iter()
1076 .find(|section| section.name == "__stubs")
1077 .unwrap();
1078 assert_eq!(stubs.size, 12);
1079 assert_eq!(stubs.reserved2, 12);
1080 let got = layout
1081 .sections
1082 .iter()
1083 .find(|section| section.name == "__got")
1084 .unwrap();
1085 assert_eq!(got.size, 16);
1086 let helper = layout
1087 .sections
1088 .iter()
1089 .find(|section| section.name == "__stub_helper")
1090 .unwrap();
1091 assert_eq!(helper.size, 36);
1092 let lazy = layout
1093 .sections
1094 .iter()
1095 .find(|section| section.name == "__la_symbol_ptr")
1096 .unwrap();
1097 assert_eq!(lazy.size, 8);
1098 let dyld_private = layout
1099 .sections
1100 .iter()
1101 .find(|section| section.name == "__data")
1102 .unwrap();
1103 assert_eq!(dyld_private.size, 8);
1104 }
1105
1106 #[test]
1107 fn synthetic_dyld_private_merges_into_existing_data_section() {
1108 let object = ObjectFile {
1109 path: PathBuf::from("/tmp/layout-data.o"),
1110 header: MachHeader64 {
1111 magic: MH_MAGIC_64,
1112 cputype: CPU_TYPE_ARM64,
1113 cpusubtype: CPU_SUBTYPE_ARM64_ALL,
1114 filetype: MH_OBJECT,
1115 ncmds: 0,
1116 sizeofcmds: 0,
1117 flags: 0,
1118 reserved: 0,
1119 },
1120 commands: Vec::new(),
1121 sections: vec![input_section(
1122 "__DATA",
1123 "__data",
1124 SectionKind::Data,
1125 3,
1126 S_REGULAR,
1127 )],
1128 symbols: Vec::new(),
1129 strings: crate::string_table::StringTable::from_bytes(vec![0]),
1130 symtab: None,
1131 dysymtab: None,
1132 data_in_code: Vec::new(),
1133 };
1134
1135 let mut atoms = AtomTable::new();
1136 atoms.push(atom(
1137 InputId(0),
1138 1,
1139 AtomSection::Data,
1140 0,
1141 16,
1142 3,
1143 vec![0xaa; 16],
1144 ));
1145
1146 let plan = SyntheticPlan {
1147 got: GotSection {
1148 entries: Vec::new(),
1149 index: HashMap::new(),
1150 },
1151 stubs: StubsSection {
1152 entries: Vec::new(),
1153 index: HashMap::new(),
1154 },
1155 lazy_pointers: LazyPointerSection {
1156 entries: Vec::new(),
1157 index: HashMap::new(),
1158 },
1159 thread_pointers: crate::synth::tlv::ThreadPointerSection {
1160 entries: Vec::new(),
1161 index: HashMap::new(),
1162 },
1163 direct_binds: Vec::new(),
1164 binder_symbol: None,
1165 tlv_bootstrap_symbol: None,
1166 needs_dyld_private: true,
1167 };
1168
1169 let layout = Layout::build_with_synthetics(
1170 OutputKind::Executable,
1171 &[LayoutInput {
1172 id: InputId(0),
1173 object: &object,
1174 load_order: 0,
1175 archive_member_offset: None,
1176 }],
1177 &atoms,
1178 0x200,
1179 Some(&plan),
1180 );
1181
1182 let data = layout
1183 .sections
1184 .iter()
1185 .find(|section| section.segment == "__DATA" && section.name == "__data")
1186 .unwrap();
1187 assert_eq!(data.atoms.len(), 1);
1188 assert_eq!(data.synthetic_offset, 16);
1189 assert_eq!(data.synthetic_data.len(), 8);
1190 assert_eq!(data.size, 24);
1191 }
1192
1193 #[test]
1194 fn custom_segments_are_carried_opaquely_before_linkedit() {
1195 let object = ObjectFile {
1196 path: PathBuf::from("/tmp/layout-custom.o"),
1197 header: MachHeader64 {
1198 magic: MH_MAGIC_64,
1199 cputype: CPU_TYPE_ARM64,
1200 cpusubtype: CPU_SUBTYPE_ARM64_ALL,
1201 filetype: MH_OBJECT,
1202 ncmds: 0,
1203 sizeofcmds: 0,
1204 flags: 0,
1205 reserved: 0,
1206 },
1207 commands: Vec::new(),
1208 sections: vec![input_section(
1209 "__FOO",
1210 "__bar",
1211 SectionKind::Data,
1212 3,
1213 S_REGULAR,
1214 )],
1215 symbols: Vec::new(),
1216 strings: crate::string_table::StringTable::from_bytes(vec![0]),
1217 symtab: None,
1218 dysymtab: None,
1219 data_in_code: Vec::new(),
1220 };
1221
1222 let mut atoms = AtomTable::new();
1223 atoms.push(atom(InputId(0), 1, AtomSection::Data, 0, 8, 3, vec![0; 8]));
1224
1225 let layout = Layout::build(
1226 OutputKind::Executable,
1227 &[LayoutInput {
1228 id: InputId(0),
1229 object: &object,
1230 load_order: 0,
1231 archive_member_offset: None,
1232 }],
1233 &atoms,
1234 0x200,
1235 );
1236
1237 assert!(layout.segment("__FOO").is_some());
1238 let custom_idx = layout
1239 .segments
1240 .iter()
1241 .position(|segment| segment.name == "__FOO")
1242 .unwrap();
1243 let linkedit_idx = layout
1244 .segments
1245 .iter()
1246 .position(|segment| segment.name == "__LINKEDIT")
1247 .unwrap();
1248 assert!(custom_idx < linkedit_idx);
1249 }
1250
1251 fn input_section(
1252 segname: &str,
1253 sectname: &str,
1254 kind: SectionKind,
1255 align_pow2: u32,
1256 flags: u32,
1257 ) -> InputSection {
1258 InputSection {
1259 segname: segname.into(),
1260 sectname: sectname.into(),
1261 kind,
1262 addr: 0,
1263 size: 0,
1264 align_pow2,
1265 flags,
1266 offset: 0,
1267 reloff: 0,
1268 nreloc: 0,
1269 reserved1: 0,
1270 reserved2: 0,
1271 reserved3: 0,
1272 data: Vec::new(),
1273 raw_relocs: Vec::new(),
1274 }
1275 }
1276
1277 fn atom(
1278 origin: InputId,
1279 input_section: u8,
1280 section: AtomSection,
1281 input_offset: u32,
1282 size: u32,
1283 align_pow2: u8,
1284 data: Vec<u8>,
1285 ) -> Atom {
1286 Atom {
1287 id: crate::resolve::AtomId(0),
1288 origin,
1289 input_section,
1290 section,
1291 input_offset,
1292 size,
1293 align_pow2,
1294 owner: None,
1295 alt_entries: Vec::new(),
1296 data,
1297 flags: AtomFlags::NONE,
1298 parent_of: None,
1299 }
1300 }
1301 }
1302