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