Skip to main content

rucc_object/
source.rs

1//! An object file written from what a file of assembly says, rather than from a compilation.
2//!
3//! Design: `spec/11-asm-objects-debug.md` section 11.1, the paragraph that says we also accept
4//! assembly as input.
5//!
6//! # Why this is not [`crate::Text`] and [`crate::Data`]
7//!
8//! Those two are the compiler's view of a file and they are the right view of one. A function is a
9//! run of bytes with a name and a length, a variable is an image with a name and a place worked out
10//! from what the variable is, and neither carries a section name because where a thing goes is an
11//! answer rather than a question. That is exactly what makes them the wrong shape for assembly.
12//!
13//! A file of assembly says the section, so the place is a question again, and it may say a section
14//! this compiler would never have chosen and flags that go with it. It puts names at offsets rather
15//! than around images, so `.long 0` followed by `foo:` is four bytes belonging to nothing with a
16//! name after them, which no list of named variables can hold. It defines names that are not at any
17//! offset at all, which is what `.set` and `.equ` produce. And it may name a symbol in the middle of
18//! a section, with a size the program stated rather than one worked out from the bytes.
19//!
20//! So this is the assembler's view: a list of sections that each know their own name, flags and
21//! bytes, and a list of names that point into them. Bending one into the other would mean deciding
22//! here what a program already said, and a wrong answer about which section something is in is not
23//! visible until a link or a load.
24//!
25//! The two views meet at the [`object`] crate's writer, which is what both call, and at the short
26//! list of format opinions beside it, which is what both ask where the formats differ. So
27//! there is one place that knows how an object file is laid out and one that knows what each format
28//! calls the things in it.
29
30use object::write::{Object as Writer, Relocation, Symbol, SymbolSection};
31use object::{Architecture, Endianness, SectionKind, SymbolFlags, elf};
32use rucc_target::TargetInfo;
33use rucc_tuple::Arch;
34
35use crate::file::{Error, Flavour};
36use crate::section::{Array, Binding, Reloc, Visibility};
37
38/// One section, as a file of assembly describes one.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct Part {
41    /// What it is called, with the leading dot the source wrote.
42    pub name: String,
43    /// Its bytes, which are empty for a section that says how big it is and holds none of them.
44    pub bytes: Vec<u8>,
45    /// How long it is. The same as the length of the bytes for every section that has any, and the
46    /// whole of what a `@nobits` section says about itself.
47    pub size: u64,
48    /// The boundary it starts on, which is the largest any directive in it asked for.
49    pub align: u64,
50    /// The flags and the type, which the source states and this does not work out.
51    pub shape: Shape,
52    /// Every place in it that names something, counted from the start of the section.
53    pub relocs: Vec<Reloc>,
54}
55
56/// What a section is, which on ELF is a handful of flag letters and a type.
57///
58/// Held as the separate facts rather than as one of a fixed list of kinds, because the list is not
59/// fixed: a program may write `.section .init.text,"ax",@progbits` and mean a section this compiler
60/// has no name for, and the letters are the whole of what it said about it. The writer underneath
61/// takes a [`SectionKind`], so `Shape::kind` is the one place that turns these back into one, and
62/// the cases it cannot say are written as flags directly.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
64pub struct Shape {
65    /// `a`: the section takes space in the loaded image. A section without this is for a debugger
66    /// or a linker to read and is not in the program at run time.
67    pub alloc: bool,
68    /// `w`: the program may write to it.
69    pub write: bool,
70    /// `x`: the processor may execute it.
71    pub exec: bool,
72    /// `T`: one copy per thread rather than one copy per program.
73    pub thread: bool,
74    /// Whether the file carries the bytes. False is `@nobits`, which is what `.bss` is.
75    pub bits: bool,
76    /// Which kind of table of function addresses this is, for the three ELF has a type for.
77    pub array: Option<Array>,
78}
79
80impl Shape {
81    /// What a section of this name is when the source named it and said nothing else.
82    ///
83    /// `.text`, `.data` and the rest are names an assembler already knows the flags of, which is
84    /// why a program may write `.data` on its own and why `.section .data` without letters is the
85    /// same section rather than an unallocated one. A name nothing here knows gets the flags of an
86    /// ordinary allocated writable section, which is what gas does with one.
87    #[must_use]
88    pub fn of(name: &str) -> Shape {
89        let base = Shape { alloc: true, bits: true, ..Shape::default() };
90        let head = name.split_once('.').map_or(name, |(_, rest)| rest);
91        let head = head.split_once('.').map_or(head, |(first, _)| first);
92        match head {
93            "text" | "init" | "fini" => Shape { exec: true, ..base },
94            "rodata" | "eh_frame_hdr" => base,
95            "bss" => Shape { write: true, bits: false, ..base },
96            "tbss" => Shape { write: true, thread: true, bits: false, ..base },
97            "tdata" => Shape { write: true, thread: true, ..base },
98            // The three the linker gathers and the startup code walks. The type is what makes one
99            // of them that, rather than the name: a section of the ordinary type under the same
100            // name is gathered into the same run and called by nobody.
101            _ if Array::of(name).is_some() => Shape { write: true, array: Array::of(name), ..base },
102            // Not allocated, because nothing in the running program reads it. A debugger reads it
103            // out of the file, and a section marked allocated would take space in every process.
104            "debug_info" | "debug_abbrev" | "debug_line" | "debug_str" | "comment" => {
105                Shape { alloc: false, bits: true, ..Shape::default() }
106            }
107            _ => Shape { write: true, ..base },
108        }
109    }
110
111    /// The flag word ELF holds these in.
112    ///
113    /// Not public, and neither are the two below it. The fields above are the whole of what a
114    /// caller says about a section, and how ELF spells them is this crate's business: a reader that
115    /// had to name an ELF constant to describe an executable section would be one that could not
116    /// describe one for any other format.
117    pub(crate) fn sh_flags(self) -> elf::SectionFlags {
118        let mut flags = 0;
119        if self.alloc {
120            flags |= elf::SHF_ALLOC.0;
121        }
122        if self.write {
123            flags |= elf::SHF_WRITE.0;
124        }
125        if self.exec {
126            flags |= elf::SHF_EXECINSTR.0;
127        }
128        if self.thread {
129            flags |= elf::SHF_TLS.0;
130        }
131        elf::SectionFlags(flags)
132    }
133
134    /// The type ELF holds in the header beside those flags.
135    pub(crate) fn sh_type(self) -> elf::SectionType {
136        match self.array {
137            _ if !self.bits => elf::SHT_NOBITS,
138            Some(Array::Init) => elf::SHT_INIT_ARRAY,
139            Some(Array::Fini) => elf::SHT_FINI_ARRAY,
140            Some(Array::Preinit) => elf::SHT_PREINIT_ARRAY,
141            None => elf::SHT_PROGBITS,
142        }
143    }
144
145    /// What the writer underneath calls the nearest thing to this.
146    ///
147    /// It is told the flags in full afterwards, so this only has to be close enough that nothing
148    /// else the writer decides from the kind comes out wrong, which is the default alignment and
149    /// whether it appends bytes or counts them.
150    pub(crate) const fn kind(self) -> SectionKind {
151        match self {
152            Shape { bits: false, thread: true, .. } => SectionKind::UninitializedTls,
153            Shape { bits: false, .. } => SectionKind::UninitializedData,
154            Shape { thread: true, .. } => SectionKind::Tls,
155            Shape { exec: true, .. } => SectionKind::Text,
156            Shape { alloc: false, .. } => SectionKind::Other,
157            Shape { write: false, .. } => SectionKind::ReadOnlyData,
158            Shape { .. } => SectionKind::Data,
159        }
160    }
161}
162
163/// One name in the symbol table, as a file of assembly defines one.
164#[derive(Debug, Clone, PartialEq, Eq)]
165pub struct Name {
166    /// The name, spelled as the source spelled it.
167    pub name: String,
168    /// Where it is.
169    pub at: Held,
170    /// How long the thing it names is, which is what `.size` said and is zero when nothing did.
171    pub size: u64,
172    /// What kind of thing it names, which is what `.type` said.
173    pub sort: Sort,
174    /// Who can see it.
175    pub binding: Binding,
176    /// How far outside a shared library it reaches.
177    pub visibility: Visibility,
178}
179
180/// Where a name is, which is four different things and not an offset with special cases.
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
182pub enum Held {
183    /// At an offset into one of the sections, which is what a label is.
184    In {
185        /// Which section, as an index into the list given alongside.
186        part: usize,
187        /// How far into it.
188        offset: u64,
189    },
190    /// A number rather than a place, which is what `.set` and `.equ` produce. The linker resolves
191    /// a reference to one to the number itself and there is nothing for it to be relative to.
192    Absolute(u64),
193    /// That much zeroed space asked of the linker under this name, which is `.comm` and `.lcomm`.
194    /// Every definition of the name across every object is merged into one.
195    Common {
196        /// How much space.
197        size: u64,
198        /// What boundary it has to start on. ELF records this where an ordinary symbol records its
199        /// address, which is why the two cannot both be said.
200        align: u64,
201    },
202    /// Named and not defined here, which the linker has to find somewhere else.
203    Undefined,
204}
205
206/// What kind of thing a name names, which is what `.type` says.
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
208pub enum Sort {
209    /// `@function`. A call through the procedure linkage table may be made to it.
210    Func,
211    /// `@object`. Data.
212    Object,
213    /// `@tls_object`. A thread-local variable, which a linker checks relocations against.
214    Thread,
215    /// `.file`, which names the source this was assembled from rather than anything in it.
216    ///
217    /// Not a thing `.type` can say, and here because it is a symbol and there is nowhere else for
218    /// it. A debugger reads it and so does `nm`, and gas writes one for every file that says its
219    /// own name, which is every file gcc produces.
220    File,
221    /// Nothing was said, which is what a plain label gets and is a real answer rather than a
222    /// missing one: gas writes `STT_NOTYPE` for a label nobody stated a type for.
223    #[default]
224    Untyped,
225}
226
227/// Everything an assembled file holds: its sections, and the names that point into them.
228#[derive(Debug, Clone, Default, PartialEq, Eq)]
229pub struct Assembled {
230    /// The sections, in the order the file first mentioned each of them.
231    pub parts: Vec<Part>,
232    /// The names, in the order the file defined or first referred to each of them.
233    pub names: Vec<Name>,
234}
235
236/// That, as a relocatable object in whichever of the two formats the target wants.
237///
238/// Both formats, the same two the module that writes a compilation writes it into, and the
239/// differences between them are the same answers there. That is the whole reason this is not two
240/// functions: a file of assembly names its own sections and a compilation does not, but what a
241/// relocation is called and whether a symbol has anywhere to keep a visibility are facts about the
242/// format rather than about where the bytes came from, and a second set of answers to them would
243/// be a second set to get wrong.
244///
245/// What a [`Part`] carries is the section type and flags the source wrote in as many words. ELF has
246/// a field for each of them and they are written down as they stand. COFF has no field they map
247/// onto, so what the section is comes from the kind on the shape and the writer underneath turns it
248/// into the characteristics every other Windows assembler writes. A program that means a Windows
249/// section to be something other than what its name says is a program that has to say so some other
250/// way, which is what `.section` with COFF's own letters is for and what tamnd/rucc#1514 left open.
251///
252/// # Errors
253///
254/// [`Error::Format`] for a machine or a platform this does not write, and [`Error::Refused`] for a
255/// relocation against a name the list does not hold or one this format has no relocation for.
256pub fn assembled(input: &Assembled, target: &TargetInfo) -> Result<Vec<u8>, Error> {
257    let flavour = match Flavour::of(target) {
258        Some(flavour) if target.tuple.arch() == Arch::X86_64 => flavour,
259        _ => return Err(Error::Format { triple: target.tuple.to_string() }),
260    };
261    let mut obj = Writer::new(flavour.binary(), Architecture::X86_64, Endianness::Little);
262
263    // Every section first, because a symbol says which one it is in and a relocation says which one
264    // it is written into, so both need the whole list before either can be added.
265    let mut made = Vec::with_capacity(input.parts.len());
266    for part in &input.parts {
267        let id = obj.add_section(Vec::new(), part.name.clone().into_bytes(), part.shape.kind());
268        // The flags in full rather than whatever the kind implied, because the kind is a summary of
269        // them and the source said them exactly. A section the program wrote `"ax"` on is executable
270        // whether or not its name is one this compiler would have made executable. Only where the
271        // format has the fields: see [`Flavour::stated`].
272        if let Some(flags) = flavour.stated(part.shape) {
273            obj.section_mut(id).flags = flags;
274        }
275        let align = part.align.max(1);
276        if part.shape.bits {
277            obj.append_section_data(id, &part.bytes, align);
278        } else {
279            obj.append_section_bss(id, part.size, align);
280        }
281        made.push(id);
282    }
283
284    // Then every name. A relocation names one, and the writer wants the symbol before the
285    // relocation that points at it, so this whole pass is in front of the one below.
286    let mut symbols = std::collections::BTreeMap::new();
287    for name in &input.names {
288        let (section, value, size) = match name.at {
289            Held::In { part, offset } => {
290                let Some(id) = made.get(part) else {
291                    let why = format!(
292                        "'{}' is in section {part} and there is no such section",
293                        name.name
294                    );
295                    return Err(Error::Refused { why });
296                };
297                (SymbolSection::Section(*id), offset, name.size)
298            }
299            Held::Absolute(value) => (SymbolSection::Absolute, value, name.size),
300            // A common symbol says what it wants rather than where it is, and ELF records the
301            // boundary it wants where an ordinary symbol records its address.
302            Held::Common { size, align } => (SymbolSection::Common, align, size),
303            Held::Undefined => (SymbolSection::Undefined, 0, 0),
304        };
305        let id = obj.add_symbol(Symbol {
306            name: name.name.clone().into_bytes(),
307            value,
308            size,
309            kind: flavour.sort(name.sort, name.binding),
310            scope: crate::file::scope_of(name.binding),
311            weak: name.binding == Binding::Weak,
312            section,
313            flags: SymbolFlags::None,
314        });
315        flavour.see(&mut obj, id, name.binding, name.visibility);
316        // The writer underneath records a common symbol as `STT_COMMON` and gas records the same
317        // symbol as `STT_OBJECT`. Both are a request for storage and a linker reads either, and the
318        // one gas writes is written here, because an object that says the same thing a different
319        // way is the kind of difference that turns up years later in a tool that only ever saw the
320        // other one. A common symbol is global by definition, so there is no binding to preserve.
321        if matches!(name.at, Held::Common { .. }) {
322            if let SymbolFlags::Elf { st_info, .. } = obj.symbol_flags_mut(id) {
323                *st_info = elf::STB_GLOBAL | elf::STT_OBJECT;
324            }
325        }
326        symbols.insert(name.name.clone(), id);
327    }
328
329    for (part, id) in input.parts.iter().zip(&made) {
330        for reloc in &part.relocs {
331            let Some(symbol) = symbols.get(&reloc.symbol) else {
332                let why =
333                    format!("'{}' is named by a relocation and by nothing else", reloc.symbol);
334                return Err(Error::Refused { why });
335            };
336            let flags = flavour.reloc(reloc.kind, reloc.after).ok_or_else(|| Error::Refused {
337                why: format!("no relocation is {:?}", reloc.kind),
338            })?;
339            obj.add_relocation(
340                *id,
341                Relocation {
342                    offset: reloc.at as u64,
343                    symbol: *symbol,
344                    addend: reloc.addend,
345                    flags,
346                },
347            )
348            .map_err(|why| Error::Refused { why: why.to_string() })?;
349        }
350    }
351
352    // The same marker every other object this compiler writes gets, and for the same reason: a
353    // linker that does not find it in every input marks the stack executable. Not a second one if
354    // the file already said it, which a file written by hand for a linker that cares often does,
355    // and nothing at all on a format whose answer to the question is in the finished image.
356    if !input.parts.iter().any(|part| part.name == ".note.GNU-stack") {
357        flavour.marker(&mut obj);
358    }
359
360    obj.write().map_err(|why| Error::Refused { why: why.to_string() })
361}
362
363/// Every name in it a linker can find, which is what an archive's symbol index is built from.
364///
365/// The same rule as [`crate::defines`]: a local is left out, because a name the static link has
366/// already finished with is not one an archive may offer, and an undefined one is left out because
367/// this file does not have it.
368#[must_use]
369pub fn assembled_defines(input: &Assembled) -> Vec<String> {
370    input
371        .names
372        .iter()
373        .filter(|name| name.binding != Binding::Local && name.at != Held::Undefined)
374        .map(|name| name.name.clone())
375        .collect()
376}
377
378#[cfg(test)]
379mod tests {
380    use super::*;
381
382    use object::read::elf::{FileHeader as _, Sym as _};
383    use object::read::{Object as _, ObjectSection as _, ObjectSymbol as _};
384    use object::{RelocationFlags, SectionFlags};
385    use rucc_target::{Arch as TargetArch, Env, Os, Triple};
386
387    use crate::section::Reference;
388
389    /// A linux x86-64 target, which is the one most of these are written against.
390    fn target() -> TargetInfo {
391        TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Linux, Env::Gnu))
392    }
393
394    /// The same machine under mingw-w64, which is the target the COFF cases below are about.
395    fn windows() -> TargetInfo {
396        TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Windows, Env::Gnu))
397    }
398
399    /// One section of that name holding those bytes, with the flags the name implies.
400    fn part(name: &str, bytes: Vec<u8>) -> Part {
401        Part {
402            name: name.to_owned(),
403            size: bytes.len() as u64,
404            bytes,
405            align: 1,
406            shape: Shape::of(name),
407            relocs: Vec::new(),
408        }
409    }
410
411    /// One name at an offset into the first section.
412    fn at(name: &str, offset: u64, sort: Sort, binding: Binding) -> Name {
413        Name {
414            name: name.to_owned(),
415            at: Held::In { part: 0, offset },
416            size: 0,
417            sort,
418            binding,
419            visibility: Visibility::Default,
420        }
421    }
422
423    /// The raw `st_info` and `st_value` of a symbol, as the file holds them.
424    ///
425    /// The reader's own `kind()`, `is_global()` and `address()` are a translation of these, and a
426    /// translation is what several of the cases below are about, so they ask the file rather than
427    /// the reading. A common symbol is the clearest of them: `address()` gives zero for one because
428    /// it has no address, and the field an ordinary symbol keeps its address in is where a common
429    /// one states the boundary it has to start on.
430    fn raw(bytes: &[u8], want: &str) -> (u8, u64) {
431        let header = elf::FileHeader64::<Endianness>::parse(bytes).expect("a header");
432        let endian = header.endian().expect("an endianness");
433        let table = header.sections(endian, bytes).expect("the sections");
434        let symbols = table.symbols(endian, bytes, elf::SHT_SYMTAB).expect("a symbol table");
435        for symbol in symbols.iter() {
436            if symbols.symbol_name(endian, symbol).expect("a name") == want.as_bytes() {
437                return (symbol.st_info().0, symbol.st_value(endian));
438            }
439        }
440        panic!("there is no symbol called '{want}'");
441    }
442
443    /// The first half of that.
444    fn st_info(bytes: &[u8], want: &str) -> u8 {
445        raw(bytes, want).0
446    }
447
448    #[test]
449    fn a_section_carries_the_flags_the_source_said_and_not_the_ones_its_name_suggests() {
450        // The whole reason a shape is separate facts rather than a kind. A program may write
451        // `.section .init.text,"ax"` and mean a section with a name this compiler has never heard
452        // of, and what it said about it is the letters.
453        let mut odd = part(".init.text", vec![0x90]);
454        odd.shape = Shape { alloc: true, exec: true, bits: true, ..Shape::default() };
455        let input = Assembled { parts: vec![odd], names: Vec::new() };
456        let bytes = assembled(&input, &target()).expect("an object");
457        let file = object::File::parse(&bytes[..]).expect("a readable object");
458        let section = file.section_by_name(".init.text").expect("the section");
459        assert_eq!(section.data().expect("the bytes"), &[0x90]);
460        let SectionFlags::Elf { sh_flags, sh_type } = section.flags() else {
461            panic!("this is an ELF file");
462        };
463        assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_EXECINSTR.0);
464        assert_eq!(sh_flags.0 & elf::SHF_WRITE.0, 0, "nothing said it was writable");
465        assert_eq!(sh_type, elf::SHT_PROGBITS);
466    }
467
468    #[test]
469    fn a_section_that_holds_no_bytes_still_says_how_long_it_is() {
470        // `.bss` is a length and no bytes, and a writer that appended its data would produce a file
471        // with that much zero in it, which is the difference between an object and a big object.
472        let mut room = part(".bss", Vec::new());
473        room.size = 4096;
474        room.align = 16;
475        let input = Assembled { parts: vec![room], names: Vec::new() };
476        let bytes = assembled(&input, &target()).expect("an object");
477        assert!(bytes.len() < 4096, "the empty space was written out: {} bytes", bytes.len());
478        let file = object::File::parse(&bytes[..]).expect("a readable object");
479        let section = file.section_by_name(".bss").expect("the section");
480        assert_eq!(section.size(), 4096);
481        assert_eq!(section.align(), 16);
482        let SectionFlags::Elf { sh_type, .. } = section.flags() else { panic!("an ELF file") };
483        assert_eq!(sh_type, elf::SHT_NOBITS);
484    }
485
486    #[test]
487    fn a_label_nobody_stated_a_type_for_is_a_symbol_with_no_type() {
488        // `STT_NOTYPE` is what gas writes for one, and it is a real answer rather than a missing
489        // one. The writer underneath refuses a defined symbol whose kind is `Unknown` outright, so
490        // this is also the case that says the mapping went to `Label` and not there.
491        let input = Assembled {
492            parts: vec![part(".text", vec![0; 8])],
493            names: vec![at("plain", 4, Sort::Untyped, Binding::Global)],
494        };
495        let bytes = assembled(&input, &target()).expect("an object");
496        let file = object::File::parse(&bytes[..]).expect("a readable object");
497        let plain = file.symbols().find(|s| s.name() == Ok("plain")).expect("the label");
498        assert_eq!(plain.address(), 4);
499        assert_eq!(st_info(&bytes, "plain") & 0xf, elf::STT_NOTYPE.0);
500    }
501
502    #[test]
503    fn what_type_said_is_what_the_symbol_gets() {
504        let input = Assembled {
505            parts: vec![part(".text", vec![0; 8])],
506            names: vec![
507                at("run", 0, Sort::Func, Binding::Global),
508                at("held", 4, Sort::Object, Binding::Local),
509            ],
510        };
511        let bytes = assembled(&input, &target()).expect("an object");
512        assert_eq!(st_info(&bytes, "run") & 0xf, elf::STT_FUNC.0);
513        assert_eq!(st_info(&bytes, "held") & 0xf, elf::STT_OBJECT.0);
514        assert_eq!(st_info(&bytes, "run") >> 4, elf::STB_GLOBAL.0);
515        assert_eq!(st_info(&bytes, "held") >> 4, elf::STB_LOCAL.0);
516    }
517
518    #[test]
519    fn a_common_symbol_is_written_the_way_gas_writes_one() {
520        // The writer underneath records `STT_COMMON` and gas records `STT_OBJECT` for the same
521        // `.comm`. Both are a request for storage and a linker takes either, and the one gas writes
522        // is the one written here, so an object of ours and an object of theirs do not differ in a
523        // field somebody's tool reads years from now.
524        let input = Assembled {
525            parts: Vec::new(),
526            names: vec![Name {
527                name: "shared".to_owned(),
528                at: Held::Common { size: 8, align: 8 },
529                size: 0,
530                sort: Sort::Object,
531                binding: Binding::Global,
532                visibility: Visibility::Default,
533            }],
534        };
535        let bytes = assembled(&input, &target()).expect("an object");
536        assert_eq!(st_info(&bytes, "shared"), elf::STB_GLOBAL.0 << 4 | elf::STT_OBJECT.0);
537        let file = object::File::parse(&bytes[..]).expect("a readable object");
538        let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the symbol");
539        assert!(shared.is_common(), "the linker has to be asked for the space");
540        assert_eq!(shared.size(), 8);
541        // Where an ordinary symbol keeps its address, which is why the two cannot both be said.
542        assert_eq!(raw(&bytes, "shared").1, 8, "the boundary it has to start on");
543    }
544
545    #[test]
546    fn a_set_is_a_number_rather_than_a_place() {
547        let input = Assembled {
548            parts: vec![part(".text", vec![0; 8])],
549            names: vec![Name {
550                name: "size_of_it".to_owned(),
551                at: Held::Absolute(25),
552                size: 0,
553                sort: Sort::Untyped,
554                binding: Binding::Global,
555                visibility: Visibility::Default,
556            }],
557        };
558        let bytes = assembled(&input, &target()).expect("an object");
559        let file = object::File::parse(&bytes[..]).expect("a readable object");
560        let sym = file.symbols().find(|s| s.name() == Ok("size_of_it")).expect("the symbol");
561        assert_eq!(sym.address(), 25);
562        assert_eq!(sym.section(), object::SymbolSection::Absolute, "it is not in any section");
563    }
564
565    #[test]
566    fn a_relocation_names_a_symbol_and_lands_where_the_bytes_are() {
567        let mut data = part(".data", vec![0; 8]);
568        data.relocs.push(Reloc {
569            at: 0,
570            symbol: "message".to_owned(),
571            kind: Reference::Address { bytes: 8 },
572            addend: 0,
573            after: 0,
574        });
575        let input = Assembled {
576            parts: vec![data],
577            names: vec![Name {
578                name: "message".to_owned(),
579                at: Held::Undefined,
580                size: 0,
581                sort: Sort::Untyped,
582                binding: Binding::Global,
583                visibility: Visibility::Default,
584            }],
585        };
586        let bytes = assembled(&input, &target()).expect("an object");
587        let file = object::File::parse(&bytes[..]).expect("a readable object");
588        let section = file.section_by_name(".data").expect("the section");
589        let (at, reloc) = section.relocations().next().expect("one relocation");
590        assert_eq!(at, 0);
591        assert_eq!(reloc.addend(), 0);
592        let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("an ELF file") };
593        assert_eq!(r_type, elf::R_X86_64_64);
594    }
595
596    #[test]
597    fn a_relocation_against_a_name_the_file_never_mentions_is_refused() {
598        // Rather than written against symbol zero, which is a file that links and resolves the
599        // reference to address zero. The list of names is the whole of what the reader found, so a
600        // relocation naming something outside it is a mistake in this compiler.
601        let mut data = part(".data", vec![0; 8]);
602        data.relocs.push(Reloc {
603            at: 0,
604            symbol: "nowhere".to_owned(),
605            kind: Reference::Address { bytes: 8 },
606            addend: 0,
607            after: 0,
608        });
609        let input = Assembled { parts: vec![data], names: Vec::new() };
610        let why = assembled(&input, &target()).expect_err("this cannot be written");
611        assert!(format!("{why}").contains("nowhere"), "{why}");
612    }
613
614    #[test]
615    fn the_stack_is_marked_once_whoever_asked_for_it() {
616        // A linker that does not find this marker in every input marks the stack executable, and a
617        // file written by hand for one that cares often says it itself.
618        let bare = Assembled { parts: vec![part(".text", vec![0x90])], names: Vec::new() };
619        let bytes = assembled(&bare, &target()).expect("an object");
620        let file = object::File::parse(&bytes[..]).expect("a readable object");
621        assert!(file.section_by_name(".note.GNU-stack").is_some(), "the marker was left out");
622
623        let said = Assembled {
624            parts: vec![part(".text", vec![0x90]), part(".note.GNU-stack", Vec::new())],
625            names: Vec::new(),
626        };
627        let bytes = assembled(&said, &target()).expect("an object");
628        let file = object::File::parse(&bytes[..]).expect("a readable object");
629        let marks = file.sections().filter(|s| s.name() == Ok(".note.GNU-stack")).count();
630        assert_eq!(marks, 1, "the file said it and it was said again");
631    }
632
633    #[test]
634    fn only_the_names_a_linker_could_find_are_offered_to_an_archive() {
635        let input = Assembled {
636            parts: vec![part(".text", vec![0; 8])],
637            names: vec![
638                at("reachable", 0, Sort::Func, Binding::Global),
639                at("mine", 4, Sort::Func, Binding::Local),
640                Name {
641                    name: "elsewhere".to_owned(),
642                    at: Held::Undefined,
643                    size: 0,
644                    sort: Sort::Untyped,
645                    binding: Binding::Global,
646                    visibility: Visibility::Default,
647                },
648            ],
649        };
650        assert_eq!(assembled_defines(&input), vec!["reachable".to_owned()]);
651    }
652
653    #[test]
654    fn a_machine_this_does_not_write_is_refused_rather_than_written_wrong() {
655        let input = Assembled { parts: vec![part(".text", vec![0x90])], names: Vec::new() };
656        let elsewhere = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Linux, Env::Gnu));
657        let why = assembled(&input, &elsewhere).expect_err("this cannot be written");
658        assert!(format!("{why}").contains("aarch64"), "{why}");
659    }
660
661    #[test]
662    fn a_file_of_assembly_for_windows_is_written_as_coff() {
663        // What tamnd/rucc#1514 was about. `runtime/builtins/chkstk.S` is a file of assembly for a
664        // Windows target, and until this it was refused with a message about there being no object
665        // writer for the triple, which read as the whole back end being missing rather than this
666        // one path through it.
667        let input = Assembled { parts: vec![part(".text", vec![0xc3])], names: Vec::new() };
668        let bytes = assembled(&input, &windows()).expect("an object");
669        let file = object::File::parse(&bytes[..]).expect("a readable object");
670        assert_eq!(file.format(), object::BinaryFormat::Coff);
671        let section = file.section_by_name(".text").expect("the section");
672        assert_eq!(section.data().expect("the bytes"), &[0xc3]);
673        assert_eq!(section.kind(), SectionKind::Text);
674        assert!(
675            file.section_by_name(".note.GNU-stack").is_none(),
676            "a format with no marker got one anyway"
677        );
678    }
679
680    #[test]
681    fn a_global_label_with_no_type_under_it_is_still_offered_on_coff() {
682        // The case a `.globl` and a label is, which is most of what a hand written file says. On
683        // ELF that is `STT_NOTYPE` and the binding is a separate field, so the name is global
684        // whatever its type. COFF has no such split: what the writer underneath calls a label is
685        // storage class `LABEL`, which is a name inside one file, and a symbol written that way is
686        // one no linker resolves against. `___chkstk_ms` came out of the archive as a local under
687        // that mapping and mingw-w64's own objects went on wanting it.
688        let input = Assembled {
689            parts: vec![part(".text", vec![0; 8])],
690            names: vec![
691                at("offered", 0, Sort::Untyped, Binding::Global),
692                at("ours", 4, Sort::Untyped, Binding::Local),
693            ],
694        };
695        let bytes = assembled(&input, &windows()).expect("an object");
696        let file = object::File::parse(&bytes[..]).expect("a readable object");
697        let offered = file.symbols().find(|s| s.name() == Ok("offered")).expect("the label");
698        assert!(offered.is_global(), "a `.globl` label came out local");
699        let ours = file.symbols().find(|s| s.name() == Ok("ours")).expect("the other label");
700        assert!(!ours.is_global(), "a label nothing offered came out global");
701        // And the same input on ELF is still what gas writes there, which is the half of this that
702        // would otherwise have been changed to fix the other half.
703        let bytes = assembled(&input, &target()).expect("an object");
704        assert_eq!(st_info(&bytes, "offered") & 0xf, elf::STT_NOTYPE.0);
705    }
706
707    #[test]
708    fn a_relocation_on_coff_says_how_much_of_the_instruction_comes_after_it() {
709        // The one real difference between the two formats' relocations. ELF folds the distance
710        // between the hole and the end of the instruction into the addend and has one type. COFF
711        // counts from the end of the instruction and has no addend field, so the count is in the
712        // type: `IMAGE_REL_AMD64_REL32_4` is four bytes of immediate behind the displacement.
713        let mut text = part(".text", vec![0; 16]);
714        text.relocs.push(Reloc {
715            at: 2,
716            symbol: "elsewhere".to_owned(),
717            kind: Reference::Data,
718            addend: -8,
719            after: 4,
720        });
721        let input = Assembled {
722            parts: vec![text],
723            names: vec![Name {
724                name: "elsewhere".to_owned(),
725                at: Held::Undefined,
726                size: 0,
727                sort: Sort::Untyped,
728                binding: Binding::Global,
729                visibility: Visibility::Default,
730            }],
731        };
732        let bytes = assembled(&input, &windows()).expect("an object");
733        let file = object::File::parse(&bytes[..]).expect("a readable object");
734        let section = file.section_by_name(".text").expect("the section");
735        let (at, reloc) = section.relocations().next().expect("the relocation");
736        assert_eq!(at, 2);
737        assert_eq!(
738            reloc.flags(),
739            RelocationFlags::Coff {
740                typ: object::pe::RelocationType(object::pe::IMAGE_REL_AMD64_REL32.0 + 4)
741            }
742        );
743    }
744}