1use object::write::{
29 Object as Writer, Relocation, StandardSection, Symbol, SymbolId, SymbolSection,
30};
31use object::{
32 Architecture, BinaryFormat, Endianness, RelocationFlags, SectionFlags, SectionKind,
33 SymbolFlags, SymbolKind, SymbolScope, elf,
34};
35use rucc_target::{ObjectFormat, TargetInfo};
36use rucc_tuple::Arch;
37
38use crate::section::{
39 Alias, Binding, Data, Object, Output, Place, Property, Reference, Reloc, Sections, Text,
40 Visibility,
41};
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum Error {
46 Format {
48 triple: String,
50 },
51 Refused {
53 why: String,
55 },
56}
57
58impl std::fmt::Display for Error {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 match self {
61 Error::Format { triple } => {
62 write!(f, "there is no object writer for {triple} in this compiler yet")
63 }
64 Error::Refused { why } => {
65 write!(f, "the object writer refused what it was given: {why}")
66 }
67 }
68 }
69}
70
71impl std::error::Error for Error {}
72
73pub fn write(
82 text: &Text,
83 data: &Data,
84 aliases: &[Alias],
85 target: &TargetInfo,
86 output: Output,
87) -> Result<Vec<u8>, Error> {
88 let Output { sections, property } = output;
89 if target.tuple.arch() != Arch::X86_64 || target.object_format != ObjectFormat::Elf {
90 return Err(Error::Format { triple: target.tuple.to_string() });
91 }
92 let mut obj = Writer::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little);
93 let whole = obj.section_id(StandardSection::Text);
97 if !sections.functions {
98 obj.append_section_data(whole, &text.bytes, u64::from(text.align));
99 }
100
101 let mut symbols = std::collections::BTreeMap::new();
105 let mut split: Vec<(object::write::SectionId, u64)> = Vec::with_capacity(text.funcs.len());
110 let mut ordered: Vec<String> = Vec::new();
113 for func in &text.funcs {
114 let ahead = func.patch.map_or(0, |patch| patch.before);
124 let (section, at) = if sections.functions {
125 let name = format!(".text.{}", func.name).into_bytes();
126 let id = obj.add_section(Vec::new(), name, SectionKind::Text);
127 let bytes = &text.bytes[func.start - ahead..func.start + func.len];
128 obj.append_section_data(id, bytes, u64::from(func.align.max(1)));
129 (id, ahead as u64)
130 } else {
131 (whole, func.start as u64)
132 };
133 if let Some(patch) = func.patch {
148 let base = if sections.functions { func.start - ahead } else { 0 };
149 let name = PATCHABLE.as_bytes().to_vec();
150 let id = obj.add_section(Vec::new(), name, SectionKind::Data);
151 obj.section_mut(id).flags = SectionFlags::Elf {
152 sh_type: elf::SHT_PROGBITS,
153 sh_flags: elf::SHF_ALLOC | elf::SHF_WRITE | elf::SHF_LINK_ORDER,
154 };
155 obj.append_section_data(id, &[0; 8], 8);
156 let symbol = obj.section_symbol(section);
157 obj.add_relocation(
158 id,
159 Relocation {
160 offset: 0,
161 symbol,
162 addend: (patch.at - base) as i64,
163 flags: RelocationFlags::Elf { r_type: elf::R_X86_64_64 },
164 },
165 )
166 .map_err(|why| Error::Refused { why: why.to_string() })?;
167 ordered.push(if sections.functions {
168 format!(".text.{}", func.name)
169 } else {
170 ".text".to_owned()
171 });
172 }
173 let id = obj.add_symbol(Symbol {
174 name: func.name.clone().into_bytes(),
175 value: at,
176 size: func.len as u64,
177 kind: SymbolKind::Text,
178 scope: scope_of(func.binding),
179 weak: func.binding == Binding::Weak,
180 section: SymbolSection::Section(section),
181 flags: SymbolFlags::None,
182 });
183 see(&mut obj, id, func.binding, func.visibility);
184 symbols.insert(func.name.clone(), id);
185 split.push((section, at));
186 }
187
188 let mut placed = Vec::with_capacity(data.objects.len());
193 let mut local = None;
197 for object in &data.objects {
198 let (section, offset) = put(&mut obj, object, &mut local, sections);
199 let id = obj.add_symbol(Symbol {
200 name: object.name.clone().into_bytes(),
201 value: if object.place == Place::Merged { object.align } else { offset },
204 size: object.size,
205 kind: SymbolKind::Data,
206 scope: scope_of(object.binding),
207 weak: object.binding == Binding::Weak,
208 section,
209 flags: SymbolFlags::None,
210 });
211 see(&mut obj, id, object.binding, object.visibility);
212 symbols.insert(object.name.clone(), id);
213 placed.push((section.id(), offset));
214 }
215
216 for alias in aliases {
222 let Some(&id) = symbols.get(&alias.target) else {
223 let why =
224 format!("'{}' is aliased to '{}', which is not here", alias.name, alias.target);
225 return Err(Error::Refused { why });
226 };
227 let (value, size) = (obj.symbol(id).value, obj.symbol(id).size);
228 let (kind, section) = (obj.symbol(id).kind, obj.symbol(id).section);
229 let id = obj.add_symbol(Symbol {
230 name: alias.name.clone().into_bytes(),
231 value,
232 size,
233 kind,
234 scope: scope_of(alias.binding),
235 weak: alias.binding == Binding::Weak,
236 section,
237 flags: SymbolFlags::None,
238 });
239 see(&mut obj, id, alias.binding, alias.visibility);
240 symbols.insert(alias.name.clone(), id);
241 }
242
243 let wanted = text.relocs.iter().chain(data.objects.iter().flat_map(|object| &object.relocs));
247 for reloc in wanted {
248 if symbols.contains_key(&reloc.symbol) {
249 continue;
250 }
251 let id = obj.add_symbol(Symbol {
252 name: reloc.symbol.clone().into_bytes(),
253 value: 0,
254 size: 0,
255 kind: SymbolKind::Unknown,
259 scope: SymbolScope::Dynamic,
260 weak: false,
261 section: SymbolSection::Undefined,
262 flags: SymbolFlags::None,
263 });
264 symbols.insert(reloc.symbol.clone(), id);
265 }
266
267 for reloc in &text.relocs {
268 let (section, at) = if sections.functions {
273 let after = text.funcs.partition_point(|func| func.start <= reloc.at);
274 let Some(func) = after.checked_sub(1).map(|i| &text.funcs[i]) else {
275 let why = format!("a relocation at {} is in front of every function", reloc.at);
276 return Err(Error::Refused { why });
277 };
278 let base = func.start - func.patch.map_or(0, |patch| patch.before);
281 (split[after - 1].0, (reloc.at - base) as u64)
282 } else {
283 (whole, reloc.at as u64)
284 };
285 add(&mut obj, section, at, reloc, &symbols)?;
286 }
287
288 if !text.unwind.bytes.is_empty() {
294 let frames = obj.add_section(Vec::new(), b".eh_frame".to_vec(), SectionKind::ReadOnlyData);
295 obj.append_section_data(frames, &text.unwind.bytes, 8);
296 for reloc in &text.unwind.relocs {
297 let found = text.funcs.iter().position(|func| func.name == reloc.symbol);
310 let Some((section, at)) = found.map(|i| split[i]) else {
311 let why =
312 format!("'{}' has an unwind record and is not a function here", reloc.symbol);
313 return Err(Error::Refused { why });
314 };
315 let symbol = obj.section_symbol(section);
316 let r_type = r_type(reloc.kind).ok_or_else(|| Error::Refused {
317 why: format!("no relocation is {:?}", reloc.kind),
318 })?;
319 let record = Relocation {
320 offset: reloc.at as u64,
321 symbol,
322 addend: reloc.addend + at as i64,
326 flags: RelocationFlags::Elf { r_type },
327 };
328 obj.add_relocation(frames, record)
329 .map_err(|why| Error::Refused { why: why.to_string() })?;
330 }
331 }
332 for (object, &(section, offset)) in data.objects.iter().zip(&placed) {
333 let Some(section) = section else { continue };
334 for reloc in &object.relocs {
335 add(&mut obj, section, offset + reloc.at as u64, reloc, &symbols)?;
336 }
337 }
338
339 if property.any() {
343 let note = obj.section_id(StandardSection::GnuProperty);
344 obj.append_section_data(note, &record(property), 8);
345 }
346
347 obj.add_section(Vec::new(), b".note.GNU-stack".to_vec(), SectionKind::Metadata);
350
351 let mut bytes = obj.write().map_err(|why| Error::Refused { why: why.to_string() })?;
352 link(&mut bytes, &ordered);
353 Ok(bytes)
354}
355
356pub fn defines(
380 text: &Text,
381 data: &Data,
382 aliases: &[Alias],
383 target: &TargetInfo,
384) -> Result<Vec<String>, Error> {
385 if target.tuple.arch() != Arch::X86_64 || target.object_format != ObjectFormat::Elf {
386 return Err(Error::Format { triple: target.tuple.to_string() });
387 }
388 let names = text
389 .funcs
390 .iter()
391 .filter(|func| func.binding != Binding::Local)
392 .map(|func| func.name.clone())
393 .chain(
394 data.objects
395 .iter()
396 .filter(|object| object.binding != Binding::Local)
397 .map(|object| object.name.clone()),
398 )
399 .chain(
400 aliases
401 .iter()
402 .filter(|alias| alias.binding != Binding::Local)
403 .map(|alias| alias.name.clone()),
404 )
405 .collect();
406 Ok(names)
407}
408
409const PATCHABLE: &str = "__patchable_function_entries";
411
412fn link(bytes: &mut [u8], ordered: &[String]) {
427 if ordered.is_empty() {
428 return;
429 }
430 let word = |bytes: &[u8], at: usize| u64::from_le_bytes(bytes[at..at + 8].try_into().unwrap());
431 let short = |bytes: &[u8], at: usize| u16::from_le_bytes(bytes[at..at + 2].try_into().unwrap());
432 let long = |bytes: &[u8], at: usize| u32::from_le_bytes(bytes[at..at + 4].try_into().unwrap());
433 let headers = word(bytes, 0x28) as usize;
438 let step = short(bytes, 0x3a) as usize;
439 let count = short(bytes, 0x3c) as usize;
440 let strings = word(bytes, headers + short(bytes, 0x3e) as usize * step + 24) as usize;
441 let name = |bytes: &[u8], header: usize| {
442 let at = strings + long(bytes, header) as usize;
443 let end = bytes[at..].iter().position(|byte| *byte == 0).map_or(at, |len| at + len);
444 String::from_utf8_lossy(&bytes[at..end]).into_owned()
445 };
446 let names: Vec<String> = (0..count).map(|i| name(bytes, headers + i * step)).collect();
447 let mut wanted = ordered.iter();
448 for (i, section) in names.iter().enumerate() {
449 if section != PATCHABLE {
450 continue;
451 }
452 let Some(target) = wanted.next() else { break };
453 let Some(at) = names.iter().position(|name| name == target) else { continue };
454 let at = u32::try_from(at).expect("a file with this many sections in it");
455 let sh_link = headers + i * step + 40;
456 bytes[sh_link..sh_link + 4].copy_from_slice(&at.to_le_bytes());
457 }
458 debug_assert!(wanted.next().is_none(), "a record whose header nothing found");
459}
460
461fn record(property: Property) -> Vec<u8> {
472 let head = [4, 16, elf::NT_GNU_PROPERTY_TYPE_0.0];
475 let desc = [Property::X86_FEATURES, 4, property.features, 0];
476 let mut out = Vec::with_capacity(32);
477 for word in head {
478 out.extend_from_slice(&word.to_le_bytes());
479 }
480 out.extend_from_slice(b"GNU\0");
483 for word in desc {
484 out.extend_from_slice(&word.to_le_bytes());
485 }
486 out
487}
488
489fn put(
496 obj: &mut Writer<'_>,
497 object: &Object,
498 local: &mut Option<object::write::SectionId>,
499 sections: Sections,
500) -> (SymbolSection, u64) {
501 if sections.data {
507 if let Some(name) = object.place.split(&object.name) {
508 let section = obj.add_section(Vec::new(), name.into_bytes(), kind_of(&object.place));
509 let offset = if object.place == Place::Zero {
510 obj.append_section_bss(section, object.size, object.align)
511 } else {
512 obj.append_section_data(section, &object.bytes, object.align)
513 };
514 return (SymbolSection::Section(section), offset);
515 }
516 }
517 let section = match &object.place {
518 Place::Written => obj.section_id(StandardSection::Data),
519 Place::ReadOnly => obj.section_id(StandardSection::ReadOnlyData),
520 Place::RelocReadOnly { local: false } => {
526 obj.section_id(StandardSection::ReadOnlyDataWithRel)
527 }
528 Place::RelocReadOnly { local: true } => *local.get_or_insert_with(|| {
529 obj.add_section(
530 Vec::new(),
531 b".data.rel.ro.local".to_vec(),
532 SectionKind::ReadOnlyDataWithRel,
533 )
534 }),
535 Place::Zero => obj.section_id(StandardSection::UninitializedData),
536 Place::Merged => return (SymbolSection::Common, 0),
537 Place::Named(name) => {
541 obj.add_section(Vec::new(), name.clone().into_bytes(), SectionKind::Data)
542 }
543 };
544 let offset = if object.place == Place::Zero {
545 obj.append_section_bss(section, object.size, object.align)
546 } else {
547 obj.append_section_data(section, &object.bytes, object.align)
548 };
549 (SymbolSection::Section(section), offset)
550}
551
552fn kind_of(place: &Place) -> SectionKind {
561 match place {
562 Place::ReadOnly => SectionKind::ReadOnlyData,
563 Place::RelocReadOnly { .. } => SectionKind::ReadOnlyDataWithRel,
564 Place::Zero => SectionKind::UninitializedData,
565 Place::Written | Place::Merged | Place::Named(_) => SectionKind::Data,
566 }
567}
568
569fn add(
576 obj: &mut Writer<'_>,
577 section: object::write::SectionId,
578 at: u64,
579 reloc: &Reloc,
580 symbols: &std::collections::BTreeMap<String, SymbolId>,
581) -> Result<(), Error> {
582 let r_type = r_type(reloc.kind)
583 .ok_or_else(|| Error::Refused { why: format!("no relocation is {:?}", reloc.kind) })?;
584 obj.add_relocation(
585 section,
586 Relocation {
587 offset: at,
588 symbol: symbols[&reloc.symbol],
589 addend: reloc.addend,
590 flags: RelocationFlags::Elf { r_type },
591 },
592 )
593 .map_err(|why| Error::Refused { why: why.to_string() })
594}
595
596fn scope_of(binding: Binding) -> SymbolScope {
609 match binding {
610 Binding::Local => SymbolScope::Compilation,
611 Binding::Global | Binding::Weak => SymbolScope::Dynamic,
612 }
613}
614
615fn see(obj: &mut Writer<'_>, id: SymbolId, binding: Binding, visibility: Visibility) {
627 if binding == Binding::Local {
628 return;
629 }
630 let wanted = match visibility {
631 Visibility::Default => elf::STV_DEFAULT,
632 Visibility::Hidden => elf::STV_HIDDEN,
633 Visibility::Protected => elf::STV_PROTECTED,
634 };
635 if let SymbolFlags::Elf { st_other, .. } = obj.symbol_flags_mut(id) {
636 *st_other = st_other.with_visibility(wanted);
637 }
638}
639
640fn r_type(reference: Reference) -> Option<elf::RelocationType> {
651 Some(match reference {
652 Reference::Call => elf::R_X86_64_PLT32,
653 Reference::Data => elf::R_X86_64_PC32,
654 Reference::Got => elf::R_X86_64_REX_GOTPCRELX,
655 Reference::Address { bytes: 8 } => elf::R_X86_64_64,
656 Reference::Address { bytes: 4 } => elf::R_X86_64_32,
657 Reference::Address { .. } => return None,
658 })
659}
660
661#[cfg(test)]
662mod tests {
663 use super::*;
664
665 use object::read::elf::Sym as _;
666 use object::read::{Object as _, ObjectSection as _, ObjectSymbol as _};
667 use rucc_target::{Arch, Env, Os, Triple};
668
669 use crate::section::{Extent, Patch, Reloc};
670
671 fn target() -> TargetInfo {
673 TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
674 }
675
676 fn extent(name: String, start: usize, len: usize, binding: Binding) -> Extent {
681 Extent {
682 name,
683 start,
684 len,
685 align: crate::FUNC_ALIGN,
686 binding,
687 visibility: Visibility::Default,
688 patch: None,
689 }
690 }
691
692 fn calling(name: &str) -> Text {
694 Text {
695 bytes: vec![0xe8, 0, 0, 0, 0, 0xc3],
696 funcs: vec![extent("f".to_owned(), 0, 6, Binding::Global)],
697 relocs: vec![Reloc {
698 at: 1,
699 symbol: name.to_owned(),
700 kind: Reference::Call,
701 addend: -4,
702 }],
703 ..Text::default()
704 }
705 }
706
707 #[test]
708 fn the_bytes_come_back_out_of_the_section_they_went_into() {
709 let text = calling("puts");
710 let bytes =
711 write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
712 let file = object::File::parse(&bytes[..]).expect("a readable object");
713 let section = file.section_by_name(".text").expect("a text section");
714 assert_eq!(section.data().expect("the bytes"), &text.bytes[..]);
715 }
716
717 #[test]
718 fn a_function_is_a_symbol_that_says_where_it_is_and_how_long_it_is() {
719 let mut text = calling("puts");
720 text.funcs.push(extent("g".to_owned(), 16, 1, Binding::Global));
721 text.bytes.resize(17, 0x90);
722 let bytes =
723 write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
724 let file = object::File::parse(&bytes[..]).expect("a readable object");
725 let g = file.symbols().find(|s| s.name() == Ok("g")).expect("the second function");
726 assert_eq!(g.address(), 16);
727 assert_eq!(g.size(), 1);
728 assert_eq!(g.kind(), SymbolKind::Text);
729 assert!(g.is_global(), "nothing said otherwise about this one");
730 }
731
732 #[test]
733 fn a_function_no_other_file_can_see_is_a_local_symbol() {
734 let mut text = calling("puts");
735 text.funcs.push(extent("hidden".to_owned(), 16, 1, Binding::Local));
736 text.funcs.push(extent("shared".to_owned(), 32, 1, Binding::Weak));
737 text.bytes.resize(33, 0x90);
738 let bytes =
739 write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
740 let file = object::File::parse(&bytes[..]).expect("a readable object");
741 let hidden = file.symbols().find(|s| s.name() == Ok("hidden")).expect("the static one");
742 assert!(hidden.is_local(), "a static function must not be offered to the linker");
745 assert!(!hidden.is_weak());
746 let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the weak one");
747 assert!(shared.is_weak(), "a weak function has to be able to lose");
748 assert!(shared.is_global());
749 }
750
751 #[test]
768 fn where_a_patcher_may_write_is_recorded_in_a_section_tied_to_the_code_it_is_about() {
769 let mut text = calling("puts");
770 text.bytes.splice(0..0, [0x90, 0x90, 0x90]);
771 text.funcs[0].start = 3;
772 text.funcs[0].patch = Some(Patch { at: 0, before: 3 });
773 text.relocs[0].at = 4;
774 let bytes =
775 write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
776 let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
777 let section = file.section_by_name(PATCHABLE).expect("a record of the room");
778 assert_eq!(section.size(), 8, "one address, and this file defines one function");
779 assert_eq!(section.align(), 8);
780 let header = section.elf_section_header();
781 assert_eq!(
782 header.sh_flags.get(Endianness::Little),
783 elf::SHF_ALLOC | elf::SHF_WRITE | elf::SHF_LINK_ORDER
784 );
785 let index = file.section_by_name(".text").expect("a text section").index().0;
788 assert_eq!(header.sh_link.get(Endianness::Little) as usize, index);
789 assert_ne!(index, 0);
790
791 let [(at, reloc)] = §ion.relocations().collect::<Vec<_>>()[..] else {
793 panic!("one address in the record")
794 };
795 assert_eq!(*at, 0);
796 assert_eq!(reloc.addend(), 0);
797 assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: elf::R_X86_64_64 });
798 }
799
800 #[test]
802 fn a_file_that_promised_a_patcher_nothing_records_nothing() {
803 let text = calling("puts");
804 let bytes =
805 write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
806 let file = object::File::parse(&bytes[..]).expect("a readable object");
807 assert!(file.section_by_name(PATCHABLE).is_none());
808 }
809
810 #[test]
816 fn each_record_is_tied_to_its_own_function_when_they_are_split_up() {
817 let mut text = calling("puts");
818 text.funcs[0].patch = Some(Patch { at: 0, before: 0 });
819 text.funcs.push(extent("g".to_owned(), 16, 1, Binding::Global));
820 text.funcs[1].patch = Some(Patch { at: 16, before: 0 });
821 text.bytes.resize(17, 0x90);
822 let output =
823 Output { sections: Sections { functions: true, data: false }, ..Output::default() };
824 let bytes = write(&text, &Data::default(), &[], &target(), output).expect("an object");
825 let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
826 let links: Vec<usize> = file
827 .sections()
828 .filter(|section| section.name() == Ok(PATCHABLE))
829 .map(|section| section.elf_section_header().sh_link.get(Endianness::Little) as usize)
830 .collect();
831 let index = |name: &str| file.section_by_name(name).expect("a text section").index().0;
832 assert_eq!(links, [index(".text.f"), index(".text.g")]);
833 }
834
835 #[test]
836 fn a_global_is_visible_to_the_dynamic_linker_and_a_static_one_is_not_a_symbol_at_all() {
837 let mut text = calling("puts");
838 text.funcs.push(extent("g".to_owned(), 16, 1, Binding::Global));
839 text.funcs.push(extent("w".to_owned(), 32, 1, Binding::Weak));
840 text.funcs.push(extent("s".to_owned(), 48, 1, Binding::Local));
841 text.bytes.resize(49, 0x90);
842 let bytes =
843 write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
844 let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
845 let visibility = |name: &str| {
846 file.symbols()
847 .find(|s| s.name() == Ok(name))
848 .expect("the function")
849 .elf_symbol()
850 .st_visibility()
851 };
852 assert_eq!(visibility("g"), elf::STV_DEFAULT);
854 assert_eq!(visibility("w"), elf::STV_DEFAULT, "a weak one is still a name others may use");
855 assert_eq!(visibility("s"), elf::STV_DEFAULT);
858 }
859
860 #[test]
870 fn a_name_that_asked_to_be_hidden_is_hidden_and_a_protected_one_is_protected() {
871 let mut text = calling("puts");
872 for (index, (name, seen)) in
873 [("h", Visibility::Hidden), ("p", Visibility::Protected)].into_iter().enumerate()
874 {
875 let mut func = extent(name.to_owned(), 16 + index * 16, 1, Binding::Global);
876 func.visibility = seen;
877 text.funcs.push(func);
878 }
879 text.bytes.resize(49, 0x90);
880 let mut data = Data::default();
881 for (name, seen) in [("vh", Visibility::Hidden), ("vp", Visibility::Protected)] {
882 let mut object = variable(name, Place::Written);
883 object.visibility = seen;
884 data.objects.push(object);
885 }
886 let bytes = write(&text, &data, &[], &target(), Output::default()).expect("an object");
887 let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
888 let visibility = |name: &str| {
889 file.symbols()
890 .find(|s| s.name() == Ok(name))
891 .expect("the symbol")
892 .elf_symbol()
893 .st_visibility()
894 };
895 assert_eq!(visibility("h"), elf::STV_HIDDEN);
896 assert_eq!(visibility("p"), elf::STV_PROTECTED);
897 assert_eq!(visibility("vh"), elf::STV_HIDDEN, "a variable goes through a second loop");
898 assert_eq!(visibility("vp"), elf::STV_PROTECTED);
899 let h = file.symbols().find(|s| s.name() == Ok("h")).expect("the function");
902 assert!(h.is_global(), "hidden is about the dynamic linker and not about the binding");
903 assert_eq!(h.size(), 1, "and it is still a function of the length it was");
904 }
905
906 #[test]
907 fn a_name_this_file_does_not_define_is_left_for_the_linker_to_find() {
908 let bytes = write(&calling("puts"), &Data::default(), &[], &target(), Output::default())
909 .expect("an object");
910 let file = object::File::parse(&bytes[..]).expect("a readable object");
911 let puts = file.symbols().find(|s| s.name() == Ok("puts")).expect("the callee");
912 assert!(puts.is_undefined(), "the file does not define it and must not claim to");
913 }
914
915 #[test]
916 fn a_call_asks_for_the_relocation_a_stub_may_answer_and_a_load_asks_for_the_one_that_may_not() {
917 for (reference, wanted) in [
918 (Reference::Call, elf::R_X86_64_PLT32),
919 (Reference::Data, elf::R_X86_64_PC32),
920 (Reference::Got, elf::R_X86_64_REX_GOTPCRELX),
921 ] {
922 let mut text = calling("puts");
923 text.relocs[0].kind = reference;
924 let bytes = write(&text, &Data::default(), &[], &target(), Output::default())
925 .expect("an object");
926 let file = object::File::parse(&bytes[..]).expect("a readable object");
927 let section = file.section_by_name(".text").expect("a text section");
928 let (offset, reloc) = section.relocations().next().expect("one relocation");
929 assert_eq!(offset, 1);
930 assert_eq!(reloc.addend(), -4);
931 assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: wanted });
932 }
933 }
934
935 #[test]
936 fn a_name_wanted_twice_is_one_symbol_rather_than_two() {
937 let mut text = calling("puts");
938 text.relocs.push(Reloc {
939 at: 1,
940 symbol: "puts".to_owned(),
941 kind: Reference::Call,
942 addend: -4,
943 });
944 let bytes =
945 write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
946 let file = object::File::parse(&bytes[..]).expect("a readable object");
947 assert_eq!(file.symbols().filter(|s| s.name() == Ok("puts")).count(), 1);
948 }
949
950 #[test]
951 fn a_function_that_is_also_called_is_not_a_second_symbol() {
952 let text = calling("f");
953 let bytes =
954 write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
955 let file = object::File::parse(&bytes[..]).expect("a readable object");
956 let mut found = file.symbols().filter(|s| s.name() == Ok("f"));
957 let f = found.next().expect("the function");
958 assert!(!f.is_undefined(), "the file defines it");
959 assert!(found.next().is_none(), "and defines it once");
960 }
961
962 #[test]
963 fn the_marker_that_says_the_stack_is_not_executable_is_written() {
964 let bytes = write(&calling("puts"), &Data::default(), &[], &target(), Output::default())
965 .expect("an object");
966 let file = object::File::parse(&bytes[..]).expect("a readable object");
967 let note = file.section_by_name(".note.GNU-stack").expect("the marker");
968 assert!(note.data().expect("no bytes").is_empty());
969 }
970
971 #[test]
978 fn the_note_that_says_what_the_file_was_built_to_have_checked_is_written() {
979 let property = Property { features: Property::IBT | Property::SHSTK };
980 let output = Output { property, ..Output::default() };
981 let bytes =
982 write(&calling("puts"), &Data::default(), &[], &target(), output).expect("an object");
983 let file = object::File::parse(&bytes[..]).expect("a readable object");
984 let note = file.section_by_name(".note.gnu.property").expect("the note");
985 assert_eq!(note.align(), 8, "a note in a sixty four bit object is read a word at a time");
986 let want: Vec<u8> = [
987 4u32,
988 16,
989 5,
990 u32::from_le_bytes(*b"GNU\0"),
991 Property::X86_FEATURES,
992 4,
993 Property::IBT | Property::SHSTK,
994 0,
995 ]
996 .iter()
997 .flat_map(|word| word.to_le_bytes())
998 .collect();
999 assert_eq!(note.data().expect("the bytes"), &want[..]);
1000 }
1001
1002 #[test]
1008 fn a_file_built_to_have_nothing_checked_says_nothing() {
1009 let bytes = write(&calling("puts"), &Data::default(), &[], &target(), Output::default())
1010 .expect("an object");
1011 let file = object::File::parse(&bytes[..]).expect("a readable object");
1012 assert!(file.section_by_name(".note.gnu.property").is_none());
1013 }
1014
1015 #[test]
1024 fn an_unwind_record_names_the_function_it_is_about_and_not_the_first_one() {
1025 let mut text = calling("puts");
1026 text.funcs.push(extent("g".to_owned(), 16, 1, Binding::Global));
1027 text.bytes.resize(17, 0x90);
1028 text.unwind.bytes = vec![0; 64];
1031 for (at, name) in [(32usize, "f"), (48usize, "g")] {
1032 text.unwind.relocs.push(Reloc {
1033 at,
1034 symbol: name.to_owned(),
1035 kind: Reference::Address { bytes: 8 },
1036 addend: 0,
1037 });
1038 }
1039 let bytes =
1040 write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
1041 let file = object::File::parse(&bytes[..]).expect("a readable object");
1042 let mut found = points_at(&file);
1043 found.sort_unstable();
1044 assert_eq!(found, [(32, ".text".to_owned(), 0), (48, ".text".to_owned(), 16)]);
1045 }
1046
1047 fn points_at(file: &object::File<'_>) -> Vec<(u64, String, i64)> {
1050 let frames = file.section_by_name(".eh_frame").expect("the table");
1051 frames
1052 .relocations()
1053 .map(|(offset, reloc)| {
1054 let object::RelocationTarget::Symbol(index) = reloc.target() else {
1055 panic!("a record points at something that is not a symbol");
1056 };
1057 let symbol = file.symbol_by_index(index).expect("a symbol that is in the table");
1058 assert_eq!(symbol.kind(), SymbolKind::Section, "a record names a section");
1059 let section = symbol.section_index().expect("a section symbol is in one");
1060 let name = file.section_by_index(section).expect("a readable section");
1061 (offset, name.name().expect("a named section").to_owned(), reloc.addend())
1062 })
1063 .collect()
1064 }
1065
1066 #[test]
1079 fn a_record_reaches_its_function_through_the_section_it_is_in() {
1080 let mut text = two();
1081 text.unwind.bytes = vec![0; 64];
1082 for (at, name) in [(32usize, "f"), (48usize, "g")] {
1083 text.unwind.relocs.push(Reloc {
1084 at,
1085 symbol: name.to_owned(),
1086 kind: Reference::Data,
1087 addend: 0,
1088 });
1089 }
1090 let bytes =
1091 write(&text, &Data::default(), &[], &target(), Output::default()).expect("an object");
1092 let file = object::File::parse(&bytes[..]).expect("a readable object");
1093 let mut whole = points_at(&file);
1094 whole.sort_unstable();
1095 assert_eq!(whole, [(32, ".text".to_owned(), 0), (48, ".text".to_owned(), 16)]);
1096
1097 let sections =
1098 Output { sections: Sections { functions: true, data: false }, ..Output::default() };
1099 let bytes = write(&text, &Data::default(), &[], &target(), sections).expect("an object");
1100 let file = object::File::parse(&bytes[..]).expect("a readable object");
1101 let mut split = points_at(&file);
1102 split.sort_unstable();
1103 assert_eq!(split, [(32, ".text.f".to_owned(), 0), (48, ".text.g".to_owned(), 0)]);
1104 }
1105
1106 #[test]
1113 fn a_record_about_something_this_file_does_not_define_is_refused() {
1114 let mut text = calling("puts");
1115 text.unwind.bytes = vec![0; 64];
1116 text.unwind.relocs.push(Reloc {
1117 at: 32,
1118 symbol: "puts".to_owned(),
1119 kind: Reference::Data,
1120 addend: 0,
1121 });
1122 let why = write(&text, &Data::default(), &[], &target(), Output::default())
1123 .expect_err("a record about a name from somewhere else");
1124 assert!(why.to_string().contains("puts"), "{why}");
1125 }
1126
1127 fn lives_in<'a>(file: &'a object::File<'a>, name: &str) -> String {
1129 let symbol = file.symbols().find(|s| s.name() == Ok(name)).expect("the symbol");
1130 let index = symbol.section_index().expect("a section to be defined in");
1131 let section = file.section_by_index(index).expect("a readable section");
1132 section.name().expect("a named section").to_owned()
1133 }
1134
1135 fn two() -> Text {
1137 let mut text = calling("puts");
1138 text.bytes.resize(16, 0x90);
1141 text.bytes.extend_from_slice(&[0xe8, 0, 0, 0, 0, 0xc3]);
1142 text.funcs.push(extent("g".to_owned(), 16, 6, Binding::Global));
1143 text.relocs.push(Reloc {
1144 at: 17,
1145 symbol: "puts".to_owned(),
1146 kind: Reference::Call,
1147 addend: -4,
1148 });
1149 text
1150 }
1151
1152 #[test]
1159 fn every_function_gets_a_section_of_its_own_when_that_is_what_was_asked_for() {
1160 let sections =
1161 Output { sections: Sections { functions: true, data: false }, ..Output::default() };
1162 let bytes = write(&two(), &Data::default(), &[], &target(), sections).expect("an object");
1163 let file = object::File::parse(&bytes[..]).expect("a readable object");
1164 assert_eq!(lives_in(&file, "f"), ".text.f");
1165 assert_eq!(lives_in(&file, "g"), ".text.g");
1166 assert!(file.section_by_name(".text").expect("the empty one").size() == 0);
1167 for name in ["f", "g"] {
1170 let symbol = file.symbols().find(|s| s.name() == Ok(name)).expect("the function");
1171 assert_eq!(symbol.address(), 0, "{name}");
1172 assert_eq!(symbol.size(), 6, "{name}");
1173 }
1174 let section = file.section_by_name(".text.g").expect("the second function");
1175 assert_eq!(section.data().expect("the bytes"), &[0xe8, 0, 0, 0, 0, 0xc3]);
1176 assert_eq!(section.align(), u64::from(crate::FUNC_ALIGN));
1179 }
1180
1181 #[test]
1187 fn a_relocation_moves_with_the_function_whose_bytes_it_is_in() {
1188 let sections =
1189 Output { sections: Sections { functions: true, data: false }, ..Output::default() };
1190 let bytes = write(&two(), &Data::default(), &[], &target(), sections).expect("an object");
1191 let file = object::File::parse(&bytes[..]).expect("a readable object");
1192 for name in [".text.f", ".text.g"] {
1193 let section = file.section_by_name(name).expect("a function");
1194 let (offset, _) = section.relocations().next().expect("the call in it");
1195 assert_eq!(offset, 1, "{name}");
1198 assert_eq!(section.relocations().count(), 1, "{name}");
1199 }
1200 }
1201
1202 fn variable(name: &str, place: Place) -> Object {
1204 Object {
1205 name: name.to_owned(),
1206 bytes: if place == Place::Zero { Vec::new() } else { vec![1, 0, 0, 0] },
1207 size: 4,
1208 align: 4,
1209 place,
1210 binding: Binding::Global,
1211 visibility: Visibility::Default,
1212 relocs: Vec::new(),
1213 }
1214 }
1215
1216 fn holding(object: Object) -> Vec<u8> {
1218 let data = Data { objects: vec![object] };
1219 write(&Text::default(), &data, &[], &target(), Output::default()).expect("an object")
1220 }
1221
1222 #[test]
1223 fn what_a_variable_is_decides_which_section_it_goes_in() {
1224 for (place, wanted) in [
1225 (Place::Written, ".data"),
1226 (Place::ReadOnly, ".rodata"),
1227 (Place::RelocReadOnly { local: false }, ".data.rel.ro"),
1228 (Place::RelocReadOnly { local: true }, ".data.rel.ro.local"),
1229 (Place::Zero, ".bss"),
1230 (Place::Named(".init_array".to_owned()), ".init_array"),
1231 ] {
1232 let bytes = holding(variable("x", place.clone()));
1233 let file = object::File::parse(&bytes[..]).expect("a readable object");
1234 let section = file.section_by_name(wanted).unwrap_or_else(|| panic!("{place:?}"));
1235 assert_eq!(section.size(), 4, "{place:?}");
1236 let carried = section.data().expect("the bytes").len();
1239 assert_eq!(carried, if place == Place::Zero { 0 } else { 4 }, "{place:?}");
1240 }
1241 }
1242
1243 #[test]
1247 fn every_variable_gets_a_section_of_its_own_when_that_is_what_was_asked_for() {
1248 let sections =
1249 Output { sections: Sections { functions: false, data: true }, ..Output::default() };
1250 for (place, wanted) in [
1251 (Place::Written, ".data.x"),
1252 (Place::ReadOnly, ".rodata.x"),
1253 (Place::RelocReadOnly { local: false }, ".data.rel.ro.x"),
1254 (Place::RelocReadOnly { local: true }, ".data.rel.ro.local.x"),
1255 (Place::Zero, ".bss.x"),
1256 ] {
1257 let data = Data { objects: vec![variable("x", place.clone())] };
1258 let bytes = write(&Text::default(), &data, &[], &target(), sections).expect("object");
1259 let file = object::File::parse(&bytes[..]).expect("a readable object");
1260 assert_eq!(lives_in(&file, "x"), wanted, "{place:?}");
1261 let section = file.section_by_name(wanted).expect("the section it named");
1262 assert_eq!(section.size(), 4, "{place:?}");
1263 let carried = section.data().expect("the bytes").len();
1266 assert_eq!(carried, if place == Place::Zero { 0 } else { 4 }, "{place:?}");
1267 }
1268 }
1269
1270 #[test]
1274 fn a_variable_that_has_no_section_of_its_own_to_be_given_is_left_where_it_was() {
1275 let sections =
1276 Output { sections: Sections { functions: false, data: true }, ..Output::default() };
1277 let named = Place::Named(".init_array".to_owned());
1278 let objects = vec![variable("m", Place::Merged), variable("n", named)];
1279 let bytes =
1280 write(&Text::default(), &Data { objects }, &[], &target(), sections).expect("object");
1281 let file = object::File::parse(&bytes[..]).expect("a readable object");
1282 let m = file.symbols().find(|s| s.name() == Ok("m")).expect("the tentative one");
1283 assert!(m.is_common(), "still the linker's to merge and not in a section at all");
1284 assert_eq!(lives_in(&file, "n"), ".init_array");
1285 assert!(file.section_by_name(".init_array.n").is_none(), "the source already answered");
1286 }
1287
1288 #[test]
1292 fn a_relocation_in_an_image_moves_with_the_variable_whose_image_it_is_in() {
1293 let sections =
1294 Output { sections: Sections { functions: false, data: true }, ..Output::default() };
1295 let pointer = Object {
1296 bytes: vec![0; 8],
1297 size: 8,
1298 align: 8,
1299 relocs: vec![Reloc {
1300 at: 0,
1301 symbol: "y".to_owned(),
1302 kind: Reference::Address { bytes: 8 },
1303 addend: 0,
1304 }],
1305 ..variable("p", Place::Written)
1306 };
1307 let objects = vec![variable("first", Place::Written), pointer];
1308 let bytes =
1309 write(&Text::default(), &Data { objects }, &[], &target(), sections).expect("object");
1310 let file = object::File::parse(&bytes[..]).expect("a readable object");
1311 let section = file.section_by_name(".data.p").expect("the pointer's own section");
1312 let (offset, reloc) = section.relocations().next().expect("one relocation");
1313 assert_eq!(offset, 0);
1316 assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: elf::R_X86_64_64 });
1317 }
1318
1319 #[test]
1327 fn every_variable_that_wants_the_local_relocated_section_shares_one() {
1328 let place = Place::RelocReadOnly { local: true };
1329 let data =
1330 Data { objects: vec![variable("first", place.clone()), variable("second", place)] };
1331 let bytes =
1332 write(&Text::default(), &data, &[], &target(), Output::default()).expect("an object");
1333 let file = object::File::parse(&bytes[..]).expect("a readable object");
1334 let named = file.sections().filter(|s| s.name() == Ok(".data.rel.ro.local")).count();
1335 assert_eq!(named, 1, "one section holding both, not one each");
1336 }
1337
1338 #[test]
1339 fn a_variable_is_a_symbol_that_says_where_it_is_and_how_long_it_is() {
1340 let mut data = Data { objects: vec![variable("first", Place::Written)] };
1341 data.objects.push(Object { align: 16, ..variable("second", Place::Written) });
1342 let bytes =
1343 write(&Text::default(), &data, &[], &target(), Output::default()).expect("an object");
1344 let file = object::File::parse(&bytes[..]).expect("a readable object");
1345 let second = file.symbols().find(|s| s.name() == Ok("second")).expect("the second one");
1346 assert_eq!(second.kind(), SymbolKind::Data);
1347 assert_eq!(second.size(), 4);
1348 assert_eq!(second.address(), 16);
1352 }
1353
1354 #[test]
1355 fn the_linkage_a_variable_had_is_the_binding_the_symbol_gets() {
1356 for (binding, global, weak) in [
1357 (Binding::Global, true, false),
1358 (Binding::Local, false, false),
1359 (Binding::Weak, true, true),
1360 ] {
1361 let bytes = holding(Object { binding, ..variable("x", Place::Written) });
1362 let file = object::File::parse(&bytes[..]).expect("a readable object");
1363 let x = file.symbols().find(|s| s.name() == Ok("x")).expect("the variable");
1364 assert_eq!(x.is_global(), global, "{binding:?}");
1365 assert_eq!(x.is_weak(), weak, "{binding:?}");
1366 }
1367 }
1368
1369 #[test]
1370 fn a_tentative_definition_asks_the_linker_for_space_rather_than_naming_any() {
1371 let bytes = holding(Object { align: 8, ..variable("x", Place::Merged) });
1372 let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
1373 let x = file.symbols().find(|s| s.name() == Ok("x")).expect("the variable");
1374 assert!(x.is_common(), "the linker merges every definition of this name into one");
1375 assert_eq!(x.size(), 4);
1376 assert_eq!(x.address(), 0);
1380 assert_eq!(x.elf_symbol().st_value(Endianness::Little), 8);
1381 }
1382
1383 #[test]
1384 fn an_address_in_an_image_is_the_address_and_not_a_distance_to_it() {
1385 let object = Object {
1386 bytes: vec![0; 8],
1387 size: 8,
1388 align: 8,
1389 relocs: vec![Reloc {
1390 at: 0,
1391 symbol: "y".to_owned(),
1392 kind: Reference::Address { bytes: 8 },
1393 addend: 16,
1394 }],
1395 ..variable("p", Place::Written)
1396 };
1397 let bytes = holding(object);
1398 let file = object::File::parse(&bytes[..]).expect("a readable object");
1399 let section = file.section_by_name(".data").expect("a data section");
1400 let (offset, reloc) = section.relocations().next().expect("one relocation");
1401 assert_eq!(offset, 0);
1402 assert_eq!(reloc.addend(), 16);
1403 assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: elf::R_X86_64_64 });
1404 let y = file.symbols().find(|s| s.name() == Ok("y")).expect("what it points at");
1405 assert!(y.is_undefined(), "nothing here defines it and the linker is being asked for it");
1406 }
1407
1408 #[test]
1410 fn a_relocation_counts_from_the_start_of_the_section_and_not_of_the_image_it_is_in() {
1411 let mut data = Data { objects: vec![variable("first", Place::Written)] };
1412 data.objects.push(Object {
1413 bytes: vec![0; 16],
1414 size: 16,
1415 align: 8,
1416 relocs: vec![Reloc {
1417 at: 8,
1418 symbol: "y".to_owned(),
1419 kind: Reference::Address { bytes: 8 },
1420 addend: 0,
1421 }],
1422 ..variable("second", Place::Written)
1423 });
1424 let bytes =
1425 write(&Text::default(), &data, &[], &target(), Output::default()).expect("an object");
1426 let file = object::File::parse(&bytes[..]).expect("a readable object");
1427 let section = file.section_by_name(".data").expect("a data section");
1428 let (offset, _) = section.relocations().next().expect("one relocation");
1429 assert_eq!(offset, 16);
1432 }
1433
1434 #[test]
1435 fn a_second_name_is_a_second_symbol_at_the_first_one_s_address_and_no_second_image() {
1436 let data = Data {
1437 objects: vec![Object { binding: Binding::Local, ..variable("a", Place::Written) }],
1438 };
1439 let aliases = [Alias {
1440 name: "b".to_owned(),
1441 target: "a".to_owned(),
1442 binding: Binding::Global,
1443 visibility: Visibility::Default,
1444 }];
1445 let bytes = write(&Text::default(), &data, &aliases, &target(), Output::default())
1446 .expect("an object");
1447 let file = object::File::parse(&bytes[..]).expect("a readable object");
1448 let a = file.symbols().find(|s| s.name() == Ok("a")).expect("the variable");
1449 let b = file.symbols().find(|s| s.name() == Ok("b")).expect("the second name");
1450 assert_eq!(b.address(), a.address(), "the same place");
1451 assert_eq!(b.size(), a.size());
1452 assert_eq!(b.section_index(), a.section_index());
1453 assert!(a.is_local(), "the target was written `static`");
1456 assert!(b.is_global(), "and the name given to it was not");
1457 assert_eq!(file.section_by_name(".data").expect("a data section").size(), 4);
1459 }
1460
1461 #[test]
1462 fn a_function_can_be_given_a_second_name_the_same_way_a_variable_can() {
1463 let text = calling("puts");
1464 let aliases = [Alias {
1465 name: "g".to_owned(),
1466 target: "f".to_owned(),
1467 binding: Binding::Weak,
1468 visibility: Visibility::Default,
1469 }];
1470 let bytes = write(&text, &Data::default(), &aliases, &target(), Output::default())
1471 .expect("an object");
1472 let file = object::File::parse(&bytes[..]).expect("a readable object");
1473 let f = file.symbols().find(|s| s.name() == Ok("f")).expect("the function");
1474 let g = file.symbols().find(|s| s.name() == Ok("g")).expect("the second name");
1475 assert_eq!(g.address(), f.address());
1476 assert_eq!(g.size(), f.size());
1477 assert_eq!(g.kind(), f.kind(), "a second name for a function is a function");
1478 assert!(g.is_weak(), "so that a program may define the name itself instead");
1479 }
1480
1481 #[test]
1484 fn a_second_name_for_something_this_file_does_not_define_is_refused() {
1485 let aliases = [Alias {
1486 name: "b".to_owned(),
1487 target: "a".to_owned(),
1488 binding: Binding::Global,
1489 visibility: Visibility::Default,
1490 }];
1491 let error =
1492 write(&Text::default(), &Data::default(), &aliases, &target(), Output::default())
1493 .expect_err("nothing to point at");
1494 assert!(matches!(error, Error::Refused { .. }), "{error:?}");
1495 }
1496
1497 #[test]
1498 fn a_platform_this_does_not_write_is_said_so_rather_than_written_as_elf() {
1499 let text = calling("puts");
1500 for triple in [
1501 Triple::new(Arch::Aarch64, Os::Linux, Env::Gnu),
1502 Triple::new(Arch::X86_64, Os::Darwin, Env::Gnu),
1503 ] {
1504 let error =
1505 write(&text, &Data::default(), &[], &TargetInfo::new(triple), Output::default())
1506 .expect_err("no writer");
1507 assert!(matches!(error, Error::Format { .. }), "{error:?}");
1508 }
1509 }
1510
1511 #[test]
1517 fn the_names_a_linker_can_find_are_the_names_the_list_gives() {
1518 let mut text = calling("puts");
1519 text.funcs.push(extent("hidden".to_owned(), 16, 1, Binding::Local));
1520 text.funcs.push(extent("shared".to_owned(), 32, 1, Binding::Weak));
1521 text.bytes.resize(33, 0x90);
1522 let data = Data {
1523 objects: vec![variable("seen", Place::Written), {
1524 let mut quiet = variable("quiet", Place::Zero);
1525 quiet.binding = Binding::Local;
1526 quiet
1527 }],
1528 };
1529 let aliases = [Alias {
1530 name: "second".to_owned(),
1531 target: "f".to_owned(),
1532 binding: Binding::Global,
1533 visibility: Visibility::Default,
1534 }];
1535
1536 let names = defines(&text, &data, &aliases, &target()).expect("a list");
1537 assert_eq!(names, ["f", "shared", "seen", "second"]);
1538
1539 let bytes = write(&text, &data, &aliases, &target(), Output::default()).expect("an object");
1540 let file = object::File::parse(&bytes[..]).expect("a readable object");
1541 let found: Vec<String> = file
1542 .symbols()
1543 .filter(|symbol| symbol.is_global() && symbol.is_definition())
1544 .map(|symbol| symbol.name().unwrap_or_default().to_owned())
1545 .collect();
1546 let mut sorted = names.clone();
1547 sorted.sort();
1548 let mut theirs = found;
1549 theirs.sort();
1550 assert_eq!(sorted, theirs, "the list and the file have to say the same thing");
1551 }
1552
1553 #[test]
1557 fn a_platform_this_does_not_write_has_no_list_of_names_either() {
1558 let text = calling("puts");
1559 for triple in [
1560 Triple::new(Arch::Aarch64, Os::Linux, Env::Gnu),
1561 Triple::new(Arch::X86_64, Os::Darwin, Env::Gnu),
1562 ] {
1563 let error = defines(&text, &Data::default(), &[], &TargetInfo::new(triple))
1564 .expect_err("no writer");
1565 assert!(matches!(error, Error::Format { .. }), "{error:?}");
1566 }
1567 }
1568}