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//! Sections other than the text and the two the writer makes on its own. Data, read-only data and
25//! the zero filled section arrive with the global variables that go in them.
26
27use object::write::{Object, Relocation, StandardSection, Symbol, SymbolSection};
28use object::{
29    Architecture, BinaryFormat, Endianness, RelocationFlags, SectionKind, SymbolFlags, SymbolKind,
30    SymbolScope, elf,
31};
32use rucc_target::{Arch, Os, TargetInfo};
33
34use crate::section::{Reference, Text};
35
36/// What a function is aligned to, which is what the assembler already padded to.
37const ALIGN: u64 = 16;
38
39/// Why an object file could not be written.
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum Error {
42    /// A machine or a platform this does not write objects for.
43    Format {
44        /// The triple that was asked for.
45        triple: String,
46    },
47    /// The writer refused something it was given, which is a bug here rather than in a program.
48    Refused {
49        /// What it said, already formatted.
50        why: String,
51    },
52}
53
54impl std::fmt::Display for Error {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        match self {
57            Error::Format { triple } => {
58                write!(f, "there is no object writer for {triple} in this compiler yet")
59            }
60            Error::Refused { why } => {
61                write!(f, "the object writer refused what it was given: {why}")
62            }
63        }
64    }
65}
66
67impl std::error::Error for Error {}
68
69/// One text section as a relocatable ELF object.
70///
71/// # Errors
72///
73/// [`Error::Format`] for a machine or a platform this does not write, and [`Error::Refused`] for
74/// anything the writer underneath objected to, which would be a bug here. See [`Error`].
75pub fn write(text: &Text, target: &TargetInfo) -> Result<Vec<u8>, Error> {
76    if target.triple.arch != Arch::X86_64 || target.triple.os == Os::Darwin {
77        return Err(Error::Format { triple: target.triple.to_string() });
78    }
79    let mut obj = Object::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little);
80    let section = obj.section_id(StandardSection::Text);
81    obj.append_section_data(section, &text.bytes, ALIGN);
82
83    // Every function defined here, then every name it wanted that is not. A name is looked up
84    // rather than added twice, because two symbols with one name is not a file a linker accepts.
85    let mut symbols = std::collections::BTreeMap::new();
86    for func in &text.funcs {
87        let id = obj.add_symbol(Symbol {
88            name: func.name.clone().into_bytes(),
89            value: func.start as u64,
90            size: func.len as u64,
91            kind: SymbolKind::Text,
92            // Every function is written global, because a machine function does not carry the
93            // linkage the C had and nothing below the driver could ask. It is wrong for a static
94            // function and it is the same thing the assembly path does, so the two go on agreeing
95            // and both stop being wrong on the day the machine IR has somewhere to keep linkage.
96            scope: SymbolScope::Linkage,
97            weak: false,
98            section: SymbolSection::Section(section),
99            flags: SymbolFlags::None,
100        });
101        symbols.insert(func.name.clone(), id);
102    }
103    for reloc in &text.relocs {
104        if symbols.contains_key(&reloc.symbol) {
105            continue;
106        }
107        let id = obj.add_symbol(Symbol {
108            name: reloc.symbol.clone().into_bytes(),
109            value: 0,
110            size: 0,
111            // What kind of thing an undefined name is is not known here and does not have to be:
112            // a linker resolves an undefined symbol by its name, and the type of one that is not
113            // defined anywhere in this file is nothing this file can say.
114            kind: SymbolKind::Unknown,
115            scope: SymbolScope::Dynamic,
116            weak: false,
117            section: SymbolSection::Undefined,
118            flags: SymbolFlags::None,
119        });
120        symbols.insert(reloc.symbol.clone(), id);
121    }
122
123    for reloc in &text.relocs {
124        let symbol = symbols[&reloc.symbol];
125        obj.add_relocation(
126            section,
127            Relocation {
128                offset: reloc.at as u64,
129                symbol,
130                addend: reloc.addend,
131                flags: RelocationFlags::Elf { r_type: r_type(reloc.kind) },
132            },
133        )
134        .map_err(|why| Error::Refused { why: why.to_string() })?;
135    }
136
137    // Written as an empty note rather than left out, because a linker that does not find it in
138    // every input marks the stack executable.
139    obj.add_section(Vec::new(), b".note.GNU-stack".to_vec(), SectionKind::Metadata);
140
141    obj.write().map_err(|why| Error::Refused { why: why.to_string() })
142}
143
144/// Which relocation of this machine one reference is.
145///
146/// Both are the distance from the end of an instruction to something, and they differ in what the
147/// linker is allowed to do about it. A call may go through a stub, which is what lets a call reach
148/// a symbol further away than four bytes can say and what makes a call to a shared library work at
149/// all. A load may not, because there is nowhere to put a stub that a load would read.
150fn r_type(reference: Reference) -> elf::RelocationType {
151    match reference {
152        Reference::Call => elf::R_X86_64_PLT32,
153        Reference::Data => elf::R_X86_64_PC32,
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    use object::read::{Object as _, ObjectSection as _, ObjectSymbol as _};
162    use rucc_target::{Env, Triple};
163
164    use crate::section::{Extent, Reloc};
165
166    /// A linux x86-64 target, which is the only one this writes.
167    fn target() -> TargetInfo {
168        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
169    }
170
171    /// A call to something outside the file, which is the shape every case here starts from.
172    fn calling(name: &str) -> Text {
173        Text {
174            bytes: vec![0xe8, 0, 0, 0, 0, 0xc3],
175            funcs: vec![Extent { name: "f".to_owned(), start: 0, len: 6 }],
176            relocs: vec![Reloc {
177                at: 1,
178                symbol: name.to_owned(),
179                kind: Reference::Call,
180                addend: -4,
181            }],
182        }
183    }
184
185    #[test]
186    fn the_bytes_come_back_out_of_the_section_they_went_into() {
187        let text = calling("puts");
188        let bytes = write(&text, &target()).expect("an object");
189        let file = object::File::parse(&bytes[..]).expect("a readable object");
190        let section = file.section_by_name(".text").expect("a text section");
191        assert_eq!(section.data().expect("the bytes"), &text.bytes[..]);
192    }
193
194    #[test]
195    fn a_function_is_a_symbol_that_says_where_it_is_and_how_long_it_is() {
196        let mut text = calling("puts");
197        text.funcs.push(Extent { name: "g".to_owned(), start: 16, len: 1 });
198        text.bytes.resize(17, 0x90);
199        let bytes = write(&text, &target()).expect("an object");
200        let file = object::File::parse(&bytes[..]).expect("a readable object");
201        let g = file.symbols().find(|s| s.name() == Ok("g")).expect("the second function");
202        assert_eq!(g.address(), 16);
203        assert_eq!(g.size(), 1);
204        assert_eq!(g.kind(), SymbolKind::Text);
205        assert!(g.is_global(), "a function is global until the machine IR can say otherwise");
206    }
207
208    #[test]
209    fn a_name_this_file_does_not_define_is_left_for_the_linker_to_find() {
210        let bytes = write(&calling("puts"), &target()).expect("an object");
211        let file = object::File::parse(&bytes[..]).expect("a readable object");
212        let puts = file.symbols().find(|s| s.name() == Ok("puts")).expect("the callee");
213        assert!(puts.is_undefined(), "the file does not define it and must not claim to");
214    }
215
216    #[test]
217    fn a_call_asks_for_the_relocation_a_stub_may_answer_and_a_load_asks_for_the_one_that_may_not() {
218        for (reference, wanted) in
219            [(Reference::Call, elf::R_X86_64_PLT32), (Reference::Data, elf::R_X86_64_PC32)]
220        {
221            let mut text = calling("puts");
222            text.relocs[0].kind = reference;
223            let bytes = write(&text, &target()).expect("an object");
224            let file = object::File::parse(&bytes[..]).expect("a readable object");
225            let section = file.section_by_name(".text").expect("a text section");
226            let (offset, reloc) = section.relocations().next().expect("one relocation");
227            assert_eq!(offset, 1);
228            assert_eq!(reloc.addend(), -4);
229            assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: wanted });
230        }
231    }
232
233    #[test]
234    fn a_name_wanted_twice_is_one_symbol_rather_than_two() {
235        let mut text = calling("puts");
236        text.relocs.push(Reloc {
237            at: 1,
238            symbol: "puts".to_owned(),
239            kind: Reference::Call,
240            addend: -4,
241        });
242        let bytes = write(&text, &target()).expect("an object");
243        let file = object::File::parse(&bytes[..]).expect("a readable object");
244        assert_eq!(file.symbols().filter(|s| s.name() == Ok("puts")).count(), 1);
245    }
246
247    #[test]
248    fn a_function_that_is_also_called_is_not_a_second_symbol() {
249        let text = calling("f");
250        let bytes = write(&text, &target()).expect("an object");
251        let file = object::File::parse(&bytes[..]).expect("a readable object");
252        let mut found = file.symbols().filter(|s| s.name() == Ok("f"));
253        let f = found.next().expect("the function");
254        assert!(!f.is_undefined(), "the file defines it");
255        assert!(found.next().is_none(), "and defines it once");
256    }
257
258    #[test]
259    fn the_marker_that_says_the_stack_is_not_executable_is_written() {
260        let bytes = write(&calling("puts"), &target()).expect("an object");
261        let file = object::File::parse(&bytes[..]).expect("a readable object");
262        let note = file.section_by_name(".note.GNU-stack").expect("the marker");
263        assert!(note.data().expect("no bytes").is_empty());
264    }
265
266    #[test]
267    fn a_platform_this_does_not_write_is_said_so_rather_than_written_as_elf() {
268        let text = calling("puts");
269        for triple in [
270            Triple::new(Arch::Aarch64, Os::Linux, Env::Gnu),
271            Triple::new(Arch::X86_64, Os::Darwin, Env::Gnu),
272        ] {
273            let error = write(&text, &TargetInfo::new(triple)).expect_err("no writer");
274            assert!(matches!(error, Error::Format { .. }), "{error:?}");
275        }
276    }
277}