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