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