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]
188 pub fn of(name: &str) -> Shape {
189 let base = Shape { alloc: true, bits: true, ..Shape::default() };
190 let head = name.split_once('.').map_or(name, |(_, rest)| rest);
191 let head = head.split_once('.').map_or(head, |(first, _)| first);
192 match head {
193 "text" | "init" | "fini" => Shape { exec: true, ..base },
194 "rodata" | "eh_frame_hdr" => base,
195 "bss" => Shape { write: true, bits: false, ..base },
196 "tbss" => Shape { write: true, thread: true, bits: false, ..base },
197 "tdata" => Shape { write: true, thread: true, ..base },
198 _ if Array::of(name).is_some() => Shape { write: true, array: Array::of(name), ..base },
202 "debug_info" | "debug_abbrev" | "debug_line" | "debug_str" | "comment" => {
205 Shape { alloc: false, bits: true, ..Shape::default() }
206 }
207 _ => Shape { write: true, ..base },
208 }
209 }
210
211 #[must_use]
221 pub fn unflagged(name: &str) -> Shape {
222 let bits = Shape { bits: true, ..Shape::default() };
223 let family = |head: &str| {
224 name.strip_prefix(head).is_some_and(|rest| rest.is_empty() || rest.starts_with('.'))
225 };
226 let alloc = Shape { alloc: true, ..bits };
227 if family(".text") || name == ".init" || name == ".fini" {
228 Shape { exec: true, ..alloc }
229 } else if family(".data") || name == ".data1" {
230 Shape { write: true, ..alloc }
231 } else if family(".rodata") || name == ".rodata1" {
232 alloc
233 } else if family(".bss") || family(".gnu.linkonce.b") {
234 Shape { write: true, bits: false, ..alloc }
235 } else if family(".tbss") {
236 Shape { write: true, thread: true, bits: false, ..alloc }
237 } else if family(".tdata") {
238 Shape { write: true, thread: true, ..alloc }
239 } else if let Some(array) = Array::of(name) {
240 Shape { write: true, array: Some(array), ..alloc }
241 } else if name.starts_with(".note") && name != ".note.GNU-stack" {
242 Shape { note: true, ..bits }
243 } else {
244 bits
245 }
246 }
247
248 #[must_use]
256 pub fn implied(name: &str) -> Shape {
257 let base = Shape { alloc: true, ..Shape::default() };
258 let head = name.split_once('.').map_or(name, |(_, rest)| rest);
259 let head = head.split_once('.').map_or(head, |(first, _)| first);
260 match head {
261 "text" => Shape { exec: true, ..base },
262 "rodata" => base,
263 "data" | "bss" => Shape { write: true, ..base },
264 "tdata" | "tbss" => Shape { write: true, thread: true, ..base },
265 _ if Array::of(name).is_some() => Shape { write: true, ..base },
266 _ => Shape::default(),
267 }
268 }
269
270 pub fn mach(
281 segment: &str,
282 section: &str,
283 kind: Option<&str>,
284 attributes: &[&str],
285 ) -> Result<Shape, String> {
286 let mach = crate::macho::section_flags(segment, section, kind, attributes)?;
287 let exec = mach & object::macho::S_ATTR_PURE_INSTRUCTIONS.0 != 0;
288 let typ = object::macho::SectionFlags(mach).typ();
289 let thread = matches!(
290 typ,
291 object::macho::S_THREAD_LOCAL_REGULAR | object::macho::S_THREAD_LOCAL_ZEROFILL
292 );
293 Ok(Shape {
294 alloc: true,
295 write: segment != "__TEXT",
296 exec,
297 thread,
298 bits: !crate::macho::zero_filled(mach),
299 mach,
300 ..Shape::default()
301 })
302 }
303
304 pub fn coff(letters: &str) -> Result<Shape, char> {
317 let (mut code, mut data, mut zero, mut drop, mut info) =
318 (false, false, false, false, false);
319 let (mut read, mut write, mut shared, mut discard) = (true, true, false, false);
320 let mut writable = false;
321 for letter in letters.chars() {
322 match letter {
323 'a' => {}
324 'b' => zero = true,
325 'd' => {
326 data = true;
327 write = true;
328 }
329 'n' => drop = true,
330 'D' => discard = true,
331 'r' => {
332 writable = false;
333 write = false;
334 data |= !code;
335 }
336 's' => {
337 shared = true;
338 data = true;
339 write = true;
340 }
341 'w' => {
342 write = true;
343 writable = true;
344 }
345 'x' => {
346 code = true;
347 write &= writable;
348 }
349 'y' => {
350 read = false;
351 write = false;
352 }
353 'i' => info = true,
354 other => return Err(other),
355 }
356 }
357 let mut flags = 0;
358 if code {
359 flags |= pe::IMAGE_SCN_CNT_CODE.0 | pe::IMAGE_SCN_MEM_EXECUTE.0;
360 }
361 if data {
362 flags |= pe::IMAGE_SCN_CNT_INITIALIZED_DATA.0;
363 }
364 if zero && !data {
365 flags |= pe::IMAGE_SCN_CNT_UNINITIALIZED_DATA.0;
366 }
367 if drop {
368 flags |= pe::IMAGE_SCN_LNK_REMOVE.0;
369 }
370 if read {
371 flags |= pe::IMAGE_SCN_MEM_READ.0;
372 }
373 if write {
374 flags |= pe::IMAGE_SCN_MEM_WRITE.0;
375 }
376 if discard {
377 flags |= pe::IMAGE_SCN_MEM_DISCARDABLE.0;
378 }
379 if shared {
380 flags |= pe::IMAGE_SCN_MEM_SHARED.0;
381 }
382 if info {
383 flags |= pe::IMAGE_SCN_LNK_INFO.0;
384 }
385 Ok(Shape {
386 alloc: !drop && !info,
387 write,
388 exec: code,
389 bits: !(zero && !data),
390 coff: flags,
391 ..Shape::default()
392 })
393 }
394
395 pub(crate) fn sh_flags(self) -> elf::SectionFlags {
402 let mut flags = 0;
403 if self.alloc {
404 flags |= elf::SHF_ALLOC.0;
405 }
406 if self.write {
407 flags |= elf::SHF_WRITE.0;
408 }
409 if self.exec {
410 flags |= elf::SHF_EXECINSTR.0;
411 }
412 if self.thread {
413 flags |= elf::SHF_TLS.0;
414 }
415 if self.retain {
416 flags |= elf::SHF_GNU_RETAIN.0;
417 }
418 if self.merge != 0 {
419 flags |= elf::SHF_MERGE.0;
420 if self.strings {
421 flags |= elf::SHF_STRINGS.0;
422 }
423 }
424 elf::SectionFlags(flags)
425 }
426
427 pub(crate) fn sh_type(self) -> elf::SectionType {
429 match self.array {
430 _ if !self.bits => elf::SHT_NOBITS,
431 _ if self.note => elf::SHT_NOTE,
432 Some(Array::Init) => elf::SHT_INIT_ARRAY,
433 Some(Array::Fini) => elf::SHT_FINI_ARRAY,
434 Some(Array::Preinit) => elf::SHT_PREINIT_ARRAY,
435 None => elf::SHT_PROGBITS,
436 }
437 }
438
439 pub(crate) const fn kind(self) -> SectionKind {
445 match self {
446 Shape { bits: false, thread: true, .. } => SectionKind::UninitializedTls,
447 Shape { bits: false, .. } => SectionKind::UninitializedData,
448 Shape { thread: true, .. } => SectionKind::Tls,
449 Shape { exec: true, .. } => SectionKind::Text,
450 Shape { alloc: false, .. } => SectionKind::Other,
451 Shape { write: false, .. } => SectionKind::ReadOnlyData,
452 Shape { .. } => SectionKind::Data,
453 }
454 }
455}
456
457#[derive(Debug, Clone, PartialEq, Eq)]
459pub struct Name {
460 pub name: String,
462 pub at: Held,
464 pub size: u64,
466 pub sort: Sort,
468 pub binding: Binding,
470 pub visibility: Visibility,
472}
473
474#[derive(Debug, Clone, Copy, PartialEq, Eq)]
476pub enum Held {
477 In {
479 part: usize,
481 offset: u64,
483 },
484 Absolute(u64),
487 Common {
490 size: u64,
492 align: u64,
495 },
496 Undefined,
498}
499
500#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
502pub enum Sort {
503 Func,
505 Object,
507 Thread,
509 Ifunc,
518 File,
524 #[default]
527 Untyped,
528}
529
530#[derive(Debug, Clone, Default, PartialEq, Eq)]
532pub struct Assembled {
533 pub parts: Vec<Part>,
535 pub names: Vec<Name>,
537 pub subsections: bool,
540}
541
542pub fn assembled(input: &Assembled, target: &TargetInfo) -> Result<Vec<u8>, Error> {
563 assembled_described(input, target, &Info::default())
564}
565
566pub fn assembled_described(
579 input: &Assembled,
580 target: &TargetInfo,
581 info: &Info,
582) -> Result<Vec<u8>, Error> {
583 let (flavour, machine) = match Flavour::of(target) {
586 Some(flavour) => match flavour.machine(target.tuple.arch()) {
587 Some(machine) => (flavour, machine),
588 None => return Err(Error::Format { triple: target.tuple.to_string() }),
589 },
590 None if target.tuple.arch() == Arch::Aarch64
591 && target.object_format == ObjectFormat::MachO =>
592 {
593 return crate::macho::write(input, target, info);
594 }
595 None => return Err(Error::Format { triple: target.tuple.to_string() }),
596 };
597 let flags_of = |kind, after| flavour.reloc(machine, kind, after);
598 let carried = machine == Architecture::Aarch64 && flavour == Flavour::Coff;
601 let mut obj = Writer::new(flavour.binary(), machine, Endianness::Little);
602 obj.set_mangling(object::write::Mangling::None);
605
606 let mut made = Vec::with_capacity(input.parts.len());
609 for part in &input.parts {
610 let id = obj.add_section(Vec::new(), part.name.clone().into_bytes(), part.shape.kind());
611 if let Some(mut flags) = flavour.stated(part.shape) {
616 if let SectionFlags::Elf { sh_flags, .. } = &mut flags {
619 if part.group.is_some() {
620 sh_flags.0 |= elf::SHF_GROUP.0;
621 }
622 if part.link.is_some() {
623 sh_flags.0 |= elf::SHF_LINK_ORDER.0;
624 }
625 }
626 obj.section_mut(id).flags = flags;
627 }
628 let align = part.align.max(1);
629 if part.shape.bits {
630 obj.append_section_data(id, &part.bytes, align);
631 } else {
632 obj.append_section_bss(id, part.size, align);
633 }
634 if part.group.is_some() && flavour == Flavour::Coff {
638 obj.section_symbol(id);
639 }
640 made.push(id);
641 }
642
643 let defined: Map<&str, &Name> =
646 input.names.iter().map(|name| (name.name.as_str(), name)).collect();
647 let pe32 = flavour == Flavour::Coff && machine == Architecture::I386;
649 let onto = |reloc: &Reloc| moved(flavour, pe32, input, &defined, reloc);
650 let wanted: Set<&str> = input
651 .parts
652 .iter()
653 .flat_map(|part| &part.relocs)
654 .filter(|reloc| onto(reloc).is_none())
655 .map(|reloc| reloc.symbol.as_str())
656 .chain(
657 input
658 .parts
659 .iter()
660 .filter_map(|part| part.group.as_ref())
661 .map(|group| group.symbol.as_str()),
662 )
663 .collect();
664
665 let mut symbols = std::collections::BTreeMap::new();
668 for name in &input.names {
669 let dropped = flavour == Flavour::Elf || pe32;
670 if dropped && unseen(name, pe32) && !wanted.contains(name.name.as_str()) {
671 continue;
672 }
673 let (section, value, size) = match name.at {
674 Held::In { part, offset } => {
675 let Some(id) = made.get(part) else {
676 let why = format!(
677 "'{}' is in section {part} and there is no such section",
678 name.name
679 );
680 return Err(Error::Refused { why });
681 };
682 (SymbolSection::Section(*id), offset, name.size)
683 }
684 Held::Absolute(value) => (SymbolSection::Absolute, value, name.size),
685 Held::Common { size, align } => (SymbolSection::Common, align, size),
688 Held::Undefined => (SymbolSection::Undefined, 0, 0),
689 };
690 let kind = match (flavour, name.at, name.sort) {
693 (Flavour::Coff, Held::Absolute(_), Sort::Untyped) => SymbolKind::Data,
694 _ => flavour.sort(name.sort, name.binding),
695 };
696 let id = obj.add_symbol(Symbol {
697 name: name.name.clone().into_bytes(),
698 value,
699 size,
700 kind,
701 scope: crate::file::scope_of(name.binding),
702 weak: name.binding == Binding::Weak,
703 section,
704 flags: SymbolFlags::None,
705 });
706 flavour.see(&mut obj, id, name.binding, name.visibility);
707 if name.sort == Sort::Ifunc {
708 if flavour != Flavour::Elf {
713 let why = format!("'{}' is an indirect function, which only ELF has", name.name);
714 return Err(Error::Refused { why });
715 }
716 crate::elf::indirect(&mut obj, id);
717 }
718 if matches!(name.at, Held::Common { .. }) {
724 if let SymbolFlags::Elf { st_info, .. } = obj.symbol_flags_mut(id) {
725 *st_info = elf::STB_GLOBAL | elf::STT_OBJECT;
726 }
727 }
728 symbols.insert(name.name.clone(), id);
729 }
730
731 let mut groups: Vec<(&Group, Vec<SectionId>)> = Vec::new();
734 let mut grouped: Map<&str, usize> = Map::default();
735 for (part, id) in input.parts.iter().zip(&made) {
736 let Some(group) = &part.group else { continue };
737 match grouped.get(group.symbol.as_str()) {
738 Some(&at) if flavour == Flavour::Elf => groups[at].1.push(*id),
739 _ => {
740 grouped.insert(group.symbol.as_str(), groups.len());
741 groups.push((group, vec![*id]));
742 }
743 }
744 }
745 let mut together = Vec::new();
746 for (group, sections) in groups {
747 let symbol = match symbols.get(&group.symbol) {
748 Some(&symbol) => symbol,
749 None if flavour == Flavour::Elf => obj.add_symbol(Symbol {
754 name: group.symbol.clone().into_bytes(),
755 value: 0,
756 size: 0,
757 kind: SymbolKind::Label,
758 scope: SymbolScope::Compilation,
759 weak: false,
760 section: SymbolSection::Section(sections[0]),
761 flags: SymbolFlags::None,
762 }),
763 None => {
764 let why = format!(
765 "section '{}' is a COMDAT about '{}', which the file does not define",
766 obj.section(sections[0]).name().unwrap_or_default(),
767 group.symbol
768 );
769 return Err(Error::Refused { why });
770 }
771 };
772 together.push(group.keep == Keep::Together);
773 obj.add_comdat(object::write::Comdat { kind: group.keep.kind(), symbol, sections });
774 }
775
776 for (part, id) in input.parts.iter().zip(&made) {
777 for reloc in &part.relocs {
778 let (symbol, addend) = match onto(reloc) {
779 Some((part, offset)) => {
780 (obj.section_symbol(made[part]), reloc.addend + offset as i64)
781 }
782 None => {
783 let Some(&symbol) = symbols.get(&reloc.symbol) else {
784 let why = format!(
785 "'{}' is named by a relocation and by nothing else",
786 reloc.symbol
787 );
788 return Err(Error::Refused { why });
789 };
790 (symbol, reloc.addend)
791 }
792 };
793 let flags = flags_of(reloc.kind, reloc.after).ok_or_else(|| Error::Refused {
794 why: format!("no relocation is {:?}", reloc.kind),
795 })?;
796 let addend = match reloc.kind {
797 crate::section::Reference::Field(fixup) if carried => {
798 let data = obj.section_mut(*id).data_mut();
799 let Some(bytes) = data.get_mut(reloc.at..reloc.at + 4) else {
800 let why = format!("a field at {} is past the end of its section", reloc.at);
801 return Err(Error::Refused { why });
802 };
803 let word = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
804 let word = crate::coff::carry(fixup, word, addend)
805 .map_err(|why| Error::Refused { why })?;
806 bytes.copy_from_slice(&word.to_le_bytes());
807 0
808 }
809 _ => addend,
810 };
811 let record = Relocation { offset: reloc.at as u64, symbol, addend, flags };
812 crate::file::relocate(&mut obj, *id, record)?;
813 }
814 }
815
816 let mut named = Map::default();
819 for chunk in &info.chunks {
820 let how = if flavour == Flavour::Elf && obj.architecture() != Architecture::I386 {
823 info.compress
824 } else {
825 Compress::None
826 };
827 let id = crate::zlib::debug_section(&mut obj, chunk, how);
828 named.insert(chunk.name.as_str(), id);
829 }
830 for chunk in &info.chunks {
831 let section = named[chunk.name.as_str()];
832 for reloc in &chunk.relocs {
833 let spelled =
836 if pe32 { crate::coff::decorate(&reloc.symbol) } else { reloc.symbol.clone() };
837 let (symbol, addend) = match named.get(reloc.symbol.as_str()) {
838 Some(&id) => (obj.section_symbol(id), reloc.addend),
839 None => match defined.get(spelled.as_str()).map(|name| name.at) {
840 Some(Held::In { part, offset }) => {
841 (obj.section_symbol(made[part]), reloc.addend + offset as i64)
842 }
843 _ => match symbols.get(&spelled) {
844 Some(&symbol) => (symbol, reloc.addend),
845 None => {
846 let why = format!(
847 "'{}' is named by the debug information and is not defined here",
848 reloc.symbol
849 );
850 return Err(Error::Refused { why });
851 }
852 },
853 },
854 };
855 let kind = flavour.debug(reloc.kind, named.contains_key(reloc.symbol.as_str()));
856 let flags = flags_of(kind, reloc.after)
857 .ok_or_else(|| Error::Refused { why: format!("no relocation is {kind:?}") })?;
858 let record = Relocation { offset: reloc.at as u64, symbol, addend, flags };
859 crate::file::relocate(&mut obj, section, record)?;
860 }
861 }
862
863 let mut bytes = obj.write().map_err(|why| Error::Refused { why: why.to_string() })?;
867 if flavour == Flavour::Elf {
868 for part in input.parts.iter().filter(|part| part.shape.merge != 0) {
869 entry_size(&mut bytes, &part.name, part.shape.merge);
870 }
871 let pointer = crate::elf::Headers::read(&bytes).entry_size().1 as u64;
873 for part in input.parts.iter().filter(|part| part.shape.array.is_some()) {
874 entry_size(&mut bytes, &part.name, pointer);
875 }
876 together_groups(&mut bytes, &together);
877 linked(&mut bytes, input, &defined)?;
878 }
879 Ok(bytes)
880}
881
882fn linked(bytes: &mut [u8], input: &Assembled, defined: &Map<&str, &Name>) -> Result<(), Error> {
896 if input.parts.iter().all(|part| part.link.is_none()) {
897 return Ok(());
898 }
899 let headers = crate::elf::Headers::read(bytes);
900 let index = |part: usize| {
901 let name = &input.parts[part].name;
902 let nth = input.parts[..part].iter().filter(|other| other.name == *name).count();
903 headers.list.iter().enumerate().filter(|(_, header)| header.name == *name).nth(nth)
904 };
905 let mut writes = Vec::new();
906 for (at, part) in input.parts.iter().enumerate() {
907 let Some(link) = &part.link else { continue };
908 let target = match defined.get(link.as_str()).map(|name| name.at) {
909 Some(Held::In { part, .. }) => Some(part),
910 _ => input.parts.iter().position(|other| other.name == *link),
911 };
912 let Some(target) = target.and_then(index) else {
913 let why = format!("section '{}' goes with '{link}', which is in no section", part.name);
914 return Err(Error::Refused { why });
915 };
916 let (_, header) = index(at).expect("a header for every section");
917 let target = u32::try_from(target.0).expect("a file with this many sections in it");
918 writes.push((header.at + headers.link(), target));
919 }
920 for (at, target) in writes {
921 bytes[at..at + 4].copy_from_slice(&target.to_le_bytes());
922 }
923 Ok(())
924}
925
926fn entry_size(bytes: &mut [u8], name: &str, size: u64) {
931 let headers = crate::elf::Headers::read(bytes);
932 let (field, width) = headers.entry_size();
933 for header in headers.list.iter().filter(|header| header.name == name) {
934 let at = header.at + field;
935 bytes[at..at + width].copy_from_slice(&size.to_le_bytes()[..width]);
936 }
937}
938
939fn together_groups(bytes: &mut [u8], together: &[bool]) {
943 if !together.contains(&true) {
944 return;
945 }
946 let headers = crate::elf::Headers::read(bytes);
947 let groups = headers.list.iter().filter(|header| header.sh_type == elf::SHT_GROUP.0);
948 for (header, _) in groups.zip(together).filter(|(_, together)| **together) {
949 bytes[header.offset..header.offset + 4].copy_from_slice(&0u32.to_le_bytes());
950 }
951}
952
953fn moved(
969 flavour: Flavour,
970 pe32: bool,
971 input: &Assembled,
972 defined: &Map<&str, &Name>,
973 reloc: &Reloc,
974) -> Option<(usize, u64)> {
975 use crate::section::Reference;
976 let name = defined.get(reloc.symbol.as_str())?;
977 let Held::In { part, offset } = name.at else { return None };
978 if name.sort == Sort::Ifunc {
982 return None;
983 }
984 if pe32 && unseen(name, pe32) {
985 return Some((part, offset));
986 }
987 if flavour != Flavour::Elf || name.binding != Binding::Local {
988 return None;
989 }
990 let near = matches!(
991 reloc.kind,
992 Reference::Data
993 | Reference::Away
994 | Reference::AwayWide
995 | Reference::Short
996 | Reference::Tiny
997 );
998 let fixed = match reloc.kind {
999 Reference::Call
1000 | Reference::Got
1001 | Reference::GotBare
1002 | Reference::GotKept
1003 | Reference::Slot
1004 | Reference::SlotKept
1005 | Reference::GotFront
1006 | Reference::Thread
1007 | Reference::Tls(_) => false,
1008 Reference::Field(
1011 Fixup::Call26
1012 | Fixup::Jump26
1013 | Fixup::GotPage21
1014 | Fixup::GotLo12
1015 | Fixup::GotTprelPage21
1016 | Fixup::GotTprelLo12Nc
1017 | Fixup::TprelHi12
1018 | Fixup::TprelLo12Nc,
1019 ) => false,
1020 _ if input.parts.get(part)?.shape.merge != 0 => !near && reloc.addend == 0,
1021 _ => true,
1022 };
1023 fixed.then_some((part, offset))
1024}
1025
1026fn unseen(name: &Name, pe32: bool) -> bool {
1034 name.binding == Binding::Local
1035 && (name.name.starts_with(".L")
1036 || name.name.starts_with("..")
1037 || name.name.contains('\u{1}')
1038 || (pe32 && name.name.starts_with('L')))
1039}
1040
1041#[must_use]
1047pub fn assembled_defines(input: &Assembled) -> Vec<String> {
1048 input
1049 .names
1050 .iter()
1051 .filter(|name| name.binding != Binding::Local && name.at != Held::Undefined)
1052 .map(|name| name.name.clone())
1053 .collect()
1054}
1055
1056#[cfg(test)]
1057mod tests {
1058 use super::*;
1059
1060 use object::read::elf::{FileHeader as _, SectionHeader as _, Sym as _};
1061 use object::read::{Object as _, ObjectComdat as _, ObjectSection as _, ObjectSymbol as _};
1062 use object::{RelocationFlags, SectionFlags};
1063 use rucc_target::{Arch as TargetArch, Env, Os, Triple};
1064
1065 use crate::section::{Reference, Tls};
1066
1067 fn target() -> TargetInfo {
1069 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Linux, Env::Gnu))
1070 }
1071
1072 fn windows() -> TargetInfo {
1074 TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Windows, Env::Gnu))
1075 }
1076
1077 fn part(name: &str, bytes: Vec<u8>) -> Part {
1079 Part {
1080 name: name.to_owned(),
1081 size: bytes.len() as u64,
1082 bytes,
1083 align: 1,
1084 shape: Shape::of(name),
1085 relocs: Vec::new(),
1086 group: None,
1087 link: None,
1088 }
1089 }
1090
1091 fn at(name: &str, offset: u64, sort: Sort, binding: Binding) -> Name {
1093 Name {
1094 name: name.to_owned(),
1095 at: Held::In { part: 0, offset },
1096 size: 0,
1097 sort,
1098 binding,
1099 visibility: Visibility::Default,
1100 }
1101 }
1102
1103 fn raw(bytes: &[u8], want: &str) -> (u8, u64) {
1111 let header = elf::FileHeader64::<Endianness>::parse(bytes).expect("a header");
1112 let endian = header.endian().expect("an endianness");
1113 let table = header.sections(endian, bytes).expect("the sections");
1114 let symbols = table.symbols(endian, bytes, elf::SHT_SYMTAB).expect("a symbol table");
1115 for symbol in symbols.iter() {
1116 if symbols.symbol_name(endian, symbol).expect("a name") == want.as_bytes() {
1117 return (symbol.st_info().0, symbol.st_value(endian));
1118 }
1119 }
1120 panic!("there is no symbol called '{want}'");
1121 }
1122
1123 fn st_info(bytes: &[u8], want: &str) -> u8 {
1125 raw(bytes, want).0
1126 }
1127
1128 #[test]
1129 fn a_section_carries_the_flags_the_source_said_and_not_the_ones_its_name_suggests() {
1130 let mut odd = part(".init.text", vec![0x90]);
1134 odd.shape = Shape { alloc: true, exec: true, bits: true, ..Shape::default() };
1135 let input = Assembled { parts: vec![odd], names: Vec::new(), subsections: false };
1136 let bytes = assembled(&input, &target()).expect("an object");
1137 let file = object::File::parse(&bytes[..]).expect("a readable object");
1138 let section = file.section_by_name(".init.text").expect("the section");
1139 assert_eq!(section.data().expect("the bytes"), &[0x90]);
1140 let SectionFlags::Elf { sh_flags, sh_type } = section.flags() else {
1141 panic!("this is an ELF file");
1142 };
1143 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_EXECINSTR.0);
1144 assert_eq!(sh_flags.0 & elf::SHF_WRITE.0, 0, "nothing said it was writable");
1145 assert_eq!(sh_type, elf::SHT_PROGBITS);
1146 }
1147
1148 #[test]
1149 fn a_section_that_holds_no_bytes_still_says_how_long_it_is() {
1150 let mut room = part(".bss", Vec::new());
1153 room.size = 4096;
1154 room.align = 16;
1155 let input = Assembled { parts: vec![room], names: Vec::new(), subsections: false };
1156 let bytes = assembled(&input, &target()).expect("an object");
1157 assert!(bytes.len() < 4096, "the empty space was written out: {} bytes", bytes.len());
1158 let file = object::File::parse(&bytes[..]).expect("a readable object");
1159 let section = file.section_by_name(".bss").expect("the section");
1160 assert_eq!(section.size(), 4096);
1161 assert_eq!(section.align(), 16);
1162 let SectionFlags::Elf { sh_type, .. } = section.flags() else { panic!("an ELF file") };
1163 assert_eq!(sh_type, elf::SHT_NOBITS);
1164 }
1165
1166 #[test]
1167 fn a_label_nobody_stated_a_type_for_is_a_symbol_with_no_type() {
1168 let input = Assembled {
1172 parts: vec![part(".text", vec![0; 8])],
1173 names: vec![at("plain", 4, Sort::Untyped, Binding::Global)],
1174 subsections: false,
1175 };
1176 let bytes = assembled(&input, &target()).expect("an object");
1177 let file = object::File::parse(&bytes[..]).expect("a readable object");
1178 let plain = file.symbols().find(|s| s.name() == Ok("plain")).expect("the label");
1179 assert_eq!(plain.address(), 4);
1180 assert_eq!(st_info(&bytes, "plain") & 0xf, elf::STT_NOTYPE.0);
1181 }
1182
1183 #[test]
1184 fn what_type_said_is_what_the_symbol_gets() {
1185 let input = Assembled {
1186 parts: vec![part(".text", vec![0; 8])],
1187 names: vec![
1188 at("run", 0, Sort::Func, Binding::Global),
1189 at("held", 4, Sort::Object, Binding::Local),
1190 ],
1191 subsections: false,
1192 };
1193 let bytes = assembled(&input, &target()).expect("an object");
1194 assert_eq!(st_info(&bytes, "run") & 0xf, elf::STT_FUNC.0);
1195 assert_eq!(st_info(&bytes, "held") & 0xf, elf::STT_OBJECT.0);
1196 assert_eq!(st_info(&bytes, "run") >> 4, elf::STB_GLOBAL.0);
1197 assert_eq!(st_info(&bytes, "held") >> 4, elf::STB_LOCAL.0);
1198 }
1199
1200 #[test]
1201 fn a_common_symbol_is_written_the_way_gas_writes_one() {
1202 let input = Assembled {
1207 parts: Vec::new(),
1208 names: vec![Name {
1209 name: "shared".to_owned(),
1210 at: Held::Common { size: 8, align: 8 },
1211 size: 0,
1212 sort: Sort::Object,
1213 binding: Binding::Global,
1214 visibility: Visibility::Default,
1215 }],
1216 subsections: false,
1217 };
1218 let bytes = assembled(&input, &target()).expect("an object");
1219 assert_eq!(st_info(&bytes, "shared"), elf::STB_GLOBAL.0 << 4 | elf::STT_OBJECT.0);
1220 let file = object::File::parse(&bytes[..]).expect("a readable object");
1221 let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the symbol");
1222 assert!(shared.is_common(), "the linker has to be asked for the space");
1223 assert_eq!(shared.size(), 8);
1224 assert_eq!(raw(&bytes, "shared").1, 8, "the boundary it has to start on");
1226 }
1227
1228 #[test]
1229 fn a_set_is_a_number_rather_than_a_place() {
1230 let input = Assembled {
1231 parts: vec![part(".text", vec![0; 8])],
1232 names: vec![Name {
1233 name: "size_of_it".to_owned(),
1234 at: Held::Absolute(25),
1235 size: 0,
1236 sort: Sort::Untyped,
1237 binding: Binding::Global,
1238 visibility: Visibility::Default,
1239 }],
1240 subsections: false,
1241 };
1242 let bytes = assembled(&input, &target()).expect("an object");
1243 let file = object::File::parse(&bytes[..]).expect("a readable object");
1244 let sym = file.symbols().find(|s| s.name() == Ok("size_of_it")).expect("the symbol");
1245 assert_eq!(sym.address(), 25);
1246 assert_eq!(sym.section(), object::SymbolSection::Absolute, "it is not in any section");
1247 }
1248
1249 #[test]
1252 fn a_local_set_on_coff_is_a_static_number() {
1253 let input = Assembled {
1254 parts: vec![part(".text", vec![0xc3])],
1255 names: vec![Name {
1256 name: "@feat.00".to_owned(),
1257 at: Held::Absolute(1),
1258 size: 0,
1259 sort: Sort::Untyped,
1260 binding: Binding::Local,
1261 visibility: Visibility::Default,
1262 }],
1263 subsections: false,
1264 };
1265 let bytes = assembled(&input, &windows()).expect("an object");
1266 let file = object::File::parse(&bytes[..]).expect("a readable object");
1267 let sym = file.symbols().find(|s| s.name() == Ok("@feat.00")).expect("the symbol");
1268 let coff = object::read::coff::CoffFile::<&[u8]>::parse(&bytes[..]).expect("COFF");
1270 let raw = coff.symbol_by_name("@feat.00").expect("the symbol");
1271 assert_eq!(object::read::coff::Symbol::value(raw.coff_symbol()), 1);
1272 assert_eq!(sym.section(), object::SymbolSection::Absolute);
1273 assert!(sym.is_local());
1274 assert!(
1275 matches!(
1276 sym.flags(),
1277 SymbolFlags::Coff { storage_class: pe::IMAGE_SYM_CLASS_STATIC, .. }
1278 ),
1279 "{:?}",
1280 sym.flags()
1281 );
1282 }
1283
1284 #[test]
1285 fn a_relocation_names_a_symbol_and_lands_where_the_bytes_are() {
1286 let mut data = part(".data", vec![0; 8]);
1287 data.relocs.push(Reloc {
1288 at: 0,
1289 symbol: "message".to_owned(),
1290 kind: Reference::Address { bytes: 8 },
1291 addend: 0,
1292 after: 0,
1293 });
1294 let input = Assembled {
1295 parts: vec![data],
1296 names: vec![Name {
1297 name: "message".to_owned(),
1298 at: Held::Undefined,
1299 size: 0,
1300 sort: Sort::Untyped,
1301 binding: Binding::Global,
1302 visibility: Visibility::Default,
1303 }],
1304 subsections: false,
1305 };
1306 let bytes = assembled(&input, &target()).expect("an object");
1307 let file = object::File::parse(&bytes[..]).expect("a readable object");
1308 let section = file.section_by_name(".data").expect("the section");
1309 let (at, reloc) = section.relocations().next().expect("one relocation");
1310 assert_eq!(at, 0);
1311 assert_eq!(reloc.addend(), 0);
1312 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("an ELF file") };
1313 assert_eq!(r_type, elf::R_X86_64_64);
1314 }
1315
1316 #[test]
1317 fn a_place_only_this_file_sees_is_reached_through_its_section_as_gas_does() {
1318 let mut text = part(".text", vec![0; 32]);
1322 for (at, symbol, kind) in [
1323 (0, ".L3", Reference::Data),
1324 (4, "helper", Reference::Data),
1325 (8, "helper", Reference::Call),
1326 (12, "shared", Reference::Data),
1327 ] {
1328 let symbol = symbol.to_owned();
1329 text.relocs.push(Reloc { at, symbol, kind, addend: -4, after: 0 });
1330 }
1331 let input = Assembled {
1332 parts: vec![text],
1333 names: vec![
1334 at(".L3", 20, Sort::Untyped, Binding::Local),
1335 at("helper", 24, Sort::Func, Binding::Local),
1336 at("shared", 28, Sort::Func, Binding::Global),
1337 ],
1338 subsections: false,
1339 };
1340 let bytes = assembled(&input, &target()).expect("an object");
1341 let file = object::File::parse(&bytes[..]).expect("a readable object");
1342 let names: Vec<_> = file.symbols().filter_map(|sym| sym.name().ok()).collect();
1343 assert!(!names.contains(&".L3") && names.contains(&"helper"), "{names:?}");
1344 let section = file.section_by_name(".text").expect("the section");
1345 let reached: Vec<_> = section
1346 .relocations()
1347 .map(|(at, reloc)| {
1348 let object::RelocationTarget::Symbol(index) = reloc.target() else {
1349 panic!("a symbol")
1350 };
1351 let symbol = file.symbol_by_index(index).expect("the symbol");
1352 let name = if symbol.kind() == SymbolKind::Section {
1353 ".text"
1354 } else {
1355 symbol.name().expect("a name")
1356 };
1357 (at, name, reloc.addend())
1358 })
1359 .collect();
1360 assert_eq!(
1361 reached,
1362 [(0, ".text", 16), (4, ".text", 20), (8, "helper", -4), (12, "shared", -4)]
1363 );
1364 }
1365
1366 #[test]
1367 fn a_section_of_constants_may_be_merged_and_a_distance_into_it_keeps_its_name() {
1368 let mut text = part(".text", vec![0; 8]);
1369 text.relocs.push(Reloc {
1370 at: 0,
1371 symbol: ".LC0".to_owned(),
1372 kind: Reference::Data,
1373 addend: -4,
1374 after: 0,
1375 });
1376 let strings = Part {
1377 shape: Shape { merge: 1, strings: true, ..Shape::of(".rodata") },
1378 ..part(".rodata.str1.1", b"hi\0".to_vec())
1379 };
1380 let mut name = at(".LC0", 0, Sort::Untyped, Binding::Local);
1381 name.at = Held::In { part: 1, offset: 0 };
1382 let input = Assembled { parts: vec![text, strings], names: vec![name], subsections: false };
1383 let bytes = assembled(&input, &target()).expect("an object");
1384 let file = object::File::parse(&bytes[..]).expect("a readable object");
1385 let section = file.section_by_name(".rodata.str1.1").expect("the section");
1386 let SectionFlags::Elf { sh_flags, .. } = section.flags() else { panic!("an ELF file") };
1387 assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_MERGE.0 | elf::SHF_STRINGS.0);
1388 let header = elf::FileHeader64::<Endianness>::parse(&bytes[..]).expect("a header");
1389 let endian = header.endian().expect("an endianness");
1390 let table = header.sections(endian, &bytes[..]).expect("the sections");
1391 let (_, found) = table.section_by_name(endian, b".rodata.str1.1").expect("the section");
1392 assert_eq!(found.sh_entsize.get(endian), 1);
1393 let text = file.section_by_name(".text").expect("the section");
1394 let (_, reloc) = text.relocations().next().expect("one relocation");
1395 let object::RelocationTarget::Symbol(index) = reloc.target() else { panic!("a symbol") };
1396 assert_eq!(file.symbol_by_index(index).and_then(|sym| sym.name()), Ok(".LC0"));
1397 }
1398
1399 #[test]
1400 fn a_relocation_against_a_name_the_file_never_mentions_is_refused() {
1401 let mut data = part(".data", vec![0; 8]);
1405 data.relocs.push(Reloc {
1406 at: 0,
1407 symbol: "nowhere".to_owned(),
1408 kind: Reference::Address { bytes: 8 },
1409 addend: 0,
1410 after: 0,
1411 });
1412 let input = Assembled { parts: vec![data], names: Vec::new(), subsections: false };
1413 let why = assembled(&input, &target()).expect_err("this cannot be written");
1414 assert!(format!("{why}").contains("nowhere"), "{why}");
1415 }
1416
1417 #[test]
1418 fn the_stack_is_marked_only_by_the_file_and_only_once() {
1419 let bare = Assembled {
1422 parts: vec![part(".text", vec![0x90])],
1423 names: Vec::new(),
1424 subsections: false,
1425 };
1426 let bytes = assembled(&bare, &target()).expect("an object");
1427 let file = object::File::parse(&bytes[..]).expect("a readable object");
1428 assert!(file.section_by_name(".note.GNU-stack").is_none(), "a marker nobody wrote");
1429
1430 let said = Assembled {
1431 parts: vec![part(".text", vec![0x90]), part(".note.GNU-stack", Vec::new())],
1432 names: Vec::new(),
1433 subsections: false,
1434 };
1435 let bytes = assembled(&said, &target()).expect("an object");
1436 let file = object::File::parse(&bytes[..]).expect("a readable object");
1437 let marks = file.sections().filter(|s| s.name() == Ok(".note.GNU-stack")).count();
1438 assert_eq!(marks, 1, "the file said it and it was said again");
1439 }
1440
1441 #[test]
1442 fn only_the_names_a_linker_could_find_are_offered_to_an_archive() {
1443 let input = Assembled {
1444 parts: vec![part(".text", vec![0; 8])],
1445 names: vec![
1446 at("reachable", 0, Sort::Func, Binding::Global),
1447 at("mine", 4, Sort::Func, Binding::Local),
1448 Name {
1449 name: "elsewhere".to_owned(),
1450 at: Held::Undefined,
1451 size: 0,
1452 sort: Sort::Untyped,
1453 binding: Binding::Global,
1454 visibility: Visibility::Default,
1455 },
1456 ],
1457 subsections: false,
1458 };
1459 assert_eq!(assembled_defines(&input), vec!["reachable".to_owned()]);
1460 }
1461
1462 #[test]
1463 fn a_machine_this_does_not_write_is_refused_rather_than_written_wrong() {
1464 let input = Assembled {
1465 parts: vec![part(".text", vec![0x90])],
1466 names: Vec::new(),
1467 subsections: false,
1468 };
1469 let elsewhere = TargetInfo::new(Triple::new(TargetArch::Riscv64, Os::Linux, Env::Gnu));
1470 let why = assembled(&input, &elsewhere).expect_err("this cannot be written");
1471 assert!(format!("{why}").contains("riscv64"), "{why}");
1472 }
1473
1474 #[test]
1475 fn a_file_of_assembly_for_aarch64_is_written_with_that_machine_s_relocations() {
1476 let mut text = part(".text", vec![0; 12]);
1481 let field = |at, symbol: &str, fixup, addend| Reloc {
1482 at,
1483 symbol: symbol.to_owned(),
1484 kind: Reference::Field(fixup),
1485 addend,
1486 after: 0,
1487 };
1488 text.relocs = vec![
1489 field(0, ".Ltable", Fixup::AdrPage21, 8),
1490 field(4, ".Ltable", Fixup::AddLo12, 8),
1491 field(8, "g", Fixup::Call26, 0),
1492 ];
1493 let mut data = part(".data", vec![0; 16]);
1494 data.relocs = vec![Reloc {
1495 at: 8,
1496 symbol: ".Ltable".to_owned(),
1497 kind: Reference::Address { bytes: 8 },
1498 addend: 0,
1499 after: 0,
1500 }];
1501 let mut table = at(".Ltable", 0, Sort::Object, Binding::Local);
1502 table.at = Held::In { part: 1, offset: 0 };
1503 let input = Assembled {
1504 parts: vec![text, data],
1505 names: vec![
1506 table,
1507 Name { at: Held::Undefined, ..at("g", 0, Sort::Untyped, Binding::Global) },
1508 ],
1509 subsections: false,
1510 };
1511 let target = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Linux, Env::Gnu));
1512 let bytes = assembled(&input, &target).expect("an object");
1513 let file = object::File::parse(&bytes[..]).expect("a readable object");
1514 assert_eq!(file.architecture(), Architecture::Aarch64);
1515 let relocs = |name: &str| -> Vec<(u64, elf::RelocationType, i64)> {
1516 let section = file.section_by_name(name).expect("the section");
1517 section
1518 .relocations()
1519 .map(|(at, reloc)| {
1520 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("ELF") };
1521 (at, r_type, reloc.addend())
1522 })
1523 .collect()
1524 };
1525 assert_eq!(
1526 relocs(".text"),
1527 [
1528 (0, elf::R_AARCH64_ADR_PREL_PG_HI21, 8),
1529 (4, elf::R_AARCH64_ADD_ABS_LO12_NC, 8),
1530 (8, elf::R_AARCH64_CALL26, 0)
1531 ]
1532 );
1533 assert_eq!(relocs(".data"), [(8, elf::R_AARCH64_ABS64, 0)]);
1534 assert!(file.symbols().all(|s| s.name() != Ok(".Ltable")), "a label only this file sees");
1535 }
1536
1537 #[test]
1538 fn a_file_of_assembly_for_aarch64_windows_carries_its_addends_in_the_instructions() {
1539 let adrp = 0x9000_0000u32;
1545 let add = 0x9100_0000u32;
1546 let ldr = 0xf940_0000u32;
1547 let bl = 0x9400_0000u32;
1548 let words = [adrp, add, ldr, bl, add | 1 << 22, add];
1549 let mut text = part(".text", words.iter().flat_map(|word| word.to_le_bytes()).collect());
1550 let field = |at, symbol: &str, fixup, addend| Reloc {
1551 at,
1552 symbol: symbol.to_owned(),
1553 kind: Reference::Field(fixup),
1554 addend,
1555 after: 0,
1556 };
1557 text.relocs = vec![
1558 field(0, "table", Fixup::AdrPage21, 8),
1559 field(4, "table", Fixup::AddLo12, 8),
1560 field(8, "table", Fixup::Ldst64Lo12, 8),
1561 field(12, "g", Fixup::Call26, 0),
1562 field(16, "counter", Fixup::SecrelHigh12A, 0),
1563 field(20, "counter", Fixup::SecrelLow12A, 0),
1564 ];
1565 let mut data = part(".data", vec![0; 16]);
1566 data.relocs = vec![
1567 Reloc {
1568 at: 0,
1569 symbol: "table".to_owned(),
1570 kind: Reference::Address { bytes: 8 },
1571 addend: 8,
1572 after: 0,
1573 },
1574 Reloc { at: 8, symbol: "g".to_owned(), kind: Reference::Data, addend: 0, after: 0 },
1575 Reloc {
1576 at: 12,
1577 symbol: "table".to_owned(),
1578 kind: Reference::Image,
1579 addend: 0,
1580 after: 0,
1581 },
1582 ];
1583 let mut table = at("table", 0, Sort::Object, Binding::Global);
1584 table.at = Held::In { part: 1, offset: 0 };
1585 let undefined =
1586 |name| Name { at: Held::Undefined, ..at(name, 0, Sort::Untyped, Binding::Global) };
1587 let input = Assembled {
1588 parts: vec![text, data],
1589 names: vec![table, undefined("g"), undefined("counter")],
1590 subsections: false,
1591 };
1592 let target = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Windows, Env::Gnu));
1593 let bytes = assembled(&input, &target).expect("an object");
1594 let file = object::File::parse(&bytes[..]).expect("a readable object");
1595 assert_eq!(file.format(), object::BinaryFormat::Coff);
1596 assert_eq!(file.architecture(), Architecture::Aarch64);
1597 let relocs = |name: &str| -> Vec<(u64, u16, String)> {
1598 let section = file.section_by_name(name).expect("the section");
1599 section
1600 .relocations()
1601 .map(|(at, reloc)| {
1602 let RelocationFlags::Coff { typ } = reloc.flags() else { panic!("COFF") };
1603 let object::RelocationTarget::Symbol(symbol) = reloc.target() else {
1604 panic!("a symbol")
1605 };
1606 let symbol = file.symbol_by_index(symbol).expect("the symbol");
1607 (at, typ.0, symbol.name().expect("a name").to_owned())
1608 })
1609 .collect()
1610 };
1611 let named = |at, typ: pe::RelocationType, name: &str| (at, typ.0, name.to_owned());
1612 assert_eq!(
1613 relocs(".text"),
1614 [
1615 named(0, pe::IMAGE_REL_ARM64_PAGEBASE_REL21, "table"),
1616 named(4, pe::IMAGE_REL_ARM64_PAGEOFFSET_12A, "table"),
1617 named(8, pe::IMAGE_REL_ARM64_PAGEOFFSET_12L, "table"),
1618 named(12, pe::IMAGE_REL_ARM64_BRANCH26, "g"),
1619 named(16, pe::IMAGE_REL_ARM64_SECREL_HIGH12A, "counter"),
1620 named(20, pe::IMAGE_REL_ARM64_SECREL_LOW12A, "counter"),
1621 ]
1622 );
1623 assert_eq!(
1624 relocs(".data"),
1625 [
1626 named(0, pe::IMAGE_REL_ARM64_ADDR64, "table"),
1627 named(8, pe::IMAGE_REL_ARM64_REL32, "g"),
1628 named(12, pe::IMAGE_REL_ARM64_ADDR32NB, "table"),
1629 ]
1630 );
1631 let text = file.section_by_name(".text").expect("the section");
1632 let text = text.data().expect("the bytes");
1633 let word = |nth: usize| u32::from_le_bytes(text[nth * 4..nth * 4 + 4].try_into().unwrap());
1634 assert_eq!(word(0), adrp | 2 << 5, "eight bytes is immhi two and immlo nothing");
1635 assert_eq!(word(1), add | 8 << 10);
1636 assert_eq!(word(2), ldr | 1 << 10, "eight bytes is one doubleword");
1637 assert_eq!([word(3), word(4), word(5)], [bl, add | 1 << 22, add]);
1638 let data = file.section_by_name(".data").expect("the section");
1639 let data = data.data().expect("the bytes");
1640 assert_eq!(data[..8], 8u64.to_le_bytes());
1641 assert_eq!(data[8..12], 4u32.to_le_bytes());
1642 }
1643
1644 #[test]
1645 fn an_addend_an_aarch64_coff_field_cannot_carry_is_refused() {
1646 for (word, fixup, addend) in
1650 [(0xb940_0000u32, Fixup::Ldst32Lo12, 2), (0x9400_0000, Fixup::Call26, 4)]
1651 {
1652 let mut text = part(".text", word.to_le_bytes().to_vec());
1653 text.relocs = vec![Reloc {
1654 at: 0,
1655 symbol: "g".to_owned(),
1656 kind: Reference::Field(fixup),
1657 addend,
1658 after: 0,
1659 }];
1660 let input = Assembled {
1661 parts: vec![text],
1662 names: vec![Name {
1663 at: Held::Undefined,
1664 ..at("g", 0, Sort::Untyped, Binding::Global)
1665 }],
1666 subsections: false,
1667 };
1668 let target = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Windows, Env::Gnu));
1669 let why = assembled(&input, &target).expect_err("a field that cannot say it");
1670 assert!(format!("{why}").contains(fixup.name()), "{why}");
1671 }
1672 }
1673
1674 #[test]
1675 fn a_file_of_assembly_for_windows_is_written_as_coff() {
1676 let input = Assembled {
1681 parts: vec![part(".text", vec![0xc3])],
1682 names: Vec::new(),
1683 subsections: false,
1684 };
1685 let bytes = assembled(&input, &windows()).expect("an object");
1686 let file = object::File::parse(&bytes[..]).expect("a readable object");
1687 assert_eq!(file.format(), object::BinaryFormat::Coff);
1688 let section = file.section_by_name(".text").expect("the section");
1689 assert_eq!(section.data().expect("the bytes"), &[0xc3]);
1690 assert_eq!(section.kind(), SectionKind::Text);
1691 assert!(
1692 file.section_by_name(".note.GNU-stack").is_none(),
1693 "a format with no marker got one anyway"
1694 );
1695 }
1696
1697 #[test]
1698 fn a_coff_section_keeps_the_letters_it_was_given_and_its_comdat() {
1699 let drectve = Part {
1702 shape: Shape::coff("yni").expect("the letters"),
1703 ..part(".drectve", b" -exclude-symbols:f".to_vec())
1704 };
1705 let refptr = Part {
1706 shape: Shape::coff("dr").expect("the letters"),
1707 group: Some(Group { symbol: ".refptr.x".to_owned(), keep: Keep::Any }),
1708 ..part(".rdata$.refptr.x", vec![0; 8])
1709 };
1710 let input = Assembled {
1711 parts: vec![refptr, drectve],
1712 names: vec![at(".refptr.x", 0, Sort::Object, Binding::Global)],
1713 subsections: false,
1714 };
1715 let bytes = assembled(&input, &windows()).expect("an object");
1716 let file = object::File::parse(&bytes[..]).expect("a readable object");
1717 let flags = |name: &str| match file.section_by_name(name).expect("the section").flags() {
1718 SectionFlags::Coff { characteristics } => characteristics.0,
1719 other => panic!("{other:?}"),
1720 };
1721 let removed = pe::IMAGE_SCN_LNK_REMOVE.0 | pe::IMAGE_SCN_LNK_INFO.0;
1722 assert_eq!(flags(".drectve") & removed, removed);
1723 assert_eq!(flags(".drectve") & (pe::IMAGE_SCN_MEM_READ.0 | pe::IMAGE_SCN_MEM_WRITE.0), 0);
1724 let refptr = flags(".rdata$.refptr.x");
1725 assert_ne!(refptr & pe::IMAGE_SCN_LNK_COMDAT.0, 0, "not a COMDAT");
1726 assert_eq!(refptr & pe::IMAGE_SCN_MEM_WRITE.0, 0, "`r` did not take writing away");
1727 let comdat = file.comdats().next().expect("the COMDAT");
1728 assert_eq!(comdat.kind(), object::ComdatKind::Any);
1729 assert_eq!(file.symbol_by_index(comdat.symbol()).unwrap().name(), Ok(".refptr.x"));
1730
1731 let input = Assembled { names: Vec::new(), ..input };
1733 assert!(assembled(&input, &windows()).is_err());
1734 }
1735
1736 #[test]
1737 fn a_global_label_with_no_type_under_it_is_still_offered_on_coff() {
1738 let input = Assembled {
1745 parts: vec![part(".text", vec![0; 8])],
1746 names: vec![
1747 at("offered", 0, Sort::Untyped, Binding::Global),
1748 at("ours", 4, Sort::Untyped, Binding::Local),
1749 ],
1750 subsections: false,
1751 };
1752 let bytes = assembled(&input, &windows()).expect("an object");
1753 let file = object::File::parse(&bytes[..]).expect("a readable object");
1754 let offered = file.symbols().find(|s| s.name() == Ok("offered")).expect("the label");
1755 assert!(offered.is_global(), "a `.globl` label came out local");
1756 let ours = file.symbols().find(|s| s.name() == Ok("ours")).expect("the other label");
1757 assert!(!ours.is_global(), "a label nothing offered came out global");
1758 let bytes = assembled(&input, &target()).expect("an object");
1761 assert_eq!(st_info(&bytes, "offered") & 0xf, elf::STT_NOTYPE.0);
1762 }
1763
1764 #[test]
1765 fn a_relocation_on_coff_says_how_much_of_the_instruction_comes_after_it() {
1766 let mut text = part(".text", vec![0; 16]);
1771 text.relocs.push(Reloc {
1772 at: 2,
1773 symbol: "elsewhere".to_owned(),
1774 kind: Reference::Data,
1775 addend: -8,
1776 after: 4,
1777 });
1778 let input = Assembled {
1779 parts: vec![text],
1780 names: vec![Name {
1781 name: "elsewhere".to_owned(),
1782 at: Held::Undefined,
1783 size: 0,
1784 sort: Sort::Untyped,
1785 binding: Binding::Global,
1786 visibility: Visibility::Default,
1787 }],
1788 subsections: false,
1789 };
1790 let bytes = assembled(&input, &windows()).expect("an object");
1791 let file = object::File::parse(&bytes[..]).expect("a readable object");
1792 let section = file.section_by_name(".text").expect("the section");
1793 let (at, reloc) = section.relocations().next().expect("the relocation");
1794 assert_eq!(at, 2);
1795 assert_eq!(
1796 reloc.flags(),
1797 RelocationFlags::Coff { typ: pe::RelocationType(pe::IMAGE_REL_AMD64_REL32.0 + 4) }
1798 );
1799 }
1800
1801 fn i386() -> TargetInfo {
1803 TargetInfo::new(Triple::new(TargetArch::X86, Os::Linux, Env::Gnu))
1804 }
1805
1806 fn implicit(file: &object::File<'_>, section: &str) -> Vec<(u64, u32, String, i64)> {
1812 let section = file.section_by_name(section).expect("the section");
1813 let data = section.data().expect("the bytes");
1814 section
1815 .relocations()
1816 .map(|(at, reloc)| {
1817 assert!(reloc.has_implicit_addend(), "a REL file keeps the addend in the bytes");
1818 assert_eq!(reloc.addend(), 0);
1819 let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("ELF") };
1820 let width = crate::elf::width_i386(r_type).expect("a width");
1821 let at_ = at as usize;
1822 let mut word = [0u8; 8];
1823 word[..width].copy_from_slice(&data[at_..at_ + width]);
1824 let shift = 64 - 8 * width as u32;
1827 let addend = (i64::from_le_bytes(word) << shift) >> shift;
1828 let object::RelocationTarget::Symbol(index) = reloc.target() else {
1829 panic!("a relocation against a symbol")
1830 };
1831 let symbol = file.symbol_by_index(index).expect("the symbol");
1832 let name = match symbol.kind() {
1833 SymbolKind::Section => {
1834 let section = symbol.section_index().expect("a section symbol's section");
1835 file.section_by_index(section)
1836 .expect("it")
1837 .name()
1838 .expect("a name")
1839 .to_owned()
1840 }
1841 _ => symbol.name().expect("a name").to_owned(),
1842 };
1843 (at, r_type.0, name, addend)
1844 })
1845 .collect()
1846 }
1847
1848 #[test]
1856 fn an_i386_object_is_32_bit_elf_with_the_addends_in_the_bytes() {
1857 let reloc = |at, symbol: &str, kind, addend| Reloc {
1858 at,
1859 symbol: symbol.to_owned(),
1860 kind,
1861 addend,
1862 after: 0,
1863 };
1864 let code = vec![
1865 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, ];
1873 let mut text = part(".text", code);
1874 text.relocs = vec![
1875 reloc(1, "foo", Reference::Call, -4),
1876 reloc(6, "bar", Reference::Data, -4),
1877 reloc(12, "_GLOBAL_OFFSET_TABLE_", Reference::GotFront, 2),
1878 reloc(18, ".LC0", Reference::GotOffset, 0),
1879 reloc(24, "foo", Reference::Slot, 0),
1880 reloc(30, "foo", Reference::SlotKept, 0),
1881 reloc(35, "counter", Reference::Signed, 8),
1882 ];
1883 let rodata = part(".rodata", b"abc\0hi\0\0".to_vec());
1884 let mut data = part(".data", vec![0; 11]);
1885 data.relocs = vec![
1886 reloc(0, "foo", Reference::Address { bytes: 4 }, 16),
1887 reloc(4, "bar", Reference::Away, 0),
1888 reloc(8, "foo", Reference::Address { bytes: 2 }, 0),
1889 reloc(10, "foo", Reference::Address { bytes: 1 }, 0),
1890 ];
1891 let undefined = |name: &str| Name {
1892 at: Held::Undefined,
1893 ..at(name, 0, Sort::Untyped, Binding::Global)
1894 };
1895 let names = vec![
1896 at("f", 0, Sort::Func, Binding::Global),
1897 Name {
1898 at: Held::In { part: 1, offset: 4 },
1899 ..at(".LC0", 0, Sort::Untyped, Binding::Local)
1900 },
1901 undefined("foo"),
1902 undefined("bar"),
1903 undefined("counter"),
1904 undefined("_GLOBAL_OFFSET_TABLE_"),
1905 ];
1906 let input = Assembled { parts: vec![text, rodata, data], names, subsections: false };
1907 let bytes = assembled(&input, &i386()).expect("an object");
1908
1909 let header = elf::FileHeader32::<Endianness>::parse(&bytes[..]).expect("a 32 bit header");
1910 let endian = header.endian().expect("an endianness");
1911 assert_eq!(bytes[4], elf::ELFCLASS32.0);
1912 assert_eq!(header.e_machine(endian), elf::EM_386);
1913 assert_eq!(header.e_type(endian), elf::ET_REL);
1914
1915 let file = object::File::parse(&bytes[..]).expect("a readable object");
1916 assert!(!file.is_64());
1917 assert_eq!(file.architecture(), Architecture::I386);
1918 for name in [".rel.text", ".rel.data"] {
1919 let section = file.section_by_name(name).expect("a REL section");
1920 let SectionFlags::Elf { sh_type, .. } = section.flags() else { panic!("ELF") };
1921 assert_eq!(sh_type, elf::SHT_REL, "{name}");
1922 }
1923 assert!(file.section_by_name(".rela.text").is_none(), "i386 has no addend field");
1924
1925 let text = implicit(&file, ".text");
1926 let want = [
1927 (1, elf::R_386_PLT32, "foo", -4),
1928 (6, elf::R_386_PC32, "bar", -4),
1929 (12, elf::R_386_GOTPC, "_GLOBAL_OFFSET_TABLE_", 2),
1930 (18, elf::R_386_GOTOFF, ".rodata", 4),
1932 (24, elf::R_386_GOT32X, "foo", 0),
1933 (30, elf::R_386_GOT32, "foo", 0),
1934 (35, elf::R_386_32, "counter", 8),
1935 ];
1936 let want: Vec<_> = want
1937 .into_iter()
1938 .map(|(at, r_type, name, addend)| (at, r_type.0, name.to_owned(), addend))
1939 .collect();
1940 assert_eq!(text, want);
1941
1942 let data = implicit(&file, ".data");
1943 let want = [
1944 (0, elf::R_386_32, "foo", 16),
1945 (4, elf::R_386_PC32, "bar", 0),
1946 (8, elf::R_386_16, "foo", 0),
1947 (10, elf::R_386_8, "foo", 0),
1948 ];
1949 let want: Vec<_> = want
1950 .into_iter()
1951 .map(|(at, r_type, name, addend)| (at, r_type.0, name.to_owned(), addend))
1952 .collect();
1953 assert_eq!(data, want);
1954 }
1955
1956 #[test]
1961 fn the_i386_thread_local_references_are_the_relocations_gas_writes() {
1962 let kinds = [
1963 (Tls::General, elf::R_386_TLS_GD),
1964 (Tls::Module, elf::R_386_TLS_LDM),
1965 (Tls::InModule, elf::R_386_TLS_LDO_32),
1966 (Tls::Slot, elf::R_386_TLS_GOTIE),
1967 (Tls::SlotAddress, elf::R_386_TLS_IE),
1968 (Tls::SlotNegated, elf::R_386_TLS_IE_32),
1969 (Tls::Offset, elf::R_386_TLS_LE),
1970 (Tls::Negated, elf::R_386_TLS_LE_32),
1971 ];
1972 let mut text = part(".text", vec![0; 4 * kinds.len()]);
1973 text.relocs = kinds
1974 .iter()
1975 .enumerate()
1976 .map(|(n, &(tls, _))| Reloc {
1977 at: 4 * n,
1978 symbol: "x".to_owned(),
1979 kind: Reference::Tls(tls),
1980 addend: n as i64,
1981 after: 0,
1982 })
1983 .collect();
1984 let tdata = part(".tdata", vec![0; 8]);
1985 let names = vec![
1986 at("f", 0, Sort::Func, Binding::Global),
1987 Name {
1988 at: Held::In { part: 1, offset: 4 },
1989 ..at("x", 0, Sort::Thread, Binding::Local)
1990 },
1991 ];
1992 let input = Assembled { parts: vec![text, tdata], names, subsections: false };
1993 let bytes = assembled(&input, &i386()).expect("an object");
1994 let file = object::File::parse(&bytes[..]).expect("a readable object");
1995 let want: Vec<_> = kinds
1996 .iter()
1997 .enumerate()
1998 .map(|(n, &(_, r_type))| (4 * n as u64, r_type.0, "x".to_owned(), n as i64))
1999 .collect();
2000 assert_eq!(implicit(&file, ".text"), want);
2001 }
2002
2003 #[test]
2007 fn an_i386_windows_file_of_assembly_keeps_its_names_as_written() {
2008 use object::pe::{
2009 IMAGE_REL_I386_DIR32, IMAGE_REL_I386_DIR32NB, IMAGE_REL_I386_REL32,
2010 IMAGE_REL_I386_SECREL,
2011 };
2012 let reloc = |at, symbol: &str, kind, addend| Reloc {
2013 at,
2014 symbol: symbol.to_owned(),
2015 kind,
2016 addend,
2017 after: 0,
2018 };
2019 let code = vec![
2020 0xe8, 0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0xa1, 0, 0, 0, 0, 0xc3,
2024 ];
2025 let mut text = part(".text", code);
2026 text.relocs = vec![
2027 reloc(1, "_puts", Reference::Call, -4),
2028 reloc(6, "@f@8", Reference::Call, -4),
2029 reloc(11, "_counter", Reference::Address { bytes: 4 }, 8),
2030 ];
2031 let mut data = part(".data", vec![0; 12]);
2032 data.relocs = vec![
2033 reloc(0, "_main", Reference::Image, 0),
2034 reloc(4, "_main", Reference::Section, 0),
2035 reloc(8, "_puts", Reference::Away, 0),
2036 ];
2037 let undefined = |name: &str| Name {
2038 at: Held::Undefined,
2039 ..at(name, 0, Sort::Untyped, Binding::Global)
2040 };
2041 let names = vec![
2042 at("_main", 0, Sort::Func, Binding::Global),
2043 undefined("_puts"),
2044 undefined("@f@8"),
2045 undefined("_counter"),
2046 ];
2047 let input = Assembled { parts: vec![text, data], names, subsections: false };
2048 let target = TargetInfo::new(Triple::new(TargetArch::X86, Os::Windows, Env::Gnu));
2049 let bytes = assembled(&input, &target).expect("an object");
2050 let file = object::File::parse(&bytes[..]).expect("a readable object");
2051 assert_eq!(file.format(), object::BinaryFormat::Coff);
2052 assert_eq!(file.architecture(), Architecture::I386);
2053 for name in ["_main", "_puts", "@f@8", "_counter"] {
2054 assert!(file.symbol_by_name(name).is_some(), "{name}");
2055 }
2056 assert!(file.symbol_by_name("__main").is_none());
2057 let types = |name: &str| -> Vec<(u64, u16, String)> {
2058 let section = file.section_by_name(name).expect("a section");
2059 section
2060 .relocations()
2061 .map(|(at, reloc)| {
2062 let RelocationFlags::Coff { typ } = reloc.flags() else { panic!("COFF") };
2063 let object::RelocationTarget::Symbol(symbol) = reloc.target() else {
2064 panic!("a symbol")
2065 };
2066 let symbol = file.symbol_by_index(symbol).expect("a symbol");
2067 (at, typ.0, symbol.name().expect("a name").to_owned())
2068 })
2069 .collect()
2070 };
2071 let want = |list: &[(u64, pe::RelocationType, &str)]| -> Vec<(u64, u16, String)> {
2072 list.iter().map(|&(at, typ, name)| (at, typ.0, name.to_owned())).collect()
2073 };
2074 assert_eq!(
2075 types(".text"),
2076 want(&[
2077 (1, IMAGE_REL_I386_REL32, "_puts"),
2078 (6, IMAGE_REL_I386_REL32, "@f@8"),
2079 (11, IMAGE_REL_I386_DIR32, "_counter"),
2080 ])
2081 );
2082 assert_eq!(
2083 types(".data"),
2084 want(&[
2085 (0, IMAGE_REL_I386_DIR32NB, "_main"),
2086 (4, IMAGE_REL_I386_SECREL, "_main"),
2087 (8, IMAGE_REL_I386_REL32, "_puts"),
2088 ])
2089 );
2090 let code = file.section_by_name(".text").expect("a text section");
2091 let code = code.data().expect("the bytes");
2092 assert_eq!(&code[1..5], &0i32.to_le_bytes(), "a call counts from the end of its bytes");
2093 assert_eq!(&code[11..15], &8i32.to_le_bytes());
2094 let image = file.section_by_name(".data").expect("a data section");
2095 assert_eq!(&image.data().expect("the bytes")[8..12], &4i32.to_le_bytes());
2096 }
2097
2098 #[test]
2102 fn an_i386_windows_label_of_gccs_own_is_its_section_and_not_a_name() {
2103 use object::pe::{IMAGE_REL_I386_DIR32, IMAGE_REL_I386_REL32};
2104 let mut text = part(".text", vec![0xc7, 0x04, 0x24, 0, 0, 0, 0, 0xe9, 0, 0, 0, 0, 0xc3]);
2106 let reloc = |at, symbol: &str, kind, addend| Reloc {
2107 at,
2108 symbol: symbol.to_owned(),
2109 kind,
2110 addend,
2111 after: 0,
2112 };
2113 text.relocs = vec![
2114 reloc(3, "LC1", Reference::Address { bytes: 4 }, 0),
2115 reloc(8, "Lfar", Reference::Data, -4),
2116 ];
2117 let rdata = part(".rdata", vec![0; 8]);
2118 let names = vec![
2119 at("_f", 0, Sort::Func, Binding::Global),
2120 at("L3", 12, Sort::Untyped, Binding::Local),
2121 Name {
2122 at: Held::In { part: 1, offset: 4 },
2123 ..at("LC1", 0, Sort::Untyped, Binding::Local)
2124 },
2125 Name {
2126 at: Held::In { part: 1, offset: 0 },
2127 ..at("Lfar", 0, Sort::Untyped, Binding::Local)
2128 },
2129 ];
2130 let input = Assembled { parts: vec![text, rdata], names, subsections: false };
2131 let target = TargetInfo::new(Triple::new(TargetArch::X86, Os::Windows, Env::Gnu));
2132 let bytes = assembled(&input, &target).expect("an object");
2133 let file = object::File::parse(&bytes[..]).expect("a readable object");
2134 assert!(file.symbol_by_name("_f").is_some());
2135 for name in ["L3", "LC1", "Lfar"] {
2136 assert!(file.symbol_by_name(name).is_none(), "{name}");
2137 }
2138 let section = file.section_by_name(".text").expect("a section");
2139 let relocs: Vec<_> = section
2140 .relocations()
2141 .map(|(at, reloc)| {
2142 let RelocationFlags::Coff { typ } = reloc.flags() else { panic!("COFF") };
2143 let object::RelocationTarget::Symbol(symbol) = reloc.target() else {
2144 panic!("a symbol")
2145 };
2146 let symbol = file.symbol_by_index(symbol).expect("a symbol");
2147 (at, typ.0, symbol.name().expect("a name").to_owned())
2148 })
2149 .collect();
2150 assert_eq!(
2151 relocs,
2152 vec![
2153 (3, IMAGE_REL_I386_DIR32.0, ".rdata".to_owned()),
2154 (8, IMAGE_REL_I386_REL32.0, ".rdata".to_owned())
2155 ]
2156 );
2157 let code = section.data().expect("the bytes");
2158 assert_eq!(&code[3..7], &4i32.to_le_bytes(), "the distance into .rdata");
2159 assert_eq!(&code[8..12], &0i32.to_le_bytes(), "the start of .rdata, counted from the end");
2160 }
2161
2162 #[test]
2168 fn a_reference_i386_has_no_relocation_for_is_refused() {
2169 for kind in [
2170 Reference::Address { bytes: 8 },
2171 Reference::AwayWide,
2172 Reference::Got,
2173 Reference::GotBare,
2174 Reference::GotKept,
2175 Reference::Thread,
2176 Reference::Image,
2177 Reference::Section,
2178 ] {
2179 let mut text = part(".text", vec![0; 8]);
2180 text.relocs =
2181 vec![Reloc { at: 0, symbol: "foo".to_owned(), kind, addend: 0, after: 0 }];
2182 let names =
2183 vec![Name { at: Held::Undefined, ..at("foo", 0, Sort::Untyped, Binding::Global) }];
2184 let input = Assembled { parts: vec![text], names, subsections: false };
2185 let Err(Error::Refused { .. }) = assembled(&input, &i386()) else {
2186 panic!("{kind:?} was written for i386");
2187 };
2188 }
2189 }
2190
2191 #[test]
2194 fn an_i386_addend_too_wide_for_its_bytes_is_refused() {
2195 let mut data = part(".data", vec![0; 2]);
2196 let kind = Reference::Address { bytes: 1 };
2197 data.relocs = vec![Reloc { at: 0, symbol: "foo".to_owned(), kind, addend: 300, after: 0 }];
2198 let names =
2199 vec![Name { at: Held::Undefined, ..at("foo", 0, Sort::Untyped, Binding::Global) }];
2200 let input = Assembled { parts: vec![data], names, subsections: false };
2201 let Err(Error::Refused { why }) = assembled(&input, &i386()) else {
2202 panic!("an addend of 300 went into one byte");
2203 };
2204 assert!(why.contains("300"), "{why}");
2205 }
2206
2207 #[test]
2210 fn a_32_bit_file_gets_its_entry_size_and_groups_in_the_right_place() {
2211 let mut strings = part(".rodata.str1.1", b"hi\0".to_vec());
2212 strings.shape.merge = 1;
2213 strings.shape.strings = true;
2214 let mut kept = part(".text.f", vec![0xc3]);
2215 kept.group = Some(Group { symbol: "f".to_owned(), keep: Keep::Together });
2216 let names = vec![Name {
2217 at: Held::In { part: 1, offset: 0 },
2218 ..at("f", 0, Sort::Func, Binding::Global)
2219 }];
2220 let input = Assembled { parts: vec![strings, kept], names, subsections: false };
2221 let bytes = assembled(&input, &i386()).expect("an object");
2222 let header = elf::FileHeader32::<Endianness>::parse(&bytes[..]).expect("a 32 bit header");
2223 let endian = header.endian().expect("an endianness");
2224 let sections = header.sections(endian, &bytes[..]).expect("the sections");
2225 let find = |name: &str| {
2226 sections
2227 .iter()
2228 .find(|section| sections.section_name(endian, section) == Ok(name.as_bytes()))
2229 .expect("the section")
2230 };
2231 assert_eq!(find(".rodata.str1.1").sh_entsize(endian), 1);
2232 let group = find(".group");
2233 let at = group.sh_offset(endian) as usize;
2234 assert_eq!(&bytes[at..at + 4], &[0; 4], "a group that is not a COMDAT says so");
2235 }
2236
2237 #[test]
2241 fn a_section_that_goes_with_a_name_points_at_the_section_the_name_is_in() {
2242 let record = |link: &str| Part {
2243 shape: Shape { write: true, ..Shape::of("__patchable_function_entries") },
2244 link: Some(link.to_owned()),
2245 ..part("__patchable_function_entries", vec![0; 8])
2246 };
2247 let parts = vec![
2248 part(".text", vec![0xc3]),
2249 part(".init.text", vec![0xc3]),
2250 record("f"),
2251 record("g"),
2252 ];
2253 let names = vec![
2254 Name { at: Held::In { part: 1, offset: 0 }, ..at("f", 0, Sort::Func, Binding::Global) },
2255 at("g", 0, Sort::Func, Binding::Global),
2256 ];
2257 let bytes = assembled(&Assembled { parts, names, subsections: false }, &target())
2258 .expect("an object");
2259 let header = elf::FileHeader64::<Endianness>::parse(&bytes[..]).expect("a header");
2260 let endian = header.endian().expect("an endianness");
2261 let sections = header.sections(endian, &bytes[..]).expect("the sections");
2262 let index = |name: &str| {
2263 sections
2264 .iter()
2265 .position(|section| sections.section_name(endian, section) == Ok(name.as_bytes()))
2266 .expect("the section") as u32
2267 };
2268 let records: Vec<_> = sections
2269 .iter()
2270 .filter(|section| {
2271 sections.section_name(endian, section) == Ok(&b"__patchable_function_entries"[..])
2272 })
2273 .collect();
2274 assert_eq!(records.len(), 2);
2275 for record in &records {
2276 assert!(
2277 record.sh_flags(endian).contains(elf::SHF_LINK_ORDER),
2278 "a record that goes with nothing"
2279 );
2280 }
2281 assert_eq!(records[0].sh_link(endian), index(".init.text"));
2282 assert_eq!(records[1].sh_link(endian), index(".text"));
2283 }
2284
2285 #[test]
2286 fn a_section_that_goes_with_a_name_in_no_section_is_refused() {
2287 let record = Part {
2288 link: Some("nowhere".to_owned()),
2289 ..part("__patchable_function_entries", vec![0; 8])
2290 };
2291 let input = Assembled { parts: vec![record], names: Vec::new(), subsections: false };
2292 assert!(assembled(&input, &target()).is_err());
2293 }
2294}