1use object::write::{Object as Writer, Relocation, Symbol, SymbolSection};
31use object::{Architecture, Endianness, RelocationFlags, SectionKind, SymbolFlags, elf};
32use rucc_target::TargetInfo;
33use rucc_target::aarch64::Fixup;
34use rucc_tuple::Arch;
35
36use crate::file::{Error, Flavour};
37use crate::section::{Array, Binding, Info, Reloc, Visibility};
38
39#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct Part {
42 pub name: String,
44 pub bytes: Vec<u8>,
46 pub size: u64,
49 pub align: u64,
51 pub shape: Shape,
53 pub relocs: Vec<Reloc>,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
65pub struct Shape {
66 pub alloc: bool,
69 pub write: bool,
71 pub exec: bool,
73 pub thread: bool,
75 pub bits: bool,
77 pub array: Option<Array>,
79 pub merge: u64,
83 pub strings: bool,
86}
87
88impl Shape {
89 #[must_use]
96 pub fn of(name: &str) -> Shape {
97 let base = Shape { alloc: true, bits: true, ..Shape::default() };
98 let head = name.split_once('.').map_or(name, |(_, rest)| rest);
99 let head = head.split_once('.').map_or(head, |(first, _)| first);
100 match head {
101 "text" | "init" | "fini" => Shape { exec: true, ..base },
102 "rodata" | "eh_frame_hdr" => base,
103 "bss" => Shape { write: true, bits: false, ..base },
104 "tbss" => Shape { write: true, thread: true, bits: false, ..base },
105 "tdata" => Shape { write: true, thread: true, ..base },
106 _ if Array::of(name).is_some() => Shape { write: true, array: Array::of(name), ..base },
110 "debug_info" | "debug_abbrev" | "debug_line" | "debug_str" | "comment" => {
113 Shape { alloc: false, bits: true, ..Shape::default() }
114 }
115 _ => Shape { write: true, ..base },
116 }
117 }
118
119 #[must_use]
127 pub fn implied(name: &str) -> Shape {
128 let base = Shape { alloc: true, ..Shape::default() };
129 let head = name.split_once('.').map_or(name, |(_, rest)| rest);
130 let head = head.split_once('.').map_or(head, |(first, _)| first);
131 match head {
132 "text" => Shape { exec: true, ..base },
133 "rodata" => base,
134 "data" | "bss" => Shape { write: true, ..base },
135 "tdata" | "tbss" => Shape { write: true, thread: true, ..base },
136 _ if Array::of(name).is_some() => Shape { write: true, ..base },
137 _ => Shape::default(),
138 }
139 }
140
141 pub(crate) fn sh_flags(self) -> elf::SectionFlags {
148 let mut flags = 0;
149 if self.alloc {
150 flags |= elf::SHF_ALLOC.0;
151 }
152 if self.write {
153 flags |= elf::SHF_WRITE.0;
154 }
155 if self.exec {
156 flags |= elf::SHF_EXECINSTR.0;
157 }
158 if self.thread {
159 flags |= elf::SHF_TLS.0;
160 }
161 if self.merge != 0 {
162 flags |= elf::SHF_MERGE.0;
163 if self.strings {
164 flags |= elf::SHF_STRINGS.0;
165 }
166 }
167 elf::SectionFlags(flags)
168 }
169
170 pub(crate) fn sh_type(self) -> elf::SectionType {
172 match self.array {
173 _ if !self.bits => elf::SHT_NOBITS,
174 Some(Array::Init) => elf::SHT_INIT_ARRAY,
175 Some(Array::Fini) => elf::SHT_FINI_ARRAY,
176 Some(Array::Preinit) => elf::SHT_PREINIT_ARRAY,
177 None => elf::SHT_PROGBITS,
178 }
179 }
180
181 pub(crate) const fn kind(self) -> SectionKind {
187 match self {
188 Shape { bits: false, thread: true, .. } => SectionKind::UninitializedTls,
189 Shape { bits: false, .. } => SectionKind::UninitializedData,
190 Shape { thread: true, .. } => SectionKind::Tls,
191 Shape { exec: true, .. } => SectionKind::Text,
192 Shape { alloc: false, .. } => SectionKind::Other,
193 Shape { write: false, .. } => SectionKind::ReadOnlyData,
194 Shape { .. } => SectionKind::Data,
195 }
196 }
197}
198
199#[derive(Debug, Clone, PartialEq, Eq)]
201pub struct Name {
202 pub name: String,
204 pub at: Held,
206 pub size: u64,
208 pub sort: Sort,
210 pub binding: Binding,
212 pub visibility: Visibility,
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
218pub enum Held {
219 In {
221 part: usize,
223 offset: u64,
225 },
226 Absolute(u64),
229 Common {
232 size: u64,
234 align: u64,
237 },
238 Undefined,
240}
241
242#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
244pub enum Sort {
245 Func,
247 Object,
249 Thread,
251 File,
257 #[default]
260 Untyped,
261}
262
263#[derive(Debug, Clone, Default, PartialEq, Eq)]
265pub struct Assembled {
266 pub parts: Vec<Part>,
268 pub names: Vec<Name>,
270}
271
272pub fn assembled(input: &Assembled, target: &TargetInfo) -> Result<Vec<u8>, Error> {
293 assembled_described(input, target, &Info::default())
294}
295
296pub fn assembled_described(
309 input: &Assembled,
310 target: &TargetInfo,
311 info: &Info,
312) -> Result<Vec<u8>, Error> {
313 let (flavour, machine) = match (Flavour::of(target), target.tuple.arch()) {
316 (Some(flavour), Arch::X86_64) => (flavour, Architecture::X86_64),
317 (Some(Flavour::Elf), Arch::Aarch64) => (Flavour::Elf, Architecture::Aarch64),
318 _ => return Err(Error::Format { triple: target.tuple.to_string() }),
319 };
320 let flags_of = |kind, after| match machine {
321 Architecture::Aarch64 => {
322 crate::elf::r_type_aarch64(kind).map(|r_type| RelocationFlags::Elf { r_type })
323 }
324 _ => flavour.reloc(kind, after),
325 };
326 let mut obj = Writer::new(flavour.binary(), machine, Endianness::Little);
327
328 let mut made = Vec::with_capacity(input.parts.len());
331 for part in &input.parts {
332 let id = obj.add_section(Vec::new(), part.name.clone().into_bytes(), part.shape.kind());
333 if let Some(flags) = flavour.stated(part.shape) {
338 obj.section_mut(id).flags = flags;
339 }
340 let align = part.align.max(1);
341 if part.shape.bits {
342 obj.append_section_data(id, &part.bytes, align);
343 } else {
344 obj.append_section_bss(id, part.size, align);
345 }
346 made.push(id);
347 }
348
349 let defined: std::collections::HashMap<&str, &Name> =
352 input.names.iter().map(|name| (name.name.as_str(), name)).collect();
353 let onto = |reloc: &Reloc| moved(flavour, input, &defined, reloc);
354 let wanted: std::collections::HashSet<&str> = input
355 .parts
356 .iter()
357 .flat_map(|part| &part.relocs)
358 .filter(|reloc| onto(reloc).is_none())
359 .map(|reloc| reloc.symbol.as_str())
360 .collect();
361
362 let mut symbols = std::collections::BTreeMap::new();
365 for name in &input.names {
366 if flavour == Flavour::Elf && unseen(name) && !wanted.contains(name.name.as_str()) {
367 continue;
368 }
369 let (section, value, size) = match name.at {
370 Held::In { part, offset } => {
371 let Some(id) = made.get(part) else {
372 let why = format!(
373 "'{}' is in section {part} and there is no such section",
374 name.name
375 );
376 return Err(Error::Refused { why });
377 };
378 (SymbolSection::Section(*id), offset, name.size)
379 }
380 Held::Absolute(value) => (SymbolSection::Absolute, value, name.size),
381 Held::Common { size, align } => (SymbolSection::Common, align, size),
384 Held::Undefined => (SymbolSection::Undefined, 0, 0),
385 };
386 let id = obj.add_symbol(Symbol {
387 name: name.name.clone().into_bytes(),
388 value,
389 size,
390 kind: flavour.sort(name.sort, name.binding),
391 scope: crate::file::scope_of(name.binding),
392 weak: name.binding == Binding::Weak,
393 section,
394 flags: SymbolFlags::None,
395 });
396 flavour.see(&mut obj, id, name.binding, name.visibility);
397 if matches!(name.at, Held::Common { .. }) {
403 if let SymbolFlags::Elf { st_info, .. } = obj.symbol_flags_mut(id) {
404 *st_info = elf::STB_GLOBAL | elf::STT_OBJECT;
405 }
406 }
407 symbols.insert(name.name.clone(), id);
408 }
409
410 for (part, id) in input.parts.iter().zip(&made) {
411 for reloc in &part.relocs {
412 let (symbol, addend) = match onto(reloc) {
413 Some((part, offset)) => {
414 (obj.section_symbol(made[part]), reloc.addend + offset as i64)
415 }
416 None => {
417 let Some(&symbol) = symbols.get(&reloc.symbol) else {
418 let why = format!(
419 "'{}' is named by a relocation and by nothing else",
420 reloc.symbol
421 );
422 return Err(Error::Refused { why });
423 };
424 (symbol, reloc.addend)
425 }
426 };
427 let flags = flags_of(reloc.kind, reloc.after).ok_or_else(|| Error::Refused {
428 why: format!("no relocation is {:?}", reloc.kind),
429 })?;
430 obj.add_relocation(*id, Relocation { offset: reloc.at as u64, symbol, addend, flags })
431 .map_err(|why| Error::Refused { why: why.to_string() })?;
432 }
433 }
434
435 let mut named = std::collections::HashMap::new();
438 for chunk in &info.chunks {
439 let id = obj.add_section(Vec::new(), chunk.name.clone().into_bytes(), SectionKind::Debug);
440 obj.append_section_data(id, &chunk.bytes, 1);
441 named.insert(chunk.name.as_str(), id);
442 }
443 for chunk in &info.chunks {
444 let section = named[chunk.name.as_str()];
445 for reloc in &chunk.relocs {
446 let (symbol, addend) = match named.get(reloc.symbol.as_str()) {
447 Some(&id) => (obj.section_symbol(id), reloc.addend),
448 None => match defined.get(reloc.symbol.as_str()).map(|name| name.at) {
449 Some(Held::In { part, offset }) => {
450 (obj.section_symbol(made[part]), reloc.addend + offset as i64)
451 }
452 _ => match symbols.get(&reloc.symbol) {
453 Some(&symbol) => (symbol, reloc.addend),
454 None => {
455 let why = format!(
456 "'{}' is named by the debug information and is not defined here",
457 reloc.symbol
458 );
459 return Err(Error::Refused { why });
460 }
461 },
462 },
463 };
464 let flags = flags_of(reloc.kind, reloc.after).ok_or_else(|| Error::Refused {
465 why: format!("no relocation is {:?}", reloc.kind),
466 })?;
467 let record = Relocation { offset: reloc.at as u64, symbol, addend, flags };
468 obj.add_relocation(section, record)
469 .map_err(|why| Error::Refused { why: why.to_string() })?;
470 }
471 }
472
473 if !input.parts.iter().any(|part| part.name == ".note.GNU-stack") {
478 flavour.marker(&mut obj);
479 }
480
481 let mut bytes = obj.write().map_err(|why| Error::Refused { why: why.to_string() })?;
482 if flavour == Flavour::Elf {
483 for part in input.parts.iter().filter(|part| part.shape.merge != 0) {
484 entry_size(&mut bytes, &part.name, part.shape.merge);
485 }
486 }
487 Ok(bytes)
488}
489
490fn entry_size(bytes: &mut [u8], name: &str, size: u64) {
496 let word = |bytes: &[u8], at: usize, width: usize| {
497 bytes[at..at + width].iter().rev().fold(0u64, |sum, &byte| sum << 8 | u64::from(byte))
498 };
499 let table = word(bytes, 0x28, 8) as usize;
500 let each = word(bytes, 0x3a, 2) as usize;
501 let count = word(bytes, 0x3c, 2) as usize;
502 let names = table + each * word(bytes, 0x3e, 2) as usize;
503 let names = word(bytes, names + 0x18, 8) as usize;
504 for header in (0..count).map(|nth| table + nth * each) {
505 let at = names + word(bytes, header, 4) as usize;
506 if bytes[at..].starts_with(name.as_bytes()) && bytes.get(at + name.len()) == Some(&0) {
507 bytes[header + 0x38..header + 0x40].copy_from_slice(&size.to_le_bytes());
508 }
509 }
510}
511
512fn moved(
523 flavour: Flavour,
524 input: &Assembled,
525 defined: &std::collections::HashMap<&str, &Name>,
526 reloc: &Reloc,
527) -> Option<(usize, u64)> {
528 use crate::section::Reference;
529 let name = defined.get(reloc.symbol.as_str())?;
530 let Held::In { part, offset } = name.at else { return None };
531 if flavour != Flavour::Elf || name.binding != Binding::Local {
532 return None;
533 }
534 let near = matches!(reloc.kind, Reference::Data | Reference::Away);
535 let fixed = match reloc.kind {
536 Reference::Call
537 | Reference::Got
538 | Reference::GotBare
539 | Reference::GotKept
540 | Reference::Thread => false,
541 Reference::Field(
544 Fixup::Call26
545 | Fixup::Jump26
546 | Fixup::GotPage21
547 | Fixup::GotLo12
548 | Fixup::GotTprelPage21
549 | Fixup::GotTprelLo12Nc
550 | Fixup::TprelHi12
551 | Fixup::TprelLo12Nc,
552 ) => false,
553 _ if input.parts.get(part)?.shape.merge != 0 => !near && reloc.addend == 0,
554 _ => true,
555 };
556 fixed.then_some((part, offset))
557}
558
559fn unseen(name: &Name) -> bool {
563 name.binding == Binding::Local
564 && (name.name.starts_with(".L")
565 || name.name.starts_with("..")
566 || name.name.contains('\u{1}'))
567}
568
569#[must_use]
575pub fn assembled_defines(input: &Assembled) -> Vec<String> {
576 input
577 .names
578 .iter()
579 .filter(|name| name.binding != Binding::Local && name.at != Held::Undefined)
580 .map(|name| name.name.clone())
581 .collect()
582}
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587
588 use object::read::elf::{FileHeader as _, Sym as _};
589 use object::read::{Object as _, ObjectSection as _, ObjectSymbol as _};
590 use object::{RelocationFlags, SectionFlags};
591 use rucc_target::{Arch as TargetArch, Env, Os, Triple};
592
593 use crate::section::Reference;
594
595 fn target() -> TargetInfo {
597 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Linux, Env::Gnu))
598 }
599
600 fn windows() -> TargetInfo {
602 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Windows, Env::Gnu))
603 }
604
605 fn part(name: &str, bytes: Vec<u8>) -> Part {
607 Part {
608 name: name.to_owned(),
609 size: bytes.len() as u64,
610 bytes,
611 align: 1,
612 shape: Shape::of(name),
613 relocs: Vec::new(),
614 }
615 }
616
617 fn at(name: &str, offset: u64, sort: Sort, binding: Binding) -> Name {
619 Name {
620 name: name.to_owned(),
621 at: Held::In { part: 0, offset },
622 size: 0,
623 sort,
624 binding,
625 visibility: Visibility::Default,
626 }
627 }
628
629 fn raw(bytes: &[u8], want: &str) -> (u8, u64) {
637 let header = elf::FileHeader64::<Endianness>::parse(bytes).expect("a header");
638 let endian = header.endian().expect("an endianness");
639 let table = header.sections(endian, bytes).expect("the sections");
640 let symbols = table.symbols(endian, bytes, elf::SHT_SYMTAB).expect("a symbol table");
641 for symbol in symbols.iter() {
642 if symbols.symbol_name(endian, symbol).expect("a name") == want.as_bytes() {
643 return (symbol.st_info().0, symbol.st_value(endian));
644 }
645 }
646 panic!("there is no symbol called '{want}'");
647 }
648
649 fn st_info(bytes: &[u8], want: &str) -> u8 {
651 raw(bytes, want).0
652 }
653
654 #[test]
655 fn a_section_carries_the_flags_the_source_said_and_not_the_ones_its_name_suggests() {
656 let mut odd = part(".init.text", vec![0x90]);
660 odd.shape = Shape { alloc: true, exec: true, bits: true, ..Shape::default() };
661 let input = Assembled { parts: vec![odd], names: Vec::new() };
662 let bytes = assembled(&input, &target()).expect("an object");
663 let file = object::File::parse(&bytes[..]).expect("a readable object");
664 let section = file.section_by_name(".init.text").expect("the section");
665 assert_eq!(section.data().expect("the bytes"), &[0x90]);
666 let SectionFlags::Elf { sh_flags, sh_type } = section.flags() else {
667 panic!("this is an ELF file");
668 };
669 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_EXECINSTR.0);
670 assert_eq!(sh_flags.0 & elf::SHF_WRITE.0, 0, "nothing said it was writable");
671 assert_eq!(sh_type, elf::SHT_PROGBITS);
672 }
673
674 #[test]
675 fn a_section_that_holds_no_bytes_still_says_how_long_it_is() {
676 let mut room = part(".bss", Vec::new());
679 room.size = 4096;
680 room.align = 16;
681 let input = Assembled { parts: vec![room], names: Vec::new() };
682 let bytes = assembled(&input, &target()).expect("an object");
683 assert!(bytes.len() < 4096, "the empty space was written out: {} bytes", bytes.len());
684 let file = object::File::parse(&bytes[..]).expect("a readable object");
685 let section = file.section_by_name(".bss").expect("the section");
686 assert_eq!(section.size(), 4096);
687 assert_eq!(section.align(), 16);
688 let SectionFlags::Elf { sh_type, .. } = section.flags() else { panic!("an ELF file") };
689 assert_eq!(sh_type, elf::SHT_NOBITS);
690 }
691
692 #[test]
693 fn a_label_nobody_stated_a_type_for_is_a_symbol_with_no_type() {
694 let input = Assembled {
698 parts: vec![part(".text", vec![0; 8])],
699 names: vec![at("plain", 4, Sort::Untyped, Binding::Global)],
700 };
701 let bytes = assembled(&input, &target()).expect("an object");
702 let file = object::File::parse(&bytes[..]).expect("a readable object");
703 let plain = file.symbols().find(|s| s.name() == Ok("plain")).expect("the label");
704 assert_eq!(plain.address(), 4);
705 assert_eq!(st_info(&bytes, "plain") & 0xf, elf::STT_NOTYPE.0);
706 }
707
708 #[test]
709 fn what_type_said_is_what_the_symbol_gets() {
710 let input = Assembled {
711 parts: vec![part(".text", vec![0; 8])],
712 names: vec![
713 at("run", 0, Sort::Func, Binding::Global),
714 at("held", 4, Sort::Object, Binding::Local),
715 ],
716 };
717 let bytes = assembled(&input, &target()).expect("an object");
718 assert_eq!(st_info(&bytes, "run") & 0xf, elf::STT_FUNC.0);
719 assert_eq!(st_info(&bytes, "held") & 0xf, elf::STT_OBJECT.0);
720 assert_eq!(st_info(&bytes, "run") >> 4, elf::STB_GLOBAL.0);
721 assert_eq!(st_info(&bytes, "held") >> 4, elf::STB_LOCAL.0);
722 }
723
724 #[test]
725 fn a_common_symbol_is_written_the_way_gas_writes_one() {
726 let input = Assembled {
731 parts: Vec::new(),
732 names: vec![Name {
733 name: "shared".to_owned(),
734 at: Held::Common { size: 8, align: 8 },
735 size: 0,
736 sort: Sort::Object,
737 binding: Binding::Global,
738 visibility: Visibility::Default,
739 }],
740 };
741 let bytes = assembled(&input, &target()).expect("an object");
742 assert_eq!(st_info(&bytes, "shared"), elf::STB_GLOBAL.0 << 4 | elf::STT_OBJECT.0);
743 let file = object::File::parse(&bytes[..]).expect("a readable object");
744 let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the symbol");
745 assert!(shared.is_common(), "the linker has to be asked for the space");
746 assert_eq!(shared.size(), 8);
747 assert_eq!(raw(&bytes, "shared").1, 8, "the boundary it has to start on");
749 }
750
751 #[test]
752 fn a_set_is_a_number_rather_than_a_place() {
753 let input = Assembled {
754 parts: vec![part(".text", vec![0; 8])],
755 names: vec![Name {
756 name: "size_of_it".to_owned(),
757 at: Held::Absolute(25),
758 size: 0,
759 sort: Sort::Untyped,
760 binding: Binding::Global,
761 visibility: Visibility::Default,
762 }],
763 };
764 let bytes = assembled(&input, &target()).expect("an object");
765 let file = object::File::parse(&bytes[..]).expect("a readable object");
766 let sym = file.symbols().find(|s| s.name() == Ok("size_of_it")).expect("the symbol");
767 assert_eq!(sym.address(), 25);
768 assert_eq!(sym.section(), object::SymbolSection::Absolute, "it is not in any section");
769 }
770
771 #[test]
772 fn a_relocation_names_a_symbol_and_lands_where_the_bytes_are() {
773 let mut data = part(".data", vec![0; 8]);
774 data.relocs.push(Reloc {
775 at: 0,
776 symbol: "message".to_owned(),
777 kind: Reference::Address { bytes: 8 },
778 addend: 0,
779 after: 0,
780 });
781 let input = Assembled {
782 parts: vec![data],
783 names: vec![Name {
784 name: "message".to_owned(),
785 at: Held::Undefined,
786 size: 0,
787 sort: Sort::Untyped,
788 binding: Binding::Global,
789 visibility: Visibility::Default,
790 }],
791 };
792 let bytes = assembled(&input, &target()).expect("an object");
793 let file = object::File::parse(&bytes[..]).expect("a readable object");
794 let section = file.section_by_name(".data").expect("the section");
795 let (at, reloc) = section.relocations().next().expect("one relocation");
796 assert_eq!(at, 0);
797 assert_eq!(reloc.addend(), 0);
798 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("an ELF file") };
799 assert_eq!(r_type, elf::R_X86_64_64);
800 }
801
802 #[test]
803 fn a_place_only_this_file_sees_is_reached_through_its_section_as_gas_does() {
804 let mut text = part(".text", vec![0; 32]);
808 for (at, symbol, kind) in [
809 (0, ".L3", Reference::Data),
810 (4, "helper", Reference::Data),
811 (8, "helper", Reference::Call),
812 (12, "shared", Reference::Data),
813 ] {
814 let symbol = symbol.to_owned();
815 text.relocs.push(Reloc { at, symbol, kind, addend: -4, after: 0 });
816 }
817 let input = Assembled {
818 parts: vec![text],
819 names: vec![
820 at(".L3", 20, Sort::Untyped, Binding::Local),
821 at("helper", 24, Sort::Func, Binding::Local),
822 at("shared", 28, Sort::Func, Binding::Global),
823 ],
824 };
825 let bytes = assembled(&input, &target()).expect("an object");
826 let file = object::File::parse(&bytes[..]).expect("a readable object");
827 let names: Vec<_> = file.symbols().filter_map(|sym| sym.name().ok()).collect();
828 assert!(!names.contains(&".L3") && names.contains(&"helper"), "{names:?}");
829 let section = file.section_by_name(".text").expect("the section");
830 let reached: Vec<_> = section
831 .relocations()
832 .map(|(at, reloc)| {
833 let object::RelocationTarget::Symbol(index) = reloc.target() else {
834 panic!("a symbol")
835 };
836 let symbol = file.symbol_by_index(index).expect("the symbol");
837 let name = if symbol.kind() == object::SymbolKind::Section {
838 ".text"
839 } else {
840 symbol.name().expect("a name")
841 };
842 (at, name, reloc.addend())
843 })
844 .collect();
845 assert_eq!(
846 reached,
847 [(0, ".text", 16), (4, ".text", 20), (8, "helper", -4), (12, "shared", -4)]
848 );
849 }
850
851 #[test]
852 fn a_section_of_constants_may_be_merged_and_a_distance_into_it_keeps_its_name() {
853 let mut text = part(".text", vec![0; 8]);
854 text.relocs.push(Reloc {
855 at: 0,
856 symbol: ".LC0".to_owned(),
857 kind: Reference::Data,
858 addend: -4,
859 after: 0,
860 });
861 let strings = Part {
862 shape: Shape { merge: 1, strings: true, ..Shape::of(".rodata") },
863 ..part(".rodata.str1.1", b"hi\0".to_vec())
864 };
865 let mut name = at(".LC0", 0, Sort::Untyped, Binding::Local);
866 name.at = Held::In { part: 1, offset: 0 };
867 let input = Assembled { parts: vec![text, strings], names: vec![name] };
868 let bytes = assembled(&input, &target()).expect("an object");
869 let file = object::File::parse(&bytes[..]).expect("a readable object");
870 let section = file.section_by_name(".rodata.str1.1").expect("the section");
871 let SectionFlags::Elf { sh_flags, .. } = section.flags() else { panic!("an ELF file") };
872 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_MERGE.0 | elf::SHF_STRINGS.0);
873 let header = elf::FileHeader64::<Endianness>::parse(&bytes[..]).expect("a header");
874 let endian = header.endian().expect("an endianness");
875 let table = header.sections(endian, &bytes[..]).expect("the sections");
876 let (_, found) = table.section_by_name(endian, b".rodata.str1.1").expect("the section");
877 assert_eq!(found.sh_entsize.get(endian), 1);
878 let text = file.section_by_name(".text").expect("the section");
879 let (_, reloc) = text.relocations().next().expect("one relocation");
880 let object::RelocationTarget::Symbol(index) = reloc.target() else { panic!("a symbol") };
881 assert_eq!(file.symbol_by_index(index).and_then(|sym| sym.name()), Ok(".LC0"));
882 }
883
884 #[test]
885 fn a_relocation_against_a_name_the_file_never_mentions_is_refused() {
886 let mut data = part(".data", vec![0; 8]);
890 data.relocs.push(Reloc {
891 at: 0,
892 symbol: "nowhere".to_owned(),
893 kind: Reference::Address { bytes: 8 },
894 addend: 0,
895 after: 0,
896 });
897 let input = Assembled { parts: vec![data], names: Vec::new() };
898 let why = assembled(&input, &target()).expect_err("this cannot be written");
899 assert!(format!("{why}").contains("nowhere"), "{why}");
900 }
901
902 #[test]
903 fn the_stack_is_marked_once_whoever_asked_for_it() {
904 let bare = Assembled { parts: vec![part(".text", vec![0x90])], names: Vec::new() };
907 let bytes = assembled(&bare, &target()).expect("an object");
908 let file = object::File::parse(&bytes[..]).expect("a readable object");
909 assert!(file.section_by_name(".note.GNU-stack").is_some(), "the marker was left out");
910
911 let said = Assembled {
912 parts: vec![part(".text", vec![0x90]), part(".note.GNU-stack", Vec::new())],
913 names: Vec::new(),
914 };
915 let bytes = assembled(&said, &target()).expect("an object");
916 let file = object::File::parse(&bytes[..]).expect("a readable object");
917 let marks = file.sections().filter(|s| s.name() == Ok(".note.GNU-stack")).count();
918 assert_eq!(marks, 1, "the file said it and it was said again");
919 }
920
921 #[test]
922 fn only_the_names_a_linker_could_find_are_offered_to_an_archive() {
923 let input = Assembled {
924 parts: vec![part(".text", vec![0; 8])],
925 names: vec![
926 at("reachable", 0, Sort::Func, Binding::Global),
927 at("mine", 4, Sort::Func, Binding::Local),
928 Name {
929 name: "elsewhere".to_owned(),
930 at: Held::Undefined,
931 size: 0,
932 sort: Sort::Untyped,
933 binding: Binding::Global,
934 visibility: Visibility::Default,
935 },
936 ],
937 };
938 assert_eq!(assembled_defines(&input), vec!["reachable".to_owned()]);
939 }
940
941 #[test]
942 fn a_machine_this_does_not_write_is_refused_rather_than_written_wrong() {
943 let input = Assembled { parts: vec![part(".text", vec![0x90])], names: Vec::new() };
944 let elsewhere = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Windows, Env::Msvc));
945 let why = assembled(&input, &elsewhere).expect_err("this cannot be written");
946 assert!(format!("{why}").contains("aarch64"), "{why}");
947 }
948
949 #[test]
950 fn a_file_of_assembly_for_aarch64_is_written_with_that_machine_s_relocations() {
951 let mut text = part(".text", vec![0; 12]);
956 let field = |at, symbol: &str, fixup, addend| Reloc {
957 at,
958 symbol: symbol.to_owned(),
959 kind: Reference::Field(fixup),
960 addend,
961 after: 0,
962 };
963 text.relocs = vec![
964 field(0, ".Ltable", Fixup::AdrPage21, 8),
965 field(4, ".Ltable", Fixup::AddLo12, 8),
966 field(8, "g", Fixup::Call26, 0),
967 ];
968 let mut data = part(".data", vec![0; 16]);
969 data.relocs = vec![Reloc {
970 at: 8,
971 symbol: ".Ltable".to_owned(),
972 kind: Reference::Address { bytes: 8 },
973 addend: 0,
974 after: 0,
975 }];
976 let mut table = at(".Ltable", 0, Sort::Object, Binding::Local);
977 table.at = Held::In { part: 1, offset: 0 };
978 let input = Assembled {
979 parts: vec![text, data],
980 names: vec![
981 table,
982 Name { at: Held::Undefined, ..at("g", 0, Sort::Untyped, Binding::Global) },
983 ],
984 };
985 let target = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Linux, Env::Gnu));
986 let bytes = assembled(&input, &target).expect("an object");
987 let file = object::File::parse(&bytes[..]).expect("a readable object");
988 assert_eq!(file.architecture(), Architecture::Aarch64);
989 let relocs = |name: &str| -> Vec<(u64, elf::RelocationType, i64)> {
990 let section = file.section_by_name(name).expect("the section");
991 section
992 .relocations()
993 .map(|(at, reloc)| {
994 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("ELF") };
995 (at, r_type, reloc.addend())
996 })
997 .collect()
998 };
999 assert_eq!(
1000 relocs(".text"),
1001 [
1002 (0, elf::R_AARCH64_ADR_PREL_PG_HI21, 8),
1003 (4, elf::R_AARCH64_ADD_ABS_LO12_NC, 8),
1004 (8, elf::R_AARCH64_CALL26, 0)
1005 ]
1006 );
1007 assert_eq!(relocs(".data"), [(8, elf::R_AARCH64_ABS64, 0)]);
1008 assert!(file.symbols().all(|s| s.name() != Ok(".Ltable")), "a label only this file sees");
1009 }
1010
1011 #[test]
1012 fn a_file_of_assembly_for_windows_is_written_as_coff() {
1013 let input = Assembled { parts: vec![part(".text", vec![0xc3])], names: Vec::new() };
1018 let bytes = assembled(&input, &windows()).expect("an object");
1019 let file = object::File::parse(&bytes[..]).expect("a readable object");
1020 assert_eq!(file.format(), object::BinaryFormat::Coff);
1021 let section = file.section_by_name(".text").expect("the section");
1022 assert_eq!(section.data().expect("the bytes"), &[0xc3]);
1023 assert_eq!(section.kind(), SectionKind::Text);
1024 assert!(
1025 file.section_by_name(".note.GNU-stack").is_none(),
1026 "a format with no marker got one anyway"
1027 );
1028 }
1029
1030 #[test]
1031 fn a_global_label_with_no_type_under_it_is_still_offered_on_coff() {
1032 let input = Assembled {
1039 parts: vec![part(".text", vec![0; 8])],
1040 names: vec![
1041 at("offered", 0, Sort::Untyped, Binding::Global),
1042 at("ours", 4, Sort::Untyped, Binding::Local),
1043 ],
1044 };
1045 let bytes = assembled(&input, &windows()).expect("an object");
1046 let file = object::File::parse(&bytes[..]).expect("a readable object");
1047 let offered = file.symbols().find(|s| s.name() == Ok("offered")).expect("the label");
1048 assert!(offered.is_global(), "a `.globl` label came out local");
1049 let ours = file.symbols().find(|s| s.name() == Ok("ours")).expect("the other label");
1050 assert!(!ours.is_global(), "a label nothing offered came out global");
1051 let bytes = assembled(&input, &target()).expect("an object");
1054 assert_eq!(st_info(&bytes, "offered") & 0xf, elf::STT_NOTYPE.0);
1055 }
1056
1057 #[test]
1058 fn a_relocation_on_coff_says_how_much_of_the_instruction_comes_after_it() {
1059 let mut text = part(".text", vec![0; 16]);
1064 text.relocs.push(Reloc {
1065 at: 2,
1066 symbol: "elsewhere".to_owned(),
1067 kind: Reference::Data,
1068 addend: -8,
1069 after: 4,
1070 });
1071 let input = Assembled {
1072 parts: vec![text],
1073 names: vec![Name {
1074 name: "elsewhere".to_owned(),
1075 at: Held::Undefined,
1076 size: 0,
1077 sort: Sort::Untyped,
1078 binding: Binding::Global,
1079 visibility: Visibility::Default,
1080 }],
1081 };
1082 let bytes = assembled(&input, &windows()).expect("an object");
1083 let file = object::File::parse(&bytes[..]).expect("a readable object");
1084 let section = file.section_by_name(".text").expect("the section");
1085 let (at, reloc) = section.relocations().next().expect("the relocation");
1086 assert_eq!(at, 2);
1087 assert_eq!(
1088 reloc.flags(),
1089 RelocationFlags::Coff {
1090 typ: object::pe::RelocationType(object::pe::IMAGE_REL_AMD64_REL32.0 + 4)
1091 }
1092 );
1093 }
1094}