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