1use object::write::{Object as Writer, Relocation, Symbol, SymbolSection};
31use object::{Architecture, Endianness, RelocationFlags, SectionKind, SymbolFlags, elf, pe};
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 pub group: Option<Group>,
59}
60
61#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct Group {
65 pub symbol: String,
67 pub keep: Keep,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum Keep {
75 One,
77 Any,
79 SameSize,
81 SameContents,
83 Largest,
85 Newest,
87}
88
89impl Keep {
90 #[must_use]
92 pub fn of(word: &str) -> Option<Keep> {
93 Some(match word {
94 "one_only" => Keep::One,
95 "discard" => Keep::Any,
96 "same_size" => Keep::SameSize,
97 "same_contents" => Keep::SameContents,
98 "largest" => Keep::Largest,
99 "newest" => Keep::Newest,
100 _ => return None,
101 })
102 }
103
104 pub(crate) const fn kind(self) -> object::ComdatKind {
105 match self {
106 Keep::One => object::ComdatKind::NoDuplicates,
107 Keep::Any => object::ComdatKind::Any,
108 Keep::SameSize => object::ComdatKind::SameSize,
109 Keep::SameContents => object::ComdatKind::ExactMatch,
110 Keep::Largest => object::ComdatKind::Largest,
111 Keep::Newest => object::ComdatKind::Newest,
112 }
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
124pub struct Shape {
125 pub alloc: bool,
128 pub write: bool,
130 pub exec: bool,
132 pub thread: bool,
134 pub bits: bool,
136 pub array: Option<Array>,
138 pub merge: u64,
142 pub strings: bool,
145 pub mach: u32,
149 pub coff: u32,
153}
154
155impl Shape {
156 #[must_use]
163 pub fn of(name: &str) -> Shape {
164 let base = Shape { alloc: true, bits: true, ..Shape::default() };
165 let head = name.split_once('.').map_or(name, |(_, rest)| rest);
166 let head = head.split_once('.').map_or(head, |(first, _)| first);
167 match head {
168 "text" | "init" | "fini" => Shape { exec: true, ..base },
169 "rodata" | "eh_frame_hdr" => base,
170 "bss" => Shape { write: true, bits: false, ..base },
171 "tbss" => Shape { write: true, thread: true, bits: false, ..base },
172 "tdata" => Shape { write: true, thread: true, ..base },
173 _ if Array::of(name).is_some() => Shape { write: true, array: Array::of(name), ..base },
177 "debug_info" | "debug_abbrev" | "debug_line" | "debug_str" | "comment" => {
180 Shape { alloc: false, bits: true, ..Shape::default() }
181 }
182 _ => Shape { write: true, ..base },
183 }
184 }
185
186 #[must_use]
194 pub fn implied(name: &str) -> Shape {
195 let base = Shape { alloc: true, ..Shape::default() };
196 let head = name.split_once('.').map_or(name, |(_, rest)| rest);
197 let head = head.split_once('.').map_or(head, |(first, _)| first);
198 match head {
199 "text" => Shape { exec: true, ..base },
200 "rodata" => base,
201 "data" | "bss" => Shape { write: true, ..base },
202 "tdata" | "tbss" => Shape { write: true, thread: true, ..base },
203 _ if Array::of(name).is_some() => Shape { write: true, ..base },
204 _ => Shape::default(),
205 }
206 }
207
208 pub fn mach(
219 segment: &str,
220 section: &str,
221 kind: Option<&str>,
222 attributes: &[&str],
223 ) -> Result<Shape, String> {
224 let mach = crate::macho::section_flags(segment, section, kind, attributes)?;
225 let exec = mach & object::macho::S_ATTR_PURE_INSTRUCTIONS.0 != 0;
226 let typ = object::macho::SectionFlags(mach).typ();
227 let thread = matches!(
228 typ,
229 object::macho::S_THREAD_LOCAL_REGULAR | object::macho::S_THREAD_LOCAL_ZEROFILL
230 );
231 Ok(Shape {
232 alloc: true,
233 write: segment != "__TEXT",
234 exec,
235 thread,
236 bits: !crate::macho::zero_filled(mach),
237 mach,
238 ..Shape::default()
239 })
240 }
241
242 pub fn coff(letters: &str) -> Result<Shape, char> {
255 let (mut code, mut data, mut zero, mut drop, mut info) =
256 (false, false, false, false, false);
257 let (mut read, mut write, mut shared, mut discard) = (true, true, false, false);
258 let mut writable = false;
259 for letter in letters.chars() {
260 match letter {
261 'a' => {}
262 'b' => zero = true,
263 'd' => {
264 data = true;
265 write = true;
266 }
267 'n' => drop = true,
268 'D' => discard = true,
269 'r' => {
270 writable = false;
271 write = false;
272 data |= !code;
273 }
274 's' => {
275 shared = true;
276 data = true;
277 write = true;
278 }
279 'w' => {
280 write = true;
281 writable = true;
282 }
283 'x' => {
284 code = true;
285 write &= writable;
286 }
287 'y' => {
288 read = false;
289 write = false;
290 }
291 'i' => info = true,
292 other => return Err(other),
293 }
294 }
295 let mut flags = 0;
296 if code {
297 flags |= pe::IMAGE_SCN_CNT_CODE.0 | pe::IMAGE_SCN_MEM_EXECUTE.0;
298 }
299 if data {
300 flags |= pe::IMAGE_SCN_CNT_INITIALIZED_DATA.0;
301 }
302 if zero && !data {
303 flags |= pe::IMAGE_SCN_CNT_UNINITIALIZED_DATA.0;
304 }
305 if drop {
306 flags |= pe::IMAGE_SCN_LNK_REMOVE.0;
307 }
308 if read {
309 flags |= pe::IMAGE_SCN_MEM_READ.0;
310 }
311 if write {
312 flags |= pe::IMAGE_SCN_MEM_WRITE.0;
313 }
314 if discard {
315 flags |= pe::IMAGE_SCN_MEM_DISCARDABLE.0;
316 }
317 if shared {
318 flags |= pe::IMAGE_SCN_MEM_SHARED.0;
319 }
320 if info {
321 flags |= pe::IMAGE_SCN_LNK_INFO.0;
322 }
323 Ok(Shape {
324 alloc: !drop && !info,
325 write,
326 exec: code,
327 bits: !(zero && !data),
328 coff: flags,
329 ..Shape::default()
330 })
331 }
332
333 pub(crate) fn sh_flags(self) -> elf::SectionFlags {
340 let mut flags = 0;
341 if self.alloc {
342 flags |= elf::SHF_ALLOC.0;
343 }
344 if self.write {
345 flags |= elf::SHF_WRITE.0;
346 }
347 if self.exec {
348 flags |= elf::SHF_EXECINSTR.0;
349 }
350 if self.thread {
351 flags |= elf::SHF_TLS.0;
352 }
353 if self.merge != 0 {
354 flags |= elf::SHF_MERGE.0;
355 if self.strings {
356 flags |= elf::SHF_STRINGS.0;
357 }
358 }
359 elf::SectionFlags(flags)
360 }
361
362 pub(crate) fn sh_type(self) -> elf::SectionType {
364 match self.array {
365 _ if !self.bits => elf::SHT_NOBITS,
366 Some(Array::Init) => elf::SHT_INIT_ARRAY,
367 Some(Array::Fini) => elf::SHT_FINI_ARRAY,
368 Some(Array::Preinit) => elf::SHT_PREINIT_ARRAY,
369 None => elf::SHT_PROGBITS,
370 }
371 }
372
373 pub(crate) const fn kind(self) -> SectionKind {
379 match self {
380 Shape { bits: false, thread: true, .. } => SectionKind::UninitializedTls,
381 Shape { bits: false, .. } => SectionKind::UninitializedData,
382 Shape { thread: true, .. } => SectionKind::Tls,
383 Shape { exec: true, .. } => SectionKind::Text,
384 Shape { alloc: false, .. } => SectionKind::Other,
385 Shape { write: false, .. } => SectionKind::ReadOnlyData,
386 Shape { .. } => SectionKind::Data,
387 }
388 }
389}
390
391#[derive(Debug, Clone, PartialEq, Eq)]
393pub struct Name {
394 pub name: String,
396 pub at: Held,
398 pub size: u64,
400 pub sort: Sort,
402 pub binding: Binding,
404 pub visibility: Visibility,
406}
407
408#[derive(Debug, Clone, Copy, PartialEq, Eq)]
410pub enum Held {
411 In {
413 part: usize,
415 offset: u64,
417 },
418 Absolute(u64),
421 Common {
424 size: u64,
426 align: u64,
429 },
430 Undefined,
432}
433
434#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
436pub enum Sort {
437 Func,
439 Object,
441 Thread,
443 File,
449 #[default]
452 Untyped,
453}
454
455#[derive(Debug, Clone, Default, PartialEq, Eq)]
457pub struct Assembled {
458 pub parts: Vec<Part>,
460 pub names: Vec<Name>,
462 pub subsections: bool,
465}
466
467pub fn assembled(input: &Assembled, target: &TargetInfo) -> Result<Vec<u8>, Error> {
488 assembled_described(input, target, &Info::default())
489}
490
491pub fn assembled_described(
504 input: &Assembled,
505 target: &TargetInfo,
506 info: &Info,
507) -> Result<Vec<u8>, Error> {
508 let (flavour, machine) = match (Flavour::of(target), target.tuple.arch()) {
513 (Some(flavour), Arch::X86_64) => (flavour, Architecture::X86_64),
514 (Some(Flavour::Elf), Arch::Aarch64) => (Flavour::Elf, Architecture::Aarch64),
515 (None, Arch::Aarch64) if target.object_format == ObjectFormat::MachO => {
516 return crate::macho::write(input, target, info);
517 }
518 _ => return Err(Error::Format { triple: target.tuple.to_string() }),
519 };
520 let flags_of = |kind, after| match machine {
521 Architecture::Aarch64 => {
522 crate::elf::r_type_aarch64(kind).map(|r_type| RelocationFlags::Elf { r_type })
523 }
524 _ => flavour.reloc(kind, after),
525 };
526 let mut obj = Writer::new(flavour.binary(), machine, Endianness::Little);
527
528 let mut made = Vec::with_capacity(input.parts.len());
531 for part in &input.parts {
532 let id = obj.add_section(Vec::new(), part.name.clone().into_bytes(), part.shape.kind());
533 if let Some(flags) = flavour.stated(part.shape) {
538 obj.section_mut(id).flags = flags;
539 }
540 let align = part.align.max(1);
541 if part.shape.bits {
542 obj.append_section_data(id, &part.bytes, align);
543 } else {
544 obj.append_section_bss(id, part.size, align);
545 }
546 if part.group.is_some() {
550 obj.section_symbol(id);
551 }
552 made.push(id);
553 }
554
555 let defined: std::collections::HashMap<&str, &Name> =
558 input.names.iter().map(|name| (name.name.as_str(), name)).collect();
559 let onto = |reloc: &Reloc| moved(flavour, input, &defined, reloc);
560 let wanted: std::collections::HashSet<&str> = input
561 .parts
562 .iter()
563 .flat_map(|part| &part.relocs)
564 .filter(|reloc| onto(reloc).is_none())
565 .map(|reloc| reloc.symbol.as_str())
566 .collect();
567
568 let mut symbols = std::collections::BTreeMap::new();
571 for name in &input.names {
572 if flavour == Flavour::Elf && unseen(name) && !wanted.contains(name.name.as_str()) {
573 continue;
574 }
575 let (section, value, size) = match name.at {
576 Held::In { part, offset } => {
577 let Some(id) = made.get(part) else {
578 let why = format!(
579 "'{}' is in section {part} and there is no such section",
580 name.name
581 );
582 return Err(Error::Refused { why });
583 };
584 (SymbolSection::Section(*id), offset, name.size)
585 }
586 Held::Absolute(value) => (SymbolSection::Absolute, value, name.size),
587 Held::Common { size, align } => (SymbolSection::Common, align, size),
590 Held::Undefined => (SymbolSection::Undefined, 0, 0),
591 };
592 let id = obj.add_symbol(Symbol {
593 name: name.name.clone().into_bytes(),
594 value,
595 size,
596 kind: flavour.sort(name.sort, name.binding),
597 scope: crate::file::scope_of(name.binding),
598 weak: name.binding == Binding::Weak,
599 section,
600 flags: SymbolFlags::None,
601 });
602 flavour.see(&mut obj, id, name.binding, name.visibility);
603 if matches!(name.at, Held::Common { .. }) {
609 if let SymbolFlags::Elf { st_info, .. } = obj.symbol_flags_mut(id) {
610 *st_info = elf::STB_GLOBAL | elf::STT_OBJECT;
611 }
612 }
613 symbols.insert(name.name.clone(), id);
614 }
615
616 for (part, id) in input.parts.iter().zip(&made) {
617 let Some(group) = &part.group else { continue };
618 let Some(&symbol) = symbols.get(&group.symbol) else {
619 let why = format!(
620 "section '{}' is a COMDAT about '{}', which the file does not define",
621 part.name, group.symbol
622 );
623 return Err(Error::Refused { why });
624 };
625 obj.add_comdat(object::write::Comdat {
626 kind: group.keep.kind(),
627 symbol,
628 sections: vec![*id],
629 });
630 }
631
632 for (part, id) in input.parts.iter().zip(&made) {
633 for reloc in &part.relocs {
634 let (symbol, addend) = match onto(reloc) {
635 Some((part, offset)) => {
636 (obj.section_symbol(made[part]), reloc.addend + offset as i64)
637 }
638 None => {
639 let Some(&symbol) = symbols.get(&reloc.symbol) else {
640 let why = format!(
641 "'{}' is named by a relocation and by nothing else",
642 reloc.symbol
643 );
644 return Err(Error::Refused { why });
645 };
646 (symbol, reloc.addend)
647 }
648 };
649 let flags = flags_of(reloc.kind, reloc.after).ok_or_else(|| Error::Refused {
650 why: format!("no relocation is {:?}", reloc.kind),
651 })?;
652 obj.add_relocation(*id, Relocation { offset: reloc.at as u64, symbol, addend, flags })
653 .map_err(|why| Error::Refused { why: why.to_string() })?;
654 }
655 }
656
657 let mut named = std::collections::HashMap::new();
660 for chunk in &info.chunks {
661 let id = obj.add_section(Vec::new(), chunk.name.clone().into_bytes(), SectionKind::Debug);
662 obj.append_section_data(id, &chunk.bytes, 1);
663 named.insert(chunk.name.as_str(), id);
664 }
665 for chunk in &info.chunks {
666 let section = named[chunk.name.as_str()];
667 for reloc in &chunk.relocs {
668 let (symbol, addend) = match named.get(reloc.symbol.as_str()) {
669 Some(&id) => (obj.section_symbol(id), reloc.addend),
670 None => match defined.get(reloc.symbol.as_str()).map(|name| name.at) {
671 Some(Held::In { part, offset }) => {
672 (obj.section_symbol(made[part]), reloc.addend + offset as i64)
673 }
674 _ => match symbols.get(&reloc.symbol) {
675 Some(&symbol) => (symbol, reloc.addend),
676 None => {
677 let why = format!(
678 "'{}' is named by the debug information and is not defined here",
679 reloc.symbol
680 );
681 return Err(Error::Refused { why });
682 }
683 },
684 },
685 };
686 let kind = flavour.debug(reloc.kind, named.contains_key(reloc.symbol.as_str()));
687 let flags = flags_of(kind, reloc.after)
688 .ok_or_else(|| Error::Refused { why: format!("no relocation is {kind:?}") })?;
689 let record = Relocation { offset: reloc.at as u64, symbol, addend, flags };
690 obj.add_relocation(section, record)
691 .map_err(|why| Error::Refused { why: why.to_string() })?;
692 }
693 }
694
695 if !input.parts.iter().any(|part| part.name == ".note.GNU-stack") {
700 flavour.marker(&mut obj);
701 }
702
703 let mut bytes = obj.write().map_err(|why| Error::Refused { why: why.to_string() })?;
704 if flavour == Flavour::Elf {
705 for part in input.parts.iter().filter(|part| part.shape.merge != 0) {
706 entry_size(&mut bytes, &part.name, part.shape.merge);
707 }
708 }
709 Ok(bytes)
710}
711
712fn entry_size(bytes: &mut [u8], name: &str, size: u64) {
718 let word = |bytes: &[u8], at: usize, width: usize| {
719 bytes[at..at + width].iter().rev().fold(0u64, |sum, &byte| sum << 8 | u64::from(byte))
720 };
721 let table = word(bytes, 0x28, 8) as usize;
722 let each = word(bytes, 0x3a, 2) as usize;
723 let count = word(bytes, 0x3c, 2) as usize;
724 let names = table + each * word(bytes, 0x3e, 2) as usize;
725 let names = word(bytes, names + 0x18, 8) as usize;
726 for header in (0..count).map(|nth| table + nth * each) {
727 let at = names + word(bytes, header, 4) as usize;
728 if bytes[at..].starts_with(name.as_bytes()) && bytes.get(at + name.len()) == Some(&0) {
729 bytes[header + 0x38..header + 0x40].copy_from_slice(&size.to_le_bytes());
730 }
731 }
732}
733
734fn moved(
745 flavour: Flavour,
746 input: &Assembled,
747 defined: &std::collections::HashMap<&str, &Name>,
748 reloc: &Reloc,
749) -> Option<(usize, u64)> {
750 use crate::section::Reference;
751 let name = defined.get(reloc.symbol.as_str())?;
752 let Held::In { part, offset } = name.at else { return None };
753 if flavour != Flavour::Elf || name.binding != Binding::Local {
754 return None;
755 }
756 let near = matches!(reloc.kind, Reference::Data | Reference::Away);
757 let fixed = match reloc.kind {
758 Reference::Call
759 | Reference::Got
760 | Reference::GotBare
761 | Reference::GotKept
762 | Reference::Thread => false,
763 Reference::Field(
766 Fixup::Call26
767 | Fixup::Jump26
768 | Fixup::GotPage21
769 | Fixup::GotLo12
770 | Fixup::GotTprelPage21
771 | Fixup::GotTprelLo12Nc
772 | Fixup::TprelHi12
773 | Fixup::TprelLo12Nc,
774 ) => false,
775 _ if input.parts.get(part)?.shape.merge != 0 => !near && reloc.addend == 0,
776 _ => true,
777 };
778 fixed.then_some((part, offset))
779}
780
781fn unseen(name: &Name) -> bool {
785 name.binding == Binding::Local
786 && (name.name.starts_with(".L")
787 || name.name.starts_with("..")
788 || name.name.contains('\u{1}'))
789}
790
791#[must_use]
797pub fn assembled_defines(input: &Assembled) -> Vec<String> {
798 input
799 .names
800 .iter()
801 .filter(|name| name.binding != Binding::Local && name.at != Held::Undefined)
802 .map(|name| name.name.clone())
803 .collect()
804}
805
806#[cfg(test)]
807mod tests {
808 use super::*;
809
810 use object::read::elf::{FileHeader as _, Sym as _};
811 use object::read::{Object as _, ObjectComdat as _, ObjectSection as _, ObjectSymbol as _};
812 use object::{RelocationFlags, SectionFlags};
813 use rucc_target::{Arch as TargetArch, Env, Os, Triple};
814
815 use crate::section::Reference;
816
817 fn target() -> TargetInfo {
819 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Linux, Env::Gnu))
820 }
821
822 fn windows() -> TargetInfo {
824 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Windows, Env::Gnu))
825 }
826
827 fn part(name: &str, bytes: Vec<u8>) -> Part {
829 Part {
830 name: name.to_owned(),
831 size: bytes.len() as u64,
832 bytes,
833 align: 1,
834 shape: Shape::of(name),
835 relocs: Vec::new(),
836 group: None,
837 }
838 }
839
840 fn at(name: &str, offset: u64, sort: Sort, binding: Binding) -> Name {
842 Name {
843 name: name.to_owned(),
844 at: Held::In { part: 0, offset },
845 size: 0,
846 sort,
847 binding,
848 visibility: Visibility::Default,
849 }
850 }
851
852 fn raw(bytes: &[u8], want: &str) -> (u8, u64) {
860 let header = elf::FileHeader64::<Endianness>::parse(bytes).expect("a header");
861 let endian = header.endian().expect("an endianness");
862 let table = header.sections(endian, bytes).expect("the sections");
863 let symbols = table.symbols(endian, bytes, elf::SHT_SYMTAB).expect("a symbol table");
864 for symbol in symbols.iter() {
865 if symbols.symbol_name(endian, symbol).expect("a name") == want.as_bytes() {
866 return (symbol.st_info().0, symbol.st_value(endian));
867 }
868 }
869 panic!("there is no symbol called '{want}'");
870 }
871
872 fn st_info(bytes: &[u8], want: &str) -> u8 {
874 raw(bytes, want).0
875 }
876
877 #[test]
878 fn a_section_carries_the_flags_the_source_said_and_not_the_ones_its_name_suggests() {
879 let mut odd = part(".init.text", vec![0x90]);
883 odd.shape = Shape { alloc: true, exec: true, bits: true, ..Shape::default() };
884 let input = Assembled { parts: vec![odd], names: Vec::new(), subsections: false };
885 let bytes = assembled(&input, &target()).expect("an object");
886 let file = object::File::parse(&bytes[..]).expect("a readable object");
887 let section = file.section_by_name(".init.text").expect("the section");
888 assert_eq!(section.data().expect("the bytes"), &[0x90]);
889 let SectionFlags::Elf { sh_flags, sh_type } = section.flags() else {
890 panic!("this is an ELF file");
891 };
892 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_EXECINSTR.0);
893 assert_eq!(sh_flags.0 & elf::SHF_WRITE.0, 0, "nothing said it was writable");
894 assert_eq!(sh_type, elf::SHT_PROGBITS);
895 }
896
897 #[test]
898 fn a_section_that_holds_no_bytes_still_says_how_long_it_is() {
899 let mut room = part(".bss", Vec::new());
902 room.size = 4096;
903 room.align = 16;
904 let input = Assembled { parts: vec![room], names: Vec::new(), subsections: false };
905 let bytes = assembled(&input, &target()).expect("an object");
906 assert!(bytes.len() < 4096, "the empty space was written out: {} bytes", bytes.len());
907 let file = object::File::parse(&bytes[..]).expect("a readable object");
908 let section = file.section_by_name(".bss").expect("the section");
909 assert_eq!(section.size(), 4096);
910 assert_eq!(section.align(), 16);
911 let SectionFlags::Elf { sh_type, .. } = section.flags() else { panic!("an ELF file") };
912 assert_eq!(sh_type, elf::SHT_NOBITS);
913 }
914
915 #[test]
916 fn a_label_nobody_stated_a_type_for_is_a_symbol_with_no_type() {
917 let input = Assembled {
921 parts: vec![part(".text", vec![0; 8])],
922 names: vec![at("plain", 4, Sort::Untyped, Binding::Global)],
923 subsections: false,
924 };
925 let bytes = assembled(&input, &target()).expect("an object");
926 let file = object::File::parse(&bytes[..]).expect("a readable object");
927 let plain = file.symbols().find(|s| s.name() == Ok("plain")).expect("the label");
928 assert_eq!(plain.address(), 4);
929 assert_eq!(st_info(&bytes, "plain") & 0xf, elf::STT_NOTYPE.0);
930 }
931
932 #[test]
933 fn what_type_said_is_what_the_symbol_gets() {
934 let input = Assembled {
935 parts: vec![part(".text", vec![0; 8])],
936 names: vec![
937 at("run", 0, Sort::Func, Binding::Global),
938 at("held", 4, Sort::Object, Binding::Local),
939 ],
940 subsections: false,
941 };
942 let bytes = assembled(&input, &target()).expect("an object");
943 assert_eq!(st_info(&bytes, "run") & 0xf, elf::STT_FUNC.0);
944 assert_eq!(st_info(&bytes, "held") & 0xf, elf::STT_OBJECT.0);
945 assert_eq!(st_info(&bytes, "run") >> 4, elf::STB_GLOBAL.0);
946 assert_eq!(st_info(&bytes, "held") >> 4, elf::STB_LOCAL.0);
947 }
948
949 #[test]
950 fn a_common_symbol_is_written_the_way_gas_writes_one() {
951 let input = Assembled {
956 parts: Vec::new(),
957 names: vec![Name {
958 name: "shared".to_owned(),
959 at: Held::Common { size: 8, align: 8 },
960 size: 0,
961 sort: Sort::Object,
962 binding: Binding::Global,
963 visibility: Visibility::Default,
964 }],
965 subsections: false,
966 };
967 let bytes = assembled(&input, &target()).expect("an object");
968 assert_eq!(st_info(&bytes, "shared"), elf::STB_GLOBAL.0 << 4 | elf::STT_OBJECT.0);
969 let file = object::File::parse(&bytes[..]).expect("a readable object");
970 let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the symbol");
971 assert!(shared.is_common(), "the linker has to be asked for the space");
972 assert_eq!(shared.size(), 8);
973 assert_eq!(raw(&bytes, "shared").1, 8, "the boundary it has to start on");
975 }
976
977 #[test]
978 fn a_set_is_a_number_rather_than_a_place() {
979 let input = Assembled {
980 parts: vec![part(".text", vec![0; 8])],
981 names: vec![Name {
982 name: "size_of_it".to_owned(),
983 at: Held::Absolute(25),
984 size: 0,
985 sort: Sort::Untyped,
986 binding: Binding::Global,
987 visibility: Visibility::Default,
988 }],
989 subsections: false,
990 };
991 let bytes = assembled(&input, &target()).expect("an object");
992 let file = object::File::parse(&bytes[..]).expect("a readable object");
993 let sym = file.symbols().find(|s| s.name() == Ok("size_of_it")).expect("the symbol");
994 assert_eq!(sym.address(), 25);
995 assert_eq!(sym.section(), object::SymbolSection::Absolute, "it is not in any section");
996 }
997
998 #[test]
999 fn a_relocation_names_a_symbol_and_lands_where_the_bytes_are() {
1000 let mut data = part(".data", vec![0; 8]);
1001 data.relocs.push(Reloc {
1002 at: 0,
1003 symbol: "message".to_owned(),
1004 kind: Reference::Address { bytes: 8 },
1005 addend: 0,
1006 after: 0,
1007 });
1008 let input = Assembled {
1009 parts: vec![data],
1010 names: vec![Name {
1011 name: "message".to_owned(),
1012 at: Held::Undefined,
1013 size: 0,
1014 sort: Sort::Untyped,
1015 binding: Binding::Global,
1016 visibility: Visibility::Default,
1017 }],
1018 subsections: false,
1019 };
1020 let bytes = assembled(&input, &target()).expect("an object");
1021 let file = object::File::parse(&bytes[..]).expect("a readable object");
1022 let section = file.section_by_name(".data").expect("the section");
1023 let (at, reloc) = section.relocations().next().expect("one relocation");
1024 assert_eq!(at, 0);
1025 assert_eq!(reloc.addend(), 0);
1026 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("an ELF file") };
1027 assert_eq!(r_type, elf::R_X86_64_64);
1028 }
1029
1030 #[test]
1031 fn a_place_only_this_file_sees_is_reached_through_its_section_as_gas_does() {
1032 let mut text = part(".text", vec![0; 32]);
1036 for (at, symbol, kind) in [
1037 (0, ".L3", Reference::Data),
1038 (4, "helper", Reference::Data),
1039 (8, "helper", Reference::Call),
1040 (12, "shared", Reference::Data),
1041 ] {
1042 let symbol = symbol.to_owned();
1043 text.relocs.push(Reloc { at, symbol, kind, addend: -4, after: 0 });
1044 }
1045 let input = Assembled {
1046 parts: vec![text],
1047 names: vec![
1048 at(".L3", 20, Sort::Untyped, Binding::Local),
1049 at("helper", 24, Sort::Func, Binding::Local),
1050 at("shared", 28, Sort::Func, Binding::Global),
1051 ],
1052 subsections: false,
1053 };
1054 let bytes = assembled(&input, &target()).expect("an object");
1055 let file = object::File::parse(&bytes[..]).expect("a readable object");
1056 let names: Vec<_> = file.symbols().filter_map(|sym| sym.name().ok()).collect();
1057 assert!(!names.contains(&".L3") && names.contains(&"helper"), "{names:?}");
1058 let section = file.section_by_name(".text").expect("the section");
1059 let reached: Vec<_> = section
1060 .relocations()
1061 .map(|(at, reloc)| {
1062 let object::RelocationTarget::Symbol(index) = reloc.target() else {
1063 panic!("a symbol")
1064 };
1065 let symbol = file.symbol_by_index(index).expect("the symbol");
1066 let name = if symbol.kind() == object::SymbolKind::Section {
1067 ".text"
1068 } else {
1069 symbol.name().expect("a name")
1070 };
1071 (at, name, reloc.addend())
1072 })
1073 .collect();
1074 assert_eq!(
1075 reached,
1076 [(0, ".text", 16), (4, ".text", 20), (8, "helper", -4), (12, "shared", -4)]
1077 );
1078 }
1079
1080 #[test]
1081 fn a_section_of_constants_may_be_merged_and_a_distance_into_it_keeps_its_name() {
1082 let mut text = part(".text", vec![0; 8]);
1083 text.relocs.push(Reloc {
1084 at: 0,
1085 symbol: ".LC0".to_owned(),
1086 kind: Reference::Data,
1087 addend: -4,
1088 after: 0,
1089 });
1090 let strings = Part {
1091 shape: Shape { merge: 1, strings: true, ..Shape::of(".rodata") },
1092 ..part(".rodata.str1.1", b"hi\0".to_vec())
1093 };
1094 let mut name = at(".LC0", 0, Sort::Untyped, Binding::Local);
1095 name.at = Held::In { part: 1, offset: 0 };
1096 let input = Assembled { parts: vec![text, strings], names: vec![name], subsections: false };
1097 let bytes = assembled(&input, &target()).expect("an object");
1098 let file = object::File::parse(&bytes[..]).expect("a readable object");
1099 let section = file.section_by_name(".rodata.str1.1").expect("the section");
1100 let SectionFlags::Elf { sh_flags, .. } = section.flags() else { panic!("an ELF file") };
1101 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_MERGE.0 | elf::SHF_STRINGS.0);
1102 let header = elf::FileHeader64::<Endianness>::parse(&bytes[..]).expect("a header");
1103 let endian = header.endian().expect("an endianness");
1104 let table = header.sections(endian, &bytes[..]).expect("the sections");
1105 let (_, found) = table.section_by_name(endian, b".rodata.str1.1").expect("the section");
1106 assert_eq!(found.sh_entsize.get(endian), 1);
1107 let text = file.section_by_name(".text").expect("the section");
1108 let (_, reloc) = text.relocations().next().expect("one relocation");
1109 let object::RelocationTarget::Symbol(index) = reloc.target() else { panic!("a symbol") };
1110 assert_eq!(file.symbol_by_index(index).and_then(|sym| sym.name()), Ok(".LC0"));
1111 }
1112
1113 #[test]
1114 fn a_relocation_against_a_name_the_file_never_mentions_is_refused() {
1115 let mut data = part(".data", vec![0; 8]);
1119 data.relocs.push(Reloc {
1120 at: 0,
1121 symbol: "nowhere".to_owned(),
1122 kind: Reference::Address { bytes: 8 },
1123 addend: 0,
1124 after: 0,
1125 });
1126 let input = Assembled { parts: vec![data], names: Vec::new(), subsections: false };
1127 let why = assembled(&input, &target()).expect_err("this cannot be written");
1128 assert!(format!("{why}").contains("nowhere"), "{why}");
1129 }
1130
1131 #[test]
1132 fn the_stack_is_marked_once_whoever_asked_for_it() {
1133 let bare = Assembled {
1136 parts: vec![part(".text", vec![0x90])],
1137 names: Vec::new(),
1138 subsections: false,
1139 };
1140 let bytes = assembled(&bare, &target()).expect("an object");
1141 let file = object::File::parse(&bytes[..]).expect("a readable object");
1142 assert!(file.section_by_name(".note.GNU-stack").is_some(), "the marker was left out");
1143
1144 let said = Assembled {
1145 parts: vec![part(".text", vec![0x90]), part(".note.GNU-stack", Vec::new())],
1146 names: Vec::new(),
1147 subsections: false,
1148 };
1149 let bytes = assembled(&said, &target()).expect("an object");
1150 let file = object::File::parse(&bytes[..]).expect("a readable object");
1151 let marks = file.sections().filter(|s| s.name() == Ok(".note.GNU-stack")).count();
1152 assert_eq!(marks, 1, "the file said it and it was said again");
1153 }
1154
1155 #[test]
1156 fn only_the_names_a_linker_could_find_are_offered_to_an_archive() {
1157 let input = Assembled {
1158 parts: vec![part(".text", vec![0; 8])],
1159 names: vec![
1160 at("reachable", 0, Sort::Func, Binding::Global),
1161 at("mine", 4, Sort::Func, Binding::Local),
1162 Name {
1163 name: "elsewhere".to_owned(),
1164 at: Held::Undefined,
1165 size: 0,
1166 sort: Sort::Untyped,
1167 binding: Binding::Global,
1168 visibility: Visibility::Default,
1169 },
1170 ],
1171 subsections: false,
1172 };
1173 assert_eq!(assembled_defines(&input), vec!["reachable".to_owned()]);
1174 }
1175
1176 #[test]
1177 fn a_machine_this_does_not_write_is_refused_rather_than_written_wrong() {
1178 let input = Assembled {
1179 parts: vec![part(".text", vec![0x90])],
1180 names: Vec::new(),
1181 subsections: false,
1182 };
1183 let elsewhere = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Windows, Env::Msvc));
1184 let why = assembled(&input, &elsewhere).expect_err("this cannot be written");
1185 assert!(format!("{why}").contains("aarch64"), "{why}");
1186 }
1187
1188 #[test]
1189 fn a_file_of_assembly_for_aarch64_is_written_with_that_machine_s_relocations() {
1190 let mut text = part(".text", vec![0; 12]);
1195 let field = |at, symbol: &str, fixup, addend| Reloc {
1196 at,
1197 symbol: symbol.to_owned(),
1198 kind: Reference::Field(fixup),
1199 addend,
1200 after: 0,
1201 };
1202 text.relocs = vec![
1203 field(0, ".Ltable", Fixup::AdrPage21, 8),
1204 field(4, ".Ltable", Fixup::AddLo12, 8),
1205 field(8, "g", Fixup::Call26, 0),
1206 ];
1207 let mut data = part(".data", vec![0; 16]);
1208 data.relocs = vec![Reloc {
1209 at: 8,
1210 symbol: ".Ltable".to_owned(),
1211 kind: Reference::Address { bytes: 8 },
1212 addend: 0,
1213 after: 0,
1214 }];
1215 let mut table = at(".Ltable", 0, Sort::Object, Binding::Local);
1216 table.at = Held::In { part: 1, offset: 0 };
1217 let input = Assembled {
1218 parts: vec![text, data],
1219 names: vec![
1220 table,
1221 Name { at: Held::Undefined, ..at("g", 0, Sort::Untyped, Binding::Global) },
1222 ],
1223 subsections: false,
1224 };
1225 let target = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Linux, Env::Gnu));
1226 let bytes = assembled(&input, &target).expect("an object");
1227 let file = object::File::parse(&bytes[..]).expect("a readable object");
1228 assert_eq!(file.architecture(), Architecture::Aarch64);
1229 let relocs = |name: &str| -> Vec<(u64, elf::RelocationType, i64)> {
1230 let section = file.section_by_name(name).expect("the section");
1231 section
1232 .relocations()
1233 .map(|(at, reloc)| {
1234 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("ELF") };
1235 (at, r_type, reloc.addend())
1236 })
1237 .collect()
1238 };
1239 assert_eq!(
1240 relocs(".text"),
1241 [
1242 (0, elf::R_AARCH64_ADR_PREL_PG_HI21, 8),
1243 (4, elf::R_AARCH64_ADD_ABS_LO12_NC, 8),
1244 (8, elf::R_AARCH64_CALL26, 0)
1245 ]
1246 );
1247 assert_eq!(relocs(".data"), [(8, elf::R_AARCH64_ABS64, 0)]);
1248 assert!(file.symbols().all(|s| s.name() != Ok(".Ltable")), "a label only this file sees");
1249 }
1250
1251 #[test]
1252 fn a_file_of_assembly_for_windows_is_written_as_coff() {
1253 let input = Assembled {
1258 parts: vec![part(".text", vec![0xc3])],
1259 names: Vec::new(),
1260 subsections: false,
1261 };
1262 let bytes = assembled(&input, &windows()).expect("an object");
1263 let file = object::File::parse(&bytes[..]).expect("a readable object");
1264 assert_eq!(file.format(), object::BinaryFormat::Coff);
1265 let section = file.section_by_name(".text").expect("the section");
1266 assert_eq!(section.data().expect("the bytes"), &[0xc3]);
1267 assert_eq!(section.kind(), SectionKind::Text);
1268 assert!(
1269 file.section_by_name(".note.GNU-stack").is_none(),
1270 "a format with no marker got one anyway"
1271 );
1272 }
1273
1274 #[test]
1275 fn a_coff_section_keeps_the_letters_it_was_given_and_its_comdat() {
1276 let drectve = Part {
1279 shape: Shape::coff("yni").expect("the letters"),
1280 ..part(".drectve", b" -exclude-symbols:f".to_vec())
1281 };
1282 let refptr = Part {
1283 shape: Shape::coff("dr").expect("the letters"),
1284 group: Some(Group { symbol: ".refptr.x".to_owned(), keep: Keep::Any }),
1285 ..part(".rdata$.refptr.x", vec![0; 8])
1286 };
1287 let input = Assembled {
1288 parts: vec![refptr, drectve],
1289 names: vec![at(".refptr.x", 0, Sort::Object, Binding::Global)],
1290 subsections: false,
1291 };
1292 let bytes = assembled(&input, &windows()).expect("an object");
1293 let file = object::File::parse(&bytes[..]).expect("a readable object");
1294 let flags = |name: &str| match file.section_by_name(name).expect("the section").flags() {
1295 SectionFlags::Coff { characteristics } => characteristics.0,
1296 other => panic!("{other:?}"),
1297 };
1298 let removed = pe::IMAGE_SCN_LNK_REMOVE.0 | pe::IMAGE_SCN_LNK_INFO.0;
1299 assert_eq!(flags(".drectve") & removed, removed);
1300 assert_eq!(flags(".drectve") & (pe::IMAGE_SCN_MEM_READ.0 | pe::IMAGE_SCN_MEM_WRITE.0), 0);
1301 let refptr = flags(".rdata$.refptr.x");
1302 assert_ne!(refptr & pe::IMAGE_SCN_LNK_COMDAT.0, 0, "not a COMDAT");
1303 assert_eq!(refptr & pe::IMAGE_SCN_MEM_WRITE.0, 0, "`r` did not take writing away");
1304 let comdat = file.comdats().next().expect("the COMDAT");
1305 assert_eq!(comdat.kind(), object::ComdatKind::Any);
1306 assert_eq!(file.symbol_by_index(comdat.symbol()).unwrap().name(), Ok(".refptr.x"));
1307
1308 let input = Assembled { names: Vec::new(), ..input };
1310 assert!(assembled(&input, &windows()).is_err());
1311 }
1312
1313 #[test]
1314 fn a_global_label_with_no_type_under_it_is_still_offered_on_coff() {
1315 let input = Assembled {
1322 parts: vec![part(".text", vec![0; 8])],
1323 names: vec![
1324 at("offered", 0, Sort::Untyped, Binding::Global),
1325 at("ours", 4, Sort::Untyped, Binding::Local),
1326 ],
1327 subsections: false,
1328 };
1329 let bytes = assembled(&input, &windows()).expect("an object");
1330 let file = object::File::parse(&bytes[..]).expect("a readable object");
1331 let offered = file.symbols().find(|s| s.name() == Ok("offered")).expect("the label");
1332 assert!(offered.is_global(), "a `.globl` label came out local");
1333 let ours = file.symbols().find(|s| s.name() == Ok("ours")).expect("the other label");
1334 assert!(!ours.is_global(), "a label nothing offered came out global");
1335 let bytes = assembled(&input, &target()).expect("an object");
1338 assert_eq!(st_info(&bytes, "offered") & 0xf, elf::STT_NOTYPE.0);
1339 }
1340
1341 #[test]
1342 fn a_relocation_on_coff_says_how_much_of_the_instruction_comes_after_it() {
1343 let mut text = part(".text", vec![0; 16]);
1348 text.relocs.push(Reloc {
1349 at: 2,
1350 symbol: "elsewhere".to_owned(),
1351 kind: Reference::Data,
1352 addend: -8,
1353 after: 4,
1354 });
1355 let input = Assembled {
1356 parts: vec![text],
1357 names: vec![Name {
1358 name: "elsewhere".to_owned(),
1359 at: Held::Undefined,
1360 size: 0,
1361 sort: Sort::Untyped,
1362 binding: Binding::Global,
1363 visibility: Visibility::Default,
1364 }],
1365 subsections: false,
1366 };
1367 let bytes = assembled(&input, &windows()).expect("an object");
1368 let file = object::File::parse(&bytes[..]).expect("a readable object");
1369 let section = file.section_by_name(".text").expect("the section");
1370 let (at, reloc) = section.relocations().next().expect("the relocation");
1371 assert_eq!(at, 2);
1372 assert_eq!(
1373 reloc.flags(),
1374 RelocationFlags::Coff { typ: pe::RelocationType(pe::IMAGE_REL_AMD64_REL32.0 + 4) }
1375 );
1376 }
1377}