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