1use object::write::{Object as Writer, Relocation, SectionId, Symbol, SymbolSection};
31use object::{
32 Architecture, Endianness, SectionFlags, SectionKind, SymbolFlags, SymbolKind, SymbolScope, elf,
33 pe,
34};
35use rucc_base::hash::{Map, Set};
36use rucc_target::aarch64::Fixup;
37use rucc_target::{ObjectFormat, TargetInfo};
38use rucc_tuple::Arch;
39
40use crate::file::{Error, Flavour};
41use crate::section::{Array, Binding, Compress, Info, Reloc, Visibility};
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct Part {
46 pub name: String,
48 pub bytes: Vec<u8>,
50 pub size: u64,
53 pub align: u64,
55 pub shape: Shape,
57 pub relocs: Vec<Reloc>,
59 pub group: Option<Group>,
64 pub link: Option<String>,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct Group {
76 pub symbol: String,
78 pub keep: Keep,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum Keep {
86 One,
88 Any,
90 SameSize,
92 SameContents,
94 Largest,
96 Newest,
98 Together,
101}
102
103impl Keep {
104 #[must_use]
106 pub fn of(word: &str) -> Option<Keep> {
107 Some(match word {
108 "one_only" => Keep::One,
109 "discard" => Keep::Any,
110 "same_size" => Keep::SameSize,
111 "same_contents" => Keep::SameContents,
112 "largest" => Keep::Largest,
113 "newest" => Keep::Newest,
114 _ => return None,
115 })
116 }
117
118 pub(crate) const fn kind(self) -> object::ComdatKind {
119 match self {
120 Keep::One => object::ComdatKind::NoDuplicates,
121 Keep::Any => object::ComdatKind::Any,
122 Keep::SameSize => object::ComdatKind::SameSize,
123 Keep::SameContents => object::ComdatKind::ExactMatch,
124 Keep::Largest => object::ComdatKind::Largest,
125 Keep::Newest => object::ComdatKind::Newest,
126 Keep::Together => object::ComdatKind::Any,
129 }
130 }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
141pub struct Shape {
142 pub alloc: bool,
145 pub write: bool,
147 pub exec: bool,
149 pub thread: bool,
151 pub bits: bool,
153 pub array: Option<Array>,
155 pub merge: u64,
159 pub strings: bool,
162 pub note: bool,
166 pub retain: bool,
169 pub mach: u32,
173 pub coff: u32,
177}
178
179impl Shape {
180 #[must_use]
187 pub fn of(name: &str) -> Shape {
188 let base = Shape { alloc: true, bits: true, ..Shape::default() };
189 let head = name.split_once('.').map_or(name, |(_, rest)| rest);
190 let head = head.split_once('.').map_or(head, |(first, _)| first);
191 match head {
192 "text" | "init" | "fini" => Shape { exec: true, ..base },
193 "rodata" | "eh_frame_hdr" => base,
194 "bss" => Shape { write: true, bits: false, ..base },
195 "tbss" => Shape { write: true, thread: true, bits: false, ..base },
196 "tdata" => Shape { write: true, thread: true, ..base },
197 _ if Array::of(name).is_some() => Shape { write: true, array: Array::of(name), ..base },
201 "debug_info" | "debug_abbrev" | "debug_line" | "debug_str" | "comment" => {
204 Shape { alloc: false, bits: true, ..Shape::default() }
205 }
206 _ => Shape { write: true, ..base },
207 }
208 }
209
210 #[must_use]
218 pub fn implied(name: &str) -> Shape {
219 let base = Shape { alloc: true, ..Shape::default() };
220 let head = name.split_once('.').map_or(name, |(_, rest)| rest);
221 let head = head.split_once('.').map_or(head, |(first, _)| first);
222 match head {
223 "text" => Shape { exec: true, ..base },
224 "rodata" => base,
225 "data" | "bss" => Shape { write: true, ..base },
226 "tdata" | "tbss" => Shape { write: true, thread: true, ..base },
227 _ if Array::of(name).is_some() => Shape { write: true, ..base },
228 _ => Shape::default(),
229 }
230 }
231
232 pub fn mach(
243 segment: &str,
244 section: &str,
245 kind: Option<&str>,
246 attributes: &[&str],
247 ) -> Result<Shape, String> {
248 let mach = crate::macho::section_flags(segment, section, kind, attributes)?;
249 let exec = mach & object::macho::S_ATTR_PURE_INSTRUCTIONS.0 != 0;
250 let typ = object::macho::SectionFlags(mach).typ();
251 let thread = matches!(
252 typ,
253 object::macho::S_THREAD_LOCAL_REGULAR | object::macho::S_THREAD_LOCAL_ZEROFILL
254 );
255 Ok(Shape {
256 alloc: true,
257 write: segment != "__TEXT",
258 exec,
259 thread,
260 bits: !crate::macho::zero_filled(mach),
261 mach,
262 ..Shape::default()
263 })
264 }
265
266 pub fn coff(letters: &str) -> Result<Shape, char> {
279 let (mut code, mut data, mut zero, mut drop, mut info) =
280 (false, false, false, false, false);
281 let (mut read, mut write, mut shared, mut discard) = (true, true, false, false);
282 let mut writable = false;
283 for letter in letters.chars() {
284 match letter {
285 'a' => {}
286 'b' => zero = true,
287 'd' => {
288 data = true;
289 write = true;
290 }
291 'n' => drop = true,
292 'D' => discard = true,
293 'r' => {
294 writable = false;
295 write = false;
296 data |= !code;
297 }
298 's' => {
299 shared = true;
300 data = true;
301 write = true;
302 }
303 'w' => {
304 write = true;
305 writable = true;
306 }
307 'x' => {
308 code = true;
309 write &= writable;
310 }
311 'y' => {
312 read = false;
313 write = false;
314 }
315 'i' => info = true,
316 other => return Err(other),
317 }
318 }
319 let mut flags = 0;
320 if code {
321 flags |= pe::IMAGE_SCN_CNT_CODE.0 | pe::IMAGE_SCN_MEM_EXECUTE.0;
322 }
323 if data {
324 flags |= pe::IMAGE_SCN_CNT_INITIALIZED_DATA.0;
325 }
326 if zero && !data {
327 flags |= pe::IMAGE_SCN_CNT_UNINITIALIZED_DATA.0;
328 }
329 if drop {
330 flags |= pe::IMAGE_SCN_LNK_REMOVE.0;
331 }
332 if read {
333 flags |= pe::IMAGE_SCN_MEM_READ.0;
334 }
335 if write {
336 flags |= pe::IMAGE_SCN_MEM_WRITE.0;
337 }
338 if discard {
339 flags |= pe::IMAGE_SCN_MEM_DISCARDABLE.0;
340 }
341 if shared {
342 flags |= pe::IMAGE_SCN_MEM_SHARED.0;
343 }
344 if info {
345 flags |= pe::IMAGE_SCN_LNK_INFO.0;
346 }
347 Ok(Shape {
348 alloc: !drop && !info,
349 write,
350 exec: code,
351 bits: !(zero && !data),
352 coff: flags,
353 ..Shape::default()
354 })
355 }
356
357 pub(crate) fn sh_flags(self) -> elf::SectionFlags {
364 let mut flags = 0;
365 if self.alloc {
366 flags |= elf::SHF_ALLOC.0;
367 }
368 if self.write {
369 flags |= elf::SHF_WRITE.0;
370 }
371 if self.exec {
372 flags |= elf::SHF_EXECINSTR.0;
373 }
374 if self.thread {
375 flags |= elf::SHF_TLS.0;
376 }
377 if self.retain {
378 flags |= elf::SHF_GNU_RETAIN.0;
379 }
380 if self.merge != 0 {
381 flags |= elf::SHF_MERGE.0;
382 if self.strings {
383 flags |= elf::SHF_STRINGS.0;
384 }
385 }
386 elf::SectionFlags(flags)
387 }
388
389 pub(crate) fn sh_type(self) -> elf::SectionType {
391 match self.array {
392 _ if !self.bits => elf::SHT_NOBITS,
393 _ if self.note => elf::SHT_NOTE,
394 Some(Array::Init) => elf::SHT_INIT_ARRAY,
395 Some(Array::Fini) => elf::SHT_FINI_ARRAY,
396 Some(Array::Preinit) => elf::SHT_PREINIT_ARRAY,
397 None => elf::SHT_PROGBITS,
398 }
399 }
400
401 pub(crate) const fn kind(self) -> SectionKind {
407 match self {
408 Shape { bits: false, thread: true, .. } => SectionKind::UninitializedTls,
409 Shape { bits: false, .. } => SectionKind::UninitializedData,
410 Shape { thread: true, .. } => SectionKind::Tls,
411 Shape { exec: true, .. } => SectionKind::Text,
412 Shape { alloc: false, .. } => SectionKind::Other,
413 Shape { write: false, .. } => SectionKind::ReadOnlyData,
414 Shape { .. } => SectionKind::Data,
415 }
416 }
417}
418
419#[derive(Debug, Clone, PartialEq, Eq)]
421pub struct Name {
422 pub name: String,
424 pub at: Held,
426 pub size: u64,
428 pub sort: Sort,
430 pub binding: Binding,
432 pub visibility: Visibility,
434}
435
436#[derive(Debug, Clone, Copy, PartialEq, Eq)]
438pub enum Held {
439 In {
441 part: usize,
443 offset: u64,
445 },
446 Absolute(u64),
449 Common {
452 size: u64,
454 align: u64,
457 },
458 Undefined,
460}
461
462#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
464pub enum Sort {
465 Func,
467 Object,
469 Thread,
471 Ifunc,
480 File,
486 #[default]
489 Untyped,
490}
491
492#[derive(Debug, Clone, Default, PartialEq, Eq)]
494pub struct Assembled {
495 pub parts: Vec<Part>,
497 pub names: Vec<Name>,
499 pub subsections: bool,
502}
503
504pub fn assembled(input: &Assembled, target: &TargetInfo) -> Result<Vec<u8>, Error> {
525 assembled_described(input, target, &Info::default())
526}
527
528pub fn assembled_described(
541 input: &Assembled,
542 target: &TargetInfo,
543 info: &Info,
544) -> Result<Vec<u8>, Error> {
545 let (flavour, machine) = match Flavour::of(target) {
548 Some(flavour) => match flavour.machine(target.tuple.arch()) {
549 Some(machine) => (flavour, machine),
550 None => return Err(Error::Format { triple: target.tuple.to_string() }),
551 },
552 None if target.tuple.arch() == Arch::Aarch64
553 && target.object_format == ObjectFormat::MachO =>
554 {
555 return crate::macho::write(input, target, info);
556 }
557 None => return Err(Error::Format { triple: target.tuple.to_string() }),
558 };
559 let flags_of = |kind, after| flavour.reloc(machine, kind, after);
560 let carried = machine == Architecture::Aarch64 && flavour == Flavour::Coff;
563 let mut obj = Writer::new(flavour.binary(), machine, Endianness::Little);
564 obj.set_mangling(object::write::Mangling::None);
567
568 let mut made = Vec::with_capacity(input.parts.len());
571 for part in &input.parts {
572 let id = obj.add_section(Vec::new(), part.name.clone().into_bytes(), part.shape.kind());
573 if let Some(mut flags) = flavour.stated(part.shape) {
578 if let SectionFlags::Elf { sh_flags, .. } = &mut flags {
581 if part.group.is_some() {
582 sh_flags.0 |= elf::SHF_GROUP.0;
583 }
584 if part.link.is_some() {
585 sh_flags.0 |= elf::SHF_LINK_ORDER.0;
586 }
587 }
588 obj.section_mut(id).flags = flags;
589 }
590 let align = part.align.max(1);
591 if part.shape.bits {
592 obj.append_section_data(id, &part.bytes, align);
593 } else {
594 obj.append_section_bss(id, part.size, align);
595 }
596 if part.group.is_some() && flavour == Flavour::Coff {
600 obj.section_symbol(id);
601 }
602 made.push(id);
603 }
604
605 let defined: Map<&str, &Name> =
608 input.names.iter().map(|name| (name.name.as_str(), name)).collect();
609 let pe32 = flavour == Flavour::Coff && machine == Architecture::I386;
611 let onto = |reloc: &Reloc| moved(flavour, pe32, input, &defined, reloc);
612 let wanted: Set<&str> = input
613 .parts
614 .iter()
615 .flat_map(|part| &part.relocs)
616 .filter(|reloc| onto(reloc).is_none())
617 .map(|reloc| reloc.symbol.as_str())
618 .chain(
619 input
620 .parts
621 .iter()
622 .filter_map(|part| part.group.as_ref())
623 .map(|group| group.symbol.as_str()),
624 )
625 .collect();
626
627 let mut symbols = std::collections::BTreeMap::new();
630 for name in &input.names {
631 let dropped = flavour == Flavour::Elf || pe32;
632 if dropped && unseen(name, pe32) && !wanted.contains(name.name.as_str()) {
633 continue;
634 }
635 let (section, value, size) = match name.at {
636 Held::In { part, offset } => {
637 let Some(id) = made.get(part) else {
638 let why = format!(
639 "'{}' is in section {part} and there is no such section",
640 name.name
641 );
642 return Err(Error::Refused { why });
643 };
644 (SymbolSection::Section(*id), offset, name.size)
645 }
646 Held::Absolute(value) => (SymbolSection::Absolute, value, name.size),
647 Held::Common { size, align } => (SymbolSection::Common, align, size),
650 Held::Undefined => (SymbolSection::Undefined, 0, 0),
651 };
652 let kind = match (flavour, name.at, name.sort) {
655 (Flavour::Coff, Held::Absolute(_), Sort::Untyped) => SymbolKind::Data,
656 _ => flavour.sort(name.sort, name.binding),
657 };
658 let id = obj.add_symbol(Symbol {
659 name: name.name.clone().into_bytes(),
660 value,
661 size,
662 kind,
663 scope: crate::file::scope_of(name.binding),
664 weak: name.binding == Binding::Weak,
665 section,
666 flags: SymbolFlags::None,
667 });
668 flavour.see(&mut obj, id, name.binding, name.visibility);
669 if name.sort == Sort::Ifunc {
670 if flavour != Flavour::Elf {
675 let why = format!("'{}' is an indirect function, which only ELF has", name.name);
676 return Err(Error::Refused { why });
677 }
678 crate::elf::indirect(&mut obj, id);
679 }
680 if matches!(name.at, Held::Common { .. }) {
686 if let SymbolFlags::Elf { st_info, .. } = obj.symbol_flags_mut(id) {
687 *st_info = elf::STB_GLOBAL | elf::STT_OBJECT;
688 }
689 }
690 symbols.insert(name.name.clone(), id);
691 }
692
693 let mut groups: Vec<(&Group, Vec<SectionId>)> = Vec::new();
696 let mut grouped: Map<&str, usize> = Map::default();
697 for (part, id) in input.parts.iter().zip(&made) {
698 let Some(group) = &part.group else { continue };
699 match grouped.get(group.symbol.as_str()) {
700 Some(&at) if flavour == Flavour::Elf => groups[at].1.push(*id),
701 _ => {
702 grouped.insert(group.symbol.as_str(), groups.len());
703 groups.push((group, vec![*id]));
704 }
705 }
706 }
707 let mut together = Vec::new();
708 for (group, sections) in groups {
709 let symbol = match symbols.get(&group.symbol) {
710 Some(&symbol) => symbol,
711 None if flavour == Flavour::Elf => obj.add_symbol(Symbol {
716 name: group.symbol.clone().into_bytes(),
717 value: 0,
718 size: 0,
719 kind: SymbolKind::Label,
720 scope: SymbolScope::Compilation,
721 weak: false,
722 section: SymbolSection::Section(sections[0]),
723 flags: SymbolFlags::None,
724 }),
725 None => {
726 let why = format!(
727 "section '{}' is a COMDAT about '{}', which the file does not define",
728 obj.section(sections[0]).name().unwrap_or_default(),
729 group.symbol
730 );
731 return Err(Error::Refused { why });
732 }
733 };
734 together.push(group.keep == Keep::Together);
735 obj.add_comdat(object::write::Comdat { kind: group.keep.kind(), symbol, sections });
736 }
737
738 for (part, id) in input.parts.iter().zip(&made) {
739 for reloc in &part.relocs {
740 let (symbol, addend) = match onto(reloc) {
741 Some((part, offset)) => {
742 (obj.section_symbol(made[part]), reloc.addend + offset as i64)
743 }
744 None => {
745 let Some(&symbol) = symbols.get(&reloc.symbol) else {
746 let why = format!(
747 "'{}' is named by a relocation and by nothing else",
748 reloc.symbol
749 );
750 return Err(Error::Refused { why });
751 };
752 (symbol, reloc.addend)
753 }
754 };
755 let flags = flags_of(reloc.kind, reloc.after).ok_or_else(|| Error::Refused {
756 why: format!("no relocation is {:?}", reloc.kind),
757 })?;
758 let addend = match reloc.kind {
759 crate::section::Reference::Field(fixup) if carried => {
760 let data = obj.section_mut(*id).data_mut();
761 let Some(bytes) = data.get_mut(reloc.at..reloc.at + 4) else {
762 let why = format!("a field at {} is past the end of its section", reloc.at);
763 return Err(Error::Refused { why });
764 };
765 let word = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
766 let word = crate::coff::carry(fixup, word, addend)
767 .map_err(|why| Error::Refused { why })?;
768 bytes.copy_from_slice(&word.to_le_bytes());
769 0
770 }
771 _ => addend,
772 };
773 let record = Relocation { offset: reloc.at as u64, symbol, addend, flags };
774 crate::file::relocate(&mut obj, *id, record)?;
775 }
776 }
777
778 let mut named = Map::default();
781 for chunk in &info.chunks {
782 let how = if flavour == Flavour::Elf && obj.architecture() != Architecture::I386 {
785 info.compress
786 } else {
787 Compress::None
788 };
789 let id = crate::zlib::debug_section(&mut obj, chunk, how);
790 named.insert(chunk.name.as_str(), id);
791 }
792 for chunk in &info.chunks {
793 let section = named[chunk.name.as_str()];
794 for reloc in &chunk.relocs {
795 let spelled =
798 if pe32 { crate::coff::decorate(&reloc.symbol) } else { reloc.symbol.clone() };
799 let (symbol, addend) = match named.get(reloc.symbol.as_str()) {
800 Some(&id) => (obj.section_symbol(id), reloc.addend),
801 None => match defined.get(spelled.as_str()).map(|name| name.at) {
802 Some(Held::In { part, offset }) => {
803 (obj.section_symbol(made[part]), reloc.addend + offset as i64)
804 }
805 _ => match symbols.get(&spelled) {
806 Some(&symbol) => (symbol, reloc.addend),
807 None => {
808 let why = format!(
809 "'{}' is named by the debug information and is not defined here",
810 reloc.symbol
811 );
812 return Err(Error::Refused { why });
813 }
814 },
815 },
816 };
817 let kind = flavour.debug(reloc.kind, named.contains_key(reloc.symbol.as_str()));
818 let flags = flags_of(kind, reloc.after)
819 .ok_or_else(|| Error::Refused { why: format!("no relocation is {kind:?}") })?;
820 let record = Relocation { offset: reloc.at as u64, symbol, addend, flags };
821 crate::file::relocate(&mut obj, section, record)?;
822 }
823 }
824
825 let mut bytes = obj.write().map_err(|why| Error::Refused { why: why.to_string() })?;
829 if flavour == Flavour::Elf {
830 for part in input.parts.iter().filter(|part| part.shape.merge != 0) {
831 entry_size(&mut bytes, &part.name, part.shape.merge);
832 }
833 together_groups(&mut bytes, &together);
834 linked(&mut bytes, input, &defined)?;
835 }
836 Ok(bytes)
837}
838
839fn linked(bytes: &mut [u8], input: &Assembled, defined: &Map<&str, &Name>) -> Result<(), Error> {
853 if input.parts.iter().all(|part| part.link.is_none()) {
854 return Ok(());
855 }
856 let headers = crate::elf::Headers::read(bytes);
857 let index = |part: usize| {
858 let name = &input.parts[part].name;
859 let nth = input.parts[..part].iter().filter(|other| other.name == *name).count();
860 headers.list.iter().enumerate().filter(|(_, header)| header.name == *name).nth(nth)
861 };
862 let mut writes = Vec::new();
863 for (at, part) in input.parts.iter().enumerate() {
864 let Some(link) = &part.link else { continue };
865 let target = match defined.get(link.as_str()).map(|name| name.at) {
866 Some(Held::In { part, .. }) => Some(part),
867 _ => input.parts.iter().position(|other| other.name == *link),
868 };
869 let Some(target) = target.and_then(index) else {
870 let why = format!("section '{}' goes with '{link}', which is in no section", part.name);
871 return Err(Error::Refused { why });
872 };
873 let (_, header) = index(at).expect("a header for every section");
874 let target = u32::try_from(target.0).expect("a file with this many sections in it");
875 writes.push((header.at + headers.link(), target));
876 }
877 for (at, target) in writes {
878 bytes[at..at + 4].copy_from_slice(&target.to_le_bytes());
879 }
880 Ok(())
881}
882
883fn entry_size(bytes: &mut [u8], name: &str, size: u64) {
888 let headers = crate::elf::Headers::read(bytes);
889 let (field, width) = headers.entry_size();
890 for header in headers.list.iter().filter(|header| header.name == name) {
891 let at = header.at + field;
892 bytes[at..at + width].copy_from_slice(&size.to_le_bytes()[..width]);
893 }
894}
895
896fn together_groups(bytes: &mut [u8], together: &[bool]) {
900 if !together.contains(&true) {
901 return;
902 }
903 let headers = crate::elf::Headers::read(bytes);
904 let groups = headers.list.iter().filter(|header| header.sh_type == elf::SHT_GROUP.0);
905 for (header, _) in groups.zip(together).filter(|(_, together)| **together) {
906 bytes[header.offset..header.offset + 4].copy_from_slice(&0u32.to_le_bytes());
907 }
908}
909
910fn moved(
926 flavour: Flavour,
927 pe32: bool,
928 input: &Assembled,
929 defined: &Map<&str, &Name>,
930 reloc: &Reloc,
931) -> Option<(usize, u64)> {
932 use crate::section::Reference;
933 let name = defined.get(reloc.symbol.as_str())?;
934 let Held::In { part, offset } = name.at else { return None };
935 if name.sort == Sort::Ifunc {
939 return None;
940 }
941 if pe32 && unseen(name, pe32) {
942 return Some((part, offset));
943 }
944 if flavour != Flavour::Elf || name.binding != Binding::Local {
945 return None;
946 }
947 let near = matches!(
948 reloc.kind,
949 Reference::Data | Reference::Away | Reference::AwayWide | Reference::Short
950 );
951 let fixed = match reloc.kind {
952 Reference::Call
953 | Reference::Got
954 | Reference::GotBare
955 | Reference::GotKept
956 | Reference::Slot
957 | Reference::SlotKept
958 | Reference::GotFront
959 | Reference::Thread
960 | Reference::Tls(_) => false,
961 Reference::Field(
964 Fixup::Call26
965 | Fixup::Jump26
966 | Fixup::GotPage21
967 | Fixup::GotLo12
968 | Fixup::GotTprelPage21
969 | Fixup::GotTprelLo12Nc
970 | Fixup::TprelHi12
971 | Fixup::TprelLo12Nc,
972 ) => false,
973 _ if input.parts.get(part)?.shape.merge != 0 => !near && reloc.addend == 0,
974 _ => true,
975 };
976 fixed.then_some((part, offset))
977}
978
979fn unseen(name: &Name, pe32: bool) -> bool {
987 name.binding == Binding::Local
988 && (name.name.starts_with(".L")
989 || name.name.starts_with("..")
990 || name.name.contains('\u{1}')
991 || (pe32 && name.name.starts_with('L')))
992}
993
994#[must_use]
1000pub fn assembled_defines(input: &Assembled) -> Vec<String> {
1001 input
1002 .names
1003 .iter()
1004 .filter(|name| name.binding != Binding::Local && name.at != Held::Undefined)
1005 .map(|name| name.name.clone())
1006 .collect()
1007}
1008
1009#[cfg(test)]
1010mod tests {
1011 use super::*;
1012
1013 use object::read::elf::{FileHeader as _, SectionHeader as _, Sym as _};
1014 use object::read::{Object as _, ObjectComdat as _, ObjectSection as _, ObjectSymbol as _};
1015 use object::{RelocationFlags, SectionFlags};
1016 use rucc_target::{Arch as TargetArch, Env, Os, Triple};
1017
1018 use crate::section::{Reference, Tls};
1019
1020 fn target() -> TargetInfo {
1022 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Linux, Env::Gnu))
1023 }
1024
1025 fn windows() -> TargetInfo {
1027 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Windows, Env::Gnu))
1028 }
1029
1030 fn part(name: &str, bytes: Vec<u8>) -> Part {
1032 Part {
1033 name: name.to_owned(),
1034 size: bytes.len() as u64,
1035 bytes,
1036 align: 1,
1037 shape: Shape::of(name),
1038 relocs: Vec::new(),
1039 group: None,
1040 link: None,
1041 }
1042 }
1043
1044 fn at(name: &str, offset: u64, sort: Sort, binding: Binding) -> Name {
1046 Name {
1047 name: name.to_owned(),
1048 at: Held::In { part: 0, offset },
1049 size: 0,
1050 sort,
1051 binding,
1052 visibility: Visibility::Default,
1053 }
1054 }
1055
1056 fn raw(bytes: &[u8], want: &str) -> (u8, u64) {
1064 let header = elf::FileHeader64::<Endianness>::parse(bytes).expect("a header");
1065 let endian = header.endian().expect("an endianness");
1066 let table = header.sections(endian, bytes).expect("the sections");
1067 let symbols = table.symbols(endian, bytes, elf::SHT_SYMTAB).expect("a symbol table");
1068 for symbol in symbols.iter() {
1069 if symbols.symbol_name(endian, symbol).expect("a name") == want.as_bytes() {
1070 return (symbol.st_info().0, symbol.st_value(endian));
1071 }
1072 }
1073 panic!("there is no symbol called '{want}'");
1074 }
1075
1076 fn st_info(bytes: &[u8], want: &str) -> u8 {
1078 raw(bytes, want).0
1079 }
1080
1081 #[test]
1082 fn a_section_carries_the_flags_the_source_said_and_not_the_ones_its_name_suggests() {
1083 let mut odd = part(".init.text", vec![0x90]);
1087 odd.shape = Shape { alloc: true, exec: true, bits: true, ..Shape::default() };
1088 let input = Assembled { parts: vec![odd], names: Vec::new(), subsections: false };
1089 let bytes = assembled(&input, &target()).expect("an object");
1090 let file = object::File::parse(&bytes[..]).expect("a readable object");
1091 let section = file.section_by_name(".init.text").expect("the section");
1092 assert_eq!(section.data().expect("the bytes"), &[0x90]);
1093 let SectionFlags::Elf { sh_flags, sh_type } = section.flags() else {
1094 panic!("this is an ELF file");
1095 };
1096 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_EXECINSTR.0);
1097 assert_eq!(sh_flags.0 & elf::SHF_WRITE.0, 0, "nothing said it was writable");
1098 assert_eq!(sh_type, elf::SHT_PROGBITS);
1099 }
1100
1101 #[test]
1102 fn a_section_that_holds_no_bytes_still_says_how_long_it_is() {
1103 let mut room = part(".bss", Vec::new());
1106 room.size = 4096;
1107 room.align = 16;
1108 let input = Assembled { parts: vec![room], names: Vec::new(), subsections: false };
1109 let bytes = assembled(&input, &target()).expect("an object");
1110 assert!(bytes.len() < 4096, "the empty space was written out: {} bytes", bytes.len());
1111 let file = object::File::parse(&bytes[..]).expect("a readable object");
1112 let section = file.section_by_name(".bss").expect("the section");
1113 assert_eq!(section.size(), 4096);
1114 assert_eq!(section.align(), 16);
1115 let SectionFlags::Elf { sh_type, .. } = section.flags() else { panic!("an ELF file") };
1116 assert_eq!(sh_type, elf::SHT_NOBITS);
1117 }
1118
1119 #[test]
1120 fn a_label_nobody_stated_a_type_for_is_a_symbol_with_no_type() {
1121 let input = Assembled {
1125 parts: vec![part(".text", vec![0; 8])],
1126 names: vec![at("plain", 4, Sort::Untyped, Binding::Global)],
1127 subsections: false,
1128 };
1129 let bytes = assembled(&input, &target()).expect("an object");
1130 let file = object::File::parse(&bytes[..]).expect("a readable object");
1131 let plain = file.symbols().find(|s| s.name() == Ok("plain")).expect("the label");
1132 assert_eq!(plain.address(), 4);
1133 assert_eq!(st_info(&bytes, "plain") & 0xf, elf::STT_NOTYPE.0);
1134 }
1135
1136 #[test]
1137 fn what_type_said_is_what_the_symbol_gets() {
1138 let input = Assembled {
1139 parts: vec![part(".text", vec![0; 8])],
1140 names: vec![
1141 at("run", 0, Sort::Func, Binding::Global),
1142 at("held", 4, Sort::Object, Binding::Local),
1143 ],
1144 subsections: false,
1145 };
1146 let bytes = assembled(&input, &target()).expect("an object");
1147 assert_eq!(st_info(&bytes, "run") & 0xf, elf::STT_FUNC.0);
1148 assert_eq!(st_info(&bytes, "held") & 0xf, elf::STT_OBJECT.0);
1149 assert_eq!(st_info(&bytes, "run") >> 4, elf::STB_GLOBAL.0);
1150 assert_eq!(st_info(&bytes, "held") >> 4, elf::STB_LOCAL.0);
1151 }
1152
1153 #[test]
1154 fn a_common_symbol_is_written_the_way_gas_writes_one() {
1155 let input = Assembled {
1160 parts: Vec::new(),
1161 names: vec![Name {
1162 name: "shared".to_owned(),
1163 at: Held::Common { size: 8, align: 8 },
1164 size: 0,
1165 sort: Sort::Object,
1166 binding: Binding::Global,
1167 visibility: Visibility::Default,
1168 }],
1169 subsections: false,
1170 };
1171 let bytes = assembled(&input, &target()).expect("an object");
1172 assert_eq!(st_info(&bytes, "shared"), elf::STB_GLOBAL.0 << 4 | elf::STT_OBJECT.0);
1173 let file = object::File::parse(&bytes[..]).expect("a readable object");
1174 let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the symbol");
1175 assert!(shared.is_common(), "the linker has to be asked for the space");
1176 assert_eq!(shared.size(), 8);
1177 assert_eq!(raw(&bytes, "shared").1, 8, "the boundary it has to start on");
1179 }
1180
1181 #[test]
1182 fn a_set_is_a_number_rather_than_a_place() {
1183 let input = Assembled {
1184 parts: vec![part(".text", vec![0; 8])],
1185 names: vec![Name {
1186 name: "size_of_it".to_owned(),
1187 at: Held::Absolute(25),
1188 size: 0,
1189 sort: Sort::Untyped,
1190 binding: Binding::Global,
1191 visibility: Visibility::Default,
1192 }],
1193 subsections: false,
1194 };
1195 let bytes = assembled(&input, &target()).expect("an object");
1196 let file = object::File::parse(&bytes[..]).expect("a readable object");
1197 let sym = file.symbols().find(|s| s.name() == Ok("size_of_it")).expect("the symbol");
1198 assert_eq!(sym.address(), 25);
1199 assert_eq!(sym.section(), object::SymbolSection::Absolute, "it is not in any section");
1200 }
1201
1202 #[test]
1205 fn a_local_set_on_coff_is_a_static_number() {
1206 let input = Assembled {
1207 parts: vec![part(".text", vec![0xc3])],
1208 names: vec![Name {
1209 name: "@feat.00".to_owned(),
1210 at: Held::Absolute(1),
1211 size: 0,
1212 sort: Sort::Untyped,
1213 binding: Binding::Local,
1214 visibility: Visibility::Default,
1215 }],
1216 subsections: false,
1217 };
1218 let bytes = assembled(&input, &windows()).expect("an object");
1219 let file = object::File::parse(&bytes[..]).expect("a readable object");
1220 let sym = file.symbols().find(|s| s.name() == Ok("@feat.00")).expect("the symbol");
1221 let coff = object::read::coff::CoffFile::<&[u8]>::parse(&bytes[..]).expect("COFF");
1223 let raw = coff.symbol_by_name("@feat.00").expect("the symbol");
1224 assert_eq!(object::read::coff::Symbol::value(raw.coff_symbol()), 1);
1225 assert_eq!(sym.section(), object::SymbolSection::Absolute);
1226 assert!(sym.is_local());
1227 assert!(
1228 matches!(
1229 sym.flags(),
1230 SymbolFlags::Coff { storage_class: pe::IMAGE_SYM_CLASS_STATIC, .. }
1231 ),
1232 "{:?}",
1233 sym.flags()
1234 );
1235 }
1236
1237 #[test]
1238 fn a_relocation_names_a_symbol_and_lands_where_the_bytes_are() {
1239 let mut data = part(".data", vec![0; 8]);
1240 data.relocs.push(Reloc {
1241 at: 0,
1242 symbol: "message".to_owned(),
1243 kind: Reference::Address { bytes: 8 },
1244 addend: 0,
1245 after: 0,
1246 });
1247 let input = Assembled {
1248 parts: vec![data],
1249 names: vec![Name {
1250 name: "message".to_owned(),
1251 at: Held::Undefined,
1252 size: 0,
1253 sort: Sort::Untyped,
1254 binding: Binding::Global,
1255 visibility: Visibility::Default,
1256 }],
1257 subsections: false,
1258 };
1259 let bytes = assembled(&input, &target()).expect("an object");
1260 let file = object::File::parse(&bytes[..]).expect("a readable object");
1261 let section = file.section_by_name(".data").expect("the section");
1262 let (at, reloc) = section.relocations().next().expect("one relocation");
1263 assert_eq!(at, 0);
1264 assert_eq!(reloc.addend(), 0);
1265 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("an ELF file") };
1266 assert_eq!(r_type, elf::R_X86_64_64);
1267 }
1268
1269 #[test]
1270 fn a_place_only_this_file_sees_is_reached_through_its_section_as_gas_does() {
1271 let mut text = part(".text", vec![0; 32]);
1275 for (at, symbol, kind) in [
1276 (0, ".L3", Reference::Data),
1277 (4, "helper", Reference::Data),
1278 (8, "helper", Reference::Call),
1279 (12, "shared", Reference::Data),
1280 ] {
1281 let symbol = symbol.to_owned();
1282 text.relocs.push(Reloc { at, symbol, kind, addend: -4, after: 0 });
1283 }
1284 let input = Assembled {
1285 parts: vec![text],
1286 names: vec![
1287 at(".L3", 20, Sort::Untyped, Binding::Local),
1288 at("helper", 24, Sort::Func, Binding::Local),
1289 at("shared", 28, Sort::Func, Binding::Global),
1290 ],
1291 subsections: false,
1292 };
1293 let bytes = assembled(&input, &target()).expect("an object");
1294 let file = object::File::parse(&bytes[..]).expect("a readable object");
1295 let names: Vec<_> = file.symbols().filter_map(|sym| sym.name().ok()).collect();
1296 assert!(!names.contains(&".L3") && names.contains(&"helper"), "{names:?}");
1297 let section = file.section_by_name(".text").expect("the section");
1298 let reached: Vec<_> = section
1299 .relocations()
1300 .map(|(at, reloc)| {
1301 let object::RelocationTarget::Symbol(index) = reloc.target() else {
1302 panic!("a symbol")
1303 };
1304 let symbol = file.symbol_by_index(index).expect("the symbol");
1305 let name = if symbol.kind() == SymbolKind::Section {
1306 ".text"
1307 } else {
1308 symbol.name().expect("a name")
1309 };
1310 (at, name, reloc.addend())
1311 })
1312 .collect();
1313 assert_eq!(
1314 reached,
1315 [(0, ".text", 16), (4, ".text", 20), (8, "helper", -4), (12, "shared", -4)]
1316 );
1317 }
1318
1319 #[test]
1320 fn a_section_of_constants_may_be_merged_and_a_distance_into_it_keeps_its_name() {
1321 let mut text = part(".text", vec![0; 8]);
1322 text.relocs.push(Reloc {
1323 at: 0,
1324 symbol: ".LC0".to_owned(),
1325 kind: Reference::Data,
1326 addend: -4,
1327 after: 0,
1328 });
1329 let strings = Part {
1330 shape: Shape { merge: 1, strings: true, ..Shape::of(".rodata") },
1331 ..part(".rodata.str1.1", b"hi\0".to_vec())
1332 };
1333 let mut name = at(".LC0", 0, Sort::Untyped, Binding::Local);
1334 name.at = Held::In { part: 1, offset: 0 };
1335 let input = Assembled { parts: vec![text, strings], names: vec![name], subsections: false };
1336 let bytes = assembled(&input, &target()).expect("an object");
1337 let file = object::File::parse(&bytes[..]).expect("a readable object");
1338 let section = file.section_by_name(".rodata.str1.1").expect("the section");
1339 let SectionFlags::Elf { sh_flags, .. } = section.flags() else { panic!("an ELF file") };
1340 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_MERGE.0 | elf::SHF_STRINGS.0);
1341 let header = elf::FileHeader64::<Endianness>::parse(&bytes[..]).expect("a header");
1342 let endian = header.endian().expect("an endianness");
1343 let table = header.sections(endian, &bytes[..]).expect("the sections");
1344 let (_, found) = table.section_by_name(endian, b".rodata.str1.1").expect("the section");
1345 assert_eq!(found.sh_entsize.get(endian), 1);
1346 let text = file.section_by_name(".text").expect("the section");
1347 let (_, reloc) = text.relocations().next().expect("one relocation");
1348 let object::RelocationTarget::Symbol(index) = reloc.target() else { panic!("a symbol") };
1349 assert_eq!(file.symbol_by_index(index).and_then(|sym| sym.name()), Ok(".LC0"));
1350 }
1351
1352 #[test]
1353 fn a_relocation_against_a_name_the_file_never_mentions_is_refused() {
1354 let mut data = part(".data", vec![0; 8]);
1358 data.relocs.push(Reloc {
1359 at: 0,
1360 symbol: "nowhere".to_owned(),
1361 kind: Reference::Address { bytes: 8 },
1362 addend: 0,
1363 after: 0,
1364 });
1365 let input = Assembled { parts: vec![data], names: Vec::new(), subsections: false };
1366 let why = assembled(&input, &target()).expect_err("this cannot be written");
1367 assert!(format!("{why}").contains("nowhere"), "{why}");
1368 }
1369
1370 #[test]
1371 fn the_stack_is_marked_only_by_the_file_and_only_once() {
1372 let bare = Assembled {
1375 parts: vec![part(".text", vec![0x90])],
1376 names: Vec::new(),
1377 subsections: false,
1378 };
1379 let bytes = assembled(&bare, &target()).expect("an object");
1380 let file = object::File::parse(&bytes[..]).expect("a readable object");
1381 assert!(file.section_by_name(".note.GNU-stack").is_none(), "a marker nobody wrote");
1382
1383 let said = Assembled {
1384 parts: vec![part(".text", vec![0x90]), part(".note.GNU-stack", Vec::new())],
1385 names: Vec::new(),
1386 subsections: false,
1387 };
1388 let bytes = assembled(&said, &target()).expect("an object");
1389 let file = object::File::parse(&bytes[..]).expect("a readable object");
1390 let marks = file.sections().filter(|s| s.name() == Ok(".note.GNU-stack")).count();
1391 assert_eq!(marks, 1, "the file said it and it was said again");
1392 }
1393
1394 #[test]
1395 fn only_the_names_a_linker_could_find_are_offered_to_an_archive() {
1396 let input = Assembled {
1397 parts: vec![part(".text", vec![0; 8])],
1398 names: vec![
1399 at("reachable", 0, Sort::Func, Binding::Global),
1400 at("mine", 4, Sort::Func, Binding::Local),
1401 Name {
1402 name: "elsewhere".to_owned(),
1403 at: Held::Undefined,
1404 size: 0,
1405 sort: Sort::Untyped,
1406 binding: Binding::Global,
1407 visibility: Visibility::Default,
1408 },
1409 ],
1410 subsections: false,
1411 };
1412 assert_eq!(assembled_defines(&input), vec!["reachable".to_owned()]);
1413 }
1414
1415 #[test]
1416 fn a_machine_this_does_not_write_is_refused_rather_than_written_wrong() {
1417 let input = Assembled {
1418 parts: vec![part(".text", vec![0x90])],
1419 names: Vec::new(),
1420 subsections: false,
1421 };
1422 let elsewhere = TargetInfo::new(Triple::new(TargetArch::Riscv64, Os::Linux, Env::Gnu));
1423 let why = assembled(&input, &elsewhere).expect_err("this cannot be written");
1424 assert!(format!("{why}").contains("riscv64"), "{why}");
1425 }
1426
1427 #[test]
1428 fn a_file_of_assembly_for_aarch64_is_written_with_that_machine_s_relocations() {
1429 let mut text = part(".text", vec![0; 12]);
1434 let field = |at, symbol: &str, fixup, addend| Reloc {
1435 at,
1436 symbol: symbol.to_owned(),
1437 kind: Reference::Field(fixup),
1438 addend,
1439 after: 0,
1440 };
1441 text.relocs = vec![
1442 field(0, ".Ltable", Fixup::AdrPage21, 8),
1443 field(4, ".Ltable", Fixup::AddLo12, 8),
1444 field(8, "g", Fixup::Call26, 0),
1445 ];
1446 let mut data = part(".data", vec![0; 16]);
1447 data.relocs = vec![Reloc {
1448 at: 8,
1449 symbol: ".Ltable".to_owned(),
1450 kind: Reference::Address { bytes: 8 },
1451 addend: 0,
1452 after: 0,
1453 }];
1454 let mut table = at(".Ltable", 0, Sort::Object, Binding::Local);
1455 table.at = Held::In { part: 1, offset: 0 };
1456 let input = Assembled {
1457 parts: vec![text, data],
1458 names: vec![
1459 table,
1460 Name { at: Held::Undefined, ..at("g", 0, Sort::Untyped, Binding::Global) },
1461 ],
1462 subsections: false,
1463 };
1464 let target = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Linux, Env::Gnu));
1465 let bytes = assembled(&input, &target).expect("an object");
1466 let file = object::File::parse(&bytes[..]).expect("a readable object");
1467 assert_eq!(file.architecture(), Architecture::Aarch64);
1468 let relocs = |name: &str| -> Vec<(u64, elf::RelocationType, i64)> {
1469 let section = file.section_by_name(name).expect("the section");
1470 section
1471 .relocations()
1472 .map(|(at, reloc)| {
1473 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("ELF") };
1474 (at, r_type, reloc.addend())
1475 })
1476 .collect()
1477 };
1478 assert_eq!(
1479 relocs(".text"),
1480 [
1481 (0, elf::R_AARCH64_ADR_PREL_PG_HI21, 8),
1482 (4, elf::R_AARCH64_ADD_ABS_LO12_NC, 8),
1483 (8, elf::R_AARCH64_CALL26, 0)
1484 ]
1485 );
1486 assert_eq!(relocs(".data"), [(8, elf::R_AARCH64_ABS64, 0)]);
1487 assert!(file.symbols().all(|s| s.name() != Ok(".Ltable")), "a label only this file sees");
1488 }
1489
1490 #[test]
1491 fn a_file_of_assembly_for_aarch64_windows_carries_its_addends_in_the_instructions() {
1492 let adrp = 0x9000_0000u32;
1498 let add = 0x9100_0000u32;
1499 let ldr = 0xf940_0000u32;
1500 let bl = 0x9400_0000u32;
1501 let words = [adrp, add, ldr, bl, add | 1 << 22, add];
1502 let mut text = part(".text", words.iter().flat_map(|word| word.to_le_bytes()).collect());
1503 let field = |at, symbol: &str, fixup, addend| Reloc {
1504 at,
1505 symbol: symbol.to_owned(),
1506 kind: Reference::Field(fixup),
1507 addend,
1508 after: 0,
1509 };
1510 text.relocs = vec![
1511 field(0, "table", Fixup::AdrPage21, 8),
1512 field(4, "table", Fixup::AddLo12, 8),
1513 field(8, "table", Fixup::Ldst64Lo12, 8),
1514 field(12, "g", Fixup::Call26, 0),
1515 field(16, "counter", Fixup::SecrelHigh12A, 0),
1516 field(20, "counter", Fixup::SecrelLow12A, 0),
1517 ];
1518 let mut data = part(".data", vec![0; 16]);
1519 data.relocs = vec![
1520 Reloc {
1521 at: 0,
1522 symbol: "table".to_owned(),
1523 kind: Reference::Address { bytes: 8 },
1524 addend: 8,
1525 after: 0,
1526 },
1527 Reloc { at: 8, symbol: "g".to_owned(), kind: Reference::Data, addend: 0, after: 0 },
1528 Reloc {
1529 at: 12,
1530 symbol: "table".to_owned(),
1531 kind: Reference::Image,
1532 addend: 0,
1533 after: 0,
1534 },
1535 ];
1536 let mut table = at("table", 0, Sort::Object, Binding::Global);
1537 table.at = Held::In { part: 1, offset: 0 };
1538 let undefined =
1539 |name| Name { at: Held::Undefined, ..at(name, 0, Sort::Untyped, Binding::Global) };
1540 let input = Assembled {
1541 parts: vec![text, data],
1542 names: vec![table, undefined("g"), undefined("counter")],
1543 subsections: false,
1544 };
1545 let target = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Windows, Env::Gnu));
1546 let bytes = assembled(&input, &target).expect("an object");
1547 let file = object::File::parse(&bytes[..]).expect("a readable object");
1548 assert_eq!(file.format(), object::BinaryFormat::Coff);
1549 assert_eq!(file.architecture(), Architecture::Aarch64);
1550 let relocs = |name: &str| -> Vec<(u64, u16, String)> {
1551 let section = file.section_by_name(name).expect("the section");
1552 section
1553 .relocations()
1554 .map(|(at, reloc)| {
1555 let RelocationFlags::Coff { typ } = reloc.flags() else { panic!("COFF") };
1556 let object::RelocationTarget::Symbol(symbol) = reloc.target() else {
1557 panic!("a symbol")
1558 };
1559 let symbol = file.symbol_by_index(symbol).expect("the symbol");
1560 (at, typ.0, symbol.name().expect("a name").to_owned())
1561 })
1562 .collect()
1563 };
1564 let named = |at, typ: pe::RelocationType, name: &str| (at, typ.0, name.to_owned());
1565 assert_eq!(
1566 relocs(".text"),
1567 [
1568 named(0, pe::IMAGE_REL_ARM64_PAGEBASE_REL21, "table"),
1569 named(4, pe::IMAGE_REL_ARM64_PAGEOFFSET_12A, "table"),
1570 named(8, pe::IMAGE_REL_ARM64_PAGEOFFSET_12L, "table"),
1571 named(12, pe::IMAGE_REL_ARM64_BRANCH26, "g"),
1572 named(16, pe::IMAGE_REL_ARM64_SECREL_HIGH12A, "counter"),
1573 named(20, pe::IMAGE_REL_ARM64_SECREL_LOW12A, "counter"),
1574 ]
1575 );
1576 assert_eq!(
1577 relocs(".data"),
1578 [
1579 named(0, pe::IMAGE_REL_ARM64_ADDR64, "table"),
1580 named(8, pe::IMAGE_REL_ARM64_REL32, "g"),
1581 named(12, pe::IMAGE_REL_ARM64_ADDR32NB, "table"),
1582 ]
1583 );
1584 let text = file.section_by_name(".text").expect("the section");
1585 let text = text.data().expect("the bytes");
1586 let word = |nth: usize| u32::from_le_bytes(text[nth * 4..nth * 4 + 4].try_into().unwrap());
1587 assert_eq!(word(0), adrp | 2 << 5, "eight bytes is immhi two and immlo nothing");
1588 assert_eq!(word(1), add | 8 << 10);
1589 assert_eq!(word(2), ldr | 1 << 10, "eight bytes is one doubleword");
1590 assert_eq!([word(3), word(4), word(5)], [bl, add | 1 << 22, add]);
1591 let data = file.section_by_name(".data").expect("the section");
1592 let data = data.data().expect("the bytes");
1593 assert_eq!(data[..8], 8u64.to_le_bytes());
1594 assert_eq!(data[8..12], 4u32.to_le_bytes());
1595 }
1596
1597 #[test]
1598 fn an_addend_an_aarch64_coff_field_cannot_carry_is_refused() {
1599 for (word, fixup, addend) in
1603 [(0xb940_0000u32, Fixup::Ldst32Lo12, 2), (0x9400_0000, Fixup::Call26, 4)]
1604 {
1605 let mut text = part(".text", word.to_le_bytes().to_vec());
1606 text.relocs = vec![Reloc {
1607 at: 0,
1608 symbol: "g".to_owned(),
1609 kind: Reference::Field(fixup),
1610 addend,
1611 after: 0,
1612 }];
1613 let input = Assembled {
1614 parts: vec![text],
1615 names: vec![Name {
1616 at: Held::Undefined,
1617 ..at("g", 0, Sort::Untyped, Binding::Global)
1618 }],
1619 subsections: false,
1620 };
1621 let target = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Windows, Env::Gnu));
1622 let why = assembled(&input, &target).expect_err("a field that cannot say it");
1623 assert!(format!("{why}").contains(fixup.name()), "{why}");
1624 }
1625 }
1626
1627 #[test]
1628 fn a_file_of_assembly_for_windows_is_written_as_coff() {
1629 let input = Assembled {
1634 parts: vec![part(".text", vec![0xc3])],
1635 names: Vec::new(),
1636 subsections: false,
1637 };
1638 let bytes = assembled(&input, &windows()).expect("an object");
1639 let file = object::File::parse(&bytes[..]).expect("a readable object");
1640 assert_eq!(file.format(), object::BinaryFormat::Coff);
1641 let section = file.section_by_name(".text").expect("the section");
1642 assert_eq!(section.data().expect("the bytes"), &[0xc3]);
1643 assert_eq!(section.kind(), SectionKind::Text);
1644 assert!(
1645 file.section_by_name(".note.GNU-stack").is_none(),
1646 "a format with no marker got one anyway"
1647 );
1648 }
1649
1650 #[test]
1651 fn a_coff_section_keeps_the_letters_it_was_given_and_its_comdat() {
1652 let drectve = Part {
1655 shape: Shape::coff("yni").expect("the letters"),
1656 ..part(".drectve", b" -exclude-symbols:f".to_vec())
1657 };
1658 let refptr = Part {
1659 shape: Shape::coff("dr").expect("the letters"),
1660 group: Some(Group { symbol: ".refptr.x".to_owned(), keep: Keep::Any }),
1661 ..part(".rdata$.refptr.x", vec![0; 8])
1662 };
1663 let input = Assembled {
1664 parts: vec![refptr, drectve],
1665 names: vec![at(".refptr.x", 0, Sort::Object, Binding::Global)],
1666 subsections: false,
1667 };
1668 let bytes = assembled(&input, &windows()).expect("an object");
1669 let file = object::File::parse(&bytes[..]).expect("a readable object");
1670 let flags = |name: &str| match file.section_by_name(name).expect("the section").flags() {
1671 SectionFlags::Coff { characteristics } => characteristics.0,
1672 other => panic!("{other:?}"),
1673 };
1674 let removed = pe::IMAGE_SCN_LNK_REMOVE.0 | pe::IMAGE_SCN_LNK_INFO.0;
1675 assert_eq!(flags(".drectve") & removed, removed);
1676 assert_eq!(flags(".drectve") & (pe::IMAGE_SCN_MEM_READ.0 | pe::IMAGE_SCN_MEM_WRITE.0), 0);
1677 let refptr = flags(".rdata$.refptr.x");
1678 assert_ne!(refptr & pe::IMAGE_SCN_LNK_COMDAT.0, 0, "not a COMDAT");
1679 assert_eq!(refptr & pe::IMAGE_SCN_MEM_WRITE.0, 0, "`r` did not take writing away");
1680 let comdat = file.comdats().next().expect("the COMDAT");
1681 assert_eq!(comdat.kind(), object::ComdatKind::Any);
1682 assert_eq!(file.symbol_by_index(comdat.symbol()).unwrap().name(), Ok(".refptr.x"));
1683
1684 let input = Assembled { names: Vec::new(), ..input };
1686 assert!(assembled(&input, &windows()).is_err());
1687 }
1688
1689 #[test]
1690 fn a_global_label_with_no_type_under_it_is_still_offered_on_coff() {
1691 let input = Assembled {
1698 parts: vec![part(".text", vec![0; 8])],
1699 names: vec![
1700 at("offered", 0, Sort::Untyped, Binding::Global),
1701 at("ours", 4, Sort::Untyped, Binding::Local),
1702 ],
1703 subsections: false,
1704 };
1705 let bytes = assembled(&input, &windows()).expect("an object");
1706 let file = object::File::parse(&bytes[..]).expect("a readable object");
1707 let offered = file.symbols().find(|s| s.name() == Ok("offered")).expect("the label");
1708 assert!(offered.is_global(), "a `.globl` label came out local");
1709 let ours = file.symbols().find(|s| s.name() == Ok("ours")).expect("the other label");
1710 assert!(!ours.is_global(), "a label nothing offered came out global");
1711 let bytes = assembled(&input, &target()).expect("an object");
1714 assert_eq!(st_info(&bytes, "offered") & 0xf, elf::STT_NOTYPE.0);
1715 }
1716
1717 #[test]
1718 fn a_relocation_on_coff_says_how_much_of_the_instruction_comes_after_it() {
1719 let mut text = part(".text", vec![0; 16]);
1724 text.relocs.push(Reloc {
1725 at: 2,
1726 symbol: "elsewhere".to_owned(),
1727 kind: Reference::Data,
1728 addend: -8,
1729 after: 4,
1730 });
1731 let input = Assembled {
1732 parts: vec![text],
1733 names: vec![Name {
1734 name: "elsewhere".to_owned(),
1735 at: Held::Undefined,
1736 size: 0,
1737 sort: Sort::Untyped,
1738 binding: Binding::Global,
1739 visibility: Visibility::Default,
1740 }],
1741 subsections: false,
1742 };
1743 let bytes = assembled(&input, &windows()).expect("an object");
1744 let file = object::File::parse(&bytes[..]).expect("a readable object");
1745 let section = file.section_by_name(".text").expect("the section");
1746 let (at, reloc) = section.relocations().next().expect("the relocation");
1747 assert_eq!(at, 2);
1748 assert_eq!(
1749 reloc.flags(),
1750 RelocationFlags::Coff { typ: pe::RelocationType(pe::IMAGE_REL_AMD64_REL32.0 + 4) }
1751 );
1752 }
1753
1754 fn i386() -> TargetInfo {
1756 TargetInfo::new(Triple::new(TargetArch::X86, Os::Linux, Env::Gnu))
1757 }
1758
1759 fn implicit(file: &object::File<'_>, section: &str) -> Vec<(u64, u32, String, i64)> {
1765 let section = file.section_by_name(section).expect("the section");
1766 let data = section.data().expect("the bytes");
1767 section
1768 .relocations()
1769 .map(|(at, reloc)| {
1770 assert!(reloc.has_implicit_addend(), "a REL file keeps the addend in the bytes");
1771 assert_eq!(reloc.addend(), 0);
1772 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("ELF") };
1773 let width = crate::elf::width_i386(r_type).expect("a width");
1774 let at_ = at as usize;
1775 let mut word = [0u8; 8];
1776 word[..width].copy_from_slice(&data[at_..at_ + width]);
1777 let shift = 64 - 8 * width as u32;
1780 let addend = (i64::from_le_bytes(word) << shift) >> shift;
1781 let object::RelocationTarget::Symbol(index) = reloc.target() else {
1782 panic!("a relocation against a symbol")
1783 };
1784 let symbol = file.symbol_by_index(index).expect("the symbol");
1785 let name = match symbol.kind() {
1786 SymbolKind::Section => {
1787 let section = symbol.section_index().expect("a section symbol's section");
1788 file.section_by_index(section)
1789 .expect("it")
1790 .name()
1791 .expect("a name")
1792 .to_owned()
1793 }
1794 _ => symbol.name().expect("a name").to_owned(),
1795 };
1796 (at, r_type.0, name, addend)
1797 })
1798 .collect()
1799 }
1800
1801 #[test]
1809 fn an_i386_object_is_32_bit_elf_with_the_addends_in_the_bytes() {
1810 let reloc = |at, symbol: &str, kind, addend| Reloc {
1811 at,
1812 symbol: symbol.to_owned(),
1813 kind,
1814 addend,
1815 after: 0,
1816 };
1817 let code = vec![
1818 0xe8, 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0x81, 0xc3, 0, 0, 0, 0, 0x8d, 0x83, 0, 0, 0, 0, 0x8b, 0x83, 0, 0, 0, 0, 0x89, 0x83, 0, 0, 0, 0, 0xa1, 0, 0, 0, 0, ];
1826 let mut text = part(".text", code);
1827 text.relocs = vec![
1828 reloc(1, "foo", Reference::Call, -4),
1829 reloc(6, "bar", Reference::Data, -4),
1830 reloc(12, "_GLOBAL_OFFSET_TABLE_", Reference::GotFront, 2),
1831 reloc(18, ".LC0", Reference::GotOffset, 0),
1832 reloc(24, "foo", Reference::Slot, 0),
1833 reloc(30, "foo", Reference::SlotKept, 0),
1834 reloc(35, "counter", Reference::Signed, 8),
1835 ];
1836 let rodata = part(".rodata", b"abc\0hi\0\0".to_vec());
1837 let mut data = part(".data", vec![0; 11]);
1838 data.relocs = vec![
1839 reloc(0, "foo", Reference::Address { bytes: 4 }, 16),
1840 reloc(4, "bar", Reference::Away, 0),
1841 reloc(8, "foo", Reference::Address { bytes: 2 }, 0),
1842 reloc(10, "foo", Reference::Address { bytes: 1 }, 0),
1843 ];
1844 let undefined = |name: &str| Name {
1845 at: Held::Undefined,
1846 ..at(name, 0, Sort::Untyped, Binding::Global)
1847 };
1848 let names = vec![
1849 at("f", 0, Sort::Func, Binding::Global),
1850 Name {
1851 at: Held::In { part: 1, offset: 4 },
1852 ..at(".LC0", 0, Sort::Untyped, Binding::Local)
1853 },
1854 undefined("foo"),
1855 undefined("bar"),
1856 undefined("counter"),
1857 undefined("_GLOBAL_OFFSET_TABLE_"),
1858 ];
1859 let input = Assembled { parts: vec![text, rodata, data], names, subsections: false };
1860 let bytes = assembled(&input, &i386()).expect("an object");
1861
1862 let header = elf::FileHeader32::<Endianness>::parse(&bytes[..]).expect("a 32 bit header");
1863 let endian = header.endian().expect("an endianness");
1864 assert_eq!(bytes[4], elf::ELFCLASS32.0);
1865 assert_eq!(header.e_machine(endian), elf::EM_386);
1866 assert_eq!(header.e_type(endian), elf::ET_REL);
1867
1868 let file = object::File::parse(&bytes[..]).expect("a readable object");
1869 assert!(!file.is_64());
1870 assert_eq!(file.architecture(), Architecture::I386);
1871 for name in [".rel.text", ".rel.data"] {
1872 let section = file.section_by_name(name).expect("a REL section");
1873 let SectionFlags::Elf { sh_type, .. } = section.flags() else { panic!("ELF") };
1874 assert_eq!(sh_type, elf::SHT_REL, "{name}");
1875 }
1876 assert!(file.section_by_name(".rela.text").is_none(), "i386 has no addend field");
1877
1878 let text = implicit(&file, ".text");
1879 let want = [
1880 (1, elf::R_386_PLT32, "foo", -4),
1881 (6, elf::R_386_PC32, "bar", -4),
1882 (12, elf::R_386_GOTPC, "_GLOBAL_OFFSET_TABLE_", 2),
1883 (18, elf::R_386_GOTOFF, ".rodata", 4),
1885 (24, elf::R_386_GOT32X, "foo", 0),
1886 (30, elf::R_386_GOT32, "foo", 0),
1887 (35, elf::R_386_32, "counter", 8),
1888 ];
1889 let want: Vec<_> = want
1890 .into_iter()
1891 .map(|(at, r_type, name, addend)| (at, r_type.0, name.to_owned(), addend))
1892 .collect();
1893 assert_eq!(text, want);
1894
1895 let data = implicit(&file, ".data");
1896 let want = [
1897 (0, elf::R_386_32, "foo", 16),
1898 (4, elf::R_386_PC32, "bar", 0),
1899 (8, elf::R_386_16, "foo", 0),
1900 (10, elf::R_386_8, "foo", 0),
1901 ];
1902 let want: Vec<_> = want
1903 .into_iter()
1904 .map(|(at, r_type, name, addend)| (at, r_type.0, name.to_owned(), addend))
1905 .collect();
1906 assert_eq!(data, want);
1907 }
1908
1909 #[test]
1914 fn the_i386_thread_local_references_are_the_relocations_gas_writes() {
1915 let kinds = [
1916 (Tls::General, elf::R_386_TLS_GD),
1917 (Tls::Module, elf::R_386_TLS_LDM),
1918 (Tls::InModule, elf::R_386_TLS_LDO_32),
1919 (Tls::Slot, elf::R_386_TLS_GOTIE),
1920 (Tls::SlotAddress, elf::R_386_TLS_IE),
1921 (Tls::SlotNegated, elf::R_386_TLS_IE_32),
1922 (Tls::Offset, elf::R_386_TLS_LE),
1923 (Tls::Negated, elf::R_386_TLS_LE_32),
1924 ];
1925 let mut text = part(".text", vec![0; 4 * kinds.len()]);
1926 text.relocs = kinds
1927 .iter()
1928 .enumerate()
1929 .map(|(n, &(tls, _))| Reloc {
1930 at: 4 * n,
1931 symbol: "x".to_owned(),
1932 kind: Reference::Tls(tls),
1933 addend: n as i64,
1934 after: 0,
1935 })
1936 .collect();
1937 let tdata = part(".tdata", vec![0; 8]);
1938 let names = vec![
1939 at("f", 0, Sort::Func, Binding::Global),
1940 Name {
1941 at: Held::In { part: 1, offset: 4 },
1942 ..at("x", 0, Sort::Thread, Binding::Local)
1943 },
1944 ];
1945 let input = Assembled { parts: vec![text, tdata], names, subsections: false };
1946 let bytes = assembled(&input, &i386()).expect("an object");
1947 let file = object::File::parse(&bytes[..]).expect("a readable object");
1948 let want: Vec<_> = kinds
1949 .iter()
1950 .enumerate()
1951 .map(|(n, &(_, r_type))| (4 * n as u64, r_type.0, "x".to_owned(), n as i64))
1952 .collect();
1953 assert_eq!(implicit(&file, ".text"), want);
1954 }
1955
1956 #[test]
1960 fn an_i386_windows_file_of_assembly_keeps_its_names_as_written() {
1961 use object::pe::{
1962 IMAGE_REL_I386_DIR32, IMAGE_REL_I386_DIR32NB, IMAGE_REL_I386_REL32,
1963 IMAGE_REL_I386_SECREL,
1964 };
1965 let reloc = |at, symbol: &str, kind, addend| Reloc {
1966 at,
1967 symbol: symbol.to_owned(),
1968 kind,
1969 addend,
1970 after: 0,
1971 };
1972 let code = vec![
1973 0xe8, 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xa1, 0, 0, 0, 0, 0xc3,
1977 ];
1978 let mut text = part(".text", code);
1979 text.relocs = vec![
1980 reloc(1, "_puts", Reference::Call, -4),
1981 reloc(6, "@f@8", Reference::Call, -4),
1982 reloc(11, "_counter", Reference::Address { bytes: 4 }, 8),
1983 ];
1984 let mut data = part(".data", vec![0; 12]);
1985 data.relocs = vec![
1986 reloc(0, "_main", Reference::Image, 0),
1987 reloc(4, "_main", Reference::Section, 0),
1988 reloc(8, "_puts", Reference::Away, 0),
1989 ];
1990 let undefined = |name: &str| Name {
1991 at: Held::Undefined,
1992 ..at(name, 0, Sort::Untyped, Binding::Global)
1993 };
1994 let names = vec![
1995 at("_main", 0, Sort::Func, Binding::Global),
1996 undefined("_puts"),
1997 undefined("@f@8"),
1998 undefined("_counter"),
1999 ];
2000 let input = Assembled { parts: vec![text, data], names, subsections: false };
2001 let target = TargetInfo::new(Triple::new(TargetArch::X86, Os::Windows, Env::Gnu));
2002 let bytes = assembled(&input, &target).expect("an object");
2003 let file = object::File::parse(&bytes[..]).expect("a readable object");
2004 assert_eq!(file.format(), object::BinaryFormat::Coff);
2005 assert_eq!(file.architecture(), Architecture::I386);
2006 for name in ["_main", "_puts", "@f@8", "_counter"] {
2007 assert!(file.symbol_by_name(name).is_some(), "{name}");
2008 }
2009 assert!(file.symbol_by_name("__main").is_none());
2010 let types = |name: &str| -> Vec<(u64, u16, String)> {
2011 let section = file.section_by_name(name).expect("a section");
2012 section
2013 .relocations()
2014 .map(|(at, reloc)| {
2015 let RelocationFlags::Coff { typ } = reloc.flags() else { panic!("COFF") };
2016 let object::RelocationTarget::Symbol(symbol) = reloc.target() else {
2017 panic!("a symbol")
2018 };
2019 let symbol = file.symbol_by_index(symbol).expect("a symbol");
2020 (at, typ.0, symbol.name().expect("a name").to_owned())
2021 })
2022 .collect()
2023 };
2024 let want = |list: &[(u64, pe::RelocationType, &str)]| -> Vec<(u64, u16, String)> {
2025 list.iter().map(|&(at, typ, name)| (at, typ.0, name.to_owned())).collect()
2026 };
2027 assert_eq!(
2028 types(".text"),
2029 want(&[
2030 (1, IMAGE_REL_I386_REL32, "_puts"),
2031 (6, IMAGE_REL_I386_REL32, "@f@8"),
2032 (11, IMAGE_REL_I386_DIR32, "_counter"),
2033 ])
2034 );
2035 assert_eq!(
2036 types(".data"),
2037 want(&[
2038 (0, IMAGE_REL_I386_DIR32NB, "_main"),
2039 (4, IMAGE_REL_I386_SECREL, "_main"),
2040 (8, IMAGE_REL_I386_REL32, "_puts"),
2041 ])
2042 );
2043 let code = file.section_by_name(".text").expect("a text section");
2044 let code = code.data().expect("the bytes");
2045 assert_eq!(&code[1..5], &0i32.to_le_bytes(), "a call counts from the end of its bytes");
2046 assert_eq!(&code[11..15], &8i32.to_le_bytes());
2047 let image = file.section_by_name(".data").expect("a data section");
2048 assert_eq!(&image.data().expect("the bytes")[8..12], &4i32.to_le_bytes());
2049 }
2050
2051 #[test]
2055 fn an_i386_windows_label_of_gccs_own_is_its_section_and_not_a_name() {
2056 use object::pe::{IMAGE_REL_I386_DIR32, IMAGE_REL_I386_REL32};
2057 let mut text = part(".text", vec![0xc7, 0x04, 0x24, 0, 0, 0, 0, 0xe9, 0, 0, 0, 0, 0xc3]);
2059 let reloc = |at, symbol: &str, kind, addend| Reloc {
2060 at,
2061 symbol: symbol.to_owned(),
2062 kind,
2063 addend,
2064 after: 0,
2065 };
2066 text.relocs = vec![
2067 reloc(3, "LC1", Reference::Address { bytes: 4 }, 0),
2068 reloc(8, "Lfar", Reference::Data, -4),
2069 ];
2070 let rdata = part(".rdata", vec![0; 8]);
2071 let names = vec![
2072 at("_f", 0, Sort::Func, Binding::Global),
2073 at("L3", 12, Sort::Untyped, Binding::Local),
2074 Name {
2075 at: Held::In { part: 1, offset: 4 },
2076 ..at("LC1", 0, Sort::Untyped, Binding::Local)
2077 },
2078 Name {
2079 at: Held::In { part: 1, offset: 0 },
2080 ..at("Lfar", 0, Sort::Untyped, Binding::Local)
2081 },
2082 ];
2083 let input = Assembled { parts: vec![text, rdata], names, subsections: false };
2084 let target = TargetInfo::new(Triple::new(TargetArch::X86, Os::Windows, Env::Gnu));
2085 let bytes = assembled(&input, &target).expect("an object");
2086 let file = object::File::parse(&bytes[..]).expect("a readable object");
2087 assert!(file.symbol_by_name("_f").is_some());
2088 for name in ["L3", "LC1", "Lfar"] {
2089 assert!(file.symbol_by_name(name).is_none(), "{name}");
2090 }
2091 let section = file.section_by_name(".text").expect("a section");
2092 let relocs: Vec<_> = section
2093 .relocations()
2094 .map(|(at, reloc)| {
2095 let RelocationFlags::Coff { typ } = reloc.flags() else { panic!("COFF") };
2096 let object::RelocationTarget::Symbol(symbol) = reloc.target() else {
2097 panic!("a symbol")
2098 };
2099 let symbol = file.symbol_by_index(symbol).expect("a symbol");
2100 (at, typ.0, symbol.name().expect("a name").to_owned())
2101 })
2102 .collect();
2103 assert_eq!(
2104 relocs,
2105 vec![
2106 (3, IMAGE_REL_I386_DIR32.0, ".rdata".to_owned()),
2107 (8, IMAGE_REL_I386_REL32.0, ".rdata".to_owned())
2108 ]
2109 );
2110 let code = section.data().expect("the bytes");
2111 assert_eq!(&code[3..7], &4i32.to_le_bytes(), "the distance into .rdata");
2112 assert_eq!(&code[8..12], &0i32.to_le_bytes(), "the start of .rdata, counted from the end");
2113 }
2114
2115 #[test]
2121 fn a_reference_i386_has_no_relocation_for_is_refused() {
2122 for kind in [
2123 Reference::Address { bytes: 8 },
2124 Reference::AwayWide,
2125 Reference::Got,
2126 Reference::GotBare,
2127 Reference::GotKept,
2128 Reference::Thread,
2129 Reference::Image,
2130 Reference::Section,
2131 ] {
2132 let mut text = part(".text", vec![0; 8]);
2133 text.relocs =
2134 vec![Reloc { at: 0, symbol: "foo".to_owned(), kind, addend: 0, after: 0 }];
2135 let names =
2136 vec![Name { at: Held::Undefined, ..at("foo", 0, Sort::Untyped, Binding::Global) }];
2137 let input = Assembled { parts: vec![text], names, subsections: false };
2138 let Err(Error::Refused { .. }) = assembled(&input, &i386()) else {
2139 panic!("{kind:?} was written for i386");
2140 };
2141 }
2142 }
2143
2144 #[test]
2147 fn an_i386_addend_too_wide_for_its_bytes_is_refused() {
2148 let mut data = part(".data", vec![0; 2]);
2149 let kind = Reference::Address { bytes: 1 };
2150 data.relocs = vec![Reloc { at: 0, symbol: "foo".to_owned(), kind, addend: 300, after: 0 }];
2151 let names =
2152 vec![Name { at: Held::Undefined, ..at("foo", 0, Sort::Untyped, Binding::Global) }];
2153 let input = Assembled { parts: vec![data], names, subsections: false };
2154 let Err(Error::Refused { why }) = assembled(&input, &i386()) else {
2155 panic!("an addend of 300 went into one byte");
2156 };
2157 assert!(why.contains("300"), "{why}");
2158 }
2159
2160 #[test]
2163 fn a_32_bit_file_gets_its_entry_size_and_groups_in_the_right_place() {
2164 let mut strings = part(".rodata.str1.1", b"hi\0".to_vec());
2165 strings.shape.merge = 1;
2166 strings.shape.strings = true;
2167 let mut kept = part(".text.f", vec![0xc3]);
2168 kept.group = Some(Group { symbol: "f".to_owned(), keep: Keep::Together });
2169 let names = vec![Name {
2170 at: Held::In { part: 1, offset: 0 },
2171 ..at("f", 0, Sort::Func, Binding::Global)
2172 }];
2173 let input = Assembled { parts: vec![strings, kept], names, subsections: false };
2174 let bytes = assembled(&input, &i386()).expect("an object");
2175 let header = elf::FileHeader32::<Endianness>::parse(&bytes[..]).expect("a 32 bit header");
2176 let endian = header.endian().expect("an endianness");
2177 let sections = header.sections(endian, &bytes[..]).expect("the sections");
2178 let find = |name: &str| {
2179 sections
2180 .iter()
2181 .find(|section| sections.section_name(endian, section) == Ok(name.as_bytes()))
2182 .expect("the section")
2183 };
2184 assert_eq!(find(".rodata.str1.1").sh_entsize(endian), 1);
2185 let group = find(".group");
2186 let at = group.sh_offset(endian) as usize;
2187 assert_eq!(&bytes[at..at + 4], &[0; 4], "a group that is not a COMDAT says so");
2188 }
2189
2190 #[test]
2194 fn a_section_that_goes_with_a_name_points_at_the_section_the_name_is_in() {
2195 let record = |link: &str| Part {
2196 shape: Shape { write: true, ..Shape::of("__patchable_function_entries") },
2197 link: Some(link.to_owned()),
2198 ..part("__patchable_function_entries", vec![0; 8])
2199 };
2200 let parts = vec![
2201 part(".text", vec![0xc3]),
2202 part(".init.text", vec![0xc3]),
2203 record("f"),
2204 record("g"),
2205 ];
2206 let names = vec![
2207 Name { at: Held::In { part: 1, offset: 0 }, ..at("f", 0, Sort::Func, Binding::Global) },
2208 at("g", 0, Sort::Func, Binding::Global),
2209 ];
2210 let bytes = assembled(&Assembled { parts, names, subsections: false }, &target())
2211 .expect("an object");
2212 let header = elf::FileHeader64::<Endianness>::parse(&bytes[..]).expect("a header");
2213 let endian = header.endian().expect("an endianness");
2214 let sections = header.sections(endian, &bytes[..]).expect("the sections");
2215 let index = |name: &str| {
2216 sections
2217 .iter()
2218 .position(|section| sections.section_name(endian, section) == Ok(name.as_bytes()))
2219 .expect("the section") as u32
2220 };
2221 let records: Vec<_> = sections
2222 .iter()
2223 .filter(|section| {
2224 sections.section_name(endian, section) == Ok(&b"__patchable_function_entries"[..])
2225 })
2226 .collect();
2227 assert_eq!(records.len(), 2);
2228 for record in &records {
2229 assert!(
2230 record.sh_flags(endian).contains(elf::SHF_LINK_ORDER),
2231 "a record that goes with nothing"
2232 );
2233 }
2234 assert_eq!(records[0].sh_link(endian), index(".init.text"));
2235 assert_eq!(records[1].sh_link(endian), index(".text"));
2236 }
2237
2238 #[test]
2239 fn a_section_that_goes_with_a_name_in_no_section_is_refused() {
2240 let record = Part {
2241 link: Some("nowhere".to_owned()),
2242 ..part("__patchable_function_entries", vec![0; 8])
2243 };
2244 let input = Assembled { parts: vec![record], names: Vec::new(), subsections: false };
2245 assert!(assembled(&input, &target()).is_err());
2246 }
2247}