1use object::write::{Object as Writer, Relocation, Symbol, SymbolSection};
31use object::{Architecture, Endianness, RelocationFlags, SectionKind, SymbolFlags, elf};
32use rucc_target::aarch64::Fixup;
33use rucc_target::{ObjectFormat, TargetInfo};
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 pub mach: u32,
90}
91
92impl Shape {
93 #[must_use]
100 pub fn of(name: &str) -> Shape {
101 let base = Shape { alloc: true, bits: true, ..Shape::default() };
102 let head = name.split_once('.').map_or(name, |(_, rest)| rest);
103 let head = head.split_once('.').map_or(head, |(first, _)| first);
104 match head {
105 "text" | "init" | "fini" => Shape { exec: true, ..base },
106 "rodata" | "eh_frame_hdr" => base,
107 "bss" => Shape { write: true, bits: false, ..base },
108 "tbss" => Shape { write: true, thread: true, bits: false, ..base },
109 "tdata" => Shape { write: true, thread: true, ..base },
110 _ if Array::of(name).is_some() => Shape { write: true, array: Array::of(name), ..base },
114 "debug_info" | "debug_abbrev" | "debug_line" | "debug_str" | "comment" => {
117 Shape { alloc: false, bits: true, ..Shape::default() }
118 }
119 _ => Shape { write: true, ..base },
120 }
121 }
122
123 #[must_use]
131 pub fn implied(name: &str) -> Shape {
132 let base = Shape { alloc: true, ..Shape::default() };
133 let head = name.split_once('.').map_or(name, |(_, rest)| rest);
134 let head = head.split_once('.').map_or(head, |(first, _)| first);
135 match head {
136 "text" => Shape { exec: true, ..base },
137 "rodata" => base,
138 "data" | "bss" => Shape { write: true, ..base },
139 "tdata" | "tbss" => Shape { write: true, thread: true, ..base },
140 _ if Array::of(name).is_some() => Shape { write: true, ..base },
141 _ => Shape::default(),
142 }
143 }
144
145 pub fn mach(
156 segment: &str,
157 section: &str,
158 kind: Option<&str>,
159 attributes: &[&str],
160 ) -> Result<Shape, String> {
161 let mach = crate::macho::section_flags(segment, section, kind, attributes)?;
162 let exec = mach & object::macho::S_ATTR_PURE_INSTRUCTIONS.0 != 0;
163 let typ = object::macho::SectionFlags(mach).typ();
164 let thread = matches!(
165 typ,
166 object::macho::S_THREAD_LOCAL_REGULAR | object::macho::S_THREAD_LOCAL_ZEROFILL
167 );
168 Ok(Shape {
169 alloc: true,
170 write: segment != "__TEXT",
171 exec,
172 thread,
173 bits: !crate::macho::zero_filled(mach),
174 mach,
175 ..Shape::default()
176 })
177 }
178
179 pub(crate) fn sh_flags(self) -> elf::SectionFlags {
186 let mut flags = 0;
187 if self.alloc {
188 flags |= elf::SHF_ALLOC.0;
189 }
190 if self.write {
191 flags |= elf::SHF_WRITE.0;
192 }
193 if self.exec {
194 flags |= elf::SHF_EXECINSTR.0;
195 }
196 if self.thread {
197 flags |= elf::SHF_TLS.0;
198 }
199 if self.merge != 0 {
200 flags |= elf::SHF_MERGE.0;
201 if self.strings {
202 flags |= elf::SHF_STRINGS.0;
203 }
204 }
205 elf::SectionFlags(flags)
206 }
207
208 pub(crate) fn sh_type(self) -> elf::SectionType {
210 match self.array {
211 _ if !self.bits => elf::SHT_NOBITS,
212 Some(Array::Init) => elf::SHT_INIT_ARRAY,
213 Some(Array::Fini) => elf::SHT_FINI_ARRAY,
214 Some(Array::Preinit) => elf::SHT_PREINIT_ARRAY,
215 None => elf::SHT_PROGBITS,
216 }
217 }
218
219 pub(crate) const fn kind(self) -> SectionKind {
225 match self {
226 Shape { bits: false, thread: true, .. } => SectionKind::UninitializedTls,
227 Shape { bits: false, .. } => SectionKind::UninitializedData,
228 Shape { thread: true, .. } => SectionKind::Tls,
229 Shape { exec: true, .. } => SectionKind::Text,
230 Shape { alloc: false, .. } => SectionKind::Other,
231 Shape { write: false, .. } => SectionKind::ReadOnlyData,
232 Shape { .. } => SectionKind::Data,
233 }
234 }
235}
236
237#[derive(Debug, Clone, PartialEq, Eq)]
239pub struct Name {
240 pub name: String,
242 pub at: Held,
244 pub size: u64,
246 pub sort: Sort,
248 pub binding: Binding,
250 pub visibility: Visibility,
252}
253
254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
256pub enum Held {
257 In {
259 part: usize,
261 offset: u64,
263 },
264 Absolute(u64),
267 Common {
270 size: u64,
272 align: u64,
275 },
276 Undefined,
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
282pub enum Sort {
283 Func,
285 Object,
287 Thread,
289 File,
295 #[default]
298 Untyped,
299}
300
301#[derive(Debug, Clone, Default, PartialEq, Eq)]
303pub struct Assembled {
304 pub parts: Vec<Part>,
306 pub names: Vec<Name>,
308 pub subsections: bool,
311}
312
313pub fn assembled(input: &Assembled, target: &TargetInfo) -> Result<Vec<u8>, Error> {
334 assembled_described(input, target, &Info::default())
335}
336
337pub fn assembled_described(
350 input: &Assembled,
351 target: &TargetInfo,
352 info: &Info,
353) -> Result<Vec<u8>, Error> {
354 let (flavour, machine) = match (Flavour::of(target), target.tuple.arch()) {
359 (Some(flavour), Arch::X86_64) => (flavour, Architecture::X86_64),
360 (Some(Flavour::Elf), Arch::Aarch64) => (Flavour::Elf, Architecture::Aarch64),
361 (None, Arch::Aarch64) if target.object_format == ObjectFormat::MachO => {
362 return crate::macho::write(input, target, info);
363 }
364 _ => return Err(Error::Format { triple: target.tuple.to_string() }),
365 };
366 let flags_of = |kind, after| match machine {
367 Architecture::Aarch64 => {
368 crate::elf::r_type_aarch64(kind).map(|r_type| RelocationFlags::Elf { r_type })
369 }
370 _ => flavour.reloc(kind, after),
371 };
372 let mut obj = Writer::new(flavour.binary(), machine, Endianness::Little);
373
374 let mut made = Vec::with_capacity(input.parts.len());
377 for part in &input.parts {
378 let id = obj.add_section(Vec::new(), part.name.clone().into_bytes(), part.shape.kind());
379 if let Some(flags) = flavour.stated(part.shape) {
384 obj.section_mut(id).flags = flags;
385 }
386 let align = part.align.max(1);
387 if part.shape.bits {
388 obj.append_section_data(id, &part.bytes, align);
389 } else {
390 obj.append_section_bss(id, part.size, align);
391 }
392 made.push(id);
393 }
394
395 let defined: std::collections::HashMap<&str, &Name> =
398 input.names.iter().map(|name| (name.name.as_str(), name)).collect();
399 let onto = |reloc: &Reloc| moved(flavour, input, &defined, reloc);
400 let wanted: std::collections::HashSet<&str> = input
401 .parts
402 .iter()
403 .flat_map(|part| &part.relocs)
404 .filter(|reloc| onto(reloc).is_none())
405 .map(|reloc| reloc.symbol.as_str())
406 .collect();
407
408 let mut symbols = std::collections::BTreeMap::new();
411 for name in &input.names {
412 if flavour == Flavour::Elf && unseen(name) && !wanted.contains(name.name.as_str()) {
413 continue;
414 }
415 let (section, value, size) = match name.at {
416 Held::In { part, offset } => {
417 let Some(id) = made.get(part) else {
418 let why = format!(
419 "'{}' is in section {part} and there is no such section",
420 name.name
421 );
422 return Err(Error::Refused { why });
423 };
424 (SymbolSection::Section(*id), offset, name.size)
425 }
426 Held::Absolute(value) => (SymbolSection::Absolute, value, name.size),
427 Held::Common { size, align } => (SymbolSection::Common, align, size),
430 Held::Undefined => (SymbolSection::Undefined, 0, 0),
431 };
432 let id = obj.add_symbol(Symbol {
433 name: name.name.clone().into_bytes(),
434 value,
435 size,
436 kind: flavour.sort(name.sort, name.binding),
437 scope: crate::file::scope_of(name.binding),
438 weak: name.binding == Binding::Weak,
439 section,
440 flags: SymbolFlags::None,
441 });
442 flavour.see(&mut obj, id, name.binding, name.visibility);
443 if matches!(name.at, Held::Common { .. }) {
449 if let SymbolFlags::Elf { st_info, .. } = obj.symbol_flags_mut(id) {
450 *st_info = elf::STB_GLOBAL | elf::STT_OBJECT;
451 }
452 }
453 symbols.insert(name.name.clone(), id);
454 }
455
456 for (part, id) in input.parts.iter().zip(&made) {
457 for reloc in &part.relocs {
458 let (symbol, addend) = match onto(reloc) {
459 Some((part, offset)) => {
460 (obj.section_symbol(made[part]), reloc.addend + offset as i64)
461 }
462 None => {
463 let Some(&symbol) = symbols.get(&reloc.symbol) else {
464 let why = format!(
465 "'{}' is named by a relocation and by nothing else",
466 reloc.symbol
467 );
468 return Err(Error::Refused { why });
469 };
470 (symbol, reloc.addend)
471 }
472 };
473 let flags = flags_of(reloc.kind, reloc.after).ok_or_else(|| Error::Refused {
474 why: format!("no relocation is {:?}", reloc.kind),
475 })?;
476 obj.add_relocation(*id, Relocation { offset: reloc.at as u64, symbol, addend, flags })
477 .map_err(|why| Error::Refused { why: why.to_string() })?;
478 }
479 }
480
481 let mut named = std::collections::HashMap::new();
484 for chunk in &info.chunks {
485 let id = obj.add_section(Vec::new(), chunk.name.clone().into_bytes(), SectionKind::Debug);
486 obj.append_section_data(id, &chunk.bytes, 1);
487 named.insert(chunk.name.as_str(), id);
488 }
489 for chunk in &info.chunks {
490 let section = named[chunk.name.as_str()];
491 for reloc in &chunk.relocs {
492 let (symbol, addend) = match named.get(reloc.symbol.as_str()) {
493 Some(&id) => (obj.section_symbol(id), reloc.addend),
494 None => match defined.get(reloc.symbol.as_str()).map(|name| name.at) {
495 Some(Held::In { part, offset }) => {
496 (obj.section_symbol(made[part]), reloc.addend + offset as i64)
497 }
498 _ => match symbols.get(&reloc.symbol) {
499 Some(&symbol) => (symbol, reloc.addend),
500 None => {
501 let why = format!(
502 "'{}' is named by the debug information and is not defined here",
503 reloc.symbol
504 );
505 return Err(Error::Refused { why });
506 }
507 },
508 },
509 };
510 let flags = flags_of(reloc.kind, reloc.after).ok_or_else(|| Error::Refused {
511 why: format!("no relocation is {:?}", reloc.kind),
512 })?;
513 let record = Relocation { offset: reloc.at as u64, symbol, addend, flags };
514 obj.add_relocation(section, record)
515 .map_err(|why| Error::Refused { why: why.to_string() })?;
516 }
517 }
518
519 if !input.parts.iter().any(|part| part.name == ".note.GNU-stack") {
524 flavour.marker(&mut obj);
525 }
526
527 let mut bytes = obj.write().map_err(|why| Error::Refused { why: why.to_string() })?;
528 if flavour == Flavour::Elf {
529 for part in input.parts.iter().filter(|part| part.shape.merge != 0) {
530 entry_size(&mut bytes, &part.name, part.shape.merge);
531 }
532 }
533 Ok(bytes)
534}
535
536fn entry_size(bytes: &mut [u8], name: &str, size: u64) {
542 let word = |bytes: &[u8], at: usize, width: usize| {
543 bytes[at..at + width].iter().rev().fold(0u64, |sum, &byte| sum << 8 | u64::from(byte))
544 };
545 let table = word(bytes, 0x28, 8) as usize;
546 let each = word(bytes, 0x3a, 2) as usize;
547 let count = word(bytes, 0x3c, 2) as usize;
548 let names = table + each * word(bytes, 0x3e, 2) as usize;
549 let names = word(bytes, names + 0x18, 8) as usize;
550 for header in (0..count).map(|nth| table + nth * each) {
551 let at = names + word(bytes, header, 4) as usize;
552 if bytes[at..].starts_with(name.as_bytes()) && bytes.get(at + name.len()) == Some(&0) {
553 bytes[header + 0x38..header + 0x40].copy_from_slice(&size.to_le_bytes());
554 }
555 }
556}
557
558fn moved(
569 flavour: Flavour,
570 input: &Assembled,
571 defined: &std::collections::HashMap<&str, &Name>,
572 reloc: &Reloc,
573) -> Option<(usize, u64)> {
574 use crate::section::Reference;
575 let name = defined.get(reloc.symbol.as_str())?;
576 let Held::In { part, offset } = name.at else { return None };
577 if flavour != Flavour::Elf || name.binding != Binding::Local {
578 return None;
579 }
580 let near = matches!(reloc.kind, Reference::Data | Reference::Away);
581 let fixed = match reloc.kind {
582 Reference::Call
583 | Reference::Got
584 | Reference::GotBare
585 | Reference::GotKept
586 | Reference::Thread => false,
587 Reference::Field(
590 Fixup::Call26
591 | Fixup::Jump26
592 | Fixup::GotPage21
593 | Fixup::GotLo12
594 | Fixup::GotTprelPage21
595 | Fixup::GotTprelLo12Nc
596 | Fixup::TprelHi12
597 | Fixup::TprelLo12Nc,
598 ) => false,
599 _ if input.parts.get(part)?.shape.merge != 0 => !near && reloc.addend == 0,
600 _ => true,
601 };
602 fixed.then_some((part, offset))
603}
604
605fn unseen(name: &Name) -> bool {
609 name.binding == Binding::Local
610 && (name.name.starts_with(".L")
611 || name.name.starts_with("..")
612 || name.name.contains('\u{1}'))
613}
614
615#[must_use]
621pub fn assembled_defines(input: &Assembled) -> Vec<String> {
622 input
623 .names
624 .iter()
625 .filter(|name| name.binding != Binding::Local && name.at != Held::Undefined)
626 .map(|name| name.name.clone())
627 .collect()
628}
629
630#[cfg(test)]
631mod tests {
632 use super::*;
633
634 use object::read::elf::{FileHeader as _, Sym as _};
635 use object::read::{Object as _, ObjectSection as _, ObjectSymbol as _};
636 use object::{RelocationFlags, SectionFlags};
637 use rucc_target::{Arch as TargetArch, Env, Os, Triple};
638
639 use crate::section::Reference;
640
641 fn target() -> TargetInfo {
643 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Linux, Env::Gnu))
644 }
645
646 fn windows() -> TargetInfo {
648 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Windows, Env::Gnu))
649 }
650
651 fn part(name: &str, bytes: Vec<u8>) -> Part {
653 Part {
654 name: name.to_owned(),
655 size: bytes.len() as u64,
656 bytes,
657 align: 1,
658 shape: Shape::of(name),
659 relocs: Vec::new(),
660 }
661 }
662
663 fn at(name: &str, offset: u64, sort: Sort, binding: Binding) -> Name {
665 Name {
666 name: name.to_owned(),
667 at: Held::In { part: 0, offset },
668 size: 0,
669 sort,
670 binding,
671 visibility: Visibility::Default,
672 }
673 }
674
675 fn raw(bytes: &[u8], want: &str) -> (u8, u64) {
683 let header = elf::FileHeader64::<Endianness>::parse(bytes).expect("a header");
684 let endian = header.endian().expect("an endianness");
685 let table = header.sections(endian, bytes).expect("the sections");
686 let symbols = table.symbols(endian, bytes, elf::SHT_SYMTAB).expect("a symbol table");
687 for symbol in symbols.iter() {
688 if symbols.symbol_name(endian, symbol).expect("a name") == want.as_bytes() {
689 return (symbol.st_info().0, symbol.st_value(endian));
690 }
691 }
692 panic!("there is no symbol called '{want}'");
693 }
694
695 fn st_info(bytes: &[u8], want: &str) -> u8 {
697 raw(bytes, want).0
698 }
699
700 #[test]
701 fn a_section_carries_the_flags_the_source_said_and_not_the_ones_its_name_suggests() {
702 let mut odd = part(".init.text", vec![0x90]);
706 odd.shape = Shape { alloc: true, exec: true, bits: true, ..Shape::default() };
707 let input = Assembled { parts: vec![odd], names: Vec::new(), subsections: false };
708 let bytes = assembled(&input, &target()).expect("an object");
709 let file = object::File::parse(&bytes[..]).expect("a readable object");
710 let section = file.section_by_name(".init.text").expect("the section");
711 assert_eq!(section.data().expect("the bytes"), &[0x90]);
712 let SectionFlags::Elf { sh_flags, sh_type } = section.flags() else {
713 panic!("this is an ELF file");
714 };
715 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_EXECINSTR.0);
716 assert_eq!(sh_flags.0 & elf::SHF_WRITE.0, 0, "nothing said it was writable");
717 assert_eq!(sh_type, elf::SHT_PROGBITS);
718 }
719
720 #[test]
721 fn a_section_that_holds_no_bytes_still_says_how_long_it_is() {
722 let mut room = part(".bss", Vec::new());
725 room.size = 4096;
726 room.align = 16;
727 let input = Assembled { parts: vec![room], names: Vec::new(), subsections: false };
728 let bytes = assembled(&input, &target()).expect("an object");
729 assert!(bytes.len() < 4096, "the empty space was written out: {} bytes", bytes.len());
730 let file = object::File::parse(&bytes[..]).expect("a readable object");
731 let section = file.section_by_name(".bss").expect("the section");
732 assert_eq!(section.size(), 4096);
733 assert_eq!(section.align(), 16);
734 let SectionFlags::Elf { sh_type, .. } = section.flags() else { panic!("an ELF file") };
735 assert_eq!(sh_type, elf::SHT_NOBITS);
736 }
737
738 #[test]
739 fn a_label_nobody_stated_a_type_for_is_a_symbol_with_no_type() {
740 let input = Assembled {
744 parts: vec![part(".text", vec![0; 8])],
745 names: vec![at("plain", 4, Sort::Untyped, Binding::Global)],
746 subsections: false,
747 };
748 let bytes = assembled(&input, &target()).expect("an object");
749 let file = object::File::parse(&bytes[..]).expect("a readable object");
750 let plain = file.symbols().find(|s| s.name() == Ok("plain")).expect("the label");
751 assert_eq!(plain.address(), 4);
752 assert_eq!(st_info(&bytes, "plain") & 0xf, elf::STT_NOTYPE.0);
753 }
754
755 #[test]
756 fn what_type_said_is_what_the_symbol_gets() {
757 let input = Assembled {
758 parts: vec![part(".text", vec![0; 8])],
759 names: vec![
760 at("run", 0, Sort::Func, Binding::Global),
761 at("held", 4, Sort::Object, Binding::Local),
762 ],
763 subsections: false,
764 };
765 let bytes = assembled(&input, &target()).expect("an object");
766 assert_eq!(st_info(&bytes, "run") & 0xf, elf::STT_FUNC.0);
767 assert_eq!(st_info(&bytes, "held") & 0xf, elf::STT_OBJECT.0);
768 assert_eq!(st_info(&bytes, "run") >> 4, elf::STB_GLOBAL.0);
769 assert_eq!(st_info(&bytes, "held") >> 4, elf::STB_LOCAL.0);
770 }
771
772 #[test]
773 fn a_common_symbol_is_written_the_way_gas_writes_one() {
774 let input = Assembled {
779 parts: Vec::new(),
780 names: vec![Name {
781 name: "shared".to_owned(),
782 at: Held::Common { size: 8, align: 8 },
783 size: 0,
784 sort: Sort::Object,
785 binding: Binding::Global,
786 visibility: Visibility::Default,
787 }],
788 subsections: false,
789 };
790 let bytes = assembled(&input, &target()).expect("an object");
791 assert_eq!(st_info(&bytes, "shared"), elf::STB_GLOBAL.0 << 4 | elf::STT_OBJECT.0);
792 let file = object::File::parse(&bytes[..]).expect("a readable object");
793 let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the symbol");
794 assert!(shared.is_common(), "the linker has to be asked for the space");
795 assert_eq!(shared.size(), 8);
796 assert_eq!(raw(&bytes, "shared").1, 8, "the boundary it has to start on");
798 }
799
800 #[test]
801 fn a_set_is_a_number_rather_than_a_place() {
802 let input = Assembled {
803 parts: vec![part(".text", vec![0; 8])],
804 names: vec![Name {
805 name: "size_of_it".to_owned(),
806 at: Held::Absolute(25),
807 size: 0,
808 sort: Sort::Untyped,
809 binding: Binding::Global,
810 visibility: Visibility::Default,
811 }],
812 subsections: false,
813 };
814 let bytes = assembled(&input, &target()).expect("an object");
815 let file = object::File::parse(&bytes[..]).expect("a readable object");
816 let sym = file.symbols().find(|s| s.name() == Ok("size_of_it")).expect("the symbol");
817 assert_eq!(sym.address(), 25);
818 assert_eq!(sym.section(), object::SymbolSection::Absolute, "it is not in any section");
819 }
820
821 #[test]
822 fn a_relocation_names_a_symbol_and_lands_where_the_bytes_are() {
823 let mut data = part(".data", vec![0; 8]);
824 data.relocs.push(Reloc {
825 at: 0,
826 symbol: "message".to_owned(),
827 kind: Reference::Address { bytes: 8 },
828 addend: 0,
829 after: 0,
830 });
831 let input = Assembled {
832 parts: vec![data],
833 names: vec![Name {
834 name: "message".to_owned(),
835 at: Held::Undefined,
836 size: 0,
837 sort: Sort::Untyped,
838 binding: Binding::Global,
839 visibility: Visibility::Default,
840 }],
841 subsections: false,
842 };
843 let bytes = assembled(&input, &target()).expect("an object");
844 let file = object::File::parse(&bytes[..]).expect("a readable object");
845 let section = file.section_by_name(".data").expect("the section");
846 let (at, reloc) = section.relocations().next().expect("one relocation");
847 assert_eq!(at, 0);
848 assert_eq!(reloc.addend(), 0);
849 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("an ELF file") };
850 assert_eq!(r_type, elf::R_X86_64_64);
851 }
852
853 #[test]
854 fn a_place_only_this_file_sees_is_reached_through_its_section_as_gas_does() {
855 let mut text = part(".text", vec![0; 32]);
859 for (at, symbol, kind) in [
860 (0, ".L3", Reference::Data),
861 (4, "helper", Reference::Data),
862 (8, "helper", Reference::Call),
863 (12, "shared", Reference::Data),
864 ] {
865 let symbol = symbol.to_owned();
866 text.relocs.push(Reloc { at, symbol, kind, addend: -4, after: 0 });
867 }
868 let input = Assembled {
869 parts: vec![text],
870 names: vec![
871 at(".L3", 20, Sort::Untyped, Binding::Local),
872 at("helper", 24, Sort::Func, Binding::Local),
873 at("shared", 28, Sort::Func, Binding::Global),
874 ],
875 subsections: false,
876 };
877 let bytes = assembled(&input, &target()).expect("an object");
878 let file = object::File::parse(&bytes[..]).expect("a readable object");
879 let names: Vec<_> = file.symbols().filter_map(|sym| sym.name().ok()).collect();
880 assert!(!names.contains(&".L3") && names.contains(&"helper"), "{names:?}");
881 let section = file.section_by_name(".text").expect("the section");
882 let reached: Vec<_> = section
883 .relocations()
884 .map(|(at, reloc)| {
885 let object::RelocationTarget::Symbol(index) = reloc.target() else {
886 panic!("a symbol")
887 };
888 let symbol = file.symbol_by_index(index).expect("the symbol");
889 let name = if symbol.kind() == object::SymbolKind::Section {
890 ".text"
891 } else {
892 symbol.name().expect("a name")
893 };
894 (at, name, reloc.addend())
895 })
896 .collect();
897 assert_eq!(
898 reached,
899 [(0, ".text", 16), (4, ".text", 20), (8, "helper", -4), (12, "shared", -4)]
900 );
901 }
902
903 #[test]
904 fn a_section_of_constants_may_be_merged_and_a_distance_into_it_keeps_its_name() {
905 let mut text = part(".text", vec![0; 8]);
906 text.relocs.push(Reloc {
907 at: 0,
908 symbol: ".LC0".to_owned(),
909 kind: Reference::Data,
910 addend: -4,
911 after: 0,
912 });
913 let strings = Part {
914 shape: Shape { merge: 1, strings: true, ..Shape::of(".rodata") },
915 ..part(".rodata.str1.1", b"hi\0".to_vec())
916 };
917 let mut name = at(".LC0", 0, Sort::Untyped, Binding::Local);
918 name.at = Held::In { part: 1, offset: 0 };
919 let input = Assembled { parts: vec![text, strings], names: vec![name], subsections: false };
920 let bytes = assembled(&input, &target()).expect("an object");
921 let file = object::File::parse(&bytes[..]).expect("a readable object");
922 let section = file.section_by_name(".rodata.str1.1").expect("the section");
923 let SectionFlags::Elf { sh_flags, .. } = section.flags() else { panic!("an ELF file") };
924 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_MERGE.0 | elf::SHF_STRINGS.0);
925 let header = elf::FileHeader64::<Endianness>::parse(&bytes[..]).expect("a header");
926 let endian = header.endian().expect("an endianness");
927 let table = header.sections(endian, &bytes[..]).expect("the sections");
928 let (_, found) = table.section_by_name(endian, b".rodata.str1.1").expect("the section");
929 assert_eq!(found.sh_entsize.get(endian), 1);
930 let text = file.section_by_name(".text").expect("the section");
931 let (_, reloc) = text.relocations().next().expect("one relocation");
932 let object::RelocationTarget::Symbol(index) = reloc.target() else { panic!("a symbol") };
933 assert_eq!(file.symbol_by_index(index).and_then(|sym| sym.name()), Ok(".LC0"));
934 }
935
936 #[test]
937 fn a_relocation_against_a_name_the_file_never_mentions_is_refused() {
938 let mut data = part(".data", vec![0; 8]);
942 data.relocs.push(Reloc {
943 at: 0,
944 symbol: "nowhere".to_owned(),
945 kind: Reference::Address { bytes: 8 },
946 addend: 0,
947 after: 0,
948 });
949 let input = Assembled { parts: vec![data], names: Vec::new(), subsections: false };
950 let why = assembled(&input, &target()).expect_err("this cannot be written");
951 assert!(format!("{why}").contains("nowhere"), "{why}");
952 }
953
954 #[test]
955 fn the_stack_is_marked_once_whoever_asked_for_it() {
956 let bare = Assembled {
959 parts: vec![part(".text", vec![0x90])],
960 names: Vec::new(),
961 subsections: false,
962 };
963 let bytes = assembled(&bare, &target()).expect("an object");
964 let file = object::File::parse(&bytes[..]).expect("a readable object");
965 assert!(file.section_by_name(".note.GNU-stack").is_some(), "the marker was left out");
966
967 let said = Assembled {
968 parts: vec![part(".text", vec![0x90]), part(".note.GNU-stack", Vec::new())],
969 names: Vec::new(),
970 subsections: false,
971 };
972 let bytes = assembled(&said, &target()).expect("an object");
973 let file = object::File::parse(&bytes[..]).expect("a readable object");
974 let marks = file.sections().filter(|s| s.name() == Ok(".note.GNU-stack")).count();
975 assert_eq!(marks, 1, "the file said it and it was said again");
976 }
977
978 #[test]
979 fn only_the_names_a_linker_could_find_are_offered_to_an_archive() {
980 let input = Assembled {
981 parts: vec![part(".text", vec![0; 8])],
982 names: vec![
983 at("reachable", 0, Sort::Func, Binding::Global),
984 at("mine", 4, Sort::Func, Binding::Local),
985 Name {
986 name: "elsewhere".to_owned(),
987 at: Held::Undefined,
988 size: 0,
989 sort: Sort::Untyped,
990 binding: Binding::Global,
991 visibility: Visibility::Default,
992 },
993 ],
994 subsections: false,
995 };
996 assert_eq!(assembled_defines(&input), vec!["reachable".to_owned()]);
997 }
998
999 #[test]
1000 fn a_machine_this_does_not_write_is_refused_rather_than_written_wrong() {
1001 let input = Assembled {
1002 parts: vec![part(".text", vec![0x90])],
1003 names: Vec::new(),
1004 subsections: false,
1005 };
1006 let elsewhere = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Windows, Env::Msvc));
1007 let why = assembled(&input, &elsewhere).expect_err("this cannot be written");
1008 assert!(format!("{why}").contains("aarch64"), "{why}");
1009 }
1010
1011 #[test]
1012 fn a_file_of_assembly_for_aarch64_is_written_with_that_machine_s_relocations() {
1013 let mut text = part(".text", vec![0; 12]);
1018 let field = |at, symbol: &str, fixup, addend| Reloc {
1019 at,
1020 symbol: symbol.to_owned(),
1021 kind: Reference::Field(fixup),
1022 addend,
1023 after: 0,
1024 };
1025 text.relocs = vec![
1026 field(0, ".Ltable", Fixup::AdrPage21, 8),
1027 field(4, ".Ltable", Fixup::AddLo12, 8),
1028 field(8, "g", Fixup::Call26, 0),
1029 ];
1030 let mut data = part(".data", vec![0; 16]);
1031 data.relocs = vec![Reloc {
1032 at: 8,
1033 symbol: ".Ltable".to_owned(),
1034 kind: Reference::Address { bytes: 8 },
1035 addend: 0,
1036 after: 0,
1037 }];
1038 let mut table = at(".Ltable", 0, Sort::Object, Binding::Local);
1039 table.at = Held::In { part: 1, offset: 0 };
1040 let input = Assembled {
1041 parts: vec![text, data],
1042 names: vec![
1043 table,
1044 Name { at: Held::Undefined, ..at("g", 0, Sort::Untyped, Binding::Global) },
1045 ],
1046 subsections: false,
1047 };
1048 let target = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Linux, Env::Gnu));
1049 let bytes = assembled(&input, &target).expect("an object");
1050 let file = object::File::parse(&bytes[..]).expect("a readable object");
1051 assert_eq!(file.architecture(), Architecture::Aarch64);
1052 let relocs = |name: &str| -> Vec<(u64, elf::RelocationType, i64)> {
1053 let section = file.section_by_name(name).expect("the section");
1054 section
1055 .relocations()
1056 .map(|(at, reloc)| {
1057 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("ELF") };
1058 (at, r_type, reloc.addend())
1059 })
1060 .collect()
1061 };
1062 assert_eq!(
1063 relocs(".text"),
1064 [
1065 (0, elf::R_AARCH64_ADR_PREL_PG_HI21, 8),
1066 (4, elf::R_AARCH64_ADD_ABS_LO12_NC, 8),
1067 (8, elf::R_AARCH64_CALL26, 0)
1068 ]
1069 );
1070 assert_eq!(relocs(".data"), [(8, elf::R_AARCH64_ABS64, 0)]);
1071 assert!(file.symbols().all(|s| s.name() != Ok(".Ltable")), "a label only this file sees");
1072 }
1073
1074 #[test]
1075 fn a_file_of_assembly_for_windows_is_written_as_coff() {
1076 let input = Assembled {
1081 parts: vec![part(".text", vec![0xc3])],
1082 names: Vec::new(),
1083 subsections: false,
1084 };
1085 let bytes = assembled(&input, &windows()).expect("an object");
1086 let file = object::File::parse(&bytes[..]).expect("a readable object");
1087 assert_eq!(file.format(), object::BinaryFormat::Coff);
1088 let section = file.section_by_name(".text").expect("the section");
1089 assert_eq!(section.data().expect("the bytes"), &[0xc3]);
1090 assert_eq!(section.kind(), SectionKind::Text);
1091 assert!(
1092 file.section_by_name(".note.GNU-stack").is_none(),
1093 "a format with no marker got one anyway"
1094 );
1095 }
1096
1097 #[test]
1098 fn a_global_label_with_no_type_under_it_is_still_offered_on_coff() {
1099 let input = Assembled {
1106 parts: vec![part(".text", vec![0; 8])],
1107 names: vec![
1108 at("offered", 0, Sort::Untyped, Binding::Global),
1109 at("ours", 4, Sort::Untyped, Binding::Local),
1110 ],
1111 subsections: false,
1112 };
1113 let bytes = assembled(&input, &windows()).expect("an object");
1114 let file = object::File::parse(&bytes[..]).expect("a readable object");
1115 let offered = file.symbols().find(|s| s.name() == Ok("offered")).expect("the label");
1116 assert!(offered.is_global(), "a `.globl` label came out local");
1117 let ours = file.symbols().find(|s| s.name() == Ok("ours")).expect("the other label");
1118 assert!(!ours.is_global(), "a label nothing offered came out global");
1119 let bytes = assembled(&input, &target()).expect("an object");
1122 assert_eq!(st_info(&bytes, "offered") & 0xf, elf::STT_NOTYPE.0);
1123 }
1124
1125 #[test]
1126 fn a_relocation_on_coff_says_how_much_of_the_instruction_comes_after_it() {
1127 let mut text = part(".text", vec![0; 16]);
1132 text.relocs.push(Reloc {
1133 at: 2,
1134 symbol: "elsewhere".to_owned(),
1135 kind: Reference::Data,
1136 addend: -8,
1137 after: 4,
1138 });
1139 let input = Assembled {
1140 parts: vec![text],
1141 names: vec![Name {
1142 name: "elsewhere".to_owned(),
1143 at: Held::Undefined,
1144 size: 0,
1145 sort: Sort::Untyped,
1146 binding: Binding::Global,
1147 visibility: Visibility::Default,
1148 }],
1149 subsections: false,
1150 };
1151 let bytes = assembled(&input, &windows()).expect("an object");
1152 let file = object::File::parse(&bytes[..]).expect("a readable object");
1153 let section = file.section_by_name(".text").expect("the section");
1154 let (at, reloc) = section.relocations().next().expect("the relocation");
1155 assert_eq!(at, 2);
1156 assert_eq!(
1157 reloc.flags(),
1158 RelocationFlags::Coff {
1159 typ: object::pe::RelocationType(object::pe::IMAGE_REL_AMD64_REL32.0 + 4)
1160 }
1161 );
1162 }
1163}