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 id = obj.add_symbol(Symbol {
653 name: name.name.clone().into_bytes(),
654 value,
655 size,
656 kind: flavour.sort(name.sort, name.binding),
657 scope: crate::file::scope_of(name.binding),
658 weak: name.binding == Binding::Weak,
659 section,
660 flags: SymbolFlags::None,
661 });
662 flavour.see(&mut obj, id, name.binding, name.visibility);
663 if name.sort == Sort::Ifunc {
664 if flavour != Flavour::Elf {
669 let why = format!("'{}' is an indirect function, which only ELF has", name.name);
670 return Err(Error::Refused { why });
671 }
672 crate::elf::indirect(&mut obj, id);
673 }
674 if matches!(name.at, Held::Common { .. }) {
680 if let SymbolFlags::Elf { st_info, .. } = obj.symbol_flags_mut(id) {
681 *st_info = elf::STB_GLOBAL | elf::STT_OBJECT;
682 }
683 }
684 symbols.insert(name.name.clone(), id);
685 }
686
687 let mut groups: Vec<(&Group, Vec<SectionId>)> = Vec::new();
690 let mut grouped: Map<&str, usize> = Map::default();
691 for (part, id) in input.parts.iter().zip(&made) {
692 let Some(group) = &part.group else { continue };
693 match grouped.get(group.symbol.as_str()) {
694 Some(&at) if flavour == Flavour::Elf => groups[at].1.push(*id),
695 _ => {
696 grouped.insert(group.symbol.as_str(), groups.len());
697 groups.push((group, vec![*id]));
698 }
699 }
700 }
701 let mut together = Vec::new();
702 for (group, sections) in groups {
703 let symbol = match symbols.get(&group.symbol) {
704 Some(&symbol) => symbol,
705 None if flavour == Flavour::Elf => obj.add_symbol(Symbol {
710 name: group.symbol.clone().into_bytes(),
711 value: 0,
712 size: 0,
713 kind: SymbolKind::Label,
714 scope: SymbolScope::Compilation,
715 weak: false,
716 section: SymbolSection::Section(sections[0]),
717 flags: SymbolFlags::None,
718 }),
719 None => {
720 let why = format!(
721 "section '{}' is a COMDAT about '{}', which the file does not define",
722 obj.section(sections[0]).name().unwrap_or_default(),
723 group.symbol
724 );
725 return Err(Error::Refused { why });
726 }
727 };
728 together.push(group.keep == Keep::Together);
729 obj.add_comdat(object::write::Comdat { kind: group.keep.kind(), symbol, sections });
730 }
731
732 for (part, id) in input.parts.iter().zip(&made) {
733 for reloc in &part.relocs {
734 let (symbol, addend) = match onto(reloc) {
735 Some((part, offset)) => {
736 (obj.section_symbol(made[part]), reloc.addend + offset as i64)
737 }
738 None => {
739 let Some(&symbol) = symbols.get(&reloc.symbol) else {
740 let why = format!(
741 "'{}' is named by a relocation and by nothing else",
742 reloc.symbol
743 );
744 return Err(Error::Refused { why });
745 };
746 (symbol, reloc.addend)
747 }
748 };
749 let flags = flags_of(reloc.kind, reloc.after).ok_or_else(|| Error::Refused {
750 why: format!("no relocation is {:?}", reloc.kind),
751 })?;
752 let addend = match reloc.kind {
753 crate::section::Reference::Field(fixup) if carried => {
754 let data = obj.section_mut(*id).data_mut();
755 let Some(bytes) = data.get_mut(reloc.at..reloc.at + 4) else {
756 let why = format!("a field at {} is past the end of its section", reloc.at);
757 return Err(Error::Refused { why });
758 };
759 let word = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
760 let word = crate::coff::carry(fixup, word, addend)
761 .map_err(|why| Error::Refused { why })?;
762 bytes.copy_from_slice(&word.to_le_bytes());
763 0
764 }
765 _ => addend,
766 };
767 let record = Relocation { offset: reloc.at as u64, symbol, addend, flags };
768 crate::file::relocate(&mut obj, *id, record)?;
769 }
770 }
771
772 let mut named = Map::default();
775 for chunk in &info.chunks {
776 let how = if flavour == Flavour::Elf && obj.architecture() != Architecture::I386 {
779 info.compress
780 } else {
781 Compress::None
782 };
783 let id = crate::zlib::debug_section(&mut obj, chunk, how);
784 named.insert(chunk.name.as_str(), id);
785 }
786 for chunk in &info.chunks {
787 let section = named[chunk.name.as_str()];
788 for reloc in &chunk.relocs {
789 let spelled =
792 if pe32 { crate::coff::decorate(&reloc.symbol) } else { reloc.symbol.clone() };
793 let (symbol, addend) = match named.get(reloc.symbol.as_str()) {
794 Some(&id) => (obj.section_symbol(id), reloc.addend),
795 None => match defined.get(spelled.as_str()).map(|name| name.at) {
796 Some(Held::In { part, offset }) => {
797 (obj.section_symbol(made[part]), reloc.addend + offset as i64)
798 }
799 _ => match symbols.get(&spelled) {
800 Some(&symbol) => (symbol, reloc.addend),
801 None => {
802 let why = format!(
803 "'{}' is named by the debug information and is not defined here",
804 reloc.symbol
805 );
806 return Err(Error::Refused { why });
807 }
808 },
809 },
810 };
811 let kind = flavour.debug(reloc.kind, named.contains_key(reloc.symbol.as_str()));
812 let flags = flags_of(kind, reloc.after)
813 .ok_or_else(|| Error::Refused { why: format!("no relocation is {kind:?}") })?;
814 let record = Relocation { offset: reloc.at as u64, symbol, addend, flags };
815 crate::file::relocate(&mut obj, section, record)?;
816 }
817 }
818
819 let mut bytes = obj.write().map_err(|why| Error::Refused { why: why.to_string() })?;
823 if flavour == Flavour::Elf {
824 for part in input.parts.iter().filter(|part| part.shape.merge != 0) {
825 entry_size(&mut bytes, &part.name, part.shape.merge);
826 }
827 together_groups(&mut bytes, &together);
828 linked(&mut bytes, input, &defined)?;
829 }
830 Ok(bytes)
831}
832
833fn linked(bytes: &mut [u8], input: &Assembled, defined: &Map<&str, &Name>) -> Result<(), Error> {
847 if input.parts.iter().all(|part| part.link.is_none()) {
848 return Ok(());
849 }
850 let headers = crate::elf::Headers::read(bytes);
851 let index = |part: usize| {
852 let name = &input.parts[part].name;
853 let nth = input.parts[..part].iter().filter(|other| other.name == *name).count();
854 headers.list.iter().enumerate().filter(|(_, header)| header.name == *name).nth(nth)
855 };
856 let mut writes = Vec::new();
857 for (at, part) in input.parts.iter().enumerate() {
858 let Some(link) = &part.link else { continue };
859 let target = match defined.get(link.as_str()).map(|name| name.at) {
860 Some(Held::In { part, .. }) => Some(part),
861 _ => input.parts.iter().position(|other| other.name == *link),
862 };
863 let Some(target) = target.and_then(index) else {
864 let why = format!("section '{}' goes with '{link}', which is in no section", part.name);
865 return Err(Error::Refused { why });
866 };
867 let (_, header) = index(at).expect("a header for every section");
868 let target = u32::try_from(target.0).expect("a file with this many sections in it");
869 writes.push((header.at + headers.link(), target));
870 }
871 for (at, target) in writes {
872 bytes[at..at + 4].copy_from_slice(&target.to_le_bytes());
873 }
874 Ok(())
875}
876
877fn entry_size(bytes: &mut [u8], name: &str, size: u64) {
882 let headers = crate::elf::Headers::read(bytes);
883 let (field, width) = headers.entry_size();
884 for header in headers.list.iter().filter(|header| header.name == name) {
885 let at = header.at + field;
886 bytes[at..at + width].copy_from_slice(&size.to_le_bytes()[..width]);
887 }
888}
889
890fn together_groups(bytes: &mut [u8], together: &[bool]) {
894 if !together.contains(&true) {
895 return;
896 }
897 let headers = crate::elf::Headers::read(bytes);
898 let groups = headers.list.iter().filter(|header| header.sh_type == elf::SHT_GROUP.0);
899 for (header, _) in groups.zip(together).filter(|(_, together)| **together) {
900 bytes[header.offset..header.offset + 4].copy_from_slice(&0u32.to_le_bytes());
901 }
902}
903
904fn moved(
920 flavour: Flavour,
921 pe32: bool,
922 input: &Assembled,
923 defined: &Map<&str, &Name>,
924 reloc: &Reloc,
925) -> Option<(usize, u64)> {
926 use crate::section::Reference;
927 let name = defined.get(reloc.symbol.as_str())?;
928 let Held::In { part, offset } = name.at else { return None };
929 if name.sort == Sort::Ifunc {
933 return None;
934 }
935 if pe32 && unseen(name, pe32) {
936 return Some((part, offset));
937 }
938 if flavour != Flavour::Elf || name.binding != Binding::Local {
939 return None;
940 }
941 let near = matches!(reloc.kind, Reference::Data | Reference::Away | Reference::AwayWide);
942 let fixed = match reloc.kind {
943 Reference::Call
944 | Reference::Got
945 | Reference::GotBare
946 | Reference::GotKept
947 | Reference::Slot
948 | Reference::SlotKept
949 | Reference::GotFront
950 | Reference::Thread
951 | Reference::Tls(_) => false,
952 Reference::Field(
955 Fixup::Call26
956 | Fixup::Jump26
957 | Fixup::GotPage21
958 | Fixup::GotLo12
959 | Fixup::GotTprelPage21
960 | Fixup::GotTprelLo12Nc
961 | Fixup::TprelHi12
962 | Fixup::TprelLo12Nc,
963 ) => false,
964 _ if input.parts.get(part)?.shape.merge != 0 => !near && reloc.addend == 0,
965 _ => true,
966 };
967 fixed.then_some((part, offset))
968}
969
970fn unseen(name: &Name, pe32: bool) -> bool {
978 name.binding == Binding::Local
979 && (name.name.starts_with(".L")
980 || name.name.starts_with("..")
981 || name.name.contains('\u{1}')
982 || (pe32 && name.name.starts_with('L')))
983}
984
985#[must_use]
991pub fn assembled_defines(input: &Assembled) -> Vec<String> {
992 input
993 .names
994 .iter()
995 .filter(|name| name.binding != Binding::Local && name.at != Held::Undefined)
996 .map(|name| name.name.clone())
997 .collect()
998}
999
1000#[cfg(test)]
1001mod tests {
1002 use super::*;
1003
1004 use object::read::elf::{FileHeader as _, SectionHeader as _, Sym as _};
1005 use object::read::{Object as _, ObjectComdat as _, ObjectSection as _, ObjectSymbol as _};
1006 use object::{RelocationFlags, SectionFlags};
1007 use rucc_target::{Arch as TargetArch, Env, Os, Triple};
1008
1009 use crate::section::{Reference, Tls};
1010
1011 fn target() -> TargetInfo {
1013 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Linux, Env::Gnu))
1014 }
1015
1016 fn windows() -> TargetInfo {
1018 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Windows, Env::Gnu))
1019 }
1020
1021 fn part(name: &str, bytes: Vec<u8>) -> Part {
1023 Part {
1024 name: name.to_owned(),
1025 size: bytes.len() as u64,
1026 bytes,
1027 align: 1,
1028 shape: Shape::of(name),
1029 relocs: Vec::new(),
1030 group: None,
1031 link: None,
1032 }
1033 }
1034
1035 fn at(name: &str, offset: u64, sort: Sort, binding: Binding) -> Name {
1037 Name {
1038 name: name.to_owned(),
1039 at: Held::In { part: 0, offset },
1040 size: 0,
1041 sort,
1042 binding,
1043 visibility: Visibility::Default,
1044 }
1045 }
1046
1047 fn raw(bytes: &[u8], want: &str) -> (u8, u64) {
1055 let header = elf::FileHeader64::<Endianness>::parse(bytes).expect("a header");
1056 let endian = header.endian().expect("an endianness");
1057 let table = header.sections(endian, bytes).expect("the sections");
1058 let symbols = table.symbols(endian, bytes, elf::SHT_SYMTAB).expect("a symbol table");
1059 for symbol in symbols.iter() {
1060 if symbols.symbol_name(endian, symbol).expect("a name") == want.as_bytes() {
1061 return (symbol.st_info().0, symbol.st_value(endian));
1062 }
1063 }
1064 panic!("there is no symbol called '{want}'");
1065 }
1066
1067 fn st_info(bytes: &[u8], want: &str) -> u8 {
1069 raw(bytes, want).0
1070 }
1071
1072 #[test]
1073 fn a_section_carries_the_flags_the_source_said_and_not_the_ones_its_name_suggests() {
1074 let mut odd = part(".init.text", vec![0x90]);
1078 odd.shape = Shape { alloc: true, exec: true, bits: true, ..Shape::default() };
1079 let input = Assembled { parts: vec![odd], names: Vec::new(), subsections: false };
1080 let bytes = assembled(&input, &target()).expect("an object");
1081 let file = object::File::parse(&bytes[..]).expect("a readable object");
1082 let section = file.section_by_name(".init.text").expect("the section");
1083 assert_eq!(section.data().expect("the bytes"), &[0x90]);
1084 let SectionFlags::Elf { sh_flags, sh_type } = section.flags() else {
1085 panic!("this is an ELF file");
1086 };
1087 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_EXECINSTR.0);
1088 assert_eq!(sh_flags.0 & elf::SHF_WRITE.0, 0, "nothing said it was writable");
1089 assert_eq!(sh_type, elf::SHT_PROGBITS);
1090 }
1091
1092 #[test]
1093 fn a_section_that_holds_no_bytes_still_says_how_long_it_is() {
1094 let mut room = part(".bss", Vec::new());
1097 room.size = 4096;
1098 room.align = 16;
1099 let input = Assembled { parts: vec![room], names: Vec::new(), subsections: false };
1100 let bytes = assembled(&input, &target()).expect("an object");
1101 assert!(bytes.len() < 4096, "the empty space was written out: {} bytes", bytes.len());
1102 let file = object::File::parse(&bytes[..]).expect("a readable object");
1103 let section = file.section_by_name(".bss").expect("the section");
1104 assert_eq!(section.size(), 4096);
1105 assert_eq!(section.align(), 16);
1106 let SectionFlags::Elf { sh_type, .. } = section.flags() else { panic!("an ELF file") };
1107 assert_eq!(sh_type, elf::SHT_NOBITS);
1108 }
1109
1110 #[test]
1111 fn a_label_nobody_stated_a_type_for_is_a_symbol_with_no_type() {
1112 let input = Assembled {
1116 parts: vec![part(".text", vec![0; 8])],
1117 names: vec![at("plain", 4, Sort::Untyped, Binding::Global)],
1118 subsections: false,
1119 };
1120 let bytes = assembled(&input, &target()).expect("an object");
1121 let file = object::File::parse(&bytes[..]).expect("a readable object");
1122 let plain = file.symbols().find(|s| s.name() == Ok("plain")).expect("the label");
1123 assert_eq!(plain.address(), 4);
1124 assert_eq!(st_info(&bytes, "plain") & 0xf, elf::STT_NOTYPE.0);
1125 }
1126
1127 #[test]
1128 fn what_type_said_is_what_the_symbol_gets() {
1129 let input = Assembled {
1130 parts: vec![part(".text", vec![0; 8])],
1131 names: vec![
1132 at("run", 0, Sort::Func, Binding::Global),
1133 at("held", 4, Sort::Object, Binding::Local),
1134 ],
1135 subsections: false,
1136 };
1137 let bytes = assembled(&input, &target()).expect("an object");
1138 assert_eq!(st_info(&bytes, "run") & 0xf, elf::STT_FUNC.0);
1139 assert_eq!(st_info(&bytes, "held") & 0xf, elf::STT_OBJECT.0);
1140 assert_eq!(st_info(&bytes, "run") >> 4, elf::STB_GLOBAL.0);
1141 assert_eq!(st_info(&bytes, "held") >> 4, elf::STB_LOCAL.0);
1142 }
1143
1144 #[test]
1145 fn a_common_symbol_is_written_the_way_gas_writes_one() {
1146 let input = Assembled {
1151 parts: Vec::new(),
1152 names: vec![Name {
1153 name: "shared".to_owned(),
1154 at: Held::Common { size: 8, align: 8 },
1155 size: 0,
1156 sort: Sort::Object,
1157 binding: Binding::Global,
1158 visibility: Visibility::Default,
1159 }],
1160 subsections: false,
1161 };
1162 let bytes = assembled(&input, &target()).expect("an object");
1163 assert_eq!(st_info(&bytes, "shared"), elf::STB_GLOBAL.0 << 4 | elf::STT_OBJECT.0);
1164 let file = object::File::parse(&bytes[..]).expect("a readable object");
1165 let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the symbol");
1166 assert!(shared.is_common(), "the linker has to be asked for the space");
1167 assert_eq!(shared.size(), 8);
1168 assert_eq!(raw(&bytes, "shared").1, 8, "the boundary it has to start on");
1170 }
1171
1172 #[test]
1173 fn a_set_is_a_number_rather_than_a_place() {
1174 let input = Assembled {
1175 parts: vec![part(".text", vec![0; 8])],
1176 names: vec![Name {
1177 name: "size_of_it".to_owned(),
1178 at: Held::Absolute(25),
1179 size: 0,
1180 sort: Sort::Untyped,
1181 binding: Binding::Global,
1182 visibility: Visibility::Default,
1183 }],
1184 subsections: false,
1185 };
1186 let bytes = assembled(&input, &target()).expect("an object");
1187 let file = object::File::parse(&bytes[..]).expect("a readable object");
1188 let sym = file.symbols().find(|s| s.name() == Ok("size_of_it")).expect("the symbol");
1189 assert_eq!(sym.address(), 25);
1190 assert_eq!(sym.section(), object::SymbolSection::Absolute, "it is not in any section");
1191 }
1192
1193 #[test]
1194 fn a_relocation_names_a_symbol_and_lands_where_the_bytes_are() {
1195 let mut data = part(".data", vec![0; 8]);
1196 data.relocs.push(Reloc {
1197 at: 0,
1198 symbol: "message".to_owned(),
1199 kind: Reference::Address { bytes: 8 },
1200 addend: 0,
1201 after: 0,
1202 });
1203 let input = Assembled {
1204 parts: vec![data],
1205 names: vec![Name {
1206 name: "message".to_owned(),
1207 at: Held::Undefined,
1208 size: 0,
1209 sort: Sort::Untyped,
1210 binding: Binding::Global,
1211 visibility: Visibility::Default,
1212 }],
1213 subsections: false,
1214 };
1215 let bytes = assembled(&input, &target()).expect("an object");
1216 let file = object::File::parse(&bytes[..]).expect("a readable object");
1217 let section = file.section_by_name(".data").expect("the section");
1218 let (at, reloc) = section.relocations().next().expect("one relocation");
1219 assert_eq!(at, 0);
1220 assert_eq!(reloc.addend(), 0);
1221 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("an ELF file") };
1222 assert_eq!(r_type, elf::R_X86_64_64);
1223 }
1224
1225 #[test]
1226 fn a_place_only_this_file_sees_is_reached_through_its_section_as_gas_does() {
1227 let mut text = part(".text", vec![0; 32]);
1231 for (at, symbol, kind) in [
1232 (0, ".L3", Reference::Data),
1233 (4, "helper", Reference::Data),
1234 (8, "helper", Reference::Call),
1235 (12, "shared", Reference::Data),
1236 ] {
1237 let symbol = symbol.to_owned();
1238 text.relocs.push(Reloc { at, symbol, kind, addend: -4, after: 0 });
1239 }
1240 let input = Assembled {
1241 parts: vec![text],
1242 names: vec![
1243 at(".L3", 20, Sort::Untyped, Binding::Local),
1244 at("helper", 24, Sort::Func, Binding::Local),
1245 at("shared", 28, Sort::Func, Binding::Global),
1246 ],
1247 subsections: false,
1248 };
1249 let bytes = assembled(&input, &target()).expect("an object");
1250 let file = object::File::parse(&bytes[..]).expect("a readable object");
1251 let names: Vec<_> = file.symbols().filter_map(|sym| sym.name().ok()).collect();
1252 assert!(!names.contains(&".L3") && names.contains(&"helper"), "{names:?}");
1253 let section = file.section_by_name(".text").expect("the section");
1254 let reached: Vec<_> = section
1255 .relocations()
1256 .map(|(at, reloc)| {
1257 let object::RelocationTarget::Symbol(index) = reloc.target() else {
1258 panic!("a symbol")
1259 };
1260 let symbol = file.symbol_by_index(index).expect("the symbol");
1261 let name = if symbol.kind() == SymbolKind::Section {
1262 ".text"
1263 } else {
1264 symbol.name().expect("a name")
1265 };
1266 (at, name, reloc.addend())
1267 })
1268 .collect();
1269 assert_eq!(
1270 reached,
1271 [(0, ".text", 16), (4, ".text", 20), (8, "helper", -4), (12, "shared", -4)]
1272 );
1273 }
1274
1275 #[test]
1276 fn a_section_of_constants_may_be_merged_and_a_distance_into_it_keeps_its_name() {
1277 let mut text = part(".text", vec![0; 8]);
1278 text.relocs.push(Reloc {
1279 at: 0,
1280 symbol: ".LC0".to_owned(),
1281 kind: Reference::Data,
1282 addend: -4,
1283 after: 0,
1284 });
1285 let strings = Part {
1286 shape: Shape { merge: 1, strings: true, ..Shape::of(".rodata") },
1287 ..part(".rodata.str1.1", b"hi\0".to_vec())
1288 };
1289 let mut name = at(".LC0", 0, Sort::Untyped, Binding::Local);
1290 name.at = Held::In { part: 1, offset: 0 };
1291 let input = Assembled { parts: vec![text, strings], names: vec![name], subsections: false };
1292 let bytes = assembled(&input, &target()).expect("an object");
1293 let file = object::File::parse(&bytes[..]).expect("a readable object");
1294 let section = file.section_by_name(".rodata.str1.1").expect("the section");
1295 let SectionFlags::Elf { sh_flags, .. } = section.flags() else { panic!("an ELF file") };
1296 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_MERGE.0 | elf::SHF_STRINGS.0);
1297 let header = elf::FileHeader64::<Endianness>::parse(&bytes[..]).expect("a header");
1298 let endian = header.endian().expect("an endianness");
1299 let table = header.sections(endian, &bytes[..]).expect("the sections");
1300 let (_, found) = table.section_by_name(endian, b".rodata.str1.1").expect("the section");
1301 assert_eq!(found.sh_entsize.get(endian), 1);
1302 let text = file.section_by_name(".text").expect("the section");
1303 let (_, reloc) = text.relocations().next().expect("one relocation");
1304 let object::RelocationTarget::Symbol(index) = reloc.target() else { panic!("a symbol") };
1305 assert_eq!(file.symbol_by_index(index).and_then(|sym| sym.name()), Ok(".LC0"));
1306 }
1307
1308 #[test]
1309 fn a_relocation_against_a_name_the_file_never_mentions_is_refused() {
1310 let mut data = part(".data", vec![0; 8]);
1314 data.relocs.push(Reloc {
1315 at: 0,
1316 symbol: "nowhere".to_owned(),
1317 kind: Reference::Address { bytes: 8 },
1318 addend: 0,
1319 after: 0,
1320 });
1321 let input = Assembled { parts: vec![data], names: Vec::new(), subsections: false };
1322 let why = assembled(&input, &target()).expect_err("this cannot be written");
1323 assert!(format!("{why}").contains("nowhere"), "{why}");
1324 }
1325
1326 #[test]
1327 fn the_stack_is_marked_only_by_the_file_and_only_once() {
1328 let bare = Assembled {
1331 parts: vec![part(".text", vec![0x90])],
1332 names: Vec::new(),
1333 subsections: false,
1334 };
1335 let bytes = assembled(&bare, &target()).expect("an object");
1336 let file = object::File::parse(&bytes[..]).expect("a readable object");
1337 assert!(file.section_by_name(".note.GNU-stack").is_none(), "a marker nobody wrote");
1338
1339 let said = Assembled {
1340 parts: vec![part(".text", vec![0x90]), part(".note.GNU-stack", Vec::new())],
1341 names: Vec::new(),
1342 subsections: false,
1343 };
1344 let bytes = assembled(&said, &target()).expect("an object");
1345 let file = object::File::parse(&bytes[..]).expect("a readable object");
1346 let marks = file.sections().filter(|s| s.name() == Ok(".note.GNU-stack")).count();
1347 assert_eq!(marks, 1, "the file said it and it was said again");
1348 }
1349
1350 #[test]
1351 fn only_the_names_a_linker_could_find_are_offered_to_an_archive() {
1352 let input = Assembled {
1353 parts: vec![part(".text", vec![0; 8])],
1354 names: vec![
1355 at("reachable", 0, Sort::Func, Binding::Global),
1356 at("mine", 4, Sort::Func, Binding::Local),
1357 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 ],
1366 subsections: false,
1367 };
1368 assert_eq!(assembled_defines(&input), vec!["reachable".to_owned()]);
1369 }
1370
1371 #[test]
1372 fn a_machine_this_does_not_write_is_refused_rather_than_written_wrong() {
1373 let input = Assembled {
1374 parts: vec![part(".text", vec![0x90])],
1375 names: Vec::new(),
1376 subsections: false,
1377 };
1378 let elsewhere = TargetInfo::new(Triple::new(TargetArch::Riscv64, Os::Linux, Env::Gnu));
1379 let why = assembled(&input, &elsewhere).expect_err("this cannot be written");
1380 assert!(format!("{why}").contains("riscv64"), "{why}");
1381 }
1382
1383 #[test]
1384 fn a_file_of_assembly_for_aarch64_is_written_with_that_machine_s_relocations() {
1385 let mut text = part(".text", vec![0; 12]);
1390 let field = |at, symbol: &str, fixup, addend| Reloc {
1391 at,
1392 symbol: symbol.to_owned(),
1393 kind: Reference::Field(fixup),
1394 addend,
1395 after: 0,
1396 };
1397 text.relocs = vec![
1398 field(0, ".Ltable", Fixup::AdrPage21, 8),
1399 field(4, ".Ltable", Fixup::AddLo12, 8),
1400 field(8, "g", Fixup::Call26, 0),
1401 ];
1402 let mut data = part(".data", vec![0; 16]);
1403 data.relocs = vec![Reloc {
1404 at: 8,
1405 symbol: ".Ltable".to_owned(),
1406 kind: Reference::Address { bytes: 8 },
1407 addend: 0,
1408 after: 0,
1409 }];
1410 let mut table = at(".Ltable", 0, Sort::Object, Binding::Local);
1411 table.at = Held::In { part: 1, offset: 0 };
1412 let input = Assembled {
1413 parts: vec![text, data],
1414 names: vec![
1415 table,
1416 Name { at: Held::Undefined, ..at("g", 0, Sort::Untyped, Binding::Global) },
1417 ],
1418 subsections: false,
1419 };
1420 let target = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Linux, Env::Gnu));
1421 let bytes = assembled(&input, &target).expect("an object");
1422 let file = object::File::parse(&bytes[..]).expect("a readable object");
1423 assert_eq!(file.architecture(), Architecture::Aarch64);
1424 let relocs = |name: &str| -> Vec<(u64, elf::RelocationType, i64)> {
1425 let section = file.section_by_name(name).expect("the section");
1426 section
1427 .relocations()
1428 .map(|(at, reloc)| {
1429 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("ELF") };
1430 (at, r_type, reloc.addend())
1431 })
1432 .collect()
1433 };
1434 assert_eq!(
1435 relocs(".text"),
1436 [
1437 (0, elf::R_AARCH64_ADR_PREL_PG_HI21, 8),
1438 (4, elf::R_AARCH64_ADD_ABS_LO12_NC, 8),
1439 (8, elf::R_AARCH64_CALL26, 0)
1440 ]
1441 );
1442 assert_eq!(relocs(".data"), [(8, elf::R_AARCH64_ABS64, 0)]);
1443 assert!(file.symbols().all(|s| s.name() != Ok(".Ltable")), "a label only this file sees");
1444 }
1445
1446 #[test]
1447 fn a_file_of_assembly_for_aarch64_windows_carries_its_addends_in_the_instructions() {
1448 let adrp = 0x9000_0000u32;
1454 let add = 0x9100_0000u32;
1455 let ldr = 0xf940_0000u32;
1456 let bl = 0x9400_0000u32;
1457 let words = [adrp, add, ldr, bl, add | 1 << 22, add];
1458 let mut text = part(".text", words.iter().flat_map(|word| word.to_le_bytes()).collect());
1459 let field = |at, symbol: &str, fixup, addend| Reloc {
1460 at,
1461 symbol: symbol.to_owned(),
1462 kind: Reference::Field(fixup),
1463 addend,
1464 after: 0,
1465 };
1466 text.relocs = vec![
1467 field(0, "table", Fixup::AdrPage21, 8),
1468 field(4, "table", Fixup::AddLo12, 8),
1469 field(8, "table", Fixup::Ldst64Lo12, 8),
1470 field(12, "g", Fixup::Call26, 0),
1471 field(16, "counter", Fixup::SecrelHigh12A, 0),
1472 field(20, "counter", Fixup::SecrelLow12A, 0),
1473 ];
1474 let mut data = part(".data", vec![0; 16]);
1475 data.relocs = vec![
1476 Reloc {
1477 at: 0,
1478 symbol: "table".to_owned(),
1479 kind: Reference::Address { bytes: 8 },
1480 addend: 8,
1481 after: 0,
1482 },
1483 Reloc { at: 8, symbol: "g".to_owned(), kind: Reference::Data, addend: 0, after: 0 },
1484 Reloc {
1485 at: 12,
1486 symbol: "table".to_owned(),
1487 kind: Reference::Image,
1488 addend: 0,
1489 after: 0,
1490 },
1491 ];
1492 let mut table = at("table", 0, Sort::Object, Binding::Global);
1493 table.at = Held::In { part: 1, offset: 0 };
1494 let undefined =
1495 |name| Name { at: Held::Undefined, ..at(name, 0, Sort::Untyped, Binding::Global) };
1496 let input = Assembled {
1497 parts: vec![text, data],
1498 names: vec![table, undefined("g"), undefined("counter")],
1499 subsections: false,
1500 };
1501 let target = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Windows, Env::Gnu));
1502 let bytes = assembled(&input, &target).expect("an object");
1503 let file = object::File::parse(&bytes[..]).expect("a readable object");
1504 assert_eq!(file.format(), object::BinaryFormat::Coff);
1505 assert_eq!(file.architecture(), Architecture::Aarch64);
1506 let relocs = |name: &str| -> Vec<(u64, u16, String)> {
1507 let section = file.section_by_name(name).expect("the section");
1508 section
1509 .relocations()
1510 .map(|(at, reloc)| {
1511 let RelocationFlags::Coff { typ } = reloc.flags() else { panic!("COFF") };
1512 let object::RelocationTarget::Symbol(symbol) = reloc.target() else {
1513 panic!("a symbol")
1514 };
1515 let symbol = file.symbol_by_index(symbol).expect("the symbol");
1516 (at, typ.0, symbol.name().expect("a name").to_owned())
1517 })
1518 .collect()
1519 };
1520 let named = |at, typ: pe::RelocationType, name: &str| (at, typ.0, name.to_owned());
1521 assert_eq!(
1522 relocs(".text"),
1523 [
1524 named(0, pe::IMAGE_REL_ARM64_PAGEBASE_REL21, "table"),
1525 named(4, pe::IMAGE_REL_ARM64_PAGEOFFSET_12A, "table"),
1526 named(8, pe::IMAGE_REL_ARM64_PAGEOFFSET_12L, "table"),
1527 named(12, pe::IMAGE_REL_ARM64_BRANCH26, "g"),
1528 named(16, pe::IMAGE_REL_ARM64_SECREL_HIGH12A, "counter"),
1529 named(20, pe::IMAGE_REL_ARM64_SECREL_LOW12A, "counter"),
1530 ]
1531 );
1532 assert_eq!(
1533 relocs(".data"),
1534 [
1535 named(0, pe::IMAGE_REL_ARM64_ADDR64, "table"),
1536 named(8, pe::IMAGE_REL_ARM64_REL32, "g"),
1537 named(12, pe::IMAGE_REL_ARM64_ADDR32NB, "table"),
1538 ]
1539 );
1540 let text = file.section_by_name(".text").expect("the section");
1541 let text = text.data().expect("the bytes");
1542 let word = |nth: usize| u32::from_le_bytes(text[nth * 4..nth * 4 + 4].try_into().unwrap());
1543 assert_eq!(word(0), adrp | 2 << 5, "eight bytes is immhi two and immlo nothing");
1544 assert_eq!(word(1), add | 8 << 10);
1545 assert_eq!(word(2), ldr | 1 << 10, "eight bytes is one doubleword");
1546 assert_eq!([word(3), word(4), word(5)], [bl, add | 1 << 22, add]);
1547 let data = file.section_by_name(".data").expect("the section");
1548 let data = data.data().expect("the bytes");
1549 assert_eq!(data[..8], 8u64.to_le_bytes());
1550 assert_eq!(data[8..12], 4u32.to_le_bytes());
1551 }
1552
1553 #[test]
1554 fn an_addend_an_aarch64_coff_field_cannot_carry_is_refused() {
1555 for (word, fixup, addend) in
1559 [(0xb940_0000u32, Fixup::Ldst32Lo12, 2), (0x9400_0000, Fixup::Call26, 4)]
1560 {
1561 let mut text = part(".text", word.to_le_bytes().to_vec());
1562 text.relocs = vec![Reloc {
1563 at: 0,
1564 symbol: "g".to_owned(),
1565 kind: Reference::Field(fixup),
1566 addend,
1567 after: 0,
1568 }];
1569 let input = Assembled {
1570 parts: vec![text],
1571 names: vec![Name {
1572 at: Held::Undefined,
1573 ..at("g", 0, Sort::Untyped, Binding::Global)
1574 }],
1575 subsections: false,
1576 };
1577 let target = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Windows, Env::Gnu));
1578 let why = assembled(&input, &target).expect_err("a field that cannot say it");
1579 assert!(format!("{why}").contains(fixup.name()), "{why}");
1580 }
1581 }
1582
1583 #[test]
1584 fn a_file_of_assembly_for_windows_is_written_as_coff() {
1585 let input = Assembled {
1590 parts: vec![part(".text", vec![0xc3])],
1591 names: Vec::new(),
1592 subsections: false,
1593 };
1594 let bytes = assembled(&input, &windows()).expect("an object");
1595 let file = object::File::parse(&bytes[..]).expect("a readable object");
1596 assert_eq!(file.format(), object::BinaryFormat::Coff);
1597 let section = file.section_by_name(".text").expect("the section");
1598 assert_eq!(section.data().expect("the bytes"), &[0xc3]);
1599 assert_eq!(section.kind(), SectionKind::Text);
1600 assert!(
1601 file.section_by_name(".note.GNU-stack").is_none(),
1602 "a format with no marker got one anyway"
1603 );
1604 }
1605
1606 #[test]
1607 fn a_coff_section_keeps_the_letters_it_was_given_and_its_comdat() {
1608 let drectve = Part {
1611 shape: Shape::coff("yni").expect("the letters"),
1612 ..part(".drectve", b" -exclude-symbols:f".to_vec())
1613 };
1614 let refptr = Part {
1615 shape: Shape::coff("dr").expect("the letters"),
1616 group: Some(Group { symbol: ".refptr.x".to_owned(), keep: Keep::Any }),
1617 ..part(".rdata$.refptr.x", vec![0; 8])
1618 };
1619 let input = Assembled {
1620 parts: vec![refptr, drectve],
1621 names: vec![at(".refptr.x", 0, Sort::Object, Binding::Global)],
1622 subsections: false,
1623 };
1624 let bytes = assembled(&input, &windows()).expect("an object");
1625 let file = object::File::parse(&bytes[..]).expect("a readable object");
1626 let flags = |name: &str| match file.section_by_name(name).expect("the section").flags() {
1627 SectionFlags::Coff { characteristics } => characteristics.0,
1628 other => panic!("{other:?}"),
1629 };
1630 let removed = pe::IMAGE_SCN_LNK_REMOVE.0 | pe::IMAGE_SCN_LNK_INFO.0;
1631 assert_eq!(flags(".drectve") & removed, removed);
1632 assert_eq!(flags(".drectve") & (pe::IMAGE_SCN_MEM_READ.0 | pe::IMAGE_SCN_MEM_WRITE.0), 0);
1633 let refptr = flags(".rdata$.refptr.x");
1634 assert_ne!(refptr & pe::IMAGE_SCN_LNK_COMDAT.0, 0, "not a COMDAT");
1635 assert_eq!(refptr & pe::IMAGE_SCN_MEM_WRITE.0, 0, "`r` did not take writing away");
1636 let comdat = file.comdats().next().expect("the COMDAT");
1637 assert_eq!(comdat.kind(), object::ComdatKind::Any);
1638 assert_eq!(file.symbol_by_index(comdat.symbol()).unwrap().name(), Ok(".refptr.x"));
1639
1640 let input = Assembled { names: Vec::new(), ..input };
1642 assert!(assembled(&input, &windows()).is_err());
1643 }
1644
1645 #[test]
1646 fn a_global_label_with_no_type_under_it_is_still_offered_on_coff() {
1647 let input = Assembled {
1654 parts: vec![part(".text", vec![0; 8])],
1655 names: vec![
1656 at("offered", 0, Sort::Untyped, Binding::Global),
1657 at("ours", 4, Sort::Untyped, Binding::Local),
1658 ],
1659 subsections: false,
1660 };
1661 let bytes = assembled(&input, &windows()).expect("an object");
1662 let file = object::File::parse(&bytes[..]).expect("a readable object");
1663 let offered = file.symbols().find(|s| s.name() == Ok("offered")).expect("the label");
1664 assert!(offered.is_global(), "a `.globl` label came out local");
1665 let ours = file.symbols().find(|s| s.name() == Ok("ours")).expect("the other label");
1666 assert!(!ours.is_global(), "a label nothing offered came out global");
1667 let bytes = assembled(&input, &target()).expect("an object");
1670 assert_eq!(st_info(&bytes, "offered") & 0xf, elf::STT_NOTYPE.0);
1671 }
1672
1673 #[test]
1674 fn a_relocation_on_coff_says_how_much_of_the_instruction_comes_after_it() {
1675 let mut text = part(".text", vec![0; 16]);
1680 text.relocs.push(Reloc {
1681 at: 2,
1682 symbol: "elsewhere".to_owned(),
1683 kind: Reference::Data,
1684 addend: -8,
1685 after: 4,
1686 });
1687 let input = Assembled {
1688 parts: vec![text],
1689 names: vec![Name {
1690 name: "elsewhere".to_owned(),
1691 at: Held::Undefined,
1692 size: 0,
1693 sort: Sort::Untyped,
1694 binding: Binding::Global,
1695 visibility: Visibility::Default,
1696 }],
1697 subsections: false,
1698 };
1699 let bytes = assembled(&input, &windows()).expect("an object");
1700 let file = object::File::parse(&bytes[..]).expect("a readable object");
1701 let section = file.section_by_name(".text").expect("the section");
1702 let (at, reloc) = section.relocations().next().expect("the relocation");
1703 assert_eq!(at, 2);
1704 assert_eq!(
1705 reloc.flags(),
1706 RelocationFlags::Coff { typ: pe::RelocationType(pe::IMAGE_REL_AMD64_REL32.0 + 4) }
1707 );
1708 }
1709
1710 fn i386() -> TargetInfo {
1712 TargetInfo::new(Triple::new(TargetArch::X86, Os::Linux, Env::Gnu))
1713 }
1714
1715 fn implicit(file: &object::File<'_>, section: &str) -> Vec<(u64, u32, String, i64)> {
1721 let section = file.section_by_name(section).expect("the section");
1722 let data = section.data().expect("the bytes");
1723 section
1724 .relocations()
1725 .map(|(at, reloc)| {
1726 assert!(reloc.has_implicit_addend(), "a REL file keeps the addend in the bytes");
1727 assert_eq!(reloc.addend(), 0);
1728 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("ELF") };
1729 let width = crate::elf::width_i386(r_type).expect("a width");
1730 let at_ = at as usize;
1731 let mut word = [0u8; 8];
1732 word[..width].copy_from_slice(&data[at_..at_ + width]);
1733 let shift = 64 - 8 * width as u32;
1736 let addend = (i64::from_le_bytes(word) << shift) >> shift;
1737 let object::RelocationTarget::Symbol(index) = reloc.target() else {
1738 panic!("a relocation against a symbol")
1739 };
1740 let symbol = file.symbol_by_index(index).expect("the symbol");
1741 let name = match symbol.kind() {
1742 SymbolKind::Section => {
1743 let section = symbol.section_index().expect("a section symbol's section");
1744 file.section_by_index(section)
1745 .expect("it")
1746 .name()
1747 .expect("a name")
1748 .to_owned()
1749 }
1750 _ => symbol.name().expect("a name").to_owned(),
1751 };
1752 (at, r_type.0, name, addend)
1753 })
1754 .collect()
1755 }
1756
1757 #[test]
1765 fn an_i386_object_is_32_bit_elf_with_the_addends_in_the_bytes() {
1766 let reloc = |at, symbol: &str, kind, addend| Reloc {
1767 at,
1768 symbol: symbol.to_owned(),
1769 kind,
1770 addend,
1771 after: 0,
1772 };
1773 let code = vec![
1774 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, ];
1782 let mut text = part(".text", code);
1783 text.relocs = vec![
1784 reloc(1, "foo", Reference::Call, -4),
1785 reloc(6, "bar", Reference::Data, -4),
1786 reloc(12, "_GLOBAL_OFFSET_TABLE_", Reference::GotFront, 2),
1787 reloc(18, ".LC0", Reference::GotOffset, 0),
1788 reloc(24, "foo", Reference::Slot, 0),
1789 reloc(30, "foo", Reference::SlotKept, 0),
1790 reloc(35, "counter", Reference::Signed, 8),
1791 ];
1792 let rodata = part(".rodata", b"abc\0hi\0\0".to_vec());
1793 let mut data = part(".data", vec![0; 11]);
1794 data.relocs = vec![
1795 reloc(0, "foo", Reference::Address { bytes: 4 }, 16),
1796 reloc(4, "bar", Reference::Away, 0),
1797 reloc(8, "foo", Reference::Address { bytes: 2 }, 0),
1798 reloc(10, "foo", Reference::Address { bytes: 1 }, 0),
1799 ];
1800 let undefined = |name: &str| Name {
1801 at: Held::Undefined,
1802 ..at(name, 0, Sort::Untyped, Binding::Global)
1803 };
1804 let names = vec![
1805 at("f", 0, Sort::Func, Binding::Global),
1806 Name {
1807 at: Held::In { part: 1, offset: 4 },
1808 ..at(".LC0", 0, Sort::Untyped, Binding::Local)
1809 },
1810 undefined("foo"),
1811 undefined("bar"),
1812 undefined("counter"),
1813 undefined("_GLOBAL_OFFSET_TABLE_"),
1814 ];
1815 let input = Assembled { parts: vec![text, rodata, data], names, subsections: false };
1816 let bytes = assembled(&input, &i386()).expect("an object");
1817
1818 let header = elf::FileHeader32::<Endianness>::parse(&bytes[..]).expect("a 32 bit header");
1819 let endian = header.endian().expect("an endianness");
1820 assert_eq!(bytes[4], elf::ELFCLASS32.0);
1821 assert_eq!(header.e_machine(endian), elf::EM_386);
1822 assert_eq!(header.e_type(endian), elf::ET_REL);
1823
1824 let file = object::File::parse(&bytes[..]).expect("a readable object");
1825 assert!(!file.is_64());
1826 assert_eq!(file.architecture(), Architecture::I386);
1827 for name in [".rel.text", ".rel.data"] {
1828 let section = file.section_by_name(name).expect("a REL section");
1829 let SectionFlags::Elf { sh_type, .. } = section.flags() else { panic!("ELF") };
1830 assert_eq!(sh_type, elf::SHT_REL, "{name}");
1831 }
1832 assert!(file.section_by_name(".rela.text").is_none(), "i386 has no addend field");
1833
1834 let text = implicit(&file, ".text");
1835 let want = [
1836 (1, elf::R_386_PLT32, "foo", -4),
1837 (6, elf::R_386_PC32, "bar", -4),
1838 (12, elf::R_386_GOTPC, "_GLOBAL_OFFSET_TABLE_", 2),
1839 (18, elf::R_386_GOTOFF, ".rodata", 4),
1841 (24, elf::R_386_GOT32X, "foo", 0),
1842 (30, elf::R_386_GOT32, "foo", 0),
1843 (35, elf::R_386_32, "counter", 8),
1844 ];
1845 let want: Vec<_> = want
1846 .into_iter()
1847 .map(|(at, r_type, name, addend)| (at, r_type.0, name.to_owned(), addend))
1848 .collect();
1849 assert_eq!(text, want);
1850
1851 let data = implicit(&file, ".data");
1852 let want = [
1853 (0, elf::R_386_32, "foo", 16),
1854 (4, elf::R_386_PC32, "bar", 0),
1855 (8, elf::R_386_16, "foo", 0),
1856 (10, elf::R_386_8, "foo", 0),
1857 ];
1858 let want: Vec<_> = want
1859 .into_iter()
1860 .map(|(at, r_type, name, addend)| (at, r_type.0, name.to_owned(), addend))
1861 .collect();
1862 assert_eq!(data, want);
1863 }
1864
1865 #[test]
1870 fn the_i386_thread_local_references_are_the_relocations_gas_writes() {
1871 let kinds = [
1872 (Tls::General, elf::R_386_TLS_GD),
1873 (Tls::Module, elf::R_386_TLS_LDM),
1874 (Tls::InModule, elf::R_386_TLS_LDO_32),
1875 (Tls::Slot, elf::R_386_TLS_GOTIE),
1876 (Tls::SlotAddress, elf::R_386_TLS_IE),
1877 (Tls::SlotNegated, elf::R_386_TLS_IE_32),
1878 (Tls::Offset, elf::R_386_TLS_LE),
1879 (Tls::Negated, elf::R_386_TLS_LE_32),
1880 ];
1881 let mut text = part(".text", vec![0; 4 * kinds.len()]);
1882 text.relocs = kinds
1883 .iter()
1884 .enumerate()
1885 .map(|(n, &(tls, _))| Reloc {
1886 at: 4 * n,
1887 symbol: "x".to_owned(),
1888 kind: Reference::Tls(tls),
1889 addend: n as i64,
1890 after: 0,
1891 })
1892 .collect();
1893 let tdata = part(".tdata", vec![0; 8]);
1894 let names = vec![
1895 at("f", 0, Sort::Func, Binding::Global),
1896 Name {
1897 at: Held::In { part: 1, offset: 4 },
1898 ..at("x", 0, Sort::Thread, Binding::Local)
1899 },
1900 ];
1901 let input = Assembled { parts: vec![text, tdata], names, subsections: false };
1902 let bytes = assembled(&input, &i386()).expect("an object");
1903 let file = object::File::parse(&bytes[..]).expect("a readable object");
1904 let want: Vec<_> = kinds
1905 .iter()
1906 .enumerate()
1907 .map(|(n, &(_, r_type))| (4 * n as u64, r_type.0, "x".to_owned(), n as i64))
1908 .collect();
1909 assert_eq!(implicit(&file, ".text"), want);
1910 }
1911
1912 #[test]
1916 fn an_i386_windows_file_of_assembly_keeps_its_names_as_written() {
1917 use object::pe::{
1918 IMAGE_REL_I386_DIR32, IMAGE_REL_I386_DIR32NB, IMAGE_REL_I386_REL32,
1919 IMAGE_REL_I386_SECREL,
1920 };
1921 let reloc = |at, symbol: &str, kind, addend| Reloc {
1922 at,
1923 symbol: symbol.to_owned(),
1924 kind,
1925 addend,
1926 after: 0,
1927 };
1928 let code = vec![
1929 0xe8, 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xa1, 0, 0, 0, 0, 0xc3,
1933 ];
1934 let mut text = part(".text", code);
1935 text.relocs = vec![
1936 reloc(1, "_puts", Reference::Call, -4),
1937 reloc(6, "@f@8", Reference::Call, -4),
1938 reloc(11, "_counter", Reference::Address { bytes: 4 }, 8),
1939 ];
1940 let mut data = part(".data", vec![0; 12]);
1941 data.relocs = vec![
1942 reloc(0, "_main", Reference::Image, 0),
1943 reloc(4, "_main", Reference::Section, 0),
1944 reloc(8, "_puts", Reference::Away, 0),
1945 ];
1946 let undefined = |name: &str| Name {
1947 at: Held::Undefined,
1948 ..at(name, 0, Sort::Untyped, Binding::Global)
1949 };
1950 let names = vec![
1951 at("_main", 0, Sort::Func, Binding::Global),
1952 undefined("_puts"),
1953 undefined("@f@8"),
1954 undefined("_counter"),
1955 ];
1956 let input = Assembled { parts: vec![text, data], names, subsections: false };
1957 let target = TargetInfo::new(Triple::new(TargetArch::X86, Os::Windows, Env::Gnu));
1958 let bytes = assembled(&input, &target).expect("an object");
1959 let file = object::File::parse(&bytes[..]).expect("a readable object");
1960 assert_eq!(file.format(), object::BinaryFormat::Coff);
1961 assert_eq!(file.architecture(), Architecture::I386);
1962 for name in ["_main", "_puts", "@f@8", "_counter"] {
1963 assert!(file.symbol_by_name(name).is_some(), "{name}");
1964 }
1965 assert!(file.symbol_by_name("__main").is_none());
1966 let types = |name: &str| -> Vec<(u64, u16, String)> {
1967 let section = file.section_by_name(name).expect("a section");
1968 section
1969 .relocations()
1970 .map(|(at, reloc)| {
1971 let RelocationFlags::Coff { typ } = reloc.flags() else { panic!("COFF") };
1972 let object::RelocationTarget::Symbol(symbol) = reloc.target() else {
1973 panic!("a symbol")
1974 };
1975 let symbol = file.symbol_by_index(symbol).expect("a symbol");
1976 (at, typ.0, symbol.name().expect("a name").to_owned())
1977 })
1978 .collect()
1979 };
1980 let want = |list: &[(u64, pe::RelocationType, &str)]| -> Vec<(u64, u16, String)> {
1981 list.iter().map(|&(at, typ, name)| (at, typ.0, name.to_owned())).collect()
1982 };
1983 assert_eq!(
1984 types(".text"),
1985 want(&[
1986 (1, IMAGE_REL_I386_REL32, "_puts"),
1987 (6, IMAGE_REL_I386_REL32, "@f@8"),
1988 (11, IMAGE_REL_I386_DIR32, "_counter"),
1989 ])
1990 );
1991 assert_eq!(
1992 types(".data"),
1993 want(&[
1994 (0, IMAGE_REL_I386_DIR32NB, "_main"),
1995 (4, IMAGE_REL_I386_SECREL, "_main"),
1996 (8, IMAGE_REL_I386_REL32, "_puts"),
1997 ])
1998 );
1999 let code = file.section_by_name(".text").expect("a text section");
2000 let code = code.data().expect("the bytes");
2001 assert_eq!(&code[1..5], &0i32.to_le_bytes(), "a call counts from the end of its bytes");
2002 assert_eq!(&code[11..15], &8i32.to_le_bytes());
2003 let image = file.section_by_name(".data").expect("a data section");
2004 assert_eq!(&image.data().expect("the bytes")[8..12], &4i32.to_le_bytes());
2005 }
2006
2007 #[test]
2011 fn an_i386_windows_label_of_gccs_own_is_its_section_and_not_a_name() {
2012 use object::pe::{IMAGE_REL_I386_DIR32, IMAGE_REL_I386_REL32};
2013 let mut text = part(".text", vec![0xc7, 0x04, 0x24, 0, 0, 0, 0, 0xe9, 0, 0, 0, 0, 0xc3]);
2015 let reloc = |at, symbol: &str, kind, addend| Reloc {
2016 at,
2017 symbol: symbol.to_owned(),
2018 kind,
2019 addend,
2020 after: 0,
2021 };
2022 text.relocs = vec![
2023 reloc(3, "LC1", Reference::Address { bytes: 4 }, 0),
2024 reloc(8, "Lfar", Reference::Data, -4),
2025 ];
2026 let rdata = part(".rdata", vec![0; 8]);
2027 let names = vec![
2028 at("_f", 0, Sort::Func, Binding::Global),
2029 at("L3", 12, Sort::Untyped, Binding::Local),
2030 Name {
2031 at: Held::In { part: 1, offset: 4 },
2032 ..at("LC1", 0, Sort::Untyped, Binding::Local)
2033 },
2034 Name {
2035 at: Held::In { part: 1, offset: 0 },
2036 ..at("Lfar", 0, Sort::Untyped, Binding::Local)
2037 },
2038 ];
2039 let input = Assembled { parts: vec![text, rdata], names, subsections: false };
2040 let target = TargetInfo::new(Triple::new(TargetArch::X86, Os::Windows, Env::Gnu));
2041 let bytes = assembled(&input, &target).expect("an object");
2042 let file = object::File::parse(&bytes[..]).expect("a readable object");
2043 assert!(file.symbol_by_name("_f").is_some());
2044 for name in ["L3", "LC1", "Lfar"] {
2045 assert!(file.symbol_by_name(name).is_none(), "{name}");
2046 }
2047 let section = file.section_by_name(".text").expect("a section");
2048 let relocs: Vec<_> = section
2049 .relocations()
2050 .map(|(at, reloc)| {
2051 let RelocationFlags::Coff { typ } = reloc.flags() else { panic!("COFF") };
2052 let object::RelocationTarget::Symbol(symbol) = reloc.target() else {
2053 panic!("a symbol")
2054 };
2055 let symbol = file.symbol_by_index(symbol).expect("a symbol");
2056 (at, typ.0, symbol.name().expect("a name").to_owned())
2057 })
2058 .collect();
2059 assert_eq!(
2060 relocs,
2061 vec![
2062 (3, IMAGE_REL_I386_DIR32.0, ".rdata".to_owned()),
2063 (8, IMAGE_REL_I386_REL32.0, ".rdata".to_owned())
2064 ]
2065 );
2066 let code = section.data().expect("the bytes");
2067 assert_eq!(&code[3..7], &4i32.to_le_bytes(), "the distance into .rdata");
2068 assert_eq!(&code[8..12], &0i32.to_le_bytes(), "the start of .rdata, counted from the end");
2069 }
2070
2071 #[test]
2077 fn a_reference_i386_has_no_relocation_for_is_refused() {
2078 for kind in [
2079 Reference::Address { bytes: 8 },
2080 Reference::AwayWide,
2081 Reference::Got,
2082 Reference::GotBare,
2083 Reference::GotKept,
2084 Reference::Thread,
2085 Reference::Image,
2086 Reference::Section,
2087 ] {
2088 let mut text = part(".text", vec![0; 8]);
2089 text.relocs =
2090 vec![Reloc { at: 0, symbol: "foo".to_owned(), kind, addend: 0, after: 0 }];
2091 let names =
2092 vec![Name { at: Held::Undefined, ..at("foo", 0, Sort::Untyped, Binding::Global) }];
2093 let input = Assembled { parts: vec![text], names, subsections: false };
2094 let Err(Error::Refused { .. }) = assembled(&input, &i386()) else {
2095 panic!("{kind:?} was written for i386");
2096 };
2097 }
2098 }
2099
2100 #[test]
2103 fn an_i386_addend_too_wide_for_its_bytes_is_refused() {
2104 let mut data = part(".data", vec![0; 2]);
2105 let kind = Reference::Address { bytes: 1 };
2106 data.relocs = vec![Reloc { at: 0, symbol: "foo".to_owned(), kind, addend: 300, after: 0 }];
2107 let names =
2108 vec![Name { at: Held::Undefined, ..at("foo", 0, Sort::Untyped, Binding::Global) }];
2109 let input = Assembled { parts: vec![data], names, subsections: false };
2110 let Err(Error::Refused { why }) = assembled(&input, &i386()) else {
2111 panic!("an addend of 300 went into one byte");
2112 };
2113 assert!(why.contains("300"), "{why}");
2114 }
2115
2116 #[test]
2119 fn a_32_bit_file_gets_its_entry_size_and_groups_in_the_right_place() {
2120 let mut strings = part(".rodata.str1.1", b"hi\0".to_vec());
2121 strings.shape.merge = 1;
2122 strings.shape.strings = true;
2123 let mut kept = part(".text.f", vec![0xc3]);
2124 kept.group = Some(Group { symbol: "f".to_owned(), keep: Keep::Together });
2125 let names = vec![Name {
2126 at: Held::In { part: 1, offset: 0 },
2127 ..at("f", 0, Sort::Func, Binding::Global)
2128 }];
2129 let input = Assembled { parts: vec![strings, kept], names, subsections: false };
2130 let bytes = assembled(&input, &i386()).expect("an object");
2131 let header = elf::FileHeader32::<Endianness>::parse(&bytes[..]).expect("a 32 bit header");
2132 let endian = header.endian().expect("an endianness");
2133 let sections = header.sections(endian, &bytes[..]).expect("the sections");
2134 let find = |name: &str| {
2135 sections
2136 .iter()
2137 .find(|section| sections.section_name(endian, section) == Ok(name.as_bytes()))
2138 .expect("the section")
2139 };
2140 assert_eq!(find(".rodata.str1.1").sh_entsize(endian), 1);
2141 let group = find(".group");
2142 let at = group.sh_offset(endian) as usize;
2143 assert_eq!(&bytes[at..at + 4], &[0; 4], "a group that is not a COMDAT says so");
2144 }
2145
2146 #[test]
2150 fn a_section_that_goes_with_a_name_points_at_the_section_the_name_is_in() {
2151 let record = |link: &str| Part {
2152 shape: Shape { write: true, ..Shape::of("__patchable_function_entries") },
2153 link: Some(link.to_owned()),
2154 ..part("__patchable_function_entries", vec![0; 8])
2155 };
2156 let parts = vec![
2157 part(".text", vec![0xc3]),
2158 part(".init.text", vec![0xc3]),
2159 record("f"),
2160 record("g"),
2161 ];
2162 let names = vec![
2163 Name { at: Held::In { part: 1, offset: 0 }, ..at("f", 0, Sort::Func, Binding::Global) },
2164 at("g", 0, Sort::Func, Binding::Global),
2165 ];
2166 let bytes = assembled(&Assembled { parts, names, subsections: false }, &target())
2167 .expect("an object");
2168 let header = elf::FileHeader64::<Endianness>::parse(&bytes[..]).expect("a header");
2169 let endian = header.endian().expect("an endianness");
2170 let sections = header.sections(endian, &bytes[..]).expect("the sections");
2171 let index = |name: &str| {
2172 sections
2173 .iter()
2174 .position(|section| sections.section_name(endian, section) == Ok(name.as_bytes()))
2175 .expect("the section") as u32
2176 };
2177 let records: Vec<_> = sections
2178 .iter()
2179 .filter(|section| {
2180 sections.section_name(endian, section) == Ok(&b"__patchable_function_entries"[..])
2181 })
2182 .collect();
2183 assert_eq!(records.len(), 2);
2184 for record in &records {
2185 assert!(
2186 record.sh_flags(endian).contains(elf::SHF_LINK_ORDER),
2187 "a record that goes with nothing"
2188 );
2189 }
2190 assert_eq!(records[0].sh_link(endian), index(".init.text"));
2191 assert_eq!(records[1].sh_link(endian), index(".text"));
2192 }
2193
2194 #[test]
2195 fn a_section_that_goes_with_a_name_in_no_section_is_refused() {
2196 let record = Part {
2197 link: Some("nowhere".to_owned()),
2198 ..part("__patchable_function_entries", vec![0; 8])
2199 };
2200 let input = Assembled { parts: vec![record], names: Vec::new(), subsections: false };
2201 assert!(assembled(&input, &target()).is_err());
2202 }
2203}