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::{Object as Writer, Relocation, StandardSection, Symbol, SymbolSection};
29use object::{
30    Architecture, BinaryFormat, Endianness, RelocationFlags, SectionKind, SymbolFlags, SymbolKind,
31    SymbolScope, elf,
32};
33use rucc_target::{Arch, Os, TargetInfo};
34
35use crate::section::{Alias, Binding, Data, Object, Place, Reference, Reloc, Text};
36
37/// Why an object file could not be written.
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum Error {
40    /// A machine or a platform this does not write objects for.
41    Format {
42        /// The triple that was asked for.
43        triple: String,
44    },
45    /// The writer refused something it was given, which is a bug here rather than in a program.
46    Refused {
47        /// What it said, already formatted.
48        why: String,
49    },
50}
51
52impl std::fmt::Display for Error {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        match self {
55            Error::Format { triple } => {
56                write!(f, "there is no object writer for {triple} in this compiler yet")
57            }
58            Error::Refused { why } => {
59                write!(f, "the object writer refused what it was given: {why}")
60            }
61        }
62    }
63}
64
65impl std::error::Error for Error {}
66
67/// One text section and the variables beside it, as a relocatable ELF object.
68///
69/// # Errors
70///
71/// [`Error::Format`] for a machine or a platform this does not write, and [`Error::Refused`] for
72/// anything the writer underneath objected to, which would be a bug here. An alias whose target
73/// this file does not define is refused the same way, since the front end is what reports that as
74/// a program's mistake and one reaching here means it did not. See [`Error`].
75pub fn write(
76    text: &Text,
77    data: &Data,
78    aliases: &[Alias],
79    target: &TargetInfo,
80) -> Result<Vec<u8>, Error> {
81    if target.triple.arch != Arch::X86_64 || target.triple.os == Os::Darwin {
82        return Err(Error::Format { triple: target.triple.to_string() });
83    }
84    let mut obj = Writer::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little);
85    let section = obj.section_id(StandardSection::Text);
86    obj.append_section_data(section, &text.bytes, u64::from(text.align));
87
88    // Every function defined here, then every variable, then every name either of them wanted that
89    // is not. A name is looked up rather than added twice, because two symbols with one name is
90    // not a file a linker accepts.
91    let mut symbols = std::collections::BTreeMap::new();
92    for func in &text.funcs {
93        let id = obj.add_symbol(Symbol {
94            name: func.name.clone().into_bytes(),
95            value: func.start as u64,
96            size: func.len as u64,
97            kind: SymbolKind::Text,
98            scope: scope_of(func.binding),
99            weak: func.binding == Binding::Weak,
100            section: SymbolSection::Section(section),
101            flags: SymbolFlags::None,
102        });
103        symbols.insert(func.name.clone(), id);
104    }
105
106    // Where each variable's image landed in the section it went into, kept because a relocation in
107    // an image counts from the start of the image and one in a file counts from the start of the
108    // section. A variable that is not in a section has no entry, since nothing in a merged one can
109    // hold a relocation: the linker is being asked for zeroed space rather than for an image.
110    let mut placed = Vec::with_capacity(data.objects.len());
111    for object in &data.objects {
112        let (section, offset) = put(&mut obj, object);
113        let id = obj.add_symbol(Symbol {
114            name: object.name.clone().into_bytes(),
115            // A common symbol says what it wants rather than where it is, and what it wants is
116            // recorded where an ordinary symbol records its address.
117            value: if object.place == Place::Merged { object.align } else { offset },
118            size: object.size,
119            kind: SymbolKind::Data,
120            scope: scope_of(object.binding),
121            weak: object.binding == Binding::Weak,
122            section,
123            flags: SymbolFlags::None,
124        });
125        symbols.insert(object.name.clone(), id);
126        placed.push((section.id(), offset));
127    }
128
129    // A second name for something already added, which is where the alias's own binding is the
130    // only thing it does not take from what it points at: the target of one may be a `static` and
131    // the alias of it may not be. Before the loop below rather than after it, because a reference
132    // to the new name is a reference to something this file defines and would otherwise be added
133    // as a name this file wants from somewhere else.
134    for alias in aliases {
135        let Some(&id) = symbols.get(&alias.target) else {
136            let why =
137                format!("'{}' is aliased to '{}', which is not here", alias.name, alias.target);
138            return Err(Error::Refused { why });
139        };
140        let (value, size) = (obj.symbol(id).value, obj.symbol(id).size);
141        let (kind, section) = (obj.symbol(id).kind, obj.symbol(id).section);
142        let id = obj.add_symbol(Symbol {
143            name: alias.name.clone().into_bytes(),
144            value,
145            size,
146            kind,
147            scope: scope_of(alias.binding),
148            weak: alias.binding == Binding::Weak,
149            section,
150            flags: SymbolFlags::None,
151        });
152        symbols.insert(alias.name.clone(), id);
153    }
154
155    let wanted = text.relocs.iter().chain(data.objects.iter().flat_map(|object| &object.relocs));
156    for reloc in wanted {
157        if symbols.contains_key(&reloc.symbol) {
158            continue;
159        }
160        let id = obj.add_symbol(Symbol {
161            name: reloc.symbol.clone().into_bytes(),
162            value: 0,
163            size: 0,
164            // What kind of thing an undefined name is is not known here and does not have to be:
165            // a linker resolves an undefined symbol by its name, and the type of one that is not
166            // defined anywhere in this file is nothing this file can say.
167            kind: SymbolKind::Unknown,
168            scope: SymbolScope::Dynamic,
169            weak: false,
170            section: SymbolSection::Undefined,
171            flags: SymbolFlags::None,
172        });
173        symbols.insert(reloc.symbol.clone(), id);
174    }
175
176    for reloc in &text.relocs {
177        add(&mut obj, section, 0, reloc, &symbols)?;
178    }
179    for (object, &(section, offset)) in data.objects.iter().zip(&placed) {
180        let Some(section) = section else { continue };
181        for reloc in &object.relocs {
182            add(&mut obj, section, offset, reloc, &symbols)?;
183        }
184    }
185
186    // Written as an empty note rather than left out, because a linker that does not find it in
187    // every input marks the stack executable.
188    obj.add_section(Vec::new(), b".note.GNU-stack".to_vec(), SectionKind::Metadata);
189
190    obj.write().map_err(|why| Error::Refused { why: why.to_string() })
191}
192
193/// One variable's image into the section it belongs in, and where in that section it landed.
194///
195/// A zero filled variable takes as many bytes of the file as it is long on the way in and none on
196/// the way out, which is the whole point of the section it goes in. A merged one goes in no section
197/// at all: the linker is being asked for that much zeroed space under that name, and where it ends
198/// up is the linker's answer rather than this file's.
199fn put(obj: &mut Writer<'_>, object: &Object) -> (SymbolSection, u64) {
200    let section = match &object.place {
201        Place::Written => obj.section_id(StandardSection::Data),
202        Place::ReadOnly => obj.section_id(StandardSection::ReadOnlyData),
203        Place::Zero => obj.section_id(StandardSection::UninitializedData),
204        Place::Merged => return (SymbolSection::Common, 0),
205        // A named section is the program's word for where this goes, and a program that names one
206        // wants what it named rather than what would have been chosen. It is written as ordinary
207        // data because nothing in the IR says otherwise.
208        Place::Named(name) => {
209            obj.add_section(Vec::new(), name.clone().into_bytes(), SectionKind::Data)
210        }
211    };
212    let offset = if object.place == Place::Zero {
213        obj.append_section_bss(section, object.size, object.align)
214    } else {
215        obj.append_section_data(section, &object.bytes, object.align)
216    };
217    (SymbolSection::Section(section), offset)
218}
219
220/// One relocation, at `offset` bytes into the section its image landed at.
221fn add(
222    obj: &mut Writer<'_>,
223    section: object::write::SectionId,
224    offset: u64,
225    reloc: &Reloc,
226    symbols: &std::collections::BTreeMap<String, object::write::SymbolId>,
227) -> Result<(), Error> {
228    let r_type = r_type(reloc.kind)
229        .ok_or_else(|| Error::Refused { why: format!("no relocation is {:?}", reloc.kind) })?;
230    obj.add_relocation(
231        section,
232        Relocation {
233            offset: offset + reloc.at as u64,
234            symbol: symbols[&reloc.symbol],
235            addend: reloc.addend,
236            flags: RelocationFlags::Elf { r_type },
237        },
238    )
239    .map_err(|why| Error::Refused { why: why.to_string() })
240}
241
242/// How far a name reaches, which is the one thing about a symbol ELF calls its binding.
243fn scope_of(binding: Binding) -> SymbolScope {
244    match binding {
245        Binding::Local => SymbolScope::Compilation,
246        // Linkage rather than Dynamic, because whether a name goes in the dynamic symbol table is
247        // its visibility and the IR keeps that separately. Nothing sets it to anything but the
248        // default yet, and when something does it belongs here rather than folded into this.
249        Binding::Global | Binding::Weak => SymbolScope::Linkage,
250    }
251}
252
253/// Which relocation of this machine one reference is, and nothing for one this machine has none of.
254///
255/// The first three are the distance from the end of an instruction to something, and they differ in
256/// what the linker is allowed to do about it. A call may go through a stub, which is what lets a
257/// call reach a symbol further away than four bytes can say and what makes a call to a shared
258/// library work at all. A load may not, because there is nowhere to put a stub that a load would
259/// read, so a load of something another object may define reads a table slot the linker fills in
260/// instead, and the relaxing form of the relocation lets the linker undo that when it turns out
261/// nobody else defines it. The fourth is the address itself, at the two widths this machine writes
262/// one at.
263fn r_type(reference: Reference) -> Option<elf::RelocationType> {
264    Some(match reference {
265        Reference::Call => elf::R_X86_64_PLT32,
266        Reference::Data => elf::R_X86_64_PC32,
267        Reference::Got => elf::R_X86_64_REX_GOTPCRELX,
268        Reference::Address { bytes: 8 } => elf::R_X86_64_64,
269        Reference::Address { bytes: 4 } => elf::R_X86_64_32,
270        Reference::Address { .. } => return None,
271    })
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277
278    use object::read::elf::Sym as _;
279    use object::read::{Object as _, ObjectSection as _, ObjectSymbol as _};
280    use rucc_target::{Env, Triple};
281
282    use crate::section::{Extent, Reloc};
283
284    /// A linux x86-64 target, which is the only one this writes.
285    fn target() -> TargetInfo {
286        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
287    }
288
289    /// A call to something outside the file, which is the shape every case here starts from.
290    fn calling(name: &str) -> Text {
291        Text {
292            bytes: vec![0xe8, 0, 0, 0, 0, 0xc3],
293            funcs: vec![Extent {
294                name: "f".to_owned(),
295                start: 0,
296                len: 6,
297                binding: Binding::Global,
298            }],
299            relocs: vec![Reloc {
300                at: 1,
301                symbol: name.to_owned(),
302                kind: Reference::Call,
303                addend: -4,
304            }],
305            ..Text::default()
306        }
307    }
308
309    #[test]
310    fn the_bytes_come_back_out_of_the_section_they_went_into() {
311        let text = calling("puts");
312        let bytes = write(&text, &Data::default(), &[], &target()).expect("an object");
313        let file = object::File::parse(&bytes[..]).expect("a readable object");
314        let section = file.section_by_name(".text").expect("a text section");
315        assert_eq!(section.data().expect("the bytes"), &text.bytes[..]);
316    }
317
318    #[test]
319    fn a_function_is_a_symbol_that_says_where_it_is_and_how_long_it_is() {
320        let mut text = calling("puts");
321        text.funcs.push(Extent {
322            name: "g".to_owned(),
323            start: 16,
324            len: 1,
325            binding: Binding::Global,
326        });
327        text.bytes.resize(17, 0x90);
328        let bytes = write(&text, &Data::default(), &[], &target()).expect("an object");
329        let file = object::File::parse(&bytes[..]).expect("a readable object");
330        let g = file.symbols().find(|s| s.name() == Ok("g")).expect("the second function");
331        assert_eq!(g.address(), 16);
332        assert_eq!(g.size(), 1);
333        assert_eq!(g.kind(), SymbolKind::Text);
334        assert!(g.is_global(), "nothing said otherwise about this one");
335    }
336
337    #[test]
338    fn a_function_no_other_file_can_see_is_a_local_symbol() {
339        let mut text = calling("puts");
340        text.funcs.push(Extent {
341            name: "hidden".to_owned(),
342            start: 16,
343            len: 1,
344            binding: Binding::Local,
345        });
346        text.funcs.push(Extent {
347            name: "shared".to_owned(),
348            start: 32,
349            len: 1,
350            binding: Binding::Weak,
351        });
352        text.bytes.resize(33, 0x90);
353        let bytes = write(&text, &Data::default(), &[], &target()).expect("an object");
354        let file = object::File::parse(&bytes[..]).expect("a readable object");
355        let hidden = file.symbols().find(|s| s.name() == Ok("hidden")).expect("the static one");
356        // A symbol the linker keeps and does not let another file reach, which is the whole of
357        // what `static` on a function means and what two files each defining their own need.
358        assert!(hidden.is_local(), "a static function must not be offered to the linker");
359        assert!(!hidden.is_weak());
360        let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the weak one");
361        assert!(shared.is_weak(), "a weak function has to be able to lose");
362        assert!(shared.is_global());
363    }
364
365    #[test]
366    fn a_name_this_file_does_not_define_is_left_for_the_linker_to_find() {
367        let bytes = write(&calling("puts"), &Data::default(), &[], &target()).expect("an object");
368        let file = object::File::parse(&bytes[..]).expect("a readable object");
369        let puts = file.symbols().find(|s| s.name() == Ok("puts")).expect("the callee");
370        assert!(puts.is_undefined(), "the file does not define it and must not claim to");
371    }
372
373    #[test]
374    fn a_call_asks_for_the_relocation_a_stub_may_answer_and_a_load_asks_for_the_one_that_may_not() {
375        for (reference, wanted) in [
376            (Reference::Call, elf::R_X86_64_PLT32),
377            (Reference::Data, elf::R_X86_64_PC32),
378            (Reference::Got, elf::R_X86_64_REX_GOTPCRELX),
379        ] {
380            let mut text = calling("puts");
381            text.relocs[0].kind = reference;
382            let bytes = write(&text, &Data::default(), &[], &target()).expect("an object");
383            let file = object::File::parse(&bytes[..]).expect("a readable object");
384            let section = file.section_by_name(".text").expect("a text section");
385            let (offset, reloc) = section.relocations().next().expect("one relocation");
386            assert_eq!(offset, 1);
387            assert_eq!(reloc.addend(), -4);
388            assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: wanted });
389        }
390    }
391
392    #[test]
393    fn a_name_wanted_twice_is_one_symbol_rather_than_two() {
394        let mut text = calling("puts");
395        text.relocs.push(Reloc {
396            at: 1,
397            symbol: "puts".to_owned(),
398            kind: Reference::Call,
399            addend: -4,
400        });
401        let bytes = write(&text, &Data::default(), &[], &target()).expect("an object");
402        let file = object::File::parse(&bytes[..]).expect("a readable object");
403        assert_eq!(file.symbols().filter(|s| s.name() == Ok("puts")).count(), 1);
404    }
405
406    #[test]
407    fn a_function_that_is_also_called_is_not_a_second_symbol() {
408        let text = calling("f");
409        let bytes = write(&text, &Data::default(), &[], &target()).expect("an object");
410        let file = object::File::parse(&bytes[..]).expect("a readable object");
411        let mut found = file.symbols().filter(|s| s.name() == Ok("f"));
412        let f = found.next().expect("the function");
413        assert!(!f.is_undefined(), "the file defines it");
414        assert!(found.next().is_none(), "and defines it once");
415    }
416
417    #[test]
418    fn the_marker_that_says_the_stack_is_not_executable_is_written() {
419        let bytes = write(&calling("puts"), &Data::default(), &[], &target()).expect("an object");
420        let file = object::File::parse(&bytes[..]).expect("a readable object");
421        let note = file.section_by_name(".note.GNU-stack").expect("the marker");
422        assert!(note.data().expect("no bytes").is_empty());
423    }
424
425    /// One variable of four bytes, in whichever section its own answer puts it.
426    fn variable(name: &str, place: Place) -> Object {
427        Object {
428            name: name.to_owned(),
429            bytes: if place == Place::Zero { Vec::new() } else { vec![1, 0, 0, 0] },
430            size: 4,
431            align: 4,
432            place,
433            binding: Binding::Global,
434            relocs: Vec::new(),
435        }
436    }
437
438    /// A file of that one variable and nothing else.
439    fn holding(object: Object) -> Vec<u8> {
440        let data = Data { objects: vec![object] };
441        write(&Text::default(), &data, &[], &target()).expect("an object")
442    }
443
444    #[test]
445    fn what_a_variable_is_decides_which_section_it_goes_in() {
446        for (place, wanted) in [
447            (Place::Written, ".data"),
448            (Place::ReadOnly, ".rodata"),
449            (Place::Zero, ".bss"),
450            (Place::Named(".init_array".to_owned()), ".init_array"),
451        ] {
452            let bytes = holding(variable("x", place.clone()));
453            let file = object::File::parse(&bytes[..]).expect("a readable object");
454            let section = file.section_by_name(wanted).unwrap_or_else(|| panic!("{place:?}"));
455            assert_eq!(section.size(), 4, "{place:?}");
456            // The zero filled one is as long as it says and carries none of it, which is the
457            // whole reason the section exists.
458            let carried = section.data().expect("the bytes").len();
459            assert_eq!(carried, if place == Place::Zero { 0 } else { 4 }, "{place:?}");
460        }
461    }
462
463    #[test]
464    fn a_variable_is_a_symbol_that_says_where_it_is_and_how_long_it_is() {
465        let mut data = Data { objects: vec![variable("first", Place::Written)] };
466        data.objects.push(Object { align: 16, ..variable("second", Place::Written) });
467        let bytes = write(&Text::default(), &data, &[], &target()).expect("an object");
468        let file = object::File::parse(&bytes[..]).expect("a readable object");
469        let second = file.symbols().find(|s| s.name() == Ok("second")).expect("the second one");
470        assert_eq!(second.kind(), SymbolKind::Data);
471        assert_eq!(second.size(), 4);
472        // Sixteen rather than four, because the second one asked for sixteen and the first one
473        // had already used four. Getting this wrong is a variable at an address it said it would
474        // never be at, which nothing downstream would notice until an aligned load faulted.
475        assert_eq!(second.address(), 16);
476    }
477
478    #[test]
479    fn the_linkage_a_variable_had_is_the_binding_the_symbol_gets() {
480        for (binding, global, weak) in [
481            (Binding::Global, true, false),
482            (Binding::Local, false, false),
483            (Binding::Weak, true, true),
484        ] {
485            let bytes = holding(Object { binding, ..variable("x", Place::Written) });
486            let file = object::File::parse(&bytes[..]).expect("a readable object");
487            let x = file.symbols().find(|s| s.name() == Ok("x")).expect("the variable");
488            assert_eq!(x.is_global(), global, "{binding:?}");
489            assert_eq!(x.is_weak(), weak, "{binding:?}");
490        }
491    }
492
493    #[test]
494    fn a_tentative_definition_asks_the_linker_for_space_rather_than_naming_any() {
495        let bytes = holding(Object { align: 8, ..variable("x", Place::Merged) });
496        let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
497        let x = file.symbols().find(|s| s.name() == Ok("x")).expect("the variable");
498        assert!(x.is_common(), "the linker merges every definition of this name into one");
499        assert_eq!(x.size(), 4);
500        // What a common symbol records where an ordinary one records its address is what it wants
501        // to be aligned to, because it has no address yet. The reader deliberately answers nothing
502        // when asked for the address of one, so this is the field itself.
503        assert_eq!(x.address(), 0);
504        assert_eq!(x.elf_symbol().st_value(Endianness::Little), 8);
505    }
506
507    #[test]
508    fn an_address_in_an_image_is_the_address_and_not_a_distance_to_it() {
509        let object = Object {
510            bytes: vec![0; 8],
511            size: 8,
512            align: 8,
513            relocs: vec![Reloc {
514                at: 0,
515                symbol: "y".to_owned(),
516                kind: Reference::Address { bytes: 8 },
517                addend: 16,
518            }],
519            ..variable("p", Place::Written)
520        };
521        let bytes = holding(object);
522        let file = object::File::parse(&bytes[..]).expect("a readable object");
523        let section = file.section_by_name(".data").expect("a data section");
524        let (offset, reloc) = section.relocations().next().expect("one relocation");
525        assert_eq!(offset, 0);
526        assert_eq!(reloc.addend(), 16);
527        assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: elf::R_X86_64_64 });
528        let y = file.symbols().find(|s| s.name() == Ok("y")).expect("what it points at");
529        assert!(y.is_undefined(), "nothing here defines it and the linker is being asked for it");
530    }
531
532    /// Not a rewording of the case above: what is checked is the arithmetic between the two.
533    #[test]
534    fn a_relocation_counts_from_the_start_of_the_section_and_not_of_the_image_it_is_in() {
535        let mut data = Data { objects: vec![variable("first", Place::Written)] };
536        data.objects.push(Object {
537            bytes: vec![0; 16],
538            size: 16,
539            align: 8,
540            relocs: vec![Reloc {
541                at: 8,
542                symbol: "y".to_owned(),
543                kind: Reference::Address { bytes: 8 },
544                addend: 0,
545            }],
546            ..variable("second", Place::Written)
547        });
548        let bytes = write(&Text::default(), &data, &[], &target()).expect("an object");
549        let file = object::File::parse(&bytes[..]).expect("a readable object");
550        let section = file.section_by_name(".data").expect("a data section");
551        let (offset, _) = section.relocations().next().expect("one relocation");
552        // Eight into the second image, which starts eight in because the first one is four long
553        // and the second is eight aligned.
554        assert_eq!(offset, 16);
555    }
556
557    #[test]
558    fn a_second_name_is_a_second_symbol_at_the_first_one_s_address_and_no_second_image() {
559        let data = Data {
560            objects: vec![Object { binding: Binding::Local, ..variable("a", Place::Written) }],
561        };
562        let aliases =
563            [Alias { name: "b".to_owned(), target: "a".to_owned(), binding: Binding::Global }];
564        let bytes = write(&Text::default(), &data, &aliases, &target()).expect("an object");
565        let file = object::File::parse(&bytes[..]).expect("a readable object");
566        let a = file.symbols().find(|s| s.name() == Ok("a")).expect("the variable");
567        let b = file.symbols().find(|s| s.name() == Ok("b")).expect("the second name");
568        assert_eq!(b.address(), a.address(), "the same place");
569        assert_eq!(b.size(), a.size());
570        assert_eq!(b.section_index(), a.section_index());
571        // The binding is the one thing the second name does not take from the first, which is
572        // what `extern int b __attribute__((alias("a")))` on a `static a` asks for.
573        assert!(a.is_local(), "the target was written `static`");
574        assert!(b.is_global(), "and the name given to it was not");
575        // Four bytes of image and not eight, since an alias is a name and not a copy.
576        assert_eq!(file.section_by_name(".data").expect("a data section").size(), 4);
577    }
578
579    #[test]
580    fn a_function_can_be_given_a_second_name_the_same_way_a_variable_can() {
581        let text = calling("puts");
582        let aliases =
583            [Alias { name: "g".to_owned(), target: "f".to_owned(), binding: Binding::Weak }];
584        let bytes = write(&text, &Data::default(), &aliases, &target()).expect("an object");
585        let file = object::File::parse(&bytes[..]).expect("a readable object");
586        let f = file.symbols().find(|s| s.name() == Ok("f")).expect("the function");
587        let g = file.symbols().find(|s| s.name() == Ok("g")).expect("the second name");
588        assert_eq!(g.address(), f.address());
589        assert_eq!(g.size(), f.size());
590        assert_eq!(g.kind(), f.kind(), "a second name for a function is a function");
591        assert!(g.is_weak(), "so that a program may define the name itself instead");
592    }
593
594    /// The front end is what reports this as a program's mistake, so one arriving here is a bug
595    /// in this compiler and is said so rather than written as an undefined symbol.
596    #[test]
597    fn a_second_name_for_something_this_file_does_not_define_is_refused() {
598        let aliases =
599            [Alias { name: "b".to_owned(), target: "a".to_owned(), binding: Binding::Global }];
600        let error = write(&Text::default(), &Data::default(), &aliases, &target())
601            .expect_err("nothing to point at");
602        assert!(matches!(error, Error::Refused { .. }), "{error:?}");
603    }
604
605    #[test]
606    fn a_platform_this_does_not_write_is_said_so_rather_than_written_as_elf() {
607        let text = calling("puts");
608        for triple in [
609            Triple::new(Arch::Aarch64, Os::Linux, Env::Gnu),
610            Triple::new(Arch::X86_64, Os::Darwin, Env::Gnu),
611        ] {
612            let error = write(&text, &Data::default(), &[], &TargetInfo::new(triple))
613                .expect_err("no writer");
614            assert!(matches!(error, Error::Format { .. }), "{error:?}");
615        }
616    }
617}