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 pub(crate) fn sh_flags(self) -> elf::SectionFlags {
126 let mut flags = 0;
127 if self.alloc {
128 flags |= elf::SHF_ALLOC.0;
129 }
130 if self.write {
131 flags |= elf::SHF_WRITE.0;
132 }
133 if self.exec {
134 flags |= elf::SHF_EXECINSTR.0;
135 }
136 if self.thread {
137 flags |= elf::SHF_TLS.0;
138 }
139 if self.merge != 0 {
140 flags |= elf::SHF_MERGE.0;
141 if self.strings {
142 flags |= elf::SHF_STRINGS.0;
143 }
144 }
145 elf::SectionFlags(flags)
146 }
147
148 pub(crate) fn sh_type(self) -> elf::SectionType {
150 match self.array {
151 _ if !self.bits => elf::SHT_NOBITS,
152 Some(Array::Init) => elf::SHT_INIT_ARRAY,
153 Some(Array::Fini) => elf::SHT_FINI_ARRAY,
154 Some(Array::Preinit) => elf::SHT_PREINIT_ARRAY,
155 None => elf::SHT_PROGBITS,
156 }
157 }
158
159 pub(crate) const fn kind(self) -> SectionKind {
165 match self {
166 Shape { bits: false, thread: true, .. } => SectionKind::UninitializedTls,
167 Shape { bits: false, .. } => SectionKind::UninitializedData,
168 Shape { thread: true, .. } => SectionKind::Tls,
169 Shape { exec: true, .. } => SectionKind::Text,
170 Shape { alloc: false, .. } => SectionKind::Other,
171 Shape { write: false, .. } => SectionKind::ReadOnlyData,
172 Shape { .. } => SectionKind::Data,
173 }
174 }
175}
176
177#[derive(Debug, Clone, PartialEq, Eq)]
179pub struct Name {
180 pub name: String,
182 pub at: Held,
184 pub size: u64,
186 pub sort: Sort,
188 pub binding: Binding,
190 pub visibility: Visibility,
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub enum Held {
197 In {
199 part: usize,
201 offset: u64,
203 },
204 Absolute(u64),
207 Common {
210 size: u64,
212 align: u64,
215 },
216 Undefined,
218}
219
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
222pub enum Sort {
223 Func,
225 Object,
227 Thread,
229 File,
235 #[default]
238 Untyped,
239}
240
241#[derive(Debug, Clone, Default, PartialEq, Eq)]
243pub struct Assembled {
244 pub parts: Vec<Part>,
246 pub names: Vec<Name>,
248}
249
250pub fn assembled(input: &Assembled, target: &TargetInfo) -> Result<Vec<u8>, Error> {
271 assembled_described(input, target, &Info::default())
272}
273
274pub fn assembled_described(
287 input: &Assembled,
288 target: &TargetInfo,
289 info: &Info,
290) -> Result<Vec<u8>, Error> {
291 let (flavour, machine) = match (Flavour::of(target), target.tuple.arch()) {
294 (Some(flavour), Arch::X86_64) => (flavour, Architecture::X86_64),
295 (Some(Flavour::Elf), Arch::Aarch64) => (Flavour::Elf, Architecture::Aarch64),
296 _ => return Err(Error::Format { triple: target.tuple.to_string() }),
297 };
298 let flags_of = |kind, after| match machine {
299 Architecture::Aarch64 => {
300 crate::elf::r_type_aarch64(kind).map(|r_type| RelocationFlags::Elf { r_type })
301 }
302 _ => flavour.reloc(kind, after),
303 };
304 let mut obj = Writer::new(flavour.binary(), machine, Endianness::Little);
305
306 let mut made = Vec::with_capacity(input.parts.len());
309 for part in &input.parts {
310 let id = obj.add_section(Vec::new(), part.name.clone().into_bytes(), part.shape.kind());
311 if let Some(flags) = flavour.stated(part.shape) {
316 obj.section_mut(id).flags = flags;
317 }
318 let align = part.align.max(1);
319 if part.shape.bits {
320 obj.append_section_data(id, &part.bytes, align);
321 } else {
322 obj.append_section_bss(id, part.size, align);
323 }
324 made.push(id);
325 }
326
327 let defined: std::collections::HashMap<&str, &Name> =
330 input.names.iter().map(|name| (name.name.as_str(), name)).collect();
331 let onto = |reloc: &Reloc| moved(flavour, input, &defined, reloc);
332 let wanted: std::collections::HashSet<&str> = input
333 .parts
334 .iter()
335 .flat_map(|part| &part.relocs)
336 .filter(|reloc| onto(reloc).is_none())
337 .map(|reloc| reloc.symbol.as_str())
338 .collect();
339
340 let mut symbols = std::collections::BTreeMap::new();
343 for name in &input.names {
344 if flavour == Flavour::Elf && unseen(name) && !wanted.contains(name.name.as_str()) {
345 continue;
346 }
347 let (section, value, size) = match name.at {
348 Held::In { part, offset } => {
349 let Some(id) = made.get(part) else {
350 let why = format!(
351 "'{}' is in section {part} and there is no such section",
352 name.name
353 );
354 return Err(Error::Refused { why });
355 };
356 (SymbolSection::Section(*id), offset, name.size)
357 }
358 Held::Absolute(value) => (SymbolSection::Absolute, value, name.size),
359 Held::Common { size, align } => (SymbolSection::Common, align, size),
362 Held::Undefined => (SymbolSection::Undefined, 0, 0),
363 };
364 let id = obj.add_symbol(Symbol {
365 name: name.name.clone().into_bytes(),
366 value,
367 size,
368 kind: flavour.sort(name.sort, name.binding),
369 scope: crate::file::scope_of(name.binding),
370 weak: name.binding == Binding::Weak,
371 section,
372 flags: SymbolFlags::None,
373 });
374 flavour.see(&mut obj, id, name.binding, name.visibility);
375 if matches!(name.at, Held::Common { .. }) {
381 if let SymbolFlags::Elf { st_info, .. } = obj.symbol_flags_mut(id) {
382 *st_info = elf::STB_GLOBAL | elf::STT_OBJECT;
383 }
384 }
385 symbols.insert(name.name.clone(), id);
386 }
387
388 for (part, id) in input.parts.iter().zip(&made) {
389 for reloc in &part.relocs {
390 let (symbol, addend) = match onto(reloc) {
391 Some((part, offset)) => {
392 (obj.section_symbol(made[part]), reloc.addend + offset as i64)
393 }
394 None => {
395 let Some(&symbol) = symbols.get(&reloc.symbol) else {
396 let why = format!(
397 "'{}' is named by a relocation and by nothing else",
398 reloc.symbol
399 );
400 return Err(Error::Refused { why });
401 };
402 (symbol, reloc.addend)
403 }
404 };
405 let flags = flags_of(reloc.kind, reloc.after).ok_or_else(|| Error::Refused {
406 why: format!("no relocation is {:?}", reloc.kind),
407 })?;
408 obj.add_relocation(*id, Relocation { offset: reloc.at as u64, symbol, addend, flags })
409 .map_err(|why| Error::Refused { why: why.to_string() })?;
410 }
411 }
412
413 let mut named = std::collections::HashMap::new();
416 for chunk in &info.chunks {
417 let id = obj.add_section(Vec::new(), chunk.name.clone().into_bytes(), SectionKind::Debug);
418 obj.append_section_data(id, &chunk.bytes, 1);
419 named.insert(chunk.name.as_str(), id);
420 }
421 for chunk in &info.chunks {
422 let section = named[chunk.name.as_str()];
423 for reloc in &chunk.relocs {
424 let (symbol, addend) = match named.get(reloc.symbol.as_str()) {
425 Some(&id) => (obj.section_symbol(id), reloc.addend),
426 None => match defined.get(reloc.symbol.as_str()).map(|name| name.at) {
427 Some(Held::In { part, offset }) => {
428 (obj.section_symbol(made[part]), reloc.addend + offset as i64)
429 }
430 _ => match symbols.get(&reloc.symbol) {
431 Some(&symbol) => (symbol, reloc.addend),
432 None => {
433 let why = format!(
434 "'{}' is named by the debug information and is not defined here",
435 reloc.symbol
436 );
437 return Err(Error::Refused { why });
438 }
439 },
440 },
441 };
442 let flags = flags_of(reloc.kind, reloc.after).ok_or_else(|| Error::Refused {
443 why: format!("no relocation is {:?}", reloc.kind),
444 })?;
445 let record = Relocation { offset: reloc.at as u64, symbol, addend, flags };
446 obj.add_relocation(section, record)
447 .map_err(|why| Error::Refused { why: why.to_string() })?;
448 }
449 }
450
451 if !input.parts.iter().any(|part| part.name == ".note.GNU-stack") {
456 flavour.marker(&mut obj);
457 }
458
459 let mut bytes = obj.write().map_err(|why| Error::Refused { why: why.to_string() })?;
460 if flavour == Flavour::Elf {
461 for part in input.parts.iter().filter(|part| part.shape.merge != 0) {
462 entry_size(&mut bytes, &part.name, part.shape.merge);
463 }
464 }
465 Ok(bytes)
466}
467
468fn entry_size(bytes: &mut [u8], name: &str, size: u64) {
474 let word = |bytes: &[u8], at: usize, width: usize| {
475 bytes[at..at + width].iter().rev().fold(0u64, |sum, &byte| sum << 8 | u64::from(byte))
476 };
477 let table = word(bytes, 0x28, 8) as usize;
478 let each = word(bytes, 0x3a, 2) as usize;
479 let count = word(bytes, 0x3c, 2) as usize;
480 let names = table + each * word(bytes, 0x3e, 2) as usize;
481 let names = word(bytes, names + 0x18, 8) as usize;
482 for header in (0..count).map(|nth| table + nth * each) {
483 let at = names + word(bytes, header, 4) as usize;
484 if bytes[at..].starts_with(name.as_bytes()) && bytes.get(at + name.len()) == Some(&0) {
485 bytes[header + 0x38..header + 0x40].copy_from_slice(&size.to_le_bytes());
486 }
487 }
488}
489
490fn moved(
501 flavour: Flavour,
502 input: &Assembled,
503 defined: &std::collections::HashMap<&str, &Name>,
504 reloc: &Reloc,
505) -> Option<(usize, u64)> {
506 use crate::section::Reference;
507 let name = defined.get(reloc.symbol.as_str())?;
508 let Held::In { part, offset } = name.at else { return None };
509 if flavour != Flavour::Elf || name.binding != Binding::Local {
510 return None;
511 }
512 let near = matches!(reloc.kind, Reference::Data | Reference::Away);
513 let fixed = match reloc.kind {
514 Reference::Call
515 | Reference::Got
516 | Reference::GotBare
517 | Reference::GotKept
518 | Reference::Thread => false,
519 Reference::Field(
522 Fixup::Call26
523 | Fixup::Jump26
524 | Fixup::GotPage21
525 | Fixup::GotLo12
526 | Fixup::GotTprelPage21
527 | Fixup::GotTprelLo12Nc
528 | Fixup::TprelHi12
529 | Fixup::TprelLo12Nc,
530 ) => false,
531 _ if input.parts.get(part)?.shape.merge != 0 => !near && reloc.addend == 0,
532 _ => true,
533 };
534 fixed.then_some((part, offset))
535}
536
537fn unseen(name: &Name) -> bool {
541 name.binding == Binding::Local
542 && (name.name.starts_with(".L")
543 || name.name.starts_with("..")
544 || name.name.contains('\u{1}'))
545}
546
547#[must_use]
553pub fn assembled_defines(input: &Assembled) -> Vec<String> {
554 input
555 .names
556 .iter()
557 .filter(|name| name.binding != Binding::Local && name.at != Held::Undefined)
558 .map(|name| name.name.clone())
559 .collect()
560}
561
562#[cfg(test)]
563mod tests {
564 use super::*;
565
566 use object::read::elf::{FileHeader as _, Sym as _};
567 use object::read::{Object as _, ObjectSection as _, ObjectSymbol as _};
568 use object::{RelocationFlags, SectionFlags};
569 use rucc_target::{Arch as TargetArch, Env, Os, Triple};
570
571 use crate::section::Reference;
572
573 fn target() -> TargetInfo {
575 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Linux, Env::Gnu))
576 }
577
578 fn windows() -> TargetInfo {
580 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Windows, Env::Gnu))
581 }
582
583 fn part(name: &str, bytes: Vec<u8>) -> Part {
585 Part {
586 name: name.to_owned(),
587 size: bytes.len() as u64,
588 bytes,
589 align: 1,
590 shape: Shape::of(name),
591 relocs: Vec::new(),
592 }
593 }
594
595 fn at(name: &str, offset: u64, sort: Sort, binding: Binding) -> Name {
597 Name {
598 name: name.to_owned(),
599 at: Held::In { part: 0, offset },
600 size: 0,
601 sort,
602 binding,
603 visibility: Visibility::Default,
604 }
605 }
606
607 fn raw(bytes: &[u8], want: &str) -> (u8, u64) {
615 let header = elf::FileHeader64::<Endianness>::parse(bytes).expect("a header");
616 let endian = header.endian().expect("an endianness");
617 let table = header.sections(endian, bytes).expect("the sections");
618 let symbols = table.symbols(endian, bytes, elf::SHT_SYMTAB).expect("a symbol table");
619 for symbol in symbols.iter() {
620 if symbols.symbol_name(endian, symbol).expect("a name") == want.as_bytes() {
621 return (symbol.st_info().0, symbol.st_value(endian));
622 }
623 }
624 panic!("there is no symbol called '{want}'");
625 }
626
627 fn st_info(bytes: &[u8], want: &str) -> u8 {
629 raw(bytes, want).0
630 }
631
632 #[test]
633 fn a_section_carries_the_flags_the_source_said_and_not_the_ones_its_name_suggests() {
634 let mut odd = part(".init.text", vec![0x90]);
638 odd.shape = Shape { alloc: true, exec: true, bits: true, ..Shape::default() };
639 let input = Assembled { parts: vec![odd], names: Vec::new() };
640 let bytes = assembled(&input, &target()).expect("an object");
641 let file = object::File::parse(&bytes[..]).expect("a readable object");
642 let section = file.section_by_name(".init.text").expect("the section");
643 assert_eq!(section.data().expect("the bytes"), &[0x90]);
644 let SectionFlags::Elf { sh_flags, sh_type } = section.flags() else {
645 panic!("this is an ELF file");
646 };
647 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_EXECINSTR.0);
648 assert_eq!(sh_flags.0 & elf::SHF_WRITE.0, 0, "nothing said it was writable");
649 assert_eq!(sh_type, elf::SHT_PROGBITS);
650 }
651
652 #[test]
653 fn a_section_that_holds_no_bytes_still_says_how_long_it_is() {
654 let mut room = part(".bss", Vec::new());
657 room.size = 4096;
658 room.align = 16;
659 let input = Assembled { parts: vec![room], names: Vec::new() };
660 let bytes = assembled(&input, &target()).expect("an object");
661 assert!(bytes.len() < 4096, "the empty space was written out: {} bytes", bytes.len());
662 let file = object::File::parse(&bytes[..]).expect("a readable object");
663 let section = file.section_by_name(".bss").expect("the section");
664 assert_eq!(section.size(), 4096);
665 assert_eq!(section.align(), 16);
666 let SectionFlags::Elf { sh_type, .. } = section.flags() else { panic!("an ELF file") };
667 assert_eq!(sh_type, elf::SHT_NOBITS);
668 }
669
670 #[test]
671 fn a_label_nobody_stated_a_type_for_is_a_symbol_with_no_type() {
672 let input = Assembled {
676 parts: vec![part(".text", vec![0; 8])],
677 names: vec![at("plain", 4, Sort::Untyped, Binding::Global)],
678 };
679 let bytes = assembled(&input, &target()).expect("an object");
680 let file = object::File::parse(&bytes[..]).expect("a readable object");
681 let plain = file.symbols().find(|s| s.name() == Ok("plain")).expect("the label");
682 assert_eq!(plain.address(), 4);
683 assert_eq!(st_info(&bytes, "plain") & 0xf, elf::STT_NOTYPE.0);
684 }
685
686 #[test]
687 fn what_type_said_is_what_the_symbol_gets() {
688 let input = Assembled {
689 parts: vec![part(".text", vec![0; 8])],
690 names: vec![
691 at("run", 0, Sort::Func, Binding::Global),
692 at("held", 4, Sort::Object, Binding::Local),
693 ],
694 };
695 let bytes = assembled(&input, &target()).expect("an object");
696 assert_eq!(st_info(&bytes, "run") & 0xf, elf::STT_FUNC.0);
697 assert_eq!(st_info(&bytes, "held") & 0xf, elf::STT_OBJECT.0);
698 assert_eq!(st_info(&bytes, "run") >> 4, elf::STB_GLOBAL.0);
699 assert_eq!(st_info(&bytes, "held") >> 4, elf::STB_LOCAL.0);
700 }
701
702 #[test]
703 fn a_common_symbol_is_written_the_way_gas_writes_one() {
704 let input = Assembled {
709 parts: Vec::new(),
710 names: vec![Name {
711 name: "shared".to_owned(),
712 at: Held::Common { size: 8, align: 8 },
713 size: 0,
714 sort: Sort::Object,
715 binding: Binding::Global,
716 visibility: Visibility::Default,
717 }],
718 };
719 let bytes = assembled(&input, &target()).expect("an object");
720 assert_eq!(st_info(&bytes, "shared"), elf::STB_GLOBAL.0 << 4 | elf::STT_OBJECT.0);
721 let file = object::File::parse(&bytes[..]).expect("a readable object");
722 let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the symbol");
723 assert!(shared.is_common(), "the linker has to be asked for the space");
724 assert_eq!(shared.size(), 8);
725 assert_eq!(raw(&bytes, "shared").1, 8, "the boundary it has to start on");
727 }
728
729 #[test]
730 fn a_set_is_a_number_rather_than_a_place() {
731 let input = Assembled {
732 parts: vec![part(".text", vec![0; 8])],
733 names: vec![Name {
734 name: "size_of_it".to_owned(),
735 at: Held::Absolute(25),
736 size: 0,
737 sort: Sort::Untyped,
738 binding: Binding::Global,
739 visibility: Visibility::Default,
740 }],
741 };
742 let bytes = assembled(&input, &target()).expect("an object");
743 let file = object::File::parse(&bytes[..]).expect("a readable object");
744 let sym = file.symbols().find(|s| s.name() == Ok("size_of_it")).expect("the symbol");
745 assert_eq!(sym.address(), 25);
746 assert_eq!(sym.section(), object::SymbolSection::Absolute, "it is not in any section");
747 }
748
749 #[test]
750 fn a_relocation_names_a_symbol_and_lands_where_the_bytes_are() {
751 let mut data = part(".data", vec![0; 8]);
752 data.relocs.push(Reloc {
753 at: 0,
754 symbol: "message".to_owned(),
755 kind: Reference::Address { bytes: 8 },
756 addend: 0,
757 after: 0,
758 });
759 let input = Assembled {
760 parts: vec![data],
761 names: vec![Name {
762 name: "message".to_owned(),
763 at: Held::Undefined,
764 size: 0,
765 sort: Sort::Untyped,
766 binding: Binding::Global,
767 visibility: Visibility::Default,
768 }],
769 };
770 let bytes = assembled(&input, &target()).expect("an object");
771 let file = object::File::parse(&bytes[..]).expect("a readable object");
772 let section = file.section_by_name(".data").expect("the section");
773 let (at, reloc) = section.relocations().next().expect("one relocation");
774 assert_eq!(at, 0);
775 assert_eq!(reloc.addend(), 0);
776 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("an ELF file") };
777 assert_eq!(r_type, elf::R_X86_64_64);
778 }
779
780 #[test]
781 fn a_place_only_this_file_sees_is_reached_through_its_section_as_gas_does() {
782 let mut text = part(".text", vec![0; 32]);
786 for (at, symbol, kind) in [
787 (0, ".L3", Reference::Data),
788 (4, "helper", Reference::Data),
789 (8, "helper", Reference::Call),
790 (12, "shared", Reference::Data),
791 ] {
792 let symbol = symbol.to_owned();
793 text.relocs.push(Reloc { at, symbol, kind, addend: -4, after: 0 });
794 }
795 let input = Assembled {
796 parts: vec![text],
797 names: vec![
798 at(".L3", 20, Sort::Untyped, Binding::Local),
799 at("helper", 24, Sort::Func, Binding::Local),
800 at("shared", 28, Sort::Func, Binding::Global),
801 ],
802 };
803 let bytes = assembled(&input, &target()).expect("an object");
804 let file = object::File::parse(&bytes[..]).expect("a readable object");
805 let names: Vec<_> = file.symbols().filter_map(|sym| sym.name().ok()).collect();
806 assert!(!names.contains(&".L3") && names.contains(&"helper"), "{names:?}");
807 let section = file.section_by_name(".text").expect("the section");
808 let reached: Vec<_> = section
809 .relocations()
810 .map(|(at, reloc)| {
811 let object::RelocationTarget::Symbol(index) = reloc.target() else {
812 panic!("a symbol")
813 };
814 let symbol = file.symbol_by_index(index).expect("the symbol");
815 let name = if symbol.kind() == object::SymbolKind::Section {
816 ".text"
817 } else {
818 symbol.name().expect("a name")
819 };
820 (at, name, reloc.addend())
821 })
822 .collect();
823 assert_eq!(
824 reached,
825 [(0, ".text", 16), (4, ".text", 20), (8, "helper", -4), (12, "shared", -4)]
826 );
827 }
828
829 #[test]
830 fn a_section_of_constants_may_be_merged_and_a_distance_into_it_keeps_its_name() {
831 let mut text = part(".text", vec![0; 8]);
832 text.relocs.push(Reloc {
833 at: 0,
834 symbol: ".LC0".to_owned(),
835 kind: Reference::Data,
836 addend: -4,
837 after: 0,
838 });
839 let strings = Part {
840 shape: Shape { merge: 1, strings: true, ..Shape::of(".rodata") },
841 ..part(".rodata.str1.1", b"hi\0".to_vec())
842 };
843 let mut name = at(".LC0", 0, Sort::Untyped, Binding::Local);
844 name.at = Held::In { part: 1, offset: 0 };
845 let input = Assembled { parts: vec![text, strings], names: vec![name] };
846 let bytes = assembled(&input, &target()).expect("an object");
847 let file = object::File::parse(&bytes[..]).expect("a readable object");
848 let section = file.section_by_name(".rodata.str1.1").expect("the section");
849 let SectionFlags::Elf { sh_flags, .. } = section.flags() else { panic!("an ELF file") };
850 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_MERGE.0 | elf::SHF_STRINGS.0);
851 let header = elf::FileHeader64::<Endianness>::parse(&bytes[..]).expect("a header");
852 let endian = header.endian().expect("an endianness");
853 let table = header.sections(endian, &bytes[..]).expect("the sections");
854 let (_, found) = table.section_by_name(endian, b".rodata.str1.1").expect("the section");
855 assert_eq!(found.sh_entsize.get(endian), 1);
856 let text = file.section_by_name(".text").expect("the section");
857 let (_, reloc) = text.relocations().next().expect("one relocation");
858 let object::RelocationTarget::Symbol(index) = reloc.target() else { panic!("a symbol") };
859 assert_eq!(file.symbol_by_index(index).and_then(|sym| sym.name()), Ok(".LC0"));
860 }
861
862 #[test]
863 fn a_relocation_against_a_name_the_file_never_mentions_is_refused() {
864 let mut data = part(".data", vec![0; 8]);
868 data.relocs.push(Reloc {
869 at: 0,
870 symbol: "nowhere".to_owned(),
871 kind: Reference::Address { bytes: 8 },
872 addend: 0,
873 after: 0,
874 });
875 let input = Assembled { parts: vec![data], names: Vec::new() };
876 let why = assembled(&input, &target()).expect_err("this cannot be written");
877 assert!(format!("{why}").contains("nowhere"), "{why}");
878 }
879
880 #[test]
881 fn the_stack_is_marked_once_whoever_asked_for_it() {
882 let bare = Assembled { parts: vec![part(".text", vec![0x90])], names: Vec::new() };
885 let bytes = assembled(&bare, &target()).expect("an object");
886 let file = object::File::parse(&bytes[..]).expect("a readable object");
887 assert!(file.section_by_name(".note.GNU-stack").is_some(), "the marker was left out");
888
889 let said = Assembled {
890 parts: vec![part(".text", vec![0x90]), part(".note.GNU-stack", Vec::new())],
891 names: Vec::new(),
892 };
893 let bytes = assembled(&said, &target()).expect("an object");
894 let file = object::File::parse(&bytes[..]).expect("a readable object");
895 let marks = file.sections().filter(|s| s.name() == Ok(".note.GNU-stack")).count();
896 assert_eq!(marks, 1, "the file said it and it was said again");
897 }
898
899 #[test]
900 fn only_the_names_a_linker_could_find_are_offered_to_an_archive() {
901 let input = Assembled {
902 parts: vec![part(".text", vec![0; 8])],
903 names: vec![
904 at("reachable", 0, Sort::Func, Binding::Global),
905 at("mine", 4, Sort::Func, Binding::Local),
906 Name {
907 name: "elsewhere".to_owned(),
908 at: Held::Undefined,
909 size: 0,
910 sort: Sort::Untyped,
911 binding: Binding::Global,
912 visibility: Visibility::Default,
913 },
914 ],
915 };
916 assert_eq!(assembled_defines(&input), vec!["reachable".to_owned()]);
917 }
918
919 #[test]
920 fn a_machine_this_does_not_write_is_refused_rather_than_written_wrong() {
921 let input = Assembled { parts: vec![part(".text", vec![0x90])], names: Vec::new() };
922 let elsewhere = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Windows, Env::Msvc));
923 let why = assembled(&input, &elsewhere).expect_err("this cannot be written");
924 assert!(format!("{why}").contains("aarch64"), "{why}");
925 }
926
927 #[test]
928 fn a_file_of_assembly_for_aarch64_is_written_with_that_machine_s_relocations() {
929 let mut text = part(".text", vec![0; 12]);
934 let field = |at, symbol: &str, fixup, addend| Reloc {
935 at,
936 symbol: symbol.to_owned(),
937 kind: Reference::Field(fixup),
938 addend,
939 after: 0,
940 };
941 text.relocs = vec![
942 field(0, ".Ltable", Fixup::AdrPage21, 8),
943 field(4, ".Ltable", Fixup::AddLo12, 8),
944 field(8, "g", Fixup::Call26, 0),
945 ];
946 let mut data = part(".data", vec![0; 16]);
947 data.relocs = vec![Reloc {
948 at: 8,
949 symbol: ".Ltable".to_owned(),
950 kind: Reference::Address { bytes: 8 },
951 addend: 0,
952 after: 0,
953 }];
954 let mut table = at(".Ltable", 0, Sort::Object, Binding::Local);
955 table.at = Held::In { part: 1, offset: 0 };
956 let input = Assembled {
957 parts: vec![text, data],
958 names: vec![
959 table,
960 Name { at: Held::Undefined, ..at("g", 0, Sort::Untyped, Binding::Global) },
961 ],
962 };
963 let target = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Linux, Env::Gnu));
964 let bytes = assembled(&input, &target).expect("an object");
965 let file = object::File::parse(&bytes[..]).expect("a readable object");
966 assert_eq!(file.architecture(), Architecture::Aarch64);
967 let relocs = |name: &str| -> Vec<(u64, elf::RelocationType, i64)> {
968 let section = file.section_by_name(name).expect("the section");
969 section
970 .relocations()
971 .map(|(at, reloc)| {
972 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("ELF") };
973 (at, r_type, reloc.addend())
974 })
975 .collect()
976 };
977 assert_eq!(
978 relocs(".text"),
979 [
980 (0, elf::R_AARCH64_ADR_PREL_PG_HI21, 8),
981 (4, elf::R_AARCH64_ADD_ABS_LO12_NC, 8),
982 (8, elf::R_AARCH64_CALL26, 0)
983 ]
984 );
985 assert_eq!(relocs(".data"), [(8, elf::R_AARCH64_ABS64, 0)]);
986 assert!(file.symbols().all(|s| s.name() != Ok(".Ltable")), "a label only this file sees");
987 }
988
989 #[test]
990 fn a_file_of_assembly_for_windows_is_written_as_coff() {
991 let input = Assembled { parts: vec![part(".text", vec![0xc3])], names: Vec::new() };
996 let bytes = assembled(&input, &windows()).expect("an object");
997 let file = object::File::parse(&bytes[..]).expect("a readable object");
998 assert_eq!(file.format(), object::BinaryFormat::Coff);
999 let section = file.section_by_name(".text").expect("the section");
1000 assert_eq!(section.data().expect("the bytes"), &[0xc3]);
1001 assert_eq!(section.kind(), SectionKind::Text);
1002 assert!(
1003 file.section_by_name(".note.GNU-stack").is_none(),
1004 "a format with no marker got one anyway"
1005 );
1006 }
1007
1008 #[test]
1009 fn a_global_label_with_no_type_under_it_is_still_offered_on_coff() {
1010 let input = Assembled {
1017 parts: vec![part(".text", vec![0; 8])],
1018 names: vec![
1019 at("offered", 0, Sort::Untyped, Binding::Global),
1020 at("ours", 4, Sort::Untyped, Binding::Local),
1021 ],
1022 };
1023 let bytes = assembled(&input, &windows()).expect("an object");
1024 let file = object::File::parse(&bytes[..]).expect("a readable object");
1025 let offered = file.symbols().find(|s| s.name() == Ok("offered")).expect("the label");
1026 assert!(offered.is_global(), "a `.globl` label came out local");
1027 let ours = file.symbols().find(|s| s.name() == Ok("ours")).expect("the other label");
1028 assert!(!ours.is_global(), "a label nothing offered came out global");
1029 let bytes = assembled(&input, &target()).expect("an object");
1032 assert_eq!(st_info(&bytes, "offered") & 0xf, elf::STT_NOTYPE.0);
1033 }
1034
1035 #[test]
1036 fn a_relocation_on_coff_says_how_much_of_the_instruction_comes_after_it() {
1037 let mut text = part(".text", vec![0; 16]);
1042 text.relocs.push(Reloc {
1043 at: 2,
1044 symbol: "elsewhere".to_owned(),
1045 kind: Reference::Data,
1046 addend: -8,
1047 after: 4,
1048 });
1049 let input = Assembled {
1050 parts: vec![text],
1051 names: vec![Name {
1052 name: "elsewhere".to_owned(),
1053 at: Held::Undefined,
1054 size: 0,
1055 sort: Sort::Untyped,
1056 binding: Binding::Global,
1057 visibility: Visibility::Default,
1058 }],
1059 };
1060 let bytes = assembled(&input, &windows()).expect("an object");
1061 let file = object::File::parse(&bytes[..]).expect("a readable object");
1062 let section = file.section_by_name(".text").expect("the section");
1063 let (at, reloc) = section.relocations().next().expect("the relocation");
1064 assert_eq!(at, 2);
1065 assert_eq!(
1066 reloc.flags(),
1067 RelocationFlags::Coff {
1068 typ: object::pe::RelocationType(object::pe::IMAGE_REL_AMD64_REL32.0 + 4)
1069 }
1070 );
1071 }
1072}