1use object::write::{Object as Writer, Relocation, Symbol, SymbolSection};
31use object::{Architecture, Endianness, SectionKind, SymbolFlags, elf};
32use rucc_target::TargetInfo;
33use rucc_tuple::Arch;
34
35use crate::file::{Error, Flavour};
36use crate::section::{Array, Binding, Reloc, Visibility};
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct Part {
41 pub name: String,
43 pub bytes: Vec<u8>,
45 pub size: u64,
48 pub align: u64,
50 pub shape: Shape,
52 pub relocs: Vec<Reloc>,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
64pub struct Shape {
65 pub alloc: bool,
68 pub write: bool,
70 pub exec: bool,
72 pub thread: bool,
74 pub bits: bool,
76 pub array: Option<Array>,
78 pub merge: u64,
82 pub strings: bool,
85}
86
87impl Shape {
88 #[must_use]
95 pub fn of(name: &str) -> Shape {
96 let base = Shape { alloc: true, bits: true, ..Shape::default() };
97 let head = name.split_once('.').map_or(name, |(_, rest)| rest);
98 let head = head.split_once('.').map_or(head, |(first, _)| first);
99 match head {
100 "text" | "init" | "fini" => Shape { exec: true, ..base },
101 "rodata" | "eh_frame_hdr" => base,
102 "bss" => Shape { write: true, bits: false, ..base },
103 "tbss" => Shape { write: true, thread: true, bits: false, ..base },
104 "tdata" => Shape { write: true, thread: true, ..base },
105 _ if Array::of(name).is_some() => Shape { write: true, array: Array::of(name), ..base },
109 "debug_info" | "debug_abbrev" | "debug_line" | "debug_str" | "comment" => {
112 Shape { alloc: false, bits: true, ..Shape::default() }
113 }
114 _ => Shape { write: true, ..base },
115 }
116 }
117
118 pub(crate) fn sh_flags(self) -> elf::SectionFlags {
125 let mut flags = 0;
126 if self.alloc {
127 flags |= elf::SHF_ALLOC.0;
128 }
129 if self.write {
130 flags |= elf::SHF_WRITE.0;
131 }
132 if self.exec {
133 flags |= elf::SHF_EXECINSTR.0;
134 }
135 if self.thread {
136 flags |= elf::SHF_TLS.0;
137 }
138 if self.merge != 0 {
139 flags |= elf::SHF_MERGE.0;
140 if self.strings {
141 flags |= elf::SHF_STRINGS.0;
142 }
143 }
144 elf::SectionFlags(flags)
145 }
146
147 pub(crate) fn sh_type(self) -> elf::SectionType {
149 match self.array {
150 _ if !self.bits => elf::SHT_NOBITS,
151 Some(Array::Init) => elf::SHT_INIT_ARRAY,
152 Some(Array::Fini) => elf::SHT_FINI_ARRAY,
153 Some(Array::Preinit) => elf::SHT_PREINIT_ARRAY,
154 None => elf::SHT_PROGBITS,
155 }
156 }
157
158 pub(crate) const fn kind(self) -> SectionKind {
164 match self {
165 Shape { bits: false, thread: true, .. } => SectionKind::UninitializedTls,
166 Shape { bits: false, .. } => SectionKind::UninitializedData,
167 Shape { thread: true, .. } => SectionKind::Tls,
168 Shape { exec: true, .. } => SectionKind::Text,
169 Shape { alloc: false, .. } => SectionKind::Other,
170 Shape { write: false, .. } => SectionKind::ReadOnlyData,
171 Shape { .. } => SectionKind::Data,
172 }
173 }
174}
175
176#[derive(Debug, Clone, PartialEq, Eq)]
178pub struct Name {
179 pub name: String,
181 pub at: Held,
183 pub size: u64,
185 pub sort: Sort,
187 pub binding: Binding,
189 pub visibility: Visibility,
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
195pub enum Held {
196 In {
198 part: usize,
200 offset: u64,
202 },
203 Absolute(u64),
206 Common {
209 size: u64,
211 align: u64,
214 },
215 Undefined,
217}
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
221pub enum Sort {
222 Func,
224 Object,
226 Thread,
228 File,
234 #[default]
237 Untyped,
238}
239
240#[derive(Debug, Clone, Default, PartialEq, Eq)]
242pub struct Assembled {
243 pub parts: Vec<Part>,
245 pub names: Vec<Name>,
247}
248
249pub fn assembled(input: &Assembled, target: &TargetInfo) -> Result<Vec<u8>, Error> {
270 let flavour = match Flavour::of(target) {
271 Some(flavour) if target.tuple.arch() == Arch::X86_64 => flavour,
272 _ => return Err(Error::Format { triple: target.tuple.to_string() }),
273 };
274 let mut obj = Writer::new(flavour.binary(), Architecture::X86_64, Endianness::Little);
275
276 let mut made = Vec::with_capacity(input.parts.len());
279 for part in &input.parts {
280 let id = obj.add_section(Vec::new(), part.name.clone().into_bytes(), part.shape.kind());
281 if let Some(flags) = flavour.stated(part.shape) {
286 obj.section_mut(id).flags = flags;
287 }
288 let align = part.align.max(1);
289 if part.shape.bits {
290 obj.append_section_data(id, &part.bytes, align);
291 } else {
292 obj.append_section_bss(id, part.size, align);
293 }
294 made.push(id);
295 }
296
297 let defined: std::collections::HashMap<&str, &Name> =
300 input.names.iter().map(|name| (name.name.as_str(), name)).collect();
301 let onto = |reloc: &Reloc| moved(flavour, input, &defined, reloc);
302 let wanted: std::collections::HashSet<&str> = input
303 .parts
304 .iter()
305 .flat_map(|part| &part.relocs)
306 .filter(|reloc| onto(reloc).is_none())
307 .map(|reloc| reloc.symbol.as_str())
308 .collect();
309
310 let mut symbols = std::collections::BTreeMap::new();
313 for name in &input.names {
314 if flavour == Flavour::Elf && unseen(name) && !wanted.contains(name.name.as_str()) {
315 continue;
316 }
317 let (section, value, size) = match name.at {
318 Held::In { part, offset } => {
319 let Some(id) = made.get(part) else {
320 let why = format!(
321 "'{}' is in section {part} and there is no such section",
322 name.name
323 );
324 return Err(Error::Refused { why });
325 };
326 (SymbolSection::Section(*id), offset, name.size)
327 }
328 Held::Absolute(value) => (SymbolSection::Absolute, value, name.size),
329 Held::Common { size, align } => (SymbolSection::Common, align, size),
332 Held::Undefined => (SymbolSection::Undefined, 0, 0),
333 };
334 let id = obj.add_symbol(Symbol {
335 name: name.name.clone().into_bytes(),
336 value,
337 size,
338 kind: flavour.sort(name.sort, name.binding),
339 scope: crate::file::scope_of(name.binding),
340 weak: name.binding == Binding::Weak,
341 section,
342 flags: SymbolFlags::None,
343 });
344 flavour.see(&mut obj, id, name.binding, name.visibility);
345 if matches!(name.at, Held::Common { .. }) {
351 if let SymbolFlags::Elf { st_info, .. } = obj.symbol_flags_mut(id) {
352 *st_info = elf::STB_GLOBAL | elf::STT_OBJECT;
353 }
354 }
355 symbols.insert(name.name.clone(), id);
356 }
357
358 for (part, id) in input.parts.iter().zip(&made) {
359 for reloc in &part.relocs {
360 let (symbol, addend) = match onto(reloc) {
361 Some((part, offset)) => {
362 (obj.section_symbol(made[part]), reloc.addend + offset as i64)
363 }
364 None => {
365 let Some(&symbol) = symbols.get(&reloc.symbol) else {
366 let why = format!(
367 "'{}' is named by a relocation and by nothing else",
368 reloc.symbol
369 );
370 return Err(Error::Refused { why });
371 };
372 (symbol, reloc.addend)
373 }
374 };
375 let flags = flavour.reloc(reloc.kind, reloc.after).ok_or_else(|| Error::Refused {
376 why: format!("no relocation is {:?}", reloc.kind),
377 })?;
378 obj.add_relocation(*id, Relocation { offset: reloc.at as u64, symbol, addend, flags })
379 .map_err(|why| Error::Refused { why: why.to_string() })?;
380 }
381 }
382
383 if !input.parts.iter().any(|part| part.name == ".note.GNU-stack") {
388 flavour.marker(&mut obj);
389 }
390
391 let mut bytes = obj.write().map_err(|why| Error::Refused { why: why.to_string() })?;
392 if flavour == Flavour::Elf {
393 for part in input.parts.iter().filter(|part| part.shape.merge != 0) {
394 entry_size(&mut bytes, &part.name, part.shape.merge);
395 }
396 }
397 Ok(bytes)
398}
399
400fn entry_size(bytes: &mut [u8], name: &str, size: u64) {
406 let word = |bytes: &[u8], at: usize, width: usize| {
407 bytes[at..at + width].iter().rev().fold(0u64, |sum, &byte| sum << 8 | u64::from(byte))
408 };
409 let table = word(bytes, 0x28, 8) as usize;
410 let each = word(bytes, 0x3a, 2) as usize;
411 let count = word(bytes, 0x3c, 2) as usize;
412 let names = table + each * word(bytes, 0x3e, 2) as usize;
413 let names = word(bytes, names + 0x18, 8) as usize;
414 for header in (0..count).map(|nth| table + nth * each) {
415 let at = names + word(bytes, header, 4) as usize;
416 if bytes[at..].starts_with(name.as_bytes()) && bytes.get(at + name.len()) == Some(&0) {
417 bytes[header + 0x38..header + 0x40].copy_from_slice(&size.to_le_bytes());
418 }
419 }
420}
421
422fn moved(
433 flavour: Flavour,
434 input: &Assembled,
435 defined: &std::collections::HashMap<&str, &Name>,
436 reloc: &Reloc,
437) -> Option<(usize, u64)> {
438 use crate::section::Reference;
439 let name = defined.get(reloc.symbol.as_str())?;
440 let Held::In { part, offset } = name.at else { return None };
441 if flavour != Flavour::Elf || name.binding != Binding::Local {
442 return None;
443 }
444 let near = matches!(reloc.kind, Reference::Data | Reference::Away);
445 let fixed = match reloc.kind {
446 Reference::Call | Reference::Got | Reference::Thread => false,
447 _ if input.parts.get(part)?.shape.merge != 0 => !near && reloc.addend == 0,
448 _ => true,
449 };
450 fixed.then_some((part, offset))
451}
452
453fn unseen(name: &Name) -> bool {
457 name.binding == Binding::Local
458 && (name.name.starts_with(".L")
459 || name.name.starts_with("..")
460 || name.name.contains('\u{1}'))
461}
462
463#[must_use]
469pub fn assembled_defines(input: &Assembled) -> Vec<String> {
470 input
471 .names
472 .iter()
473 .filter(|name| name.binding != Binding::Local && name.at != Held::Undefined)
474 .map(|name| name.name.clone())
475 .collect()
476}
477
478#[cfg(test)]
479mod tests {
480 use super::*;
481
482 use object::read::elf::{FileHeader as _, Sym as _};
483 use object::read::{Object as _, ObjectSection as _, ObjectSymbol as _};
484 use object::{RelocationFlags, SectionFlags};
485 use rucc_target::{Arch as TargetArch, Env, Os, Triple};
486
487 use crate::section::Reference;
488
489 fn target() -> TargetInfo {
491 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Linux, Env::Gnu))
492 }
493
494 fn windows() -> TargetInfo {
496 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Windows, Env::Gnu))
497 }
498
499 fn part(name: &str, bytes: Vec<u8>) -> Part {
501 Part {
502 name: name.to_owned(),
503 size: bytes.len() as u64,
504 bytes,
505 align: 1,
506 shape: Shape::of(name),
507 relocs: Vec::new(),
508 }
509 }
510
511 fn at(name: &str, offset: u64, sort: Sort, binding: Binding) -> Name {
513 Name {
514 name: name.to_owned(),
515 at: Held::In { part: 0, offset },
516 size: 0,
517 sort,
518 binding,
519 visibility: Visibility::Default,
520 }
521 }
522
523 fn raw(bytes: &[u8], want: &str) -> (u8, u64) {
531 let header = elf::FileHeader64::<Endianness>::parse(bytes).expect("a header");
532 let endian = header.endian().expect("an endianness");
533 let table = header.sections(endian, bytes).expect("the sections");
534 let symbols = table.symbols(endian, bytes, elf::SHT_SYMTAB).expect("a symbol table");
535 for symbol in symbols.iter() {
536 if symbols.symbol_name(endian, symbol).expect("a name") == want.as_bytes() {
537 return (symbol.st_info().0, symbol.st_value(endian));
538 }
539 }
540 panic!("there is no symbol called '{want}'");
541 }
542
543 fn st_info(bytes: &[u8], want: &str) -> u8 {
545 raw(bytes, want).0
546 }
547
548 #[test]
549 fn a_section_carries_the_flags_the_source_said_and_not_the_ones_its_name_suggests() {
550 let mut odd = part(".init.text", vec![0x90]);
554 odd.shape = Shape { alloc: true, exec: true, bits: true, ..Shape::default() };
555 let input = Assembled { parts: vec![odd], names: Vec::new() };
556 let bytes = assembled(&input, &target()).expect("an object");
557 let file = object::File::parse(&bytes[..]).expect("a readable object");
558 let section = file.section_by_name(".init.text").expect("the section");
559 assert_eq!(section.data().expect("the bytes"), &[0x90]);
560 let SectionFlags::Elf { sh_flags, sh_type } = section.flags() else {
561 panic!("this is an ELF file");
562 };
563 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_EXECINSTR.0);
564 assert_eq!(sh_flags.0 & elf::SHF_WRITE.0, 0, "nothing said it was writable");
565 assert_eq!(sh_type, elf::SHT_PROGBITS);
566 }
567
568 #[test]
569 fn a_section_that_holds_no_bytes_still_says_how_long_it_is() {
570 let mut room = part(".bss", Vec::new());
573 room.size = 4096;
574 room.align = 16;
575 let input = Assembled { parts: vec![room], names: Vec::new() };
576 let bytes = assembled(&input, &target()).expect("an object");
577 assert!(bytes.len() < 4096, "the empty space was written out: {} bytes", bytes.len());
578 let file = object::File::parse(&bytes[..]).expect("a readable object");
579 let section = file.section_by_name(".bss").expect("the section");
580 assert_eq!(section.size(), 4096);
581 assert_eq!(section.align(), 16);
582 let SectionFlags::Elf { sh_type, .. } = section.flags() else { panic!("an ELF file") };
583 assert_eq!(sh_type, elf::SHT_NOBITS);
584 }
585
586 #[test]
587 fn a_label_nobody_stated_a_type_for_is_a_symbol_with_no_type() {
588 let input = Assembled {
592 parts: vec![part(".text", vec![0; 8])],
593 names: vec![at("plain", 4, Sort::Untyped, Binding::Global)],
594 };
595 let bytes = assembled(&input, &target()).expect("an object");
596 let file = object::File::parse(&bytes[..]).expect("a readable object");
597 let plain = file.symbols().find(|s| s.name() == Ok("plain")).expect("the label");
598 assert_eq!(plain.address(), 4);
599 assert_eq!(st_info(&bytes, "plain") & 0xf, elf::STT_NOTYPE.0);
600 }
601
602 #[test]
603 fn what_type_said_is_what_the_symbol_gets() {
604 let input = Assembled {
605 parts: vec![part(".text", vec![0; 8])],
606 names: vec![
607 at("run", 0, Sort::Func, Binding::Global),
608 at("held", 4, Sort::Object, Binding::Local),
609 ],
610 };
611 let bytes = assembled(&input, &target()).expect("an object");
612 assert_eq!(st_info(&bytes, "run") & 0xf, elf::STT_FUNC.0);
613 assert_eq!(st_info(&bytes, "held") & 0xf, elf::STT_OBJECT.0);
614 assert_eq!(st_info(&bytes, "run") >> 4, elf::STB_GLOBAL.0);
615 assert_eq!(st_info(&bytes, "held") >> 4, elf::STB_LOCAL.0);
616 }
617
618 #[test]
619 fn a_common_symbol_is_written_the_way_gas_writes_one() {
620 let input = Assembled {
625 parts: Vec::new(),
626 names: vec![Name {
627 name: "shared".to_owned(),
628 at: Held::Common { size: 8, align: 8 },
629 size: 0,
630 sort: Sort::Object,
631 binding: Binding::Global,
632 visibility: Visibility::Default,
633 }],
634 };
635 let bytes = assembled(&input, &target()).expect("an object");
636 assert_eq!(st_info(&bytes, "shared"), elf::STB_GLOBAL.0 << 4 | elf::STT_OBJECT.0);
637 let file = object::File::parse(&bytes[..]).expect("a readable object");
638 let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the symbol");
639 assert!(shared.is_common(), "the linker has to be asked for the space");
640 assert_eq!(shared.size(), 8);
641 assert_eq!(raw(&bytes, "shared").1, 8, "the boundary it has to start on");
643 }
644
645 #[test]
646 fn a_set_is_a_number_rather_than_a_place() {
647 let input = Assembled {
648 parts: vec![part(".text", vec![0; 8])],
649 names: vec![Name {
650 name: "size_of_it".to_owned(),
651 at: Held::Absolute(25),
652 size: 0,
653 sort: Sort::Untyped,
654 binding: Binding::Global,
655 visibility: Visibility::Default,
656 }],
657 };
658 let bytes = assembled(&input, &target()).expect("an object");
659 let file = object::File::parse(&bytes[..]).expect("a readable object");
660 let sym = file.symbols().find(|s| s.name() == Ok("size_of_it")).expect("the symbol");
661 assert_eq!(sym.address(), 25);
662 assert_eq!(sym.section(), object::SymbolSection::Absolute, "it is not in any section");
663 }
664
665 #[test]
666 fn a_relocation_names_a_symbol_and_lands_where_the_bytes_are() {
667 let mut data = part(".data", vec![0; 8]);
668 data.relocs.push(Reloc {
669 at: 0,
670 symbol: "message".to_owned(),
671 kind: Reference::Address { bytes: 8 },
672 addend: 0,
673 after: 0,
674 });
675 let input = Assembled {
676 parts: vec![data],
677 names: vec![Name {
678 name: "message".to_owned(),
679 at: Held::Undefined,
680 size: 0,
681 sort: Sort::Untyped,
682 binding: Binding::Global,
683 visibility: Visibility::Default,
684 }],
685 };
686 let bytes = assembled(&input, &target()).expect("an object");
687 let file = object::File::parse(&bytes[..]).expect("a readable object");
688 let section = file.section_by_name(".data").expect("the section");
689 let (at, reloc) = section.relocations().next().expect("one relocation");
690 assert_eq!(at, 0);
691 assert_eq!(reloc.addend(), 0);
692 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("an ELF file") };
693 assert_eq!(r_type, elf::R_X86_64_64);
694 }
695
696 #[test]
697 fn a_place_only_this_file_sees_is_reached_through_its_section_as_gas_does() {
698 let mut text = part(".text", vec![0; 32]);
702 for (at, symbol, kind) in [
703 (0, ".L3", Reference::Data),
704 (4, "helper", Reference::Data),
705 (8, "helper", Reference::Call),
706 (12, "shared", Reference::Data),
707 ] {
708 let symbol = symbol.to_owned();
709 text.relocs.push(Reloc { at, symbol, kind, addend: -4, after: 0 });
710 }
711 let input = Assembled {
712 parts: vec![text],
713 names: vec![
714 at(".L3", 20, Sort::Untyped, Binding::Local),
715 at("helper", 24, Sort::Func, Binding::Local),
716 at("shared", 28, Sort::Func, Binding::Global),
717 ],
718 };
719 let bytes = assembled(&input, &target()).expect("an object");
720 let file = object::File::parse(&bytes[..]).expect("a readable object");
721 let names: Vec<_> = file.symbols().filter_map(|sym| sym.name().ok()).collect();
722 assert!(!names.contains(&".L3") && names.contains(&"helper"), "{names:?}");
723 let section = file.section_by_name(".text").expect("the section");
724 let reached: Vec<_> = section
725 .relocations()
726 .map(|(at, reloc)| {
727 let object::RelocationTarget::Symbol(index) = reloc.target() else {
728 panic!("a symbol")
729 };
730 let symbol = file.symbol_by_index(index).expect("the symbol");
731 let name = if symbol.kind() == object::SymbolKind::Section {
732 ".text"
733 } else {
734 symbol.name().expect("a name")
735 };
736 (at, name, reloc.addend())
737 })
738 .collect();
739 assert_eq!(
740 reached,
741 [(0, ".text", 16), (4, ".text", 20), (8, "helper", -4), (12, "shared", -4)]
742 );
743 }
744
745 #[test]
746 fn a_section_of_constants_may_be_merged_and_a_distance_into_it_keeps_its_name() {
747 let mut text = part(".text", vec![0; 8]);
748 text.relocs.push(Reloc {
749 at: 0,
750 symbol: ".LC0".to_owned(),
751 kind: Reference::Data,
752 addend: -4,
753 after: 0,
754 });
755 let strings = Part {
756 shape: Shape { merge: 1, strings: true, ..Shape::of(".rodata") },
757 ..part(".rodata.str1.1", b"hi\0".to_vec())
758 };
759 let mut name = at(".LC0", 0, Sort::Untyped, Binding::Local);
760 name.at = Held::In { part: 1, offset: 0 };
761 let input = Assembled { parts: vec![text, strings], names: vec![name] };
762 let bytes = assembled(&input, &target()).expect("an object");
763 let file = object::File::parse(&bytes[..]).expect("a readable object");
764 let section = file.section_by_name(".rodata.str1.1").expect("the section");
765 let SectionFlags::Elf { sh_flags, .. } = section.flags() else { panic!("an ELF file") };
766 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_MERGE.0 | elf::SHF_STRINGS.0);
767 let header = elf::FileHeader64::<Endianness>::parse(&bytes[..]).expect("a header");
768 let endian = header.endian().expect("an endianness");
769 let table = header.sections(endian, &bytes[..]).expect("the sections");
770 let (_, found) = table.section_by_name(endian, b".rodata.str1.1").expect("the section");
771 assert_eq!(found.sh_entsize.get(endian), 1);
772 let text = file.section_by_name(".text").expect("the section");
773 let (_, reloc) = text.relocations().next().expect("one relocation");
774 let object::RelocationTarget::Symbol(index) = reloc.target() else { panic!("a symbol") };
775 assert_eq!(file.symbol_by_index(index).and_then(|sym| sym.name()), Ok(".LC0"));
776 }
777
778 #[test]
779 fn a_relocation_against_a_name_the_file_never_mentions_is_refused() {
780 let mut data = part(".data", vec![0; 8]);
784 data.relocs.push(Reloc {
785 at: 0,
786 symbol: "nowhere".to_owned(),
787 kind: Reference::Address { bytes: 8 },
788 addend: 0,
789 after: 0,
790 });
791 let input = Assembled { parts: vec![data], names: Vec::new() };
792 let why = assembled(&input, &target()).expect_err("this cannot be written");
793 assert!(format!("{why}").contains("nowhere"), "{why}");
794 }
795
796 #[test]
797 fn the_stack_is_marked_once_whoever_asked_for_it() {
798 let bare = Assembled { parts: vec![part(".text", vec![0x90])], names: Vec::new() };
801 let bytes = assembled(&bare, &target()).expect("an object");
802 let file = object::File::parse(&bytes[..]).expect("a readable object");
803 assert!(file.section_by_name(".note.GNU-stack").is_some(), "the marker was left out");
804
805 let said = Assembled {
806 parts: vec![part(".text", vec![0x90]), part(".note.GNU-stack", Vec::new())],
807 names: Vec::new(),
808 };
809 let bytes = assembled(&said, &target()).expect("an object");
810 let file = object::File::parse(&bytes[..]).expect("a readable object");
811 let marks = file.sections().filter(|s| s.name() == Ok(".note.GNU-stack")).count();
812 assert_eq!(marks, 1, "the file said it and it was said again");
813 }
814
815 #[test]
816 fn only_the_names_a_linker_could_find_are_offered_to_an_archive() {
817 let input = Assembled {
818 parts: vec![part(".text", vec![0; 8])],
819 names: vec![
820 at("reachable", 0, Sort::Func, Binding::Global),
821 at("mine", 4, Sort::Func, Binding::Local),
822 Name {
823 name: "elsewhere".to_owned(),
824 at: Held::Undefined,
825 size: 0,
826 sort: Sort::Untyped,
827 binding: Binding::Global,
828 visibility: Visibility::Default,
829 },
830 ],
831 };
832 assert_eq!(assembled_defines(&input), vec!["reachable".to_owned()]);
833 }
834
835 #[test]
836 fn a_machine_this_does_not_write_is_refused_rather_than_written_wrong() {
837 let input = Assembled { parts: vec![part(".text", vec![0x90])], names: Vec::new() };
838 let elsewhere = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Linux, Env::Gnu));
839 let why = assembled(&input, &elsewhere).expect_err("this cannot be written");
840 assert!(format!("{why}").contains("aarch64"), "{why}");
841 }
842
843 #[test]
844 fn a_file_of_assembly_for_windows_is_written_as_coff() {
845 let input = Assembled { parts: vec![part(".text", vec![0xc3])], names: Vec::new() };
850 let bytes = assembled(&input, &windows()).expect("an object");
851 let file = object::File::parse(&bytes[..]).expect("a readable object");
852 assert_eq!(file.format(), object::BinaryFormat::Coff);
853 let section = file.section_by_name(".text").expect("the section");
854 assert_eq!(section.data().expect("the bytes"), &[0xc3]);
855 assert_eq!(section.kind(), SectionKind::Text);
856 assert!(
857 file.section_by_name(".note.GNU-stack").is_none(),
858 "a format with no marker got one anyway"
859 );
860 }
861
862 #[test]
863 fn a_global_label_with_no_type_under_it_is_still_offered_on_coff() {
864 let input = Assembled {
871 parts: vec![part(".text", vec![0; 8])],
872 names: vec![
873 at("offered", 0, Sort::Untyped, Binding::Global),
874 at("ours", 4, Sort::Untyped, Binding::Local),
875 ],
876 };
877 let bytes = assembled(&input, &windows()).expect("an object");
878 let file = object::File::parse(&bytes[..]).expect("a readable object");
879 let offered = file.symbols().find(|s| s.name() == Ok("offered")).expect("the label");
880 assert!(offered.is_global(), "a `.globl` label came out local");
881 let ours = file.symbols().find(|s| s.name() == Ok("ours")).expect("the other label");
882 assert!(!ours.is_global(), "a label nothing offered came out global");
883 let bytes = assembled(&input, &target()).expect("an object");
886 assert_eq!(st_info(&bytes, "offered") & 0xf, elf::STT_NOTYPE.0);
887 }
888
889 #[test]
890 fn a_relocation_on_coff_says_how_much_of_the_instruction_comes_after_it() {
891 let mut text = part(".text", vec![0; 16]);
896 text.relocs.push(Reloc {
897 at: 2,
898 symbol: "elsewhere".to_owned(),
899 kind: Reference::Data,
900 addend: -8,
901 after: 4,
902 });
903 let input = Assembled {
904 parts: vec![text],
905 names: vec![Name {
906 name: "elsewhere".to_owned(),
907 at: Held::Undefined,
908 size: 0,
909 sort: Sort::Untyped,
910 binding: Binding::Global,
911 visibility: Visibility::Default,
912 }],
913 };
914 let bytes = assembled(&input, &windows()).expect("an object");
915 let file = object::File::parse(&bytes[..]).expect("a readable object");
916 let section = file.section_by_name(".text").expect("the section");
917 let (at, reloc) = section.relocations().next().expect("the relocation");
918 assert_eq!(at, 2);
919 assert_eq!(
920 reloc.flags(),
921 RelocationFlags::Coff {
922 typ: object::pe::RelocationType(object::pe::IMAGE_REL_AMD64_REL32.0 + 4)
923 }
924 );
925 }
926}