Skip to main content

rucc_object/
elf.rs

1//! Relocatable ELF objects.
2//!
3//! Design: `spec/11-asm-objects-debug.md` section 11.3, which says the three formats are written
4//! through the [`object`] crate's writer with our own layer above it for the parts it does not
5//! model. This is that layer for ELF, and what it holds is the part `object` cannot decide: which
6//! relocation an instruction wants, what a symbol's binding and type are, and the sections a
7//! linker expects to find whether or not anything was put in them.
8//!
9//! # The marker that has to be there
10//!
11//! `.note.GNU-stack`. A linker that does not find it in every input marks the stack executable,
12//! which section 11.3 calls out as a real and recurring security bug rather than a missing
13//! nicety. It is an empty section and nothing reads its contents, and leaving it out is the kind
14//! of mistake that produces a working program with a weakness in it, so it is written here and a
15//! test says so.
16//!
17//! # What is not here
18//!
19//! Mach-O and COFF. The formats disagree about more than their headers: an Apple symbol carries
20//! an underscore in front of the C name, Mach-O has no way to say how long a function is and
21//! wants `.subsections_via_symbols` instead, and COFF wants storage classes and `.pdata`. Each is
22//! its own piece of work and each is written when the target that needs it is.
23//!
24//! Thread-local storage. Reaching a thread-local variable is a different instruction sequence per
25//! model and the back end writes none of them, so a module carrying one is refused before it
26//! reaches here rather than written as an ordinary variable in the wrong section.
27
28use object::write::{
29    Object as Writer, Relocation, StandardSection, Symbol, SymbolId, SymbolSection,
30};
31use object::{
32    Architecture, BinaryFormat, Endianness, RelocationFlags, SectionKind, SymbolFlags, SymbolKind,
33    SymbolScope, elf,
34};
35use rucc_target::{ObjectFormat, TargetInfo};
36use rucc_tuple::Arch;
37
38use crate::section::{
39    Alias, Binding, Data, Object, Place, Reference, Reloc, Sections, Text, Visibility,
40};
41
42/// Why an object file could not be written.
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum Error {
45    /// A machine or a platform this does not write objects for.
46    Format {
47        /// The triple that was asked for.
48        triple: String,
49    },
50    /// The writer refused something it was given, which is a bug here rather than in a program.
51    Refused {
52        /// What it said, already formatted.
53        why: String,
54    },
55}
56
57impl std::fmt::Display for Error {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        match self {
60            Error::Format { triple } => {
61                write!(f, "there is no object writer for {triple} in this compiler yet")
62            }
63            Error::Refused { why } => {
64                write!(f, "the object writer refused what it was given: {why}")
65            }
66        }
67    }
68}
69
70impl std::error::Error for Error {}
71
72/// One text section and the variables beside it, as a relocatable ELF object.
73///
74/// # Errors
75///
76/// [`Error::Format`] for a machine or a platform this does not write, and [`Error::Refused`] for
77/// anything the writer underneath objected to, which would be a bug here. An alias whose target
78/// this file does not define is refused the same way, since the front end is what reports that as
79/// a program's mistake and one reaching here means it did not. See [`Error`].
80pub fn write(
81    text: &Text,
82    data: &Data,
83    aliases: &[Alias],
84    target: &TargetInfo,
85    sections: Sections,
86) -> Result<Vec<u8>, Error> {
87    if target.tuple.arch() != Arch::X86_64 || target.object_format != ObjectFormat::Elf {
88        return Err(Error::Format { triple: target.tuple.to_string() });
89    }
90    let mut obj = Writer::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little);
91    // The one that holds every function when they are not being split up. Asked for even when it
92    // will stay empty, because it is the section the writer underneath starts a file with anyway
93    // and gcc writes an empty `.text` under `-ffunction-sections` too.
94    let whole = obj.section_id(StandardSection::Text);
95    if !sections.functions {
96        obj.append_section_data(whole, &text.bytes, u64::from(text.align));
97    }
98
99    // Every function defined here, then every variable, then every name either of them wanted that
100    // is not. A name is looked up rather than added twice, because two symbols with one name is
101    // not a file a linker accepts.
102    let mut symbols = std::collections::BTreeMap::new();
103    // Where each function ended up, in the order they were written, so that a relocation inside
104    // one goes into the section that one is in. The same list as `text.funcs` and in the same
105    // order, so the two are walked together below.
106    let mut split = Vec::with_capacity(text.funcs.len());
107    for func in &text.funcs {
108        // A section of its own, holding this function's bytes and nothing else, so the linker can
109        // drop it when nothing reaches it. The name is what gcc writes, and the leading `.text.`
110        // is not decoration: `--gc-sections` and the linker scripts that place code both match on
111        // it, and a section called something else would be placed by the catch all rule.
112        let (section, at) = if sections.functions {
113            let name = format!(".text.{}", func.name).into_bytes();
114            let id = obj.add_section(Vec::new(), name, SectionKind::Text);
115            let bytes = &text.bytes[func.start..func.start + func.len];
116            obj.append_section_data(id, bytes, u64::from(func.align.max(1)));
117            (id, 0)
118        } else {
119            (whole, func.start as u64)
120        };
121        let id = obj.add_symbol(Symbol {
122            name: func.name.clone().into_bytes(),
123            value: at,
124            size: func.len as u64,
125            kind: SymbolKind::Text,
126            scope: scope_of(func.binding),
127            weak: func.binding == Binding::Weak,
128            section: SymbolSection::Section(section),
129            flags: SymbolFlags::None,
130        });
131        see(&mut obj, id, func.binding, func.visibility);
132        symbols.insert(func.name.clone(), id);
133        split.push(section);
134    }
135
136    // Where each variable's image landed in the section it went into, kept because a relocation in
137    // an image counts from the start of the image and one in a file counts from the start of the
138    // section. A variable that is not in a section has no entry, since nothing in a merged one can
139    // hold a relocation: the linker is being asked for zeroed space rather than for an image.
140    let mut placed = Vec::with_capacity(data.objects.len());
141    // The one section the writer has no name of its own for, remembered so that every variable that
142    // wants it lands in the same one. The rest come back from `section_id`, which already answers
143    // with the section it made the first time it was asked.
144    let mut local = None;
145    for object in &data.objects {
146        let (section, offset) = put(&mut obj, object, &mut local, sections);
147        let id = obj.add_symbol(Symbol {
148            name: object.name.clone().into_bytes(),
149            // A common symbol says what it wants rather than where it is, and what it wants is
150            // recorded where an ordinary symbol records its address.
151            value: if object.place == Place::Merged { object.align } else { offset },
152            size: object.size,
153            kind: SymbolKind::Data,
154            scope: scope_of(object.binding),
155            weak: object.binding == Binding::Weak,
156            section,
157            flags: SymbolFlags::None,
158        });
159        see(&mut obj, id, object.binding, object.visibility);
160        symbols.insert(object.name.clone(), id);
161        placed.push((section.id(), offset));
162    }
163
164    // A second name for something already added, which is where the alias's own binding is the
165    // only thing it does not take from what it points at: the target of one may be a `static` and
166    // the alias of it may not be. Before the loop below rather than after it, because a reference
167    // to the new name is a reference to something this file defines and would otherwise be added
168    // as a name this file wants from somewhere else.
169    for alias in aliases {
170        let Some(&id) = symbols.get(&alias.target) else {
171            let why =
172                format!("'{}' is aliased to '{}', which is not here", alias.name, alias.target);
173            return Err(Error::Refused { why });
174        };
175        let (value, size) = (obj.symbol(id).value, obj.symbol(id).size);
176        let (kind, section) = (obj.symbol(id).kind, obj.symbol(id).section);
177        let id = obj.add_symbol(Symbol {
178            name: alias.name.clone().into_bytes(),
179            value,
180            size,
181            kind,
182            scope: scope_of(alias.binding),
183            weak: alias.binding == Binding::Weak,
184            section,
185            flags: SymbolFlags::None,
186        });
187        see(&mut obj, id, alias.binding, alias.visibility);
188        symbols.insert(alias.name.clone(), id);
189    }
190
191    let wanted = text
192        .relocs
193        .iter()
194        .chain(text.unwind.relocs.iter())
195        .chain(data.objects.iter().flat_map(|object| &object.relocs));
196    for reloc in wanted {
197        if symbols.contains_key(&reloc.symbol) {
198            continue;
199        }
200        let id = obj.add_symbol(Symbol {
201            name: reloc.symbol.clone().into_bytes(),
202            value: 0,
203            size: 0,
204            // What kind of thing an undefined name is is not known here and does not have to be:
205            // a linker resolves an undefined symbol by its name, and the type of one that is not
206            // defined anywhere in this file is nothing this file can say.
207            kind: SymbolKind::Unknown,
208            scope: SymbolScope::Dynamic,
209            weak: false,
210            section: SymbolSection::Undefined,
211            flags: SymbolFlags::None,
212        });
213        symbols.insert(reloc.symbol.clone(), id);
214    }
215
216    for reloc in &text.relocs {
217        // Which function's bytes this one is in, which is the question only the split path has to
218        // ask: when there is one text section every offset in it is already the offset in it.
219        // Every relocation is inside some function, since the padding between two of them is
220        // instructions that do nothing and holds nothing a linker fills in.
221        let (section, at) = if sections.functions {
222            let after = text.funcs.partition_point(|func| func.start <= reloc.at);
223            let Some(func) = after.checked_sub(1).map(|i| &text.funcs[i]) else {
224                let why = format!("a relocation at {} is in front of every function", reloc.at);
225                return Err(Error::Refused { why });
226            };
227            (split[after - 1], (reloc.at - func.start) as u64)
228        } else {
229            (whole, reloc.at as u64)
230        };
231        add(&mut obj, section, at, reloc, &symbols)?;
232    }
233
234    // The unwind table, if there is one. Its own section rather than part of the text, because it
235    // is read rather than run: the loader maps it and the linker gathers every input's into one
236    // table and builds the index the unwinder binary searches. Eight, because a record is looked
237    // up by address at a point where the program is usually already crashing and an unaligned read
238    // there is a second fault on top of the first.
239    if !text.unwind.bytes.is_empty() {
240        let frames = obj.add_section(Vec::new(), b".eh_frame".to_vec(), SectionKind::ReadOnlyData);
241        obj.append_section_data(frames, &text.unwind.bytes, 8);
242        for reloc in &text.unwind.relocs {
243            add(&mut obj, frames, reloc.at as u64, reloc, &symbols)?;
244        }
245    }
246    for (object, &(section, offset)) in data.objects.iter().zip(&placed) {
247        let Some(section) = section else { continue };
248        for reloc in &object.relocs {
249            add(&mut obj, section, offset + reloc.at as u64, reloc, &symbols)?;
250        }
251    }
252
253    // Written as an empty note rather than left out, because a linker that does not find it in
254    // every input marks the stack executable.
255    obj.add_section(Vec::new(), b".note.GNU-stack".to_vec(), SectionKind::Metadata);
256
257    obj.write().map_err(|why| Error::Refused { why: why.to_string() })
258}
259
260/// One variable's image into the section it belongs in, and where in that section it landed.
261///
262/// A zero filled variable takes as many bytes of the file as it is long on the way in and none on
263/// the way out, which is the whole point of the section it goes in. A merged one goes in no section
264/// at all: the linker is being asked for that much zeroed space under that name, and where it ends
265/// up is the linker's answer rather than this file's.
266fn put(
267    obj: &mut Writer<'_>,
268    object: &Object,
269    local: &mut Option<object::write::SectionId>,
270    sections: Sections,
271) -> (SymbolSection, u64) {
272    // A section of its own, named after the variable and after the section it would have gone in,
273    // which is what `-fdata-sections` asks for. A merged variable has no section to split and a
274    // named one was named by the program, so both are left where they are: the first is a request
275    // to the linker rather than an image, and the second would otherwise have the flag silently
276    // overrule what the source said.
277    if sections.data {
278        if let Some(name) = object.place.split(&object.name) {
279            let section = obj.add_section(Vec::new(), name.into_bytes(), kind_of(&object.place));
280            let offset = if object.place == Place::Zero {
281                obj.append_section_bss(section, object.size, object.align)
282            } else {
283                obj.append_section_data(section, &object.bytes, object.align)
284            };
285            return (SymbolSection::Section(section), offset);
286        }
287    }
288    let section = match &object.place {
289        Place::Written => obj.section_id(StandardSection::Data),
290        Place::ReadOnly => obj.section_id(StandardSection::ReadOnlyData),
291        // Read only after the loader has written it, which the writer knows as the relocatable
292        // read only data section and which is `.data.rel.ro` on ELF. The `.local` half is a layout
293        // hint the writer has no name for, so it is added by hand and remembered: asking again
294        // would make a second section with the same name, and a file with one of those per variable
295        // is a file whose section headers outweigh what they describe.
296        Place::RelocReadOnly { local: false } => {
297            obj.section_id(StandardSection::ReadOnlyDataWithRel)
298        }
299        Place::RelocReadOnly { local: true } => *local.get_or_insert_with(|| {
300            obj.add_section(
301                Vec::new(),
302                b".data.rel.ro.local".to_vec(),
303                SectionKind::ReadOnlyDataWithRel,
304            )
305        }),
306        Place::Zero => obj.section_id(StandardSection::UninitializedData),
307        Place::Merged => return (SymbolSection::Common, 0),
308        // A named section is the program's word for where this goes, and a program that names one
309        // wants what it named rather than what would have been chosen. It is written as ordinary
310        // data because nothing in the IR says otherwise.
311        Place::Named(name) => {
312            obj.add_section(Vec::new(), name.clone().into_bytes(), SectionKind::Data)
313        }
314    };
315    let offset = if object.place == Place::Zero {
316        obj.append_section_bss(section, object.size, object.align)
317    } else {
318        obj.append_section_data(section, &object.bytes, object.align)
319    };
320    (SymbolSection::Section(section), offset)
321}
322
323/// What a section split off for one variable is, which is what the section it was split off from
324/// was.
325///
326/// Splitting changes the name and nothing else. A variable that was going to be in a page the
327/// loader maps read only is still in one, and a zero filled variable still costs the file nothing,
328/// so the flags a linker reads off the section header have to come out the same as they would
329/// have. The two kinds with no section of their own never reach here, and `Data` for them is a
330/// value that is never used rather than a claim about either.
331fn kind_of(place: &Place) -> SectionKind {
332    match place {
333        Place::ReadOnly => SectionKind::ReadOnlyData,
334        Place::RelocReadOnly { .. } => SectionKind::ReadOnlyDataWithRel,
335        Place::Zero => SectionKind::UninitializedData,
336        Place::Written | Place::Merged | Place::Named(_) => SectionKind::Data,
337    }
338}
339
340/// One relocation, `at` bytes into the section it ended up in.
341///
342/// The offset is worked out by the caller rather than here, because the two callers count from
343/// different places: a relocation in an image counts from the start of that image and a relocation
344/// in a function counts from the start of that function, and neither of those is where the section
345/// begins once something else is in front of it.
346fn add(
347    obj: &mut Writer<'_>,
348    section: object::write::SectionId,
349    at: u64,
350    reloc: &Reloc,
351    symbols: &std::collections::BTreeMap<String, SymbolId>,
352) -> Result<(), Error> {
353    let r_type = r_type(reloc.kind)
354        .ok_or_else(|| Error::Refused { why: format!("no relocation is {:?}", reloc.kind) })?;
355    obj.add_relocation(
356        section,
357        Relocation {
358            offset: at,
359            symbol: symbols[&reloc.symbol],
360            addend: reloc.addend,
361            flags: RelocationFlags::Elf { r_type },
362        },
363    )
364    .map_err(|why| Error::Refused { why: why.to_string() })
365}
366
367/// How far a name reaches, which is the one thing about a symbol ELF calls its binding.
368///
369/// `SymbolScope` is two facts in one word, and the trap is that the middle one is not the neutral
370/// answer it reads as. The writer turns `Compilation` into a local symbol, and it turns the choice
371/// between `Linkage` and `Dynamic` into `st_other`: `Linkage` is `STV_HIDDEN` and `Dynamic` is
372/// `STV_DEFAULT`. So there is no way to say global and decline to say anything about visibility,
373/// and picking the one whose name sounds like the smaller claim is picking hidden. That is what
374/// tamnd/rucc#733 was.
375///
376/// `Dynamic` is what every global asks for here, and the visibility is said afterwards by
377/// [`see`] rather than through this, so that nothing about `st_other` depends on reading one of
378/// these four names the way its author meant it.
379fn scope_of(binding: Binding) -> SymbolScope {
380    match binding {
381        Binding::Local => SymbolScope::Compilation,
382        Binding::Global | Binding::Weak => SymbolScope::Dynamic,
383    }
384}
385
386/// Say what `st_other` is for a symbol that has just been added, rather than leave it to be
387/// inferred from the scope.
388///
389/// The writer underneath fills `st_info` in from the kind, the binding and whether the symbol is
390/// defined, and there is nothing to add to that. `st_other` is the field this compiler has an
391/// opinion about and the field the `SymbolScope` mapping got wrong, so it is written here in the
392/// two bits ELF puts the visibility in and the rest of the byte is left as it was found.
393///
394/// A local symbol is left alone. Its visibility means nothing, since a name the static link has
395/// already finished with cannot be in a dynamic symbol table whatever `st_other` says, and gcc
396/// writes `STV_DEFAULT` for one, which is what the writer underneath produces on its own.
397fn see(obj: &mut Writer<'_>, id: SymbolId, binding: Binding, visibility: Visibility) {
398    if binding == Binding::Local {
399        return;
400    }
401    let wanted = match visibility {
402        Visibility::Default => elf::STV_DEFAULT,
403        Visibility::Hidden => elf::STV_HIDDEN,
404        Visibility::Protected => elf::STV_PROTECTED,
405    };
406    if let SymbolFlags::Elf { st_other, .. } = obj.symbol_flags_mut(id) {
407        *st_other = st_other.with_visibility(wanted);
408    }
409}
410
411/// Which relocation of this machine one reference is, and nothing for one this machine has none of.
412///
413/// The first three are the distance from the end of an instruction to something, and they differ in
414/// what the linker is allowed to do about it. A call may go through a stub, which is what lets a
415/// call reach a symbol further away than four bytes can say and what makes a call to a shared
416/// library work at all. A load may not, because there is nowhere to put a stub that a load would
417/// read, so a load of something another object may define reads a table slot the linker fills in
418/// instead, and the relaxing form of the relocation lets the linker undo that when it turns out
419/// nobody else defines it. The fourth is the address itself, at the two widths this machine writes
420/// one at.
421fn r_type(reference: Reference) -> Option<elf::RelocationType> {
422    Some(match reference {
423        Reference::Call => elf::R_X86_64_PLT32,
424        Reference::Data => elf::R_X86_64_PC32,
425        Reference::Got => elf::R_X86_64_REX_GOTPCRELX,
426        Reference::Address { bytes: 8 } => elf::R_X86_64_64,
427        Reference::Address { bytes: 4 } => elf::R_X86_64_32,
428        Reference::Address { .. } => return None,
429    })
430}
431
432#[cfg(test)]
433mod tests {
434    use super::*;
435
436    use object::read::elf::Sym as _;
437    use object::read::{Object as _, ObjectSection as _, ObjectSymbol as _};
438    use rucc_target::{Arch, Env, Os, Triple};
439
440    use crate::section::{Extent, Reloc};
441
442    /// A linux x86-64 target, which is the only one this writes.
443    fn target() -> TargetInfo {
444        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
445    }
446
447    /// One function of that name, at that offset, that many bytes long, and visible that far.
448    ///
449    /// Visibility is the field these cases mostly have no opinion about, so it is the one the
450    /// helper fills in and the two that do have an opinion write for themselves.
451    fn extent(name: String, start: usize, len: usize, binding: Binding) -> Extent {
452        Extent {
453            name,
454            start,
455            len,
456            align: crate::FUNC_ALIGN,
457            binding,
458            visibility: Visibility::Default,
459        }
460    }
461
462    /// A call to something outside the file, which is the shape every case here starts from.
463    fn calling(name: &str) -> Text {
464        Text {
465            bytes: vec![0xe8, 0, 0, 0, 0, 0xc3],
466            funcs: vec![extent("f".to_owned(), 0, 6, Binding::Global)],
467            relocs: vec![Reloc {
468                at: 1,
469                symbol: name.to_owned(),
470                kind: Reference::Call,
471                addend: -4,
472            }],
473            ..Text::default()
474        }
475    }
476
477    #[test]
478    fn the_bytes_come_back_out_of_the_section_they_went_into() {
479        let text = calling("puts");
480        let bytes =
481            write(&text, &Data::default(), &[], &target(), Sections::default()).expect("an object");
482        let file = object::File::parse(&bytes[..]).expect("a readable object");
483        let section = file.section_by_name(".text").expect("a text section");
484        assert_eq!(section.data().expect("the bytes"), &text.bytes[..]);
485    }
486
487    #[test]
488    fn a_function_is_a_symbol_that_says_where_it_is_and_how_long_it_is() {
489        let mut text = calling("puts");
490        text.funcs.push(extent("g".to_owned(), 16, 1, Binding::Global));
491        text.bytes.resize(17, 0x90);
492        let bytes =
493            write(&text, &Data::default(), &[], &target(), Sections::default()).expect("an object");
494        let file = object::File::parse(&bytes[..]).expect("a readable object");
495        let g = file.symbols().find(|s| s.name() == Ok("g")).expect("the second function");
496        assert_eq!(g.address(), 16);
497        assert_eq!(g.size(), 1);
498        assert_eq!(g.kind(), SymbolKind::Text);
499        assert!(g.is_global(), "nothing said otherwise about this one");
500    }
501
502    #[test]
503    fn a_function_no_other_file_can_see_is_a_local_symbol() {
504        let mut text = calling("puts");
505        text.funcs.push(extent("hidden".to_owned(), 16, 1, Binding::Local));
506        text.funcs.push(extent("shared".to_owned(), 32, 1, Binding::Weak));
507        text.bytes.resize(33, 0x90);
508        let bytes =
509            write(&text, &Data::default(), &[], &target(), Sections::default()).expect("an object");
510        let file = object::File::parse(&bytes[..]).expect("a readable object");
511        let hidden = file.symbols().find(|s| s.name() == Ok("hidden")).expect("the static one");
512        // A symbol the linker keeps and does not let another file reach, which is the whole of
513        // what `static` on a function means and what two files each defining their own need.
514        assert!(hidden.is_local(), "a static function must not be offered to the linker");
515        assert!(!hidden.is_weak());
516        let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the weak one");
517        assert!(shared.is_weak(), "a weak function has to be able to lose");
518        assert!(shared.is_global());
519    }
520
521    /// A global is `STV_DEFAULT`, so a shared library built from these objects exports something.
522    ///
523    /// The bug in tamnd/rucc#733. Every global came out `STV_HIDDEN`, which a static link does not
524    /// look at, so nothing here noticed and SQLite linked and ran and the whole test suite passed.
525    /// What it costs is the dynamic symbol table: `gcc -shared` over one of these objects produced
526    /// a library with an empty one, and `dlsym` could not find a function the file plainly defines.
527    ///
528    /// Written against `st_other` itself rather than against the reader's `scope`, because `scope`
529    /// is the word that was misread in the first place and a test that asks it the same question
530    /// would agree with whatever the writer did.
531    #[test]
532    fn a_global_is_visible_to_the_dynamic_linker_and_a_static_one_is_not_a_symbol_at_all() {
533        let mut text = calling("puts");
534        text.funcs.push(extent("g".to_owned(), 16, 1, Binding::Global));
535        text.funcs.push(extent("w".to_owned(), 32, 1, Binding::Weak));
536        text.funcs.push(extent("s".to_owned(), 48, 1, Binding::Local));
537        text.bytes.resize(49, 0x90);
538        let bytes =
539            write(&text, &Data::default(), &[], &target(), Sections::default()).expect("an object");
540        let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
541        let visibility = |name: &str| {
542            file.symbols()
543                .find(|s| s.name() == Ok(name))
544                .expect("the function")
545                .elf_symbol()
546                .st_visibility()
547        };
548        // Nothing said hidden about either of these, so neither is.
549        assert_eq!(visibility("g"), elf::STV_DEFAULT);
550        assert_eq!(visibility("w"), elf::STV_DEFAULT, "a weak one is still a name others may use");
551        // The `static` one is local, and a local symbol's visibility means nothing either way,
552        // which is why the binding is what this asks about.
553        assert_eq!(visibility("s"), elf::STV_DEFAULT);
554    }
555
556    /// And the other direction: a name that did ask to be hidden is hidden, and a protected one is
557    /// protected.
558    ///
559    /// The half of tamnd/rucc#733 that the fix above left open. Saying `STV_DEFAULT` for everything
560    /// is right for everything nobody marked and wrong the moment something is marked, so the two
561    /// tests together are what says the field carries an answer rather than a constant.
562    ///
563    /// Both are asked of a function and of a variable, because they are added by two different
564    /// loops in `write` and a field one of them fills in is not a field the other one does.
565    #[test]
566    fn a_name_that_asked_to_be_hidden_is_hidden_and_a_protected_one_is_protected() {
567        let mut text = calling("puts");
568        for (index, (name, seen)) in
569            [("h", Visibility::Hidden), ("p", Visibility::Protected)].into_iter().enumerate()
570        {
571            let mut func = extent(name.to_owned(), 16 + index * 16, 1, Binding::Global);
572            func.visibility = seen;
573            text.funcs.push(func);
574        }
575        text.bytes.resize(49, 0x90);
576        let mut data = Data::default();
577        for (name, seen) in [("vh", Visibility::Hidden), ("vp", Visibility::Protected)] {
578            let mut object = variable(name, Place::Written);
579            object.visibility = seen;
580            data.objects.push(object);
581        }
582        let bytes = write(&text, &data, &[], &target(), Sections::default()).expect("an object");
583        let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
584        let visibility = |name: &str| {
585            file.symbols()
586                .find(|s| s.name() == Ok(name))
587                .expect("the symbol")
588                .elf_symbol()
589                .st_visibility()
590        };
591        assert_eq!(visibility("h"), elf::STV_HIDDEN);
592        assert_eq!(visibility("p"), elf::STV_PROTECTED);
593        assert_eq!(visibility("vh"), elf::STV_HIDDEN, "a variable goes through a second loop");
594        assert_eq!(visibility("vp"), elf::STV_PROTECTED);
595        // The one thing a visibility must not disturb, since `st_info` and `st_other` are written
596        // in one go and the second was set after the first.
597        let h = file.symbols().find(|s| s.name() == Ok("h")).expect("the function");
598        assert!(h.is_global(), "hidden is about the dynamic linker and not about the binding");
599        assert_eq!(h.size(), 1, "and it is still a function of the length it was");
600    }
601
602    #[test]
603    fn a_name_this_file_does_not_define_is_left_for_the_linker_to_find() {
604        let bytes = write(&calling("puts"), &Data::default(), &[], &target(), Sections::default())
605            .expect("an object");
606        let file = object::File::parse(&bytes[..]).expect("a readable object");
607        let puts = file.symbols().find(|s| s.name() == Ok("puts")).expect("the callee");
608        assert!(puts.is_undefined(), "the file does not define it and must not claim to");
609    }
610
611    #[test]
612    fn a_call_asks_for_the_relocation_a_stub_may_answer_and_a_load_asks_for_the_one_that_may_not() {
613        for (reference, wanted) in [
614            (Reference::Call, elf::R_X86_64_PLT32),
615            (Reference::Data, elf::R_X86_64_PC32),
616            (Reference::Got, elf::R_X86_64_REX_GOTPCRELX),
617        ] {
618            let mut text = calling("puts");
619            text.relocs[0].kind = reference;
620            let bytes = write(&text, &Data::default(), &[], &target(), Sections::default())
621                .expect("an object");
622            let file = object::File::parse(&bytes[..]).expect("a readable object");
623            let section = file.section_by_name(".text").expect("a text section");
624            let (offset, reloc) = section.relocations().next().expect("one relocation");
625            assert_eq!(offset, 1);
626            assert_eq!(reloc.addend(), -4);
627            assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: wanted });
628        }
629    }
630
631    #[test]
632    fn a_name_wanted_twice_is_one_symbol_rather_than_two() {
633        let mut text = calling("puts");
634        text.relocs.push(Reloc {
635            at: 1,
636            symbol: "puts".to_owned(),
637            kind: Reference::Call,
638            addend: -4,
639        });
640        let bytes =
641            write(&text, &Data::default(), &[], &target(), Sections::default()).expect("an object");
642        let file = object::File::parse(&bytes[..]).expect("a readable object");
643        assert_eq!(file.symbols().filter(|s| s.name() == Ok("puts")).count(), 1);
644    }
645
646    #[test]
647    fn a_function_that_is_also_called_is_not_a_second_symbol() {
648        let text = calling("f");
649        let bytes =
650            write(&text, &Data::default(), &[], &target(), Sections::default()).expect("an object");
651        let file = object::File::parse(&bytes[..]).expect("a readable object");
652        let mut found = file.symbols().filter(|s| s.name() == Ok("f"));
653        let f = found.next().expect("the function");
654        assert!(!f.is_undefined(), "the file defines it");
655        assert!(found.next().is_none(), "and defines it once");
656    }
657
658    #[test]
659    fn the_marker_that_says_the_stack_is_not_executable_is_written() {
660        let bytes = write(&calling("puts"), &Data::default(), &[], &target(), Sections::default())
661            .expect("an object");
662        let file = object::File::parse(&bytes[..]).expect("a readable object");
663        let note = file.section_by_name(".note.GNU-stack").expect("the marker");
664        assert!(note.data().expect("no bytes").is_empty());
665    }
666
667    /// Every unwind record names the function it is about, and each name goes where it is in the
668    /// table rather than at the start of it.
669    ///
670    /// Written because working the offset out is the caller's job here, which is what the two text
671    /// paths differ about, and a third caller that let it default to nothing would put every record
672    /// in the table on the same function. Nothing else would notice: the section is the right
673    /// length, the symbols are right, the link succeeds, and what comes of it is an unwinder that
674    /// walks out of the wrong frame the first time something throws or a backtrace is taken.
675    #[test]
676    fn an_unwind_record_names_the_function_it_is_about_and_not_the_first_one() {
677        let mut text = calling("puts");
678        text.funcs.push(extent("g".to_owned(), 16, 1, Binding::Global));
679        text.bytes.resize(17, 0x90);
680        // A shared header and two records, whose contents nothing here reads: what is being asked
681        // is where in them each name landed.
682        text.unwind.bytes = vec![0; 64];
683        for (at, name) in [(32usize, "f"), (48usize, "g")] {
684            text.unwind.relocs.push(Reloc {
685                at,
686                symbol: name.to_owned(),
687                kind: Reference::Address { bytes: 8 },
688                addend: 0,
689            });
690        }
691        let bytes =
692            write(&text, &Data::default(), &[], &target(), Sections::default()).expect("an object");
693        let file = object::File::parse(&bytes[..]).expect("a readable object");
694        let frames = file.section_by_name(".eh_frame").expect("the table");
695        let mut at = frames.relocations().map(|(offset, _)| offset).collect::<Vec<_>>();
696        at.sort_unstable();
697        assert_eq!(at, [32, 48]);
698    }
699
700    /// The name of the section that symbol is defined in.
701    fn lives_in<'a>(file: &'a object::File<'a>, name: &str) -> String {
702        let symbol = file.symbols().find(|s| s.name() == Ok(name)).expect("the symbol");
703        let index = symbol.section_index().expect("a section to be defined in");
704        let section = file.section_by_index(index).expect("a readable section");
705        section.name().expect("a named section").to_owned()
706    }
707
708    /// Two functions, the second of them sixteen bytes in and calling something outside the file.
709    fn two() -> Text {
710        let mut text = calling("puts");
711        // Padded to where the second one is aligned to, with the instruction that does nothing,
712        // because the space in front of a function is reached by falling off the end of one.
713        text.bytes.resize(16, 0x90);
714        text.bytes.extend_from_slice(&[0xe8, 0, 0, 0, 0, 0xc3]);
715        text.funcs.push(extent("g".to_owned(), 16, 6, Binding::Global));
716        text.relocs.push(Reloc {
717            at: 17,
718            symbol: "puts".to_owned(),
719            kind: Reference::Call,
720            addend: -4,
721        });
722        text
723    }
724
725    /// What `-ffunction-sections` comes down to in an object file, which is the flag that makes
726    /// `--gc-sections` able to drop anything: a linker can leave out a section nothing reaches and
727    /// cannot leave out half of one.
728    ///
729    /// The empty `.text` stays, because it is the section the writer underneath opens a file with
730    /// and gcc 16 leaves an empty one behind under the flag too.
731    #[test]
732    fn every_function_gets_a_section_of_its_own_when_that_is_what_was_asked_for() {
733        let sections = Sections { functions: true, data: false };
734        let bytes = write(&two(), &Data::default(), &[], &target(), sections).expect("an object");
735        let file = object::File::parse(&bytes[..]).expect("a readable object");
736        assert_eq!(lives_in(&file, "f"), ".text.f");
737        assert_eq!(lives_in(&file, "g"), ".text.g");
738        assert!(file.section_by_name(".text").expect("the empty one").size() == 0);
739        // Each one at nothing into its own section, and as long as it was: a function alone in a
740        // section starts where the section does, whatever it started at when they shared one.
741        for name in ["f", "g"] {
742            let symbol = file.symbols().find(|s| s.name() == Ok(name)).expect("the function");
743            assert_eq!(symbol.address(), 0, "{name}");
744            assert_eq!(symbol.size(), 6, "{name}");
745        }
746        let section = file.section_by_name(".text.g").expect("the second function");
747        assert_eq!(section.data().expect("the bytes"), &[0xe8, 0, 0, 0, 0, 0xc3]);
748        // The padding between the two is gone with them, since it was there to align the second
749        // one inside a section they shared and each section is aligned by the linker now.
750        assert_eq!(section.align(), u64::from(crate::FUNC_ALIGN));
751    }
752
753    /// A relocation counts from the start of whichever section its function ended up in, which is
754    /// the arithmetic the split path has to do and the unsplit one never does.
755    ///
756    /// Getting it wrong is a call patched over the wrong bytes, which assembles, links, and jumps
757    /// into the middle of an instruction at run time.
758    #[test]
759    fn a_relocation_moves_with_the_function_whose_bytes_it_is_in() {
760        let sections = Sections { functions: true, data: false };
761        let bytes = write(&two(), &Data::default(), &[], &target(), sections).expect("an object");
762        let file = object::File::parse(&bytes[..]).expect("a readable object");
763        for name in [".text.f", ".text.g"] {
764            let section = file.section_by_name(name).expect("a function");
765            let (offset, _) = section.relocations().next().expect("the call in it");
766            // One byte in either way, because the call is the first instruction of both and the
767            // opcode is one byte in front of the address the linker fills in.
768            assert_eq!(offset, 1, "{name}");
769            assert_eq!(section.relocations().count(), 1, "{name}");
770        }
771    }
772
773    /// One variable of four bytes, in whichever section its own answer puts it.
774    fn variable(name: &str, place: Place) -> Object {
775        Object {
776            name: name.to_owned(),
777            bytes: if place == Place::Zero { Vec::new() } else { vec![1, 0, 0, 0] },
778            size: 4,
779            align: 4,
780            place,
781            binding: Binding::Global,
782            visibility: Visibility::Default,
783            relocs: Vec::new(),
784        }
785    }
786
787    /// A file of that one variable and nothing else.
788    fn holding(object: Object) -> Vec<u8> {
789        let data = Data { objects: vec![object] };
790        write(&Text::default(), &data, &[], &target(), Sections::default()).expect("an object")
791    }
792
793    #[test]
794    fn what_a_variable_is_decides_which_section_it_goes_in() {
795        for (place, wanted) in [
796            (Place::Written, ".data"),
797            (Place::ReadOnly, ".rodata"),
798            (Place::RelocReadOnly { local: false }, ".data.rel.ro"),
799            (Place::RelocReadOnly { local: true }, ".data.rel.ro.local"),
800            (Place::Zero, ".bss"),
801            (Place::Named(".init_array".to_owned()), ".init_array"),
802        ] {
803            let bytes = holding(variable("x", place.clone()));
804            let file = object::File::parse(&bytes[..]).expect("a readable object");
805            let section = file.section_by_name(wanted).unwrap_or_else(|| panic!("{place:?}"));
806            assert_eq!(section.size(), 4, "{place:?}");
807            // The zero filled one is as long as it says and carries none of it, which is the
808            // whole reason the section exists.
809            let carried = section.data().expect("the bytes").len();
810            assert_eq!(carried, if place == Place::Zero { 0 } else { 4 }, "{place:?}");
811        }
812    }
813
814    /// What `-fdata-sections` comes down to in an object file: the section a variable would have
815    /// shared, with its own name after it. The names are gcc 16's, checked against it on a Linux
816    /// host, and the part in front of the dot is what a linker script and `--gc-sections` match on.
817    #[test]
818    fn every_variable_gets_a_section_of_its_own_when_that_is_what_was_asked_for() {
819        let sections = Sections { functions: false, data: true };
820        for (place, wanted) in [
821            (Place::Written, ".data.x"),
822            (Place::ReadOnly, ".rodata.x"),
823            (Place::RelocReadOnly { local: false }, ".data.rel.ro.x"),
824            (Place::RelocReadOnly { local: true }, ".data.rel.ro.local.x"),
825            (Place::Zero, ".bss.x"),
826        ] {
827            let data = Data { objects: vec![variable("x", place.clone())] };
828            let bytes = write(&Text::default(), &data, &[], &target(), sections).expect("object");
829            let file = object::File::parse(&bytes[..]).expect("a readable object");
830            assert_eq!(lives_in(&file, "x"), wanted, "{place:?}");
831            let section = file.section_by_name(wanted).expect("the section it named");
832            assert_eq!(section.size(), 4, "{place:?}");
833            // Which page it lands in is what the section it came out of decided, and splitting
834            // must not quietly change it: the zero filled one still carries none of its bytes.
835            let carried = section.data().expect("the bytes").len();
836            assert_eq!(carried, if place == Place::Zero { 0 } else { 4 }, "{place:?}");
837        }
838    }
839
840    /// The two kinds of variable the flag leaves alone. A tentative definition is a request to the
841    /// linker for that much zeroed space rather than an image, so there is no section to split off,
842    /// and one the program named has the answer the source gave, which a flag must not overrule.
843    #[test]
844    fn a_variable_that_has_no_section_of_its_own_to_be_given_is_left_where_it_was() {
845        let sections = Sections { functions: false, data: true };
846        let named = Place::Named(".init_array".to_owned());
847        let objects = vec![variable("m", Place::Merged), variable("n", named)];
848        let bytes =
849            write(&Text::default(), &Data { objects }, &[], &target(), sections).expect("object");
850        let file = object::File::parse(&bytes[..]).expect("a readable object");
851        let m = file.symbols().find(|s| s.name() == Ok("m")).expect("the tentative one");
852        assert!(m.is_common(), "still the linker's to merge and not in a section at all");
853        assert_eq!(lives_in(&file, "n"), ".init_array");
854        assert!(file.section_by_name(".init_array.n").is_none(), "the source already answered");
855    }
856
857    /// A relocation in a variable's image counts from the start of the section it ended up in, the
858    /// same question the split text has to answer and a shorter answer: a variable alone in a
859    /// section starts where the section does.
860    #[test]
861    fn a_relocation_in_an_image_moves_with_the_variable_whose_image_it_is_in() {
862        let sections = Sections { functions: false, data: true };
863        let pointer = Object {
864            bytes: vec![0; 8],
865            size: 8,
866            align: 8,
867            relocs: vec![Reloc {
868                at: 0,
869                symbol: "y".to_owned(),
870                kind: Reference::Address { bytes: 8 },
871                addend: 0,
872            }],
873            ..variable("p", Place::Written)
874        };
875        let objects = vec![variable("first", Place::Written), pointer];
876        let bytes =
877            write(&Text::default(), &Data { objects }, &[], &target(), sections).expect("object");
878        let file = object::File::parse(&bytes[..]).expect("a readable object");
879        let section = file.section_by_name(".data.p").expect("the pointer's own section");
880        let (offset, reloc) = section.relocations().next().expect("one relocation");
881        // Nothing rather than the eight it would be if the variable in front of it were still
882        // counted, which is what a section of its own means.
883        assert_eq!(offset, 0);
884        assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: elf::R_X86_64_64 });
885    }
886
887    /// Two variables that want `.data.rel.ro.local` end up in one section, not two of one name.
888    ///
889    /// The writer has no name of its own for that section, so it is added by hand, and asking for
890    /// it again makes a second section rather than handing back the first. SQLite has enough const
891    /// tables of function pointers in it to turn that into eighty odd sections in one object, each
892    /// with its own relocation section beside it, which is a pile of section headers describing
893    /// eight bytes apiece.
894    #[test]
895    fn every_variable_that_wants_the_local_relocated_section_shares_one() {
896        let place = Place::RelocReadOnly { local: true };
897        let data =
898            Data { objects: vec![variable("first", place.clone()), variable("second", place)] };
899        let bytes =
900            write(&Text::default(), &data, &[], &target(), Sections::default()).expect("an object");
901        let file = object::File::parse(&bytes[..]).expect("a readable object");
902        let named = file.sections().filter(|s| s.name() == Ok(".data.rel.ro.local")).count();
903        assert_eq!(named, 1, "one section holding both, not one each");
904    }
905
906    #[test]
907    fn a_variable_is_a_symbol_that_says_where_it_is_and_how_long_it_is() {
908        let mut data = Data { objects: vec![variable("first", Place::Written)] };
909        data.objects.push(Object { align: 16, ..variable("second", Place::Written) });
910        let bytes =
911            write(&Text::default(), &data, &[], &target(), Sections::default()).expect("an object");
912        let file = object::File::parse(&bytes[..]).expect("a readable object");
913        let second = file.symbols().find(|s| s.name() == Ok("second")).expect("the second one");
914        assert_eq!(second.kind(), SymbolKind::Data);
915        assert_eq!(second.size(), 4);
916        // Sixteen rather than four, because the second one asked for sixteen and the first one
917        // had already used four. Getting this wrong is a variable at an address it said it would
918        // never be at, which nothing downstream would notice until an aligned load faulted.
919        assert_eq!(second.address(), 16);
920    }
921
922    #[test]
923    fn the_linkage_a_variable_had_is_the_binding_the_symbol_gets() {
924        for (binding, global, weak) in [
925            (Binding::Global, true, false),
926            (Binding::Local, false, false),
927            (Binding::Weak, true, true),
928        ] {
929            let bytes = holding(Object { binding, ..variable("x", Place::Written) });
930            let file = object::File::parse(&bytes[..]).expect("a readable object");
931            let x = file.symbols().find(|s| s.name() == Ok("x")).expect("the variable");
932            assert_eq!(x.is_global(), global, "{binding:?}");
933            assert_eq!(x.is_weak(), weak, "{binding:?}");
934        }
935    }
936
937    #[test]
938    fn a_tentative_definition_asks_the_linker_for_space_rather_than_naming_any() {
939        let bytes = holding(Object { align: 8, ..variable("x", Place::Merged) });
940        let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
941        let x = file.symbols().find(|s| s.name() == Ok("x")).expect("the variable");
942        assert!(x.is_common(), "the linker merges every definition of this name into one");
943        assert_eq!(x.size(), 4);
944        // What a common symbol records where an ordinary one records its address is what it wants
945        // to be aligned to, because it has no address yet. The reader deliberately answers nothing
946        // when asked for the address of one, so this is the field itself.
947        assert_eq!(x.address(), 0);
948        assert_eq!(x.elf_symbol().st_value(Endianness::Little), 8);
949    }
950
951    #[test]
952    fn an_address_in_an_image_is_the_address_and_not_a_distance_to_it() {
953        let object = Object {
954            bytes: vec![0; 8],
955            size: 8,
956            align: 8,
957            relocs: vec![Reloc {
958                at: 0,
959                symbol: "y".to_owned(),
960                kind: Reference::Address { bytes: 8 },
961                addend: 16,
962            }],
963            ..variable("p", Place::Written)
964        };
965        let bytes = holding(object);
966        let file = object::File::parse(&bytes[..]).expect("a readable object");
967        let section = file.section_by_name(".data").expect("a data section");
968        let (offset, reloc) = section.relocations().next().expect("one relocation");
969        assert_eq!(offset, 0);
970        assert_eq!(reloc.addend(), 16);
971        assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: elf::R_X86_64_64 });
972        let y = file.symbols().find(|s| s.name() == Ok("y")).expect("what it points at");
973        assert!(y.is_undefined(), "nothing here defines it and the linker is being asked for it");
974    }
975
976    /// Not a rewording of the case above: what is checked is the arithmetic between the two.
977    #[test]
978    fn a_relocation_counts_from_the_start_of_the_section_and_not_of_the_image_it_is_in() {
979        let mut data = Data { objects: vec![variable("first", Place::Written)] };
980        data.objects.push(Object {
981            bytes: vec![0; 16],
982            size: 16,
983            align: 8,
984            relocs: vec![Reloc {
985                at: 8,
986                symbol: "y".to_owned(),
987                kind: Reference::Address { bytes: 8 },
988                addend: 0,
989            }],
990            ..variable("second", Place::Written)
991        });
992        let bytes =
993            write(&Text::default(), &data, &[], &target(), Sections::default()).expect("an object");
994        let file = object::File::parse(&bytes[..]).expect("a readable object");
995        let section = file.section_by_name(".data").expect("a data section");
996        let (offset, _) = section.relocations().next().expect("one relocation");
997        // Eight into the second image, which starts eight in because the first one is four long
998        // and the second is eight aligned.
999        assert_eq!(offset, 16);
1000    }
1001
1002    #[test]
1003    fn a_second_name_is_a_second_symbol_at_the_first_one_s_address_and_no_second_image() {
1004        let data = Data {
1005            objects: vec![Object { binding: Binding::Local, ..variable("a", Place::Written) }],
1006        };
1007        let aliases = [Alias {
1008            name: "b".to_owned(),
1009            target: "a".to_owned(),
1010            binding: Binding::Global,
1011            visibility: Visibility::Default,
1012        }];
1013        let bytes = write(&Text::default(), &data, &aliases, &target(), Sections::default())
1014            .expect("an object");
1015        let file = object::File::parse(&bytes[..]).expect("a readable object");
1016        let a = file.symbols().find(|s| s.name() == Ok("a")).expect("the variable");
1017        let b = file.symbols().find(|s| s.name() == Ok("b")).expect("the second name");
1018        assert_eq!(b.address(), a.address(), "the same place");
1019        assert_eq!(b.size(), a.size());
1020        assert_eq!(b.section_index(), a.section_index());
1021        // The binding is the one thing the second name does not take from the first, which is
1022        // what `extern int b __attribute__((alias("a")))` on a `static a` asks for.
1023        assert!(a.is_local(), "the target was written `static`");
1024        assert!(b.is_global(), "and the name given to it was not");
1025        // Four bytes of image and not eight, since an alias is a name and not a copy.
1026        assert_eq!(file.section_by_name(".data").expect("a data section").size(), 4);
1027    }
1028
1029    #[test]
1030    fn a_function_can_be_given_a_second_name_the_same_way_a_variable_can() {
1031        let text = calling("puts");
1032        let aliases = [Alias {
1033            name: "g".to_owned(),
1034            target: "f".to_owned(),
1035            binding: Binding::Weak,
1036            visibility: Visibility::Default,
1037        }];
1038        let bytes = write(&text, &Data::default(), &aliases, &target(), Sections::default())
1039            .expect("an object");
1040        let file = object::File::parse(&bytes[..]).expect("a readable object");
1041        let f = file.symbols().find(|s| s.name() == Ok("f")).expect("the function");
1042        let g = file.symbols().find(|s| s.name() == Ok("g")).expect("the second name");
1043        assert_eq!(g.address(), f.address());
1044        assert_eq!(g.size(), f.size());
1045        assert_eq!(g.kind(), f.kind(), "a second name for a function is a function");
1046        assert!(g.is_weak(), "so that a program may define the name itself instead");
1047    }
1048
1049    /// The front end is what reports this as a program's mistake, so one arriving here is a bug
1050    /// in this compiler and is said so rather than written as an undefined symbol.
1051    #[test]
1052    fn a_second_name_for_something_this_file_does_not_define_is_refused() {
1053        let aliases = [Alias {
1054            name: "b".to_owned(),
1055            target: "a".to_owned(),
1056            binding: Binding::Global,
1057            visibility: Visibility::Default,
1058        }];
1059        let error =
1060            write(&Text::default(), &Data::default(), &aliases, &target(), Sections::default())
1061                .expect_err("nothing to point at");
1062        assert!(matches!(error, Error::Refused { .. }), "{error:?}");
1063    }
1064
1065    #[test]
1066    fn a_platform_this_does_not_write_is_said_so_rather_than_written_as_elf() {
1067        let text = calling("puts");
1068        for triple in [
1069            Triple::new(Arch::Aarch64, Os::Linux, Env::Gnu),
1070            Triple::new(Arch::X86_64, Os::Darwin, Env::Gnu),
1071        ] {
1072            let error =
1073                write(&text, &Data::default(), &[], &TargetInfo::new(triple), Sections::default())
1074                    .expect_err("no writer");
1075            assert!(matches!(error, Error::Format { .. }), "{error:?}");
1076        }
1077    }
1078}