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, 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 let (flavour, machine) = match (Flavour::of(target), target.tuple.arch()) {
274 (Some(flavour), Arch::X86_64) => (flavour, Architecture::X86_64),
275 (Some(Flavour::Elf), Arch::Aarch64) => (Flavour::Elf, Architecture::Aarch64),
276 _ => return Err(Error::Format { triple: target.tuple.to_string() }),
277 };
278 let flags_of = |kind, after| match machine {
279 Architecture::Aarch64 => {
280 crate::elf::r_type_aarch64(kind).map(|r_type| RelocationFlags::Elf { r_type })
281 }
282 _ => flavour.reloc(kind, after),
283 };
284 let mut obj = Writer::new(flavour.binary(), machine, Endianness::Little);
285
286 let mut made = Vec::with_capacity(input.parts.len());
289 for part in &input.parts {
290 let id = obj.add_section(Vec::new(), part.name.clone().into_bytes(), part.shape.kind());
291 if let Some(flags) = flavour.stated(part.shape) {
296 obj.section_mut(id).flags = flags;
297 }
298 let align = part.align.max(1);
299 if part.shape.bits {
300 obj.append_section_data(id, &part.bytes, align);
301 } else {
302 obj.append_section_bss(id, part.size, align);
303 }
304 made.push(id);
305 }
306
307 let defined: std::collections::HashMap<&str, &Name> =
310 input.names.iter().map(|name| (name.name.as_str(), name)).collect();
311 let onto = |reloc: &Reloc| moved(flavour, input, &defined, reloc);
312 let wanted: std::collections::HashSet<&str> = input
313 .parts
314 .iter()
315 .flat_map(|part| &part.relocs)
316 .filter(|reloc| onto(reloc).is_none())
317 .map(|reloc| reloc.symbol.as_str())
318 .collect();
319
320 let mut symbols = std::collections::BTreeMap::new();
323 for name in &input.names {
324 if flavour == Flavour::Elf && unseen(name) && !wanted.contains(name.name.as_str()) {
325 continue;
326 }
327 let (section, value, size) = match name.at {
328 Held::In { part, offset } => {
329 let Some(id) = made.get(part) else {
330 let why = format!(
331 "'{}' is in section {part} and there is no such section",
332 name.name
333 );
334 return Err(Error::Refused { why });
335 };
336 (SymbolSection::Section(*id), offset, name.size)
337 }
338 Held::Absolute(value) => (SymbolSection::Absolute, value, name.size),
339 Held::Common { size, align } => (SymbolSection::Common, align, size),
342 Held::Undefined => (SymbolSection::Undefined, 0, 0),
343 };
344 let id = obj.add_symbol(Symbol {
345 name: name.name.clone().into_bytes(),
346 value,
347 size,
348 kind: flavour.sort(name.sort, name.binding),
349 scope: crate::file::scope_of(name.binding),
350 weak: name.binding == Binding::Weak,
351 section,
352 flags: SymbolFlags::None,
353 });
354 flavour.see(&mut obj, id, name.binding, name.visibility);
355 if matches!(name.at, Held::Common { .. }) {
361 if let SymbolFlags::Elf { st_info, .. } = obj.symbol_flags_mut(id) {
362 *st_info = elf::STB_GLOBAL | elf::STT_OBJECT;
363 }
364 }
365 symbols.insert(name.name.clone(), id);
366 }
367
368 for (part, id) in input.parts.iter().zip(&made) {
369 for reloc in &part.relocs {
370 let (symbol, addend) = match onto(reloc) {
371 Some((part, offset)) => {
372 (obj.section_symbol(made[part]), reloc.addend + offset as i64)
373 }
374 None => {
375 let Some(&symbol) = symbols.get(&reloc.symbol) else {
376 let why = format!(
377 "'{}' is named by a relocation and by nothing else",
378 reloc.symbol
379 );
380 return Err(Error::Refused { why });
381 };
382 (symbol, reloc.addend)
383 }
384 };
385 let flags = flags_of(reloc.kind, reloc.after).ok_or_else(|| Error::Refused {
386 why: format!("no relocation is {:?}", reloc.kind),
387 })?;
388 obj.add_relocation(*id, Relocation { offset: reloc.at as u64, symbol, addend, flags })
389 .map_err(|why| Error::Refused { why: why.to_string() })?;
390 }
391 }
392
393 if !input.parts.iter().any(|part| part.name == ".note.GNU-stack") {
398 flavour.marker(&mut obj);
399 }
400
401 let mut bytes = obj.write().map_err(|why| Error::Refused { why: why.to_string() })?;
402 if flavour == Flavour::Elf {
403 for part in input.parts.iter().filter(|part| part.shape.merge != 0) {
404 entry_size(&mut bytes, &part.name, part.shape.merge);
405 }
406 }
407 Ok(bytes)
408}
409
410fn entry_size(bytes: &mut [u8], name: &str, size: u64) {
416 let word = |bytes: &[u8], at: usize, width: usize| {
417 bytes[at..at + width].iter().rev().fold(0u64, |sum, &byte| sum << 8 | u64::from(byte))
418 };
419 let table = word(bytes, 0x28, 8) as usize;
420 let each = word(bytes, 0x3a, 2) as usize;
421 let count = word(bytes, 0x3c, 2) as usize;
422 let names = table + each * word(bytes, 0x3e, 2) as usize;
423 let names = word(bytes, names + 0x18, 8) as usize;
424 for header in (0..count).map(|nth| table + nth * each) {
425 let at = names + word(bytes, header, 4) as usize;
426 if bytes[at..].starts_with(name.as_bytes()) && bytes.get(at + name.len()) == Some(&0) {
427 bytes[header + 0x38..header + 0x40].copy_from_slice(&size.to_le_bytes());
428 }
429 }
430}
431
432fn moved(
443 flavour: Flavour,
444 input: &Assembled,
445 defined: &std::collections::HashMap<&str, &Name>,
446 reloc: &Reloc,
447) -> Option<(usize, u64)> {
448 use crate::section::Reference;
449 let name = defined.get(reloc.symbol.as_str())?;
450 let Held::In { part, offset } = name.at else { return None };
451 if flavour != Flavour::Elf || name.binding != Binding::Local {
452 return None;
453 }
454 let near = matches!(reloc.kind, Reference::Data | Reference::Away);
455 let fixed = match reloc.kind {
456 Reference::Call
457 | Reference::Got
458 | Reference::GotBare
459 | Reference::GotKept
460 | Reference::Thread => false,
461 Reference::Field(
464 Fixup::Call26
465 | Fixup::Jump26
466 | Fixup::GotPage21
467 | Fixup::GotLo12
468 | Fixup::GotTprelPage21
469 | Fixup::GotTprelLo12Nc
470 | Fixup::TprelHi12
471 | Fixup::TprelLo12Nc,
472 ) => false,
473 _ if input.parts.get(part)?.shape.merge != 0 => !near && reloc.addend == 0,
474 _ => true,
475 };
476 fixed.then_some((part, offset))
477}
478
479fn unseen(name: &Name) -> bool {
483 name.binding == Binding::Local
484 && (name.name.starts_with(".L")
485 || name.name.starts_with("..")
486 || name.name.contains('\u{1}'))
487}
488
489#[must_use]
495pub fn assembled_defines(input: &Assembled) -> Vec<String> {
496 input
497 .names
498 .iter()
499 .filter(|name| name.binding != Binding::Local && name.at != Held::Undefined)
500 .map(|name| name.name.clone())
501 .collect()
502}
503
504#[cfg(test)]
505mod tests {
506 use super::*;
507
508 use object::read::elf::{FileHeader as _, Sym as _};
509 use object::read::{Object as _, ObjectSection as _, ObjectSymbol as _};
510 use object::{RelocationFlags, SectionFlags};
511 use rucc_target::{Arch as TargetArch, Env, Os, Triple};
512
513 use crate::section::Reference;
514
515 fn target() -> TargetInfo {
517 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Linux, Env::Gnu))
518 }
519
520 fn windows() -> TargetInfo {
522 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Windows, Env::Gnu))
523 }
524
525 fn part(name: &str, bytes: Vec<u8>) -> Part {
527 Part {
528 name: name.to_owned(),
529 size: bytes.len() as u64,
530 bytes,
531 align: 1,
532 shape: Shape::of(name),
533 relocs: Vec::new(),
534 }
535 }
536
537 fn at(name: &str, offset: u64, sort: Sort, binding: Binding) -> Name {
539 Name {
540 name: name.to_owned(),
541 at: Held::In { part: 0, offset },
542 size: 0,
543 sort,
544 binding,
545 visibility: Visibility::Default,
546 }
547 }
548
549 fn raw(bytes: &[u8], want: &str) -> (u8, u64) {
557 let header = elf::FileHeader64::<Endianness>::parse(bytes).expect("a header");
558 let endian = header.endian().expect("an endianness");
559 let table = header.sections(endian, bytes).expect("the sections");
560 let symbols = table.symbols(endian, bytes, elf::SHT_SYMTAB).expect("a symbol table");
561 for symbol in symbols.iter() {
562 if symbols.symbol_name(endian, symbol).expect("a name") == want.as_bytes() {
563 return (symbol.st_info().0, symbol.st_value(endian));
564 }
565 }
566 panic!("there is no symbol called '{want}'");
567 }
568
569 fn st_info(bytes: &[u8], want: &str) -> u8 {
571 raw(bytes, want).0
572 }
573
574 #[test]
575 fn a_section_carries_the_flags_the_source_said_and_not_the_ones_its_name_suggests() {
576 let mut odd = part(".init.text", vec![0x90]);
580 odd.shape = Shape { alloc: true, exec: true, bits: true, ..Shape::default() };
581 let input = Assembled { parts: vec![odd], names: Vec::new() };
582 let bytes = assembled(&input, &target()).expect("an object");
583 let file = object::File::parse(&bytes[..]).expect("a readable object");
584 let section = file.section_by_name(".init.text").expect("the section");
585 assert_eq!(section.data().expect("the bytes"), &[0x90]);
586 let SectionFlags::Elf { sh_flags, sh_type } = section.flags() else {
587 panic!("this is an ELF file");
588 };
589 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_EXECINSTR.0);
590 assert_eq!(sh_flags.0 & elf::SHF_WRITE.0, 0, "nothing said it was writable");
591 assert_eq!(sh_type, elf::SHT_PROGBITS);
592 }
593
594 #[test]
595 fn a_section_that_holds_no_bytes_still_says_how_long_it_is() {
596 let mut room = part(".bss", Vec::new());
599 room.size = 4096;
600 room.align = 16;
601 let input = Assembled { parts: vec![room], names: Vec::new() };
602 let bytes = assembled(&input, &target()).expect("an object");
603 assert!(bytes.len() < 4096, "the empty space was written out: {} bytes", bytes.len());
604 let file = object::File::parse(&bytes[..]).expect("a readable object");
605 let section = file.section_by_name(".bss").expect("the section");
606 assert_eq!(section.size(), 4096);
607 assert_eq!(section.align(), 16);
608 let SectionFlags::Elf { sh_type, .. } = section.flags() else { panic!("an ELF file") };
609 assert_eq!(sh_type, elf::SHT_NOBITS);
610 }
611
612 #[test]
613 fn a_label_nobody_stated_a_type_for_is_a_symbol_with_no_type() {
614 let input = Assembled {
618 parts: vec![part(".text", vec![0; 8])],
619 names: vec![at("plain", 4, Sort::Untyped, Binding::Global)],
620 };
621 let bytes = assembled(&input, &target()).expect("an object");
622 let file = object::File::parse(&bytes[..]).expect("a readable object");
623 let plain = file.symbols().find(|s| s.name() == Ok("plain")).expect("the label");
624 assert_eq!(plain.address(), 4);
625 assert_eq!(st_info(&bytes, "plain") & 0xf, elf::STT_NOTYPE.0);
626 }
627
628 #[test]
629 fn what_type_said_is_what_the_symbol_gets() {
630 let input = Assembled {
631 parts: vec![part(".text", vec![0; 8])],
632 names: vec![
633 at("run", 0, Sort::Func, Binding::Global),
634 at("held", 4, Sort::Object, Binding::Local),
635 ],
636 };
637 let bytes = assembled(&input, &target()).expect("an object");
638 assert_eq!(st_info(&bytes, "run") & 0xf, elf::STT_FUNC.0);
639 assert_eq!(st_info(&bytes, "held") & 0xf, elf::STT_OBJECT.0);
640 assert_eq!(st_info(&bytes, "run") >> 4, elf::STB_GLOBAL.0);
641 assert_eq!(st_info(&bytes, "held") >> 4, elf::STB_LOCAL.0);
642 }
643
644 #[test]
645 fn a_common_symbol_is_written_the_way_gas_writes_one() {
646 let input = Assembled {
651 parts: Vec::new(),
652 names: vec![Name {
653 name: "shared".to_owned(),
654 at: Held::Common { size: 8, align: 8 },
655 size: 0,
656 sort: Sort::Object,
657 binding: Binding::Global,
658 visibility: Visibility::Default,
659 }],
660 };
661 let bytes = assembled(&input, &target()).expect("an object");
662 assert_eq!(st_info(&bytes, "shared"), elf::STB_GLOBAL.0 << 4 | elf::STT_OBJECT.0);
663 let file = object::File::parse(&bytes[..]).expect("a readable object");
664 let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the symbol");
665 assert!(shared.is_common(), "the linker has to be asked for the space");
666 assert_eq!(shared.size(), 8);
667 assert_eq!(raw(&bytes, "shared").1, 8, "the boundary it has to start on");
669 }
670
671 #[test]
672 fn a_set_is_a_number_rather_than_a_place() {
673 let input = Assembled {
674 parts: vec![part(".text", vec![0; 8])],
675 names: vec![Name {
676 name: "size_of_it".to_owned(),
677 at: Held::Absolute(25),
678 size: 0,
679 sort: Sort::Untyped,
680 binding: Binding::Global,
681 visibility: Visibility::Default,
682 }],
683 };
684 let bytes = assembled(&input, &target()).expect("an object");
685 let file = object::File::parse(&bytes[..]).expect("a readable object");
686 let sym = file.symbols().find(|s| s.name() == Ok("size_of_it")).expect("the symbol");
687 assert_eq!(sym.address(), 25);
688 assert_eq!(sym.section(), object::SymbolSection::Absolute, "it is not in any section");
689 }
690
691 #[test]
692 fn a_relocation_names_a_symbol_and_lands_where_the_bytes_are() {
693 let mut data = part(".data", vec![0; 8]);
694 data.relocs.push(Reloc {
695 at: 0,
696 symbol: "message".to_owned(),
697 kind: Reference::Address { bytes: 8 },
698 addend: 0,
699 after: 0,
700 });
701 let input = Assembled {
702 parts: vec![data],
703 names: vec![Name {
704 name: "message".to_owned(),
705 at: Held::Undefined,
706 size: 0,
707 sort: Sort::Untyped,
708 binding: Binding::Global,
709 visibility: Visibility::Default,
710 }],
711 };
712 let bytes = assembled(&input, &target()).expect("an object");
713 let file = object::File::parse(&bytes[..]).expect("a readable object");
714 let section = file.section_by_name(".data").expect("the section");
715 let (at, reloc) = section.relocations().next().expect("one relocation");
716 assert_eq!(at, 0);
717 assert_eq!(reloc.addend(), 0);
718 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("an ELF file") };
719 assert_eq!(r_type, elf::R_X86_64_64);
720 }
721
722 #[test]
723 fn a_place_only_this_file_sees_is_reached_through_its_section_as_gas_does() {
724 let mut text = part(".text", vec![0; 32]);
728 for (at, symbol, kind) in [
729 (0, ".L3", Reference::Data),
730 (4, "helper", Reference::Data),
731 (8, "helper", Reference::Call),
732 (12, "shared", Reference::Data),
733 ] {
734 let symbol = symbol.to_owned();
735 text.relocs.push(Reloc { at, symbol, kind, addend: -4, after: 0 });
736 }
737 let input = Assembled {
738 parts: vec![text],
739 names: vec![
740 at(".L3", 20, Sort::Untyped, Binding::Local),
741 at("helper", 24, Sort::Func, Binding::Local),
742 at("shared", 28, Sort::Func, Binding::Global),
743 ],
744 };
745 let bytes = assembled(&input, &target()).expect("an object");
746 let file = object::File::parse(&bytes[..]).expect("a readable object");
747 let names: Vec<_> = file.symbols().filter_map(|sym| sym.name().ok()).collect();
748 assert!(!names.contains(&".L3") && names.contains(&"helper"), "{names:?}");
749 let section = file.section_by_name(".text").expect("the section");
750 let reached: Vec<_> = section
751 .relocations()
752 .map(|(at, reloc)| {
753 let object::RelocationTarget::Symbol(index) = reloc.target() else {
754 panic!("a symbol")
755 };
756 let symbol = file.symbol_by_index(index).expect("the symbol");
757 let name = if symbol.kind() == object::SymbolKind::Section {
758 ".text"
759 } else {
760 symbol.name().expect("a name")
761 };
762 (at, name, reloc.addend())
763 })
764 .collect();
765 assert_eq!(
766 reached,
767 [(0, ".text", 16), (4, ".text", 20), (8, "helper", -4), (12, "shared", -4)]
768 );
769 }
770
771 #[test]
772 fn a_section_of_constants_may_be_merged_and_a_distance_into_it_keeps_its_name() {
773 let mut text = part(".text", vec![0; 8]);
774 text.relocs.push(Reloc {
775 at: 0,
776 symbol: ".LC0".to_owned(),
777 kind: Reference::Data,
778 addend: -4,
779 after: 0,
780 });
781 let strings = Part {
782 shape: Shape { merge: 1, strings: true, ..Shape::of(".rodata") },
783 ..part(".rodata.str1.1", b"hi\0".to_vec())
784 };
785 let mut name = at(".LC0", 0, Sort::Untyped, Binding::Local);
786 name.at = Held::In { part: 1, offset: 0 };
787 let input = Assembled { parts: vec![text, strings], names: vec![name] };
788 let bytes = assembled(&input, &target()).expect("an object");
789 let file = object::File::parse(&bytes[..]).expect("a readable object");
790 let section = file.section_by_name(".rodata.str1.1").expect("the section");
791 let SectionFlags::Elf { sh_flags, .. } = section.flags() else { panic!("an ELF file") };
792 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_MERGE.0 | elf::SHF_STRINGS.0);
793 let header = elf::FileHeader64::<Endianness>::parse(&bytes[..]).expect("a header");
794 let endian = header.endian().expect("an endianness");
795 let table = header.sections(endian, &bytes[..]).expect("the sections");
796 let (_, found) = table.section_by_name(endian, b".rodata.str1.1").expect("the section");
797 assert_eq!(found.sh_entsize.get(endian), 1);
798 let text = file.section_by_name(".text").expect("the section");
799 let (_, reloc) = text.relocations().next().expect("one relocation");
800 let object::RelocationTarget::Symbol(index) = reloc.target() else { panic!("a symbol") };
801 assert_eq!(file.symbol_by_index(index).and_then(|sym| sym.name()), Ok(".LC0"));
802 }
803
804 #[test]
805 fn a_relocation_against_a_name_the_file_never_mentions_is_refused() {
806 let mut data = part(".data", vec![0; 8]);
810 data.relocs.push(Reloc {
811 at: 0,
812 symbol: "nowhere".to_owned(),
813 kind: Reference::Address { bytes: 8 },
814 addend: 0,
815 after: 0,
816 });
817 let input = Assembled { parts: vec![data], names: Vec::new() };
818 let why = assembled(&input, &target()).expect_err("this cannot be written");
819 assert!(format!("{why}").contains("nowhere"), "{why}");
820 }
821
822 #[test]
823 fn the_stack_is_marked_once_whoever_asked_for_it() {
824 let bare = Assembled { parts: vec![part(".text", vec![0x90])], names: Vec::new() };
827 let bytes = assembled(&bare, &target()).expect("an object");
828 let file = object::File::parse(&bytes[..]).expect("a readable object");
829 assert!(file.section_by_name(".note.GNU-stack").is_some(), "the marker was left out");
830
831 let said = Assembled {
832 parts: vec![part(".text", vec![0x90]), part(".note.GNU-stack", Vec::new())],
833 names: Vec::new(),
834 };
835 let bytes = assembled(&said, &target()).expect("an object");
836 let file = object::File::parse(&bytes[..]).expect("a readable object");
837 let marks = file.sections().filter(|s| s.name() == Ok(".note.GNU-stack")).count();
838 assert_eq!(marks, 1, "the file said it and it was said again");
839 }
840
841 #[test]
842 fn only_the_names_a_linker_could_find_are_offered_to_an_archive() {
843 let input = Assembled {
844 parts: vec![part(".text", vec![0; 8])],
845 names: vec![
846 at("reachable", 0, Sort::Func, Binding::Global),
847 at("mine", 4, Sort::Func, Binding::Local),
848 Name {
849 name: "elsewhere".to_owned(),
850 at: Held::Undefined,
851 size: 0,
852 sort: Sort::Untyped,
853 binding: Binding::Global,
854 visibility: Visibility::Default,
855 },
856 ],
857 };
858 assert_eq!(assembled_defines(&input), vec!["reachable".to_owned()]);
859 }
860
861 #[test]
862 fn a_machine_this_does_not_write_is_refused_rather_than_written_wrong() {
863 let input = Assembled { parts: vec![part(".text", vec![0x90])], names: Vec::new() };
864 let elsewhere = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Windows, Env::Msvc));
865 let why = assembled(&input, &elsewhere).expect_err("this cannot be written");
866 assert!(format!("{why}").contains("aarch64"), "{why}");
867 }
868
869 #[test]
870 fn a_file_of_assembly_for_aarch64_is_written_with_that_machine_s_relocations() {
871 let mut text = part(".text", vec![0; 12]);
876 let field = |at, symbol: &str, fixup, addend| Reloc {
877 at,
878 symbol: symbol.to_owned(),
879 kind: Reference::Field(fixup),
880 addend,
881 after: 0,
882 };
883 text.relocs = vec![
884 field(0, ".Ltable", Fixup::AdrPage21, 8),
885 field(4, ".Ltable", Fixup::AddLo12, 8),
886 field(8, "g", Fixup::Call26, 0),
887 ];
888 let mut data = part(".data", vec![0; 16]);
889 data.relocs = vec![Reloc {
890 at: 8,
891 symbol: ".Ltable".to_owned(),
892 kind: Reference::Address { bytes: 8 },
893 addend: 0,
894 after: 0,
895 }];
896 let mut table = at(".Ltable", 0, Sort::Object, Binding::Local);
897 table.at = Held::In { part: 1, offset: 0 };
898 let input = Assembled {
899 parts: vec![text, data],
900 names: vec![
901 table,
902 Name { at: Held::Undefined, ..at("g", 0, Sort::Untyped, Binding::Global) },
903 ],
904 };
905 let target = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Linux, Env::Gnu));
906 let bytes = assembled(&input, &target).expect("an object");
907 let file = object::File::parse(&bytes[..]).expect("a readable object");
908 assert_eq!(file.architecture(), Architecture::Aarch64);
909 let relocs = |name: &str| -> Vec<(u64, elf::RelocationType, i64)> {
910 let section = file.section_by_name(name).expect("the section");
911 section
912 .relocations()
913 .map(|(at, reloc)| {
914 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("ELF") };
915 (at, r_type, reloc.addend())
916 })
917 .collect()
918 };
919 assert_eq!(
920 relocs(".text"),
921 [
922 (0, elf::R_AARCH64_ADR_PREL_PG_HI21, 8),
923 (4, elf::R_AARCH64_ADD_ABS_LO12_NC, 8),
924 (8, elf::R_AARCH64_CALL26, 0)
925 ]
926 );
927 assert_eq!(relocs(".data"), [(8, elf::R_AARCH64_ABS64, 0)]);
928 assert!(file.symbols().all(|s| s.name() != Ok(".Ltable")), "a label only this file sees");
929 }
930
931 #[test]
932 fn a_file_of_assembly_for_windows_is_written_as_coff() {
933 let input = Assembled { parts: vec![part(".text", vec![0xc3])], names: Vec::new() };
938 let bytes = assembled(&input, &windows()).expect("an object");
939 let file = object::File::parse(&bytes[..]).expect("a readable object");
940 assert_eq!(file.format(), object::BinaryFormat::Coff);
941 let section = file.section_by_name(".text").expect("the section");
942 assert_eq!(section.data().expect("the bytes"), &[0xc3]);
943 assert_eq!(section.kind(), SectionKind::Text);
944 assert!(
945 file.section_by_name(".note.GNU-stack").is_none(),
946 "a format with no marker got one anyway"
947 );
948 }
949
950 #[test]
951 fn a_global_label_with_no_type_under_it_is_still_offered_on_coff() {
952 let input = Assembled {
959 parts: vec![part(".text", vec![0; 8])],
960 names: vec![
961 at("offered", 0, Sort::Untyped, Binding::Global),
962 at("ours", 4, Sort::Untyped, Binding::Local),
963 ],
964 };
965 let bytes = assembled(&input, &windows()).expect("an object");
966 let file = object::File::parse(&bytes[..]).expect("a readable object");
967 let offered = file.symbols().find(|s| s.name() == Ok("offered")).expect("the label");
968 assert!(offered.is_global(), "a `.globl` label came out local");
969 let ours = file.symbols().find(|s| s.name() == Ok("ours")).expect("the other label");
970 assert!(!ours.is_global(), "a label nothing offered came out global");
971 let bytes = assembled(&input, &target()).expect("an object");
974 assert_eq!(st_info(&bytes, "offered") & 0xf, elf::STT_NOTYPE.0);
975 }
976
977 #[test]
978 fn a_relocation_on_coff_says_how_much_of_the_instruction_comes_after_it() {
979 let mut text = part(".text", vec![0; 16]);
984 text.relocs.push(Reloc {
985 at: 2,
986 symbol: "elsewhere".to_owned(),
987 kind: Reference::Data,
988 addend: -8,
989 after: 4,
990 });
991 let input = Assembled {
992 parts: vec![text],
993 names: vec![Name {
994 name: "elsewhere".to_owned(),
995 at: Held::Undefined,
996 size: 0,
997 sort: Sort::Untyped,
998 binding: Binding::Global,
999 visibility: Visibility::Default,
1000 }],
1001 };
1002 let bytes = assembled(&input, &windows()).expect("an object");
1003 let file = object::File::parse(&bytes[..]).expect("a readable object");
1004 let section = file.section_by_name(".text").expect("the section");
1005 let (at, reloc) = section.relocations().next().expect("the relocation");
1006 assert_eq!(at, 2);
1007 assert_eq!(
1008 reloc.flags(),
1009 RelocationFlags::Coff {
1010 typ: object::pe::RelocationType(object::pe::IMAGE_REL_AMD64_REL32.0 + 4)
1011 }
1012 );
1013 }
1014}