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    /// `M`: how long each entry is in a section of constants the linker may keep one copy of
79    /// wherever two objects hold the same one, and zero for a section that is not one of those.
80    /// gcc puts a `double` it loads from memory in `.rodata.cst8`, which is one of these.
81    pub merge: u64,
82    /// `S`: the entries are strings ended by a zero rather than all of one length, which is where
83    /// gcc puts every string literal. Only means anything beside `merge`.
84    pub strings: bool,
85}
86
87impl Shape {
88    /// What a section of this name is when the source named it and said nothing else.
89    ///
90    /// `.text`, `.data` and the rest are names an assembler already knows the flags of, which is
91    /// why a program may write `.data` on its own and why `.section .data` without letters is the
92    /// same section rather than an unallocated one. A name nothing here knows gets the flags of an
93    /// ordinary allocated writable section, which is what gas does with one.
94    #[must_use]
95    pub fn of(name: &str) -> Shape {
96        let base = Shape { alloc: true, bits: true, ..Shape::default() };
97        let head = name.split_once('.').map_or(name, |(_, rest)| rest);
98        let head = head.split_once('.').map_or(head, |(first, _)| first);
99        match head {
100            "text" | "init" | "fini" => Shape { exec: true, ..base },
101            "rodata" | "eh_frame_hdr" => base,
102            "bss" => Shape { write: true, bits: false, ..base },
103            "tbss" => Shape { write: true, thread: true, bits: false, ..base },
104            "tdata" => Shape { write: true, thread: true, ..base },
105            // The three the linker gathers and the startup code walks. The type is what makes one
106            // of them that, rather than the name: a section of the ordinary type under the same
107            // name is gathered into the same run and called by nobody.
108            _ if Array::of(name).is_some() => Shape { write: true, array: Array::of(name), ..base },
109            // Not allocated, because nothing in the running program reads it. A debugger reads it
110            // out of the file, and a section marked allocated would take space in every process.
111            "debug_info" | "debug_abbrev" | "debug_line" | "debug_str" | "comment" => {
112                Shape { alloc: false, bits: true, ..Shape::default() }
113            }
114            _ => Shape { write: true, ..base },
115        }
116    }
117
118    /// The flag word ELF holds these in.
119    ///
120    /// Not public, and neither are the two below it. The fields above are the whole of what a
121    /// caller says about a section, and how ELF spells them is this crate's business: a reader that
122    /// had to name an ELF constant to describe an executable section would be one that could not
123    /// describe one for any other format.
124    pub(crate) fn sh_flags(self) -> elf::SectionFlags {
125        let mut flags = 0;
126        if self.alloc {
127            flags |= elf::SHF_ALLOC.0;
128        }
129        if self.write {
130            flags |= elf::SHF_WRITE.0;
131        }
132        if self.exec {
133            flags |= elf::SHF_EXECINSTR.0;
134        }
135        if self.thread {
136            flags |= elf::SHF_TLS.0;
137        }
138        if self.merge != 0 {
139            flags |= elf::SHF_MERGE.0;
140            if self.strings {
141                flags |= elf::SHF_STRINGS.0;
142            }
143        }
144        elf::SectionFlags(flags)
145    }
146
147    /// The type ELF holds in the header beside those flags.
148    pub(crate) fn sh_type(self) -> elf::SectionType {
149        match self.array {
150            _ if !self.bits => elf::SHT_NOBITS,
151            Some(Array::Init) => elf::SHT_INIT_ARRAY,
152            Some(Array::Fini) => elf::SHT_FINI_ARRAY,
153            Some(Array::Preinit) => elf::SHT_PREINIT_ARRAY,
154            None => elf::SHT_PROGBITS,
155        }
156    }
157
158    /// What the writer underneath calls the nearest thing to this.
159    ///
160    /// It is told the flags in full afterwards, so this only has to be close enough that nothing
161    /// else the writer decides from the kind comes out wrong, which is the default alignment and
162    /// whether it appends bytes or counts them.
163    pub(crate) const fn kind(self) -> SectionKind {
164        match self {
165            Shape { bits: false, thread: true, .. } => SectionKind::UninitializedTls,
166            Shape { bits: false, .. } => SectionKind::UninitializedData,
167            Shape { thread: true, .. } => SectionKind::Tls,
168            Shape { exec: true, .. } => SectionKind::Text,
169            Shape { alloc: false, .. } => SectionKind::Other,
170            Shape { write: false, .. } => SectionKind::ReadOnlyData,
171            Shape { .. } => SectionKind::Data,
172        }
173    }
174}
175
176/// One name in the symbol table, as a file of assembly defines one.
177#[derive(Debug, Clone, PartialEq, Eq)]
178pub struct Name {
179    /// The name, spelled as the source spelled it.
180    pub name: String,
181    /// Where it is.
182    pub at: Held,
183    /// How long the thing it names is, which is what `.size` said and is zero when nothing did.
184    pub size: u64,
185    /// What kind of thing it names, which is what `.type` said.
186    pub sort: Sort,
187    /// Who can see it.
188    pub binding: Binding,
189    /// How far outside a shared library it reaches.
190    pub visibility: Visibility,
191}
192
193/// Where a name is, which is four different things and not an offset with special cases.
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
195pub enum Held {
196    /// At an offset into one of the sections, which is what a label is.
197    In {
198        /// Which section, as an index into the list given alongside.
199        part: usize,
200        /// How far into it.
201        offset: u64,
202    },
203    /// A number rather than a place, which is what `.set` and `.equ` produce. The linker resolves
204    /// a reference to one to the number itself and there is nothing for it to be relative to.
205    Absolute(u64),
206    /// That much zeroed space asked of the linker under this name, which is `.comm` and `.lcomm`.
207    /// Every definition of the name across every object is merged into one.
208    Common {
209        /// How much space.
210        size: u64,
211        /// What boundary it has to start on. ELF records this where an ordinary symbol records its
212        /// address, which is why the two cannot both be said.
213        align: u64,
214    },
215    /// Named and not defined here, which the linker has to find somewhere else.
216    Undefined,
217}
218
219/// What kind of thing a name names, which is what `.type` says.
220#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
221pub enum Sort {
222    /// `@function`. A call through the procedure linkage table may be made to it.
223    Func,
224    /// `@object`. Data.
225    Object,
226    /// `@tls_object`. A thread-local variable, which a linker checks relocations against.
227    Thread,
228    /// `.file`, which names the source this was assembled from rather than anything in it.
229    ///
230    /// Not a thing `.type` can say, and here because it is a symbol and there is nowhere else for
231    /// it. A debugger reads it and so does `nm`, and gas writes one for every file that says its
232    /// own name, which is every file gcc produces.
233    File,
234    /// Nothing was said, which is what a plain label gets and is a real answer rather than a
235    /// missing one: gas writes `STT_NOTYPE` for a label nobody stated a type for.
236    #[default]
237    Untyped,
238}
239
240/// Everything an assembled file holds: its sections, and the names that point into them.
241#[derive(Debug, Clone, Default, PartialEq, Eq)]
242pub struct Assembled {
243    /// The sections, in the order the file first mentioned each of them.
244    pub parts: Vec<Part>,
245    /// The names, in the order the file defined or first referred to each of them.
246    pub names: Vec<Name>,
247}
248
249/// That, as a relocatable object in whichever of the two formats the target wants.
250///
251/// Both formats, the same two the module that writes a compilation writes it into, and the
252/// differences between them are the same answers there. That is the whole reason this is not two
253/// functions: a file of assembly names its own sections and a compilation does not, but what a
254/// relocation is called and whether a symbol has anywhere to keep a visibility are facts about the
255/// format rather than about where the bytes came from, and a second set of answers to them would
256/// be a second set to get wrong.
257///
258/// What a [`Part`] carries is the section type and flags the source wrote in as many words. ELF has
259/// a field for each of them and they are written down as they stand. COFF has no field they map
260/// onto, so what the section is comes from the kind on the shape and the writer underneath turns it
261/// into the characteristics every other Windows assembler writes. A program that means a Windows
262/// section to be something other than what its name says is a program that has to say so some other
263/// way, which is what `.section` with COFF's own letters is for and what tamnd/rucc#1514 left open.
264///
265/// # Errors
266///
267/// [`Error::Format`] for a machine or a platform this does not write, and [`Error::Refused`] for a
268/// relocation against a name the list does not hold or one this format has no relocation for.
269pub fn assembled(input: &Assembled, target: &TargetInfo) -> Result<Vec<u8>, Error> {
270    let flavour = match Flavour::of(target) {
271        Some(flavour) if target.tuple.arch() == Arch::X86_64 => flavour,
272        _ => return Err(Error::Format { triple: target.tuple.to_string() }),
273    };
274    let mut obj = Writer::new(flavour.binary(), Architecture::X86_64, Endianness::Little);
275
276    // Every section first, because a symbol says which one it is in and a relocation says which one
277    // it is written into, so both need the whole list before either can be added.
278    let mut made = Vec::with_capacity(input.parts.len());
279    for part in &input.parts {
280        let id = obj.add_section(Vec::new(), part.name.clone().into_bytes(), part.shape.kind());
281        // The flags in full rather than whatever the kind implied, because the kind is a summary of
282        // them and the source said them exactly. A section the program wrote `"ax"` on is executable
283        // whether or not its name is one this compiler would have made executable. Only where the
284        // format has the fields: see [`Flavour::stated`].
285        if let Some(flags) = flavour.stated(part.shape) {
286            obj.section_mut(id).flags = flags;
287        }
288        let align = part.align.max(1);
289        if part.shape.bits {
290            obj.append_section_data(id, &part.bytes, align);
291        } else {
292            obj.append_section_bss(id, part.size, align);
293        }
294        made.push(id);
295    }
296
297    // Which relocations point at the section a name is in rather than at the name, and which names
298    // are then asked for by nothing and left out, before either is written down.
299    let defined: std::collections::HashMap<&str, &Name> =
300        input.names.iter().map(|name| (name.name.as_str(), name)).collect();
301    let onto = |reloc: &Reloc| moved(flavour, input, &defined, reloc);
302    let wanted: std::collections::HashSet<&str> = input
303        .parts
304        .iter()
305        .flat_map(|part| &part.relocs)
306        .filter(|reloc| onto(reloc).is_none())
307        .map(|reloc| reloc.symbol.as_str())
308        .collect();
309
310    // Then every name. A relocation names one, and the writer wants the symbol before the
311    // relocation that points at it, so this whole pass is in front of the one below.
312    let mut symbols = std::collections::BTreeMap::new();
313    for name in &input.names {
314        if flavour == Flavour::Elf && unseen(name) && !wanted.contains(name.name.as_str()) {
315            continue;
316        }
317        let (section, value, size) = match name.at {
318            Held::In { part, offset } => {
319                let Some(id) = made.get(part) else {
320                    let why = format!(
321                        "'{}' is in section {part} and there is no such section",
322                        name.name
323                    );
324                    return Err(Error::Refused { why });
325                };
326                (SymbolSection::Section(*id), offset, name.size)
327            }
328            Held::Absolute(value) => (SymbolSection::Absolute, value, name.size),
329            // A common symbol says what it wants rather than where it is, and ELF records the
330            // boundary it wants where an ordinary symbol records its address.
331            Held::Common { size, align } => (SymbolSection::Common, align, size),
332            Held::Undefined => (SymbolSection::Undefined, 0, 0),
333        };
334        let id = obj.add_symbol(Symbol {
335            name: name.name.clone().into_bytes(),
336            value,
337            size,
338            kind: flavour.sort(name.sort, name.binding),
339            scope: crate::file::scope_of(name.binding),
340            weak: name.binding == Binding::Weak,
341            section,
342            flags: SymbolFlags::None,
343        });
344        flavour.see(&mut obj, id, name.binding, name.visibility);
345        // The writer underneath records a common symbol as `STT_COMMON` and gas records the same
346        // symbol as `STT_OBJECT`. Both are a request for storage and a linker reads either, and the
347        // one gas writes is written here, because an object that says the same thing a different
348        // way is the kind of difference that turns up years later in a tool that only ever saw the
349        // other one. A common symbol is global by definition, so there is no binding to preserve.
350        if matches!(name.at, Held::Common { .. }) {
351            if let SymbolFlags::Elf { st_info, .. } = obj.symbol_flags_mut(id) {
352                *st_info = elf::STB_GLOBAL | elf::STT_OBJECT;
353            }
354        }
355        symbols.insert(name.name.clone(), id);
356    }
357
358    for (part, id) in input.parts.iter().zip(&made) {
359        for reloc in &part.relocs {
360            let (symbol, addend) = match onto(reloc) {
361                Some((part, offset)) => {
362                    (obj.section_symbol(made[part]), reloc.addend + offset as i64)
363                }
364                None => {
365                    let Some(&symbol) = symbols.get(&reloc.symbol) else {
366                        let why = format!(
367                            "'{}' is named by a relocation and by nothing else",
368                            reloc.symbol
369                        );
370                        return Err(Error::Refused { why });
371                    };
372                    (symbol, reloc.addend)
373                }
374            };
375            let flags = flavour.reloc(reloc.kind, reloc.after).ok_or_else(|| Error::Refused {
376                why: format!("no relocation is {:?}", reloc.kind),
377            })?;
378            obj.add_relocation(*id, Relocation { offset: reloc.at as u64, symbol, addend, flags })
379                .map_err(|why| Error::Refused { why: why.to_string() })?;
380        }
381    }
382
383    // The same marker every other object this compiler writes gets, and for the same reason: a
384    // linker that does not find it in every input marks the stack executable. Not a second one if
385    // the file already said it, which a file written by hand for a linker that cares often does,
386    // and nothing at all on a format whose answer to the question is in the finished image.
387    if !input.parts.iter().any(|part| part.name == ".note.GNU-stack") {
388        flavour.marker(&mut obj);
389    }
390
391    let mut bytes = obj.write().map_err(|why| Error::Refused { why: why.to_string() })?;
392    if flavour == Flavour::Elf {
393        for part in input.parts.iter().filter(|part| part.shape.merge != 0) {
394            entry_size(&mut bytes, &part.name, part.shape.merge);
395        }
396    }
397    Ok(bytes)
398}
399
400/// Write how long an entry of a mergeable section is into its header, which the linker needs and
401/// the writer underneath has no field for. It writes one only for a section of strings it made
402/// itself. The file is a 64 bit little endian ELF one, since that is the only kind this writes, and
403/// the section is found by its name, which is unique because the assembler gave every name one
404/// section.
405fn entry_size(bytes: &mut [u8], name: &str, size: u64) {
406    let word = |bytes: &[u8], at: usize, width: usize| {
407        bytes[at..at + width].iter().rev().fold(0u64, |sum, &byte| sum << 8 | u64::from(byte))
408    };
409    let table = word(bytes, 0x28, 8) as usize;
410    let each = word(bytes, 0x3a, 2) as usize;
411    let count = word(bytes, 0x3c, 2) as usize;
412    let names = table + each * word(bytes, 0x3e, 2) as usize;
413    let names = word(bytes, names + 0x18, 8) as usize;
414    for header in (0..count).map(|nth| table + nth * each) {
415        let at = names + word(bytes, header, 4) as usize;
416        if bytes[at..].starts_with(name.as_bytes()) && bytes.get(at + name.len()) == Some(&0) {
417            bytes[header + 0x38..header + 0x40].copy_from_slice(&size.to_le_bytes());
418        }
419    }
420}
421
422/// The section and the offset into it a relocation is written against in place of the name it
423/// gave, when gas would do the same.
424///
425/// A name only this file can see is a place in a section and nothing more, so gas writes the
426/// section's own symbol and how far into it the place is, and a `.L` label then has no reason to be
427/// in the table at all. It keeps the name where the linker has to see it: a call, which may go
428/// through a stub the linker makes for that name, a slot of the global offset table, and a place in
429/// a section the linker may merge, where the offset into the section is not an offset into the
430/// merged one. The last of those is only a problem for a distance, or for an address with
431/// something added to it, since the address of the start of a string is what the linker follows.
432fn moved(
433    flavour: Flavour,
434    input: &Assembled,
435    defined: &std::collections::HashMap<&str, &Name>,
436    reloc: &Reloc,
437) -> Option<(usize, u64)> {
438    use crate::section::Reference;
439    let name = defined.get(reloc.symbol.as_str())?;
440    let Held::In { part, offset } = name.at else { return None };
441    if flavour != Flavour::Elf || name.binding != Binding::Local {
442        return None;
443    }
444    let near = matches!(reloc.kind, Reference::Data | Reference::Away);
445    let fixed = match reloc.kind {
446        Reference::Call | Reference::Got | Reference::Thread => false,
447        _ if input.parts.get(part)?.shape.merge != 0 => !near && reloc.addend == 0,
448        _ => true,
449    };
450    fixed.then_some((part, offset))
451}
452
453/// Whether a name is one the assembler made up or a label only it sees, which gas leaves out of the
454/// table unless a relocation still names it. `.L` is the prefix for those that ELF assemblers agree
455/// on, and a name with a `\u{1}` in it is one this assembler made for a numbered label or a frame.
456fn unseen(name: &Name) -> bool {
457    name.binding == Binding::Local
458        && (name.name.starts_with(".L")
459            || name.name.starts_with("..")
460            || name.name.contains('\u{1}'))
461}
462
463/// Every name in it a linker can find, which is what an archive's symbol index is built from.
464///
465/// The same rule as [`crate::defines`]: a local is left out, because a name the static link has
466/// already finished with is not one an archive may offer, and an undefined one is left out because
467/// this file does not have it.
468#[must_use]
469pub fn assembled_defines(input: &Assembled) -> Vec<String> {
470    input
471        .names
472        .iter()
473        .filter(|name| name.binding != Binding::Local && name.at != Held::Undefined)
474        .map(|name| name.name.clone())
475        .collect()
476}
477
478#[cfg(test)]
479mod tests {
480    use super::*;
481
482    use object::read::elf::{FileHeader as _, Sym as _};
483    use object::read::{Object as _, ObjectSection as _, ObjectSymbol as _};
484    use object::{RelocationFlags, SectionFlags};
485    use rucc_target::{Arch as TargetArch, Env, Os, Triple};
486
487    use crate::section::Reference;
488
489    /// A linux x86-64 target, which is the one most of these are written against.
490    fn target() -> TargetInfo {
491        TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Linux, Env::Gnu))
492    }
493
494    /// The same machine under mingw-w64, which is the target the COFF cases below are about.
495    fn windows() -> TargetInfo {
496        TargetInfo::new(Triple::new(TargetArch::X86_64, Os::Windows, Env::Gnu))
497    }
498
499    /// One section of that name holding those bytes, with the flags the name implies.
500    fn part(name: &str, bytes: Vec<u8>) -> Part {
501        Part {
502            name: name.to_owned(),
503            size: bytes.len() as u64,
504            bytes,
505            align: 1,
506            shape: Shape::of(name),
507            relocs: Vec::new(),
508        }
509    }
510
511    /// One name at an offset into the first section.
512    fn at(name: &str, offset: u64, sort: Sort, binding: Binding) -> Name {
513        Name {
514            name: name.to_owned(),
515            at: Held::In { part: 0, offset },
516            size: 0,
517            sort,
518            binding,
519            visibility: Visibility::Default,
520        }
521    }
522
523    /// The raw `st_info` and `st_value` of a symbol, as the file holds them.
524    ///
525    /// The reader's own `kind()`, `is_global()` and `address()` are a translation of these, and a
526    /// translation is what several of the cases below are about, so they ask the file rather than
527    /// the reading. A common symbol is the clearest of them: `address()` gives zero for one because
528    /// it has no address, and the field an ordinary symbol keeps its address in is where a common
529    /// one states the boundary it has to start on.
530    fn raw(bytes: &[u8], want: &str) -> (u8, u64) {
531        let header = elf::FileHeader64::<Endianness>::parse(bytes).expect("a header");
532        let endian = header.endian().expect("an endianness");
533        let table = header.sections(endian, bytes).expect("the sections");
534        let symbols = table.symbols(endian, bytes, elf::SHT_SYMTAB).expect("a symbol table");
535        for symbol in symbols.iter() {
536            if symbols.symbol_name(endian, symbol).expect("a name") == want.as_bytes() {
537                return (symbol.st_info().0, symbol.st_value(endian));
538            }
539        }
540        panic!("there is no symbol called '{want}'");
541    }
542
543    /// The first half of that.
544    fn st_info(bytes: &[u8], want: &str) -> u8 {
545        raw(bytes, want).0
546    }
547
548    #[test]
549    fn a_section_carries_the_flags_the_source_said_and_not_the_ones_its_name_suggests() {
550        // The whole reason a shape is separate facts rather than a kind. A program may write
551        // `.section .init.text,"ax"` and mean a section with a name this compiler has never heard
552        // of, and what it said about it is the letters.
553        let mut odd = part(".init.text", vec![0x90]);
554        odd.shape = Shape { alloc: true, exec: true, bits: true, ..Shape::default() };
555        let input = Assembled { parts: vec![odd], names: Vec::new() };
556        let bytes = assembled(&input, &target()).expect("an object");
557        let file = object::File::parse(&bytes[..]).expect("a readable object");
558        let section = file.section_by_name(".init.text").expect("the section");
559        assert_eq!(section.data().expect("the bytes"), &[0x90]);
560        let SectionFlags::Elf { sh_flags, sh_type } = section.flags() else {
561            panic!("this is an ELF file");
562        };
563        assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_EXECINSTR.0);
564        assert_eq!(sh_flags.0 & elf::SHF_WRITE.0, 0, "nothing said it was writable");
565        assert_eq!(sh_type, elf::SHT_PROGBITS);
566    }
567
568    #[test]
569    fn a_section_that_holds_no_bytes_still_says_how_long_it_is() {
570        // `.bss` is a length and no bytes, and a writer that appended its data would produce a file
571        // with that much zero in it, which is the difference between an object and a big object.
572        let mut room = part(".bss", Vec::new());
573        room.size = 4096;
574        room.align = 16;
575        let input = Assembled { parts: vec![room], names: Vec::new() };
576        let bytes = assembled(&input, &target()).expect("an object");
577        assert!(bytes.len() < 4096, "the empty space was written out: {} bytes", bytes.len());
578        let file = object::File::parse(&bytes[..]).expect("a readable object");
579        let section = file.section_by_name(".bss").expect("the section");
580        assert_eq!(section.size(), 4096);
581        assert_eq!(section.align(), 16);
582        let SectionFlags::Elf { sh_type, .. } = section.flags() else { panic!("an ELF file") };
583        assert_eq!(sh_type, elf::SHT_NOBITS);
584    }
585
586    #[test]
587    fn a_label_nobody_stated_a_type_for_is_a_symbol_with_no_type() {
588        // `STT_NOTYPE` is what gas writes for one, and it is a real answer rather than a missing
589        // one. The writer underneath refuses a defined symbol whose kind is `Unknown` outright, so
590        // this is also the case that says the mapping went to `Label` and not there.
591        let input = Assembled {
592            parts: vec![part(".text", vec![0; 8])],
593            names: vec![at("plain", 4, Sort::Untyped, Binding::Global)],
594        };
595        let bytes = assembled(&input, &target()).expect("an object");
596        let file = object::File::parse(&bytes[..]).expect("a readable object");
597        let plain = file.symbols().find(|s| s.name() == Ok("plain")).expect("the label");
598        assert_eq!(plain.address(), 4);
599        assert_eq!(st_info(&bytes, "plain") & 0xf, elf::STT_NOTYPE.0);
600    }
601
602    #[test]
603    fn what_type_said_is_what_the_symbol_gets() {
604        let input = Assembled {
605            parts: vec![part(".text", vec![0; 8])],
606            names: vec![
607                at("run", 0, Sort::Func, Binding::Global),
608                at("held", 4, Sort::Object, Binding::Local),
609            ],
610        };
611        let bytes = assembled(&input, &target()).expect("an object");
612        assert_eq!(st_info(&bytes, "run") & 0xf, elf::STT_FUNC.0);
613        assert_eq!(st_info(&bytes, "held") & 0xf, elf::STT_OBJECT.0);
614        assert_eq!(st_info(&bytes, "run") >> 4, elf::STB_GLOBAL.0);
615        assert_eq!(st_info(&bytes, "held") >> 4, elf::STB_LOCAL.0);
616    }
617
618    #[test]
619    fn a_common_symbol_is_written_the_way_gas_writes_one() {
620        // The writer underneath records `STT_COMMON` and gas records `STT_OBJECT` for the same
621        // `.comm`. Both are a request for storage and a linker takes either, and the one gas writes
622        // is the one written here, so an object of ours and an object of theirs do not differ in a
623        // field somebody's tool reads years from now.
624        let input = Assembled {
625            parts: Vec::new(),
626            names: vec![Name {
627                name: "shared".to_owned(),
628                at: Held::Common { size: 8, align: 8 },
629                size: 0,
630                sort: Sort::Object,
631                binding: Binding::Global,
632                visibility: Visibility::Default,
633            }],
634        };
635        let bytes = assembled(&input, &target()).expect("an object");
636        assert_eq!(st_info(&bytes, "shared"), elf::STB_GLOBAL.0 << 4 | elf::STT_OBJECT.0);
637        let file = object::File::parse(&bytes[..]).expect("a readable object");
638        let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the symbol");
639        assert!(shared.is_common(), "the linker has to be asked for the space");
640        assert_eq!(shared.size(), 8);
641        // Where an ordinary symbol keeps its address, which is why the two cannot both be said.
642        assert_eq!(raw(&bytes, "shared").1, 8, "the boundary it has to start on");
643    }
644
645    #[test]
646    fn a_set_is_a_number_rather_than_a_place() {
647        let input = Assembled {
648            parts: vec![part(".text", vec![0; 8])],
649            names: vec![Name {
650                name: "size_of_it".to_owned(),
651                at: Held::Absolute(25),
652                size: 0,
653                sort: Sort::Untyped,
654                binding: Binding::Global,
655                visibility: Visibility::Default,
656            }],
657        };
658        let bytes = assembled(&input, &target()).expect("an object");
659        let file = object::File::parse(&bytes[..]).expect("a readable object");
660        let sym = file.symbols().find(|s| s.name() == Ok("size_of_it")).expect("the symbol");
661        assert_eq!(sym.address(), 25);
662        assert_eq!(sym.section(), object::SymbolSection::Absolute, "it is not in any section");
663    }
664
665    #[test]
666    fn a_relocation_names_a_symbol_and_lands_where_the_bytes_are() {
667        let mut data = part(".data", vec![0; 8]);
668        data.relocs.push(Reloc {
669            at: 0,
670            symbol: "message".to_owned(),
671            kind: Reference::Address { bytes: 8 },
672            addend: 0,
673            after: 0,
674        });
675        let input = Assembled {
676            parts: vec![data],
677            names: vec![Name {
678                name: "message".to_owned(),
679                at: Held::Undefined,
680                size: 0,
681                sort: Sort::Untyped,
682                binding: Binding::Global,
683                visibility: Visibility::Default,
684            }],
685        };
686        let bytes = assembled(&input, &target()).expect("an object");
687        let file = object::File::parse(&bytes[..]).expect("a readable object");
688        let section = file.section_by_name(".data").expect("the section");
689        let (at, reloc) = section.relocations().next().expect("one relocation");
690        assert_eq!(at, 0);
691        assert_eq!(reloc.addend(), 0);
692        let RelocationFlags::Elf { r_type } = reloc.flags() else { panic!("an ELF file") };
693        assert_eq!(r_type, elf::R_X86_64_64);
694    }
695
696    #[test]
697    fn a_place_only_this_file_sees_is_reached_through_its_section_as_gas_does() {
698        // The `.L` label goes, the static function stays in the table, and both relocations are
699        // against `.text` at their offsets. A call keeps its name, since the linker may give it a
700        // stub, and so does a name the linker is allowed to see.
701        let mut text = part(".text", vec![0; 32]);
702        for (at, symbol, kind) in [
703            (0, ".L3", Reference::Data),
704            (4, "helper", Reference::Data),
705            (8, "helper", Reference::Call),
706            (12, "shared", Reference::Data),
707        ] {
708            let symbol = symbol.to_owned();
709            text.relocs.push(Reloc { at, symbol, kind, addend: -4, after: 0 });
710        }
711        let input = Assembled {
712            parts: vec![text],
713            names: vec![
714                at(".L3", 20, Sort::Untyped, Binding::Local),
715                at("helper", 24, Sort::Func, Binding::Local),
716                at("shared", 28, Sort::Func, Binding::Global),
717            ],
718        };
719        let bytes = assembled(&input, &target()).expect("an object");
720        let file = object::File::parse(&bytes[..]).expect("a readable object");
721        let names: Vec<_> = file.symbols().filter_map(|sym| sym.name().ok()).collect();
722        assert!(!names.contains(&".L3") && names.contains(&"helper"), "{names:?}");
723        let section = file.section_by_name(".text").expect("the section");
724        let reached: Vec<_> = section
725            .relocations()
726            .map(|(at, reloc)| {
727                let object::RelocationTarget::Symbol(index) = reloc.target() else {
728                    panic!("a symbol")
729                };
730                let symbol = file.symbol_by_index(index).expect("the symbol");
731                let name = if symbol.kind() == object::SymbolKind::Section {
732                    ".text"
733                } else {
734                    symbol.name().expect("a name")
735                };
736                (at, name, reloc.addend())
737            })
738            .collect();
739        assert_eq!(
740            reached,
741            [(0, ".text", 16), (4, ".text", 20), (8, "helper", -4), (12, "shared", -4)]
742        );
743    }
744
745    #[test]
746    fn a_section_of_constants_may_be_merged_and_a_distance_into_it_keeps_its_name() {
747        let mut text = part(".text", vec![0; 8]);
748        text.relocs.push(Reloc {
749            at: 0,
750            symbol: ".LC0".to_owned(),
751            kind: Reference::Data,
752            addend: -4,
753            after: 0,
754        });
755        let strings = Part {
756            shape: Shape { merge: 1, strings: true, ..Shape::of(".rodata") },
757            ..part(".rodata.str1.1", b"hi\0".to_vec())
758        };
759        let mut name = at(".LC0", 0, Sort::Untyped, Binding::Local);
760        name.at = Held::In { part: 1, offset: 0 };
761        let input = Assembled { parts: vec![text, strings], names: vec![name] };
762        let bytes = assembled(&input, &target()).expect("an object");
763        let file = object::File::parse(&bytes[..]).expect("a readable object");
764        let section = file.section_by_name(".rodata.str1.1").expect("the section");
765        let SectionFlags::Elf { sh_flags, .. } = section.flags() else { panic!("an ELF file") };
766        assert_eq!(sh_flags.0, elf::SHF_ALLOC.0 | elf::SHF_MERGE.0 | elf::SHF_STRINGS.0);
767        let header = elf::FileHeader64::<Endianness>::parse(&bytes[..]).expect("a header");
768        let endian = header.endian().expect("an endianness");
769        let table = header.sections(endian, &bytes[..]).expect("the sections");
770        let (_, found) = table.section_by_name(endian, b".rodata.str1.1").expect("the section");
771        assert_eq!(found.sh_entsize.get(endian), 1);
772        let text = file.section_by_name(".text").expect("the section");
773        let (_, reloc) = text.relocations().next().expect("one relocation");
774        let object::RelocationTarget::Symbol(index) = reloc.target() else { panic!("a symbol") };
775        assert_eq!(file.symbol_by_index(index).and_then(|sym| sym.name()), Ok(".LC0"));
776    }
777
778    #[test]
779    fn a_relocation_against_a_name_the_file_never_mentions_is_refused() {
780        // Rather than written against symbol zero, which is a file that links and resolves the
781        // reference to address zero. The list of names is the whole of what the reader found, so a
782        // relocation naming something outside it is a mistake in this compiler.
783        let mut data = part(".data", vec![0; 8]);
784        data.relocs.push(Reloc {
785            at: 0,
786            symbol: "nowhere".to_owned(),
787            kind: Reference::Address { bytes: 8 },
788            addend: 0,
789            after: 0,
790        });
791        let input = Assembled { parts: vec![data], names: Vec::new() };
792        let why = assembled(&input, &target()).expect_err("this cannot be written");
793        assert!(format!("{why}").contains("nowhere"), "{why}");
794    }
795
796    #[test]
797    fn the_stack_is_marked_once_whoever_asked_for_it() {
798        // A linker that does not find this marker in every input marks the stack executable, and a
799        // file written by hand for one that cares often says it itself.
800        let bare = Assembled { parts: vec![part(".text", vec![0x90])], names: Vec::new() };
801        let bytes = assembled(&bare, &target()).expect("an object");
802        let file = object::File::parse(&bytes[..]).expect("a readable object");
803        assert!(file.section_by_name(".note.GNU-stack").is_some(), "the marker was left out");
804
805        let said = Assembled {
806            parts: vec![part(".text", vec![0x90]), part(".note.GNU-stack", Vec::new())],
807            names: Vec::new(),
808        };
809        let bytes = assembled(&said, &target()).expect("an object");
810        let file = object::File::parse(&bytes[..]).expect("a readable object");
811        let marks = file.sections().filter(|s| s.name() == Ok(".note.GNU-stack")).count();
812        assert_eq!(marks, 1, "the file said it and it was said again");
813    }
814
815    #[test]
816    fn only_the_names_a_linker_could_find_are_offered_to_an_archive() {
817        let input = Assembled {
818            parts: vec![part(".text", vec![0; 8])],
819            names: vec![
820                at("reachable", 0, Sort::Func, Binding::Global),
821                at("mine", 4, Sort::Func, Binding::Local),
822                Name {
823                    name: "elsewhere".to_owned(),
824                    at: Held::Undefined,
825                    size: 0,
826                    sort: Sort::Untyped,
827                    binding: Binding::Global,
828                    visibility: Visibility::Default,
829                },
830            ],
831        };
832        assert_eq!(assembled_defines(&input), vec!["reachable".to_owned()]);
833    }
834
835    #[test]
836    fn a_machine_this_does_not_write_is_refused_rather_than_written_wrong() {
837        let input = Assembled { parts: vec![part(".text", vec![0x90])], names: Vec::new() };
838        let elsewhere = TargetInfo::new(Triple::new(TargetArch::Aarch64, Os::Linux, Env::Gnu));
839        let why = assembled(&input, &elsewhere).expect_err("this cannot be written");
840        assert!(format!("{why}").contains("aarch64"), "{why}");
841    }
842
843    #[test]
844    fn a_file_of_assembly_for_windows_is_written_as_coff() {
845        // What tamnd/rucc#1514 was about. `runtime/builtins/chkstk.S` is a file of assembly for a
846        // Windows target, and until this it was refused with a message about there being no object
847        // writer for the triple, which read as the whole back end being missing rather than this
848        // one path through it.
849        let input = Assembled { parts: vec![part(".text", vec![0xc3])], names: Vec::new() };
850        let bytes = assembled(&input, &windows()).expect("an object");
851        let file = object::File::parse(&bytes[..]).expect("a readable object");
852        assert_eq!(file.format(), object::BinaryFormat::Coff);
853        let section = file.section_by_name(".text").expect("the section");
854        assert_eq!(section.data().expect("the bytes"), &[0xc3]);
855        assert_eq!(section.kind(), SectionKind::Text);
856        assert!(
857            file.section_by_name(".note.GNU-stack").is_none(),
858            "a format with no marker got one anyway"
859        );
860    }
861
862    #[test]
863    fn a_global_label_with_no_type_under_it_is_still_offered_on_coff() {
864        // The case a `.globl` and a label is, which is most of what a hand written file says. On
865        // ELF that is `STT_NOTYPE` and the binding is a separate field, so the name is global
866        // whatever its type. COFF has no such split: what the writer underneath calls a label is
867        // storage class `LABEL`, which is a name inside one file, and a symbol written that way is
868        // one no linker resolves against. `___chkstk_ms` came out of the archive as a local under
869        // that mapping and mingw-w64's own objects went on wanting it.
870        let input = Assembled {
871            parts: vec![part(".text", vec![0; 8])],
872            names: vec![
873                at("offered", 0, Sort::Untyped, Binding::Global),
874                at("ours", 4, Sort::Untyped, Binding::Local),
875            ],
876        };
877        let bytes = assembled(&input, &windows()).expect("an object");
878        let file = object::File::parse(&bytes[..]).expect("a readable object");
879        let offered = file.symbols().find(|s| s.name() == Ok("offered")).expect("the label");
880        assert!(offered.is_global(), "a `.globl` label came out local");
881        let ours = file.symbols().find(|s| s.name() == Ok("ours")).expect("the other label");
882        assert!(!ours.is_global(), "a label nothing offered came out global");
883        // And the same input on ELF is still what gas writes there, which is the half of this that
884        // would otherwise have been changed to fix the other half.
885        let bytes = assembled(&input, &target()).expect("an object");
886        assert_eq!(st_info(&bytes, "offered") & 0xf, elf::STT_NOTYPE.0);
887    }
888
889    #[test]
890    fn a_relocation_on_coff_says_how_much_of_the_instruction_comes_after_it() {
891        // The one real difference between the two formats' relocations. ELF folds the distance
892        // between the hole and the end of the instruction into the addend and has one type. COFF
893        // counts from the end of the instruction and has no addend field, so the count is in the
894        // type: `IMAGE_REL_AMD64_REL32_4` is four bytes of immediate behind the displacement.
895        let mut text = part(".text", vec![0; 16]);
896        text.relocs.push(Reloc {
897            at: 2,
898            symbol: "elsewhere".to_owned(),
899            kind: Reference::Data,
900            addend: -8,
901            after: 4,
902        });
903        let input = Assembled {
904            parts: vec![text],
905            names: vec![Name {
906                name: "elsewhere".to_owned(),
907                at: Held::Undefined,
908                size: 0,
909                sort: Sort::Untyped,
910                binding: Binding::Global,
911                visibility: Visibility::Default,
912            }],
913        };
914        let bytes = assembled(&input, &windows()).expect("an object");
915        let file = object::File::parse(&bytes[..]).expect("a readable object");
916        let section = file.section_by_name(".text").expect("the section");
917        let (at, reloc) = section.relocations().next().expect("the relocation");
918        assert_eq!(at, 2);
919        assert_eq!(
920            reloc.flags(),
921            RelocationFlags::Coff {
922                typ: object::pe::RelocationType(object::pe::IMAGE_REL_AMD64_REL32.0 + 4)
923            }
924        );
925    }
926}