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::{Alias, Binding, Data, Object, Place, Reference, Reloc, Text, Visibility};
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. An alias whose target
76/// this file does not define is refused the same way, since the front end is what reports that as
77/// a program's mistake and one reaching here means it did not. See [`Error`].
78pub fn write(
79    text: &Text,
80    data: &Data,
81    aliases: &[Alias],
82    target: &TargetInfo,
83) -> Result<Vec<u8>, Error> {
84    if target.tuple.arch() != Arch::X86_64 || target.object_format != ObjectFormat::Elf {
85        return Err(Error::Format { triple: target.tuple.to_string() });
86    }
87    let mut obj = Writer::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little);
88    let section = obj.section_id(StandardSection::Text);
89    obj.append_section_data(section, &text.bytes, u64::from(text.align));
90
91    // Every function defined here, then every variable, then every name either of them wanted that
92    // is not. A name is looked up rather than added twice, because two symbols with one name is
93    // not a file a linker accepts.
94    let mut symbols = std::collections::BTreeMap::new();
95    for func in &text.funcs {
96        let id = obj.add_symbol(Symbol {
97            name: func.name.clone().into_bytes(),
98            value: func.start as u64,
99            size: func.len as u64,
100            kind: SymbolKind::Text,
101            scope: scope_of(func.binding),
102            weak: func.binding == Binding::Weak,
103            section: SymbolSection::Section(section),
104            flags: SymbolFlags::None,
105        });
106        see(&mut obj, id, func.binding, func.visibility);
107        symbols.insert(func.name.clone(), id);
108    }
109
110    // Where each variable's image landed in the section it went into, kept because a relocation in
111    // an image counts from the start of the image and one in a file counts from the start of the
112    // section. A variable that is not in a section has no entry, since nothing in a merged one can
113    // hold a relocation: the linker is being asked for zeroed space rather than for an image.
114    let mut placed = Vec::with_capacity(data.objects.len());
115    // The one section the writer has no name of its own for, remembered so that every variable that
116    // wants it lands in the same one. The rest come back from `section_id`, which already answers
117    // with the section it made the first time it was asked.
118    let mut local = None;
119    for object in &data.objects {
120        let (section, offset) = put(&mut obj, object, &mut local);
121        let id = obj.add_symbol(Symbol {
122            name: object.name.clone().into_bytes(),
123            // A common symbol says what it wants rather than where it is, and what it wants is
124            // recorded where an ordinary symbol records its address.
125            value: if object.place == Place::Merged { object.align } else { offset },
126            size: object.size,
127            kind: SymbolKind::Data,
128            scope: scope_of(object.binding),
129            weak: object.binding == Binding::Weak,
130            section,
131            flags: SymbolFlags::None,
132        });
133        see(&mut obj, id, object.binding, object.visibility);
134        symbols.insert(object.name.clone(), id);
135        placed.push((section.id(), offset));
136    }
137
138    // A second name for something already added, which is where the alias's own binding is the
139    // only thing it does not take from what it points at: the target of one may be a `static` and
140    // the alias of it may not be. Before the loop below rather than after it, because a reference
141    // to the new name is a reference to something this file defines and would otherwise be added
142    // as a name this file wants from somewhere else.
143    for alias in aliases {
144        let Some(&id) = symbols.get(&alias.target) else {
145            let why =
146                format!("'{}' is aliased to '{}', which is not here", alias.name, alias.target);
147            return Err(Error::Refused { why });
148        };
149        let (value, size) = (obj.symbol(id).value, obj.symbol(id).size);
150        let (kind, section) = (obj.symbol(id).kind, obj.symbol(id).section);
151        let id = obj.add_symbol(Symbol {
152            name: alias.name.clone().into_bytes(),
153            value,
154            size,
155            kind,
156            scope: scope_of(alias.binding),
157            weak: alias.binding == Binding::Weak,
158            section,
159            flags: SymbolFlags::None,
160        });
161        see(&mut obj, id, alias.binding, alias.visibility);
162        symbols.insert(alias.name.clone(), id);
163    }
164
165    let wanted = text
166        .relocs
167        .iter()
168        .chain(text.unwind.relocs.iter())
169        .chain(data.objects.iter().flat_map(|object| &object.relocs));
170    for reloc in wanted {
171        if symbols.contains_key(&reloc.symbol) {
172            continue;
173        }
174        let id = obj.add_symbol(Symbol {
175            name: reloc.symbol.clone().into_bytes(),
176            value: 0,
177            size: 0,
178            // What kind of thing an undefined name is is not known here and does not have to be:
179            // a linker resolves an undefined symbol by its name, and the type of one that is not
180            // defined anywhere in this file is nothing this file can say.
181            kind: SymbolKind::Unknown,
182            scope: SymbolScope::Dynamic,
183            weak: false,
184            section: SymbolSection::Undefined,
185            flags: SymbolFlags::None,
186        });
187        symbols.insert(reloc.symbol.clone(), id);
188    }
189
190    for reloc in &text.relocs {
191        add(&mut obj, section, 0, reloc, &symbols)?;
192    }
193
194    // The unwind table, if there is one. Its own section rather than part of the text, because it
195    // is read rather than run: the loader maps it and the linker gathers every input's into one
196    // table and builds the index the unwinder binary searches. Eight, because a record is looked
197    // up by address at a point where the program is usually already crashing and an unaligned read
198    // there is a second fault on top of the first.
199    if !text.unwind.bytes.is_empty() {
200        let frames = obj.add_section(Vec::new(), b".eh_frame".to_vec(), SectionKind::ReadOnlyData);
201        obj.append_section_data(frames, &text.unwind.bytes, 8);
202        for reloc in &text.unwind.relocs {
203            add(&mut obj, frames, 0, reloc, &symbols)?;
204        }
205    }
206    for (object, &(section, offset)) in data.objects.iter().zip(&placed) {
207        let Some(section) = section else { continue };
208        for reloc in &object.relocs {
209            add(&mut obj, section, offset, reloc, &symbols)?;
210        }
211    }
212
213    // Written as an empty note rather than left out, because a linker that does not find it in
214    // every input marks the stack executable.
215    obj.add_section(Vec::new(), b".note.GNU-stack".to_vec(), SectionKind::Metadata);
216
217    obj.write().map_err(|why| Error::Refused { why: why.to_string() })
218}
219
220/// One variable's image into the section it belongs in, and where in that section it landed.
221///
222/// A zero filled variable takes as many bytes of the file as it is long on the way in and none on
223/// the way out, which is the whole point of the section it goes in. A merged one goes in no section
224/// at all: the linker is being asked for that much zeroed space under that name, and where it ends
225/// up is the linker's answer rather than this file's.
226fn put(
227    obj: &mut Writer<'_>,
228    object: &Object,
229    local: &mut Option<object::write::SectionId>,
230) -> (SymbolSection, u64) {
231    let section = match &object.place {
232        Place::Written => obj.section_id(StandardSection::Data),
233        Place::ReadOnly => obj.section_id(StandardSection::ReadOnlyData),
234        // Read only after the loader has written it, which the writer knows as the relocatable
235        // read only data section and which is `.data.rel.ro` on ELF. The `.local` half is a layout
236        // hint the writer has no name for, so it is added by hand and remembered: asking again
237        // would make a second section with the same name, and a file with one of those per variable
238        // is a file whose section headers outweigh what they describe.
239        Place::RelocReadOnly { local: false } => {
240            obj.section_id(StandardSection::ReadOnlyDataWithRel)
241        }
242        Place::RelocReadOnly { local: true } => *local.get_or_insert_with(|| {
243            obj.add_section(
244                Vec::new(),
245                b".data.rel.ro.local".to_vec(),
246                SectionKind::ReadOnlyDataWithRel,
247            )
248        }),
249        Place::Zero => obj.section_id(StandardSection::UninitializedData),
250        Place::Merged => return (SymbolSection::Common, 0),
251        // A named section is the program's word for where this goes, and a program that names one
252        // wants what it named rather than what would have been chosen. It is written as ordinary
253        // data because nothing in the IR says otherwise.
254        Place::Named(name) => {
255            obj.add_section(Vec::new(), name.clone().into_bytes(), SectionKind::Data)
256        }
257    };
258    let offset = if object.place == Place::Zero {
259        obj.append_section_bss(section, object.size, object.align)
260    } else {
261        obj.append_section_data(section, &object.bytes, object.align)
262    };
263    (SymbolSection::Section(section), offset)
264}
265
266/// One relocation, at `offset` bytes into the section its image landed at.
267fn add(
268    obj: &mut Writer<'_>,
269    section: object::write::SectionId,
270    offset: u64,
271    reloc: &Reloc,
272    symbols: &std::collections::BTreeMap<String, SymbolId>,
273) -> Result<(), Error> {
274    let r_type = r_type(reloc.kind)
275        .ok_or_else(|| Error::Refused { why: format!("no relocation is {:?}", reloc.kind) })?;
276    obj.add_relocation(
277        section,
278        Relocation {
279            offset: offset + reloc.at as u64,
280            symbol: symbols[&reloc.symbol],
281            addend: reloc.addend,
282            flags: RelocationFlags::Elf { r_type },
283        },
284    )
285    .map_err(|why| Error::Refused { why: why.to_string() })
286}
287
288/// How far a name reaches, which is the one thing about a symbol ELF calls its binding.
289///
290/// `SymbolScope` is two facts in one word, and the trap is that the middle one is not the neutral
291/// answer it reads as. The writer turns `Compilation` into a local symbol, and it turns the choice
292/// between `Linkage` and `Dynamic` into `st_other`: `Linkage` is `STV_HIDDEN` and `Dynamic` is
293/// `STV_DEFAULT`. So there is no way to say global and decline to say anything about visibility,
294/// and picking the one whose name sounds like the smaller claim is picking hidden. That is what
295/// tamnd/rucc#733 was.
296///
297/// `Dynamic` is what every global asks for here, and the visibility is said afterwards by
298/// [`see`] rather than through this, so that nothing about `st_other` depends on reading one of
299/// these four names the way its author meant it.
300fn scope_of(binding: Binding) -> SymbolScope {
301    match binding {
302        Binding::Local => SymbolScope::Compilation,
303        Binding::Global | Binding::Weak => SymbolScope::Dynamic,
304    }
305}
306
307/// Say what `st_other` is for a symbol that has just been added, rather than leave it to be
308/// inferred from the scope.
309///
310/// The writer underneath fills `st_info` in from the kind, the binding and whether the symbol is
311/// defined, and there is nothing to add to that. `st_other` is the field this compiler has an
312/// opinion about and the field the `SymbolScope` mapping got wrong, so it is written here in the
313/// two bits ELF puts the visibility in and the rest of the byte is left as it was found.
314///
315/// A local symbol is left alone. Its visibility means nothing, since a name the static link has
316/// already finished with cannot be in a dynamic symbol table whatever `st_other` says, and gcc
317/// writes `STV_DEFAULT` for one, which is what the writer underneath produces on its own.
318fn see(obj: &mut Writer<'_>, id: SymbolId, binding: Binding, visibility: Visibility) {
319    if binding == Binding::Local {
320        return;
321    }
322    let wanted = match visibility {
323        Visibility::Default => elf::STV_DEFAULT,
324        Visibility::Hidden => elf::STV_HIDDEN,
325        Visibility::Protected => elf::STV_PROTECTED,
326    };
327    if let SymbolFlags::Elf { st_other, .. } = obj.symbol_flags_mut(id) {
328        *st_other = st_other.with_visibility(wanted);
329    }
330}
331
332/// Which relocation of this machine one reference is, and nothing for one this machine has none of.
333///
334/// The first three are the distance from the end of an instruction to something, and they differ in
335/// what the linker is allowed to do about it. A call may go through a stub, which is what lets a
336/// call reach a symbol further away than four bytes can say and what makes a call to a shared
337/// library work at all. A load may not, because there is nowhere to put a stub that a load would
338/// read, so a load of something another object may define reads a table slot the linker fills in
339/// instead, and the relaxing form of the relocation lets the linker undo that when it turns out
340/// nobody else defines it. The fourth is the address itself, at the two widths this machine writes
341/// one at.
342fn r_type(reference: Reference) -> Option<elf::RelocationType> {
343    Some(match reference {
344        Reference::Call => elf::R_X86_64_PLT32,
345        Reference::Data => elf::R_X86_64_PC32,
346        Reference::Got => elf::R_X86_64_REX_GOTPCRELX,
347        Reference::Address { bytes: 8 } => elf::R_X86_64_64,
348        Reference::Address { bytes: 4 } => elf::R_X86_64_32,
349        Reference::Address { .. } => return None,
350    })
351}
352
353#[cfg(test)]
354mod tests {
355    use super::*;
356
357    use object::read::elf::Sym as _;
358    use object::read::{Object as _, ObjectSection as _, ObjectSymbol as _};
359    use rucc_target::{Arch, Env, Os, Triple};
360
361    use crate::section::{Extent, Reloc};
362
363    /// A linux x86-64 target, which is the only one this writes.
364    fn target() -> TargetInfo {
365        TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu))
366    }
367
368    /// One function of that name, at that offset, that many bytes long, and visible that far.
369    ///
370    /// Visibility is the field these cases mostly have no opinion about, so it is the one the
371    /// helper fills in and the two that do have an opinion write for themselves.
372    fn extent(name: String, start: usize, len: usize, binding: Binding) -> Extent {
373        Extent { name, start, len, binding, visibility: Visibility::Default }
374    }
375
376    /// A call to something outside the file, which is the shape every case here starts from.
377    fn calling(name: &str) -> Text {
378        Text {
379            bytes: vec![0xe8, 0, 0, 0, 0, 0xc3],
380            funcs: vec![extent("f".to_owned(), 0, 6, Binding::Global)],
381            relocs: vec![Reloc {
382                at: 1,
383                symbol: name.to_owned(),
384                kind: Reference::Call,
385                addend: -4,
386            }],
387            ..Text::default()
388        }
389    }
390
391    #[test]
392    fn the_bytes_come_back_out_of_the_section_they_went_into() {
393        let text = calling("puts");
394        let bytes = write(&text, &Data::default(), &[], &target()).expect("an object");
395        let file = object::File::parse(&bytes[..]).expect("a readable object");
396        let section = file.section_by_name(".text").expect("a text section");
397        assert_eq!(section.data().expect("the bytes"), &text.bytes[..]);
398    }
399
400    #[test]
401    fn a_function_is_a_symbol_that_says_where_it_is_and_how_long_it_is() {
402        let mut text = calling("puts");
403        text.funcs.push(extent("g".to_owned(), 16, 1, Binding::Global));
404        text.bytes.resize(17, 0x90);
405        let bytes = write(&text, &Data::default(), &[], &target()).expect("an object");
406        let file = object::File::parse(&bytes[..]).expect("a readable object");
407        let g = file.symbols().find(|s| s.name() == Ok("g")).expect("the second function");
408        assert_eq!(g.address(), 16);
409        assert_eq!(g.size(), 1);
410        assert_eq!(g.kind(), SymbolKind::Text);
411        assert!(g.is_global(), "nothing said otherwise about this one");
412    }
413
414    #[test]
415    fn a_function_no_other_file_can_see_is_a_local_symbol() {
416        let mut text = calling("puts");
417        text.funcs.push(extent("hidden".to_owned(), 16, 1, Binding::Local));
418        text.funcs.push(extent("shared".to_owned(), 32, 1, Binding::Weak));
419        text.bytes.resize(33, 0x90);
420        let bytes = write(&text, &Data::default(), &[], &target()).expect("an object");
421        let file = object::File::parse(&bytes[..]).expect("a readable object");
422        let hidden = file.symbols().find(|s| s.name() == Ok("hidden")).expect("the static one");
423        // A symbol the linker keeps and does not let another file reach, which is the whole of
424        // what `static` on a function means and what two files each defining their own need.
425        assert!(hidden.is_local(), "a static function must not be offered to the linker");
426        assert!(!hidden.is_weak());
427        let shared = file.symbols().find(|s| s.name() == Ok("shared")).expect("the weak one");
428        assert!(shared.is_weak(), "a weak function has to be able to lose");
429        assert!(shared.is_global());
430    }
431
432    /// A global is `STV_DEFAULT`, so a shared library built from these objects exports something.
433    ///
434    /// The bug in tamnd/rucc#733. Every global came out `STV_HIDDEN`, which a static link does not
435    /// look at, so nothing here noticed and SQLite linked and ran and the whole test suite passed.
436    /// What it costs is the dynamic symbol table: `gcc -shared` over one of these objects produced
437    /// a library with an empty one, and `dlsym` could not find a function the file plainly defines.
438    ///
439    /// Written against `st_other` itself rather than against the reader's `scope`, because `scope`
440    /// is the word that was misread in the first place and a test that asks it the same question
441    /// would agree with whatever the writer did.
442    #[test]
443    fn a_global_is_visible_to_the_dynamic_linker_and_a_static_one_is_not_a_symbol_at_all() {
444        let mut text = calling("puts");
445        text.funcs.push(extent("g".to_owned(), 16, 1, Binding::Global));
446        text.funcs.push(extent("w".to_owned(), 32, 1, Binding::Weak));
447        text.funcs.push(extent("s".to_owned(), 48, 1, Binding::Local));
448        text.bytes.resize(49, 0x90);
449        let bytes = write(&text, &Data::default(), &[], &target()).expect("an object");
450        let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
451        let visibility = |name: &str| {
452            file.symbols()
453                .find(|s| s.name() == Ok(name))
454                .expect("the function")
455                .elf_symbol()
456                .st_visibility()
457        };
458        // Nothing said hidden about either of these, so neither is.
459        assert_eq!(visibility("g"), elf::STV_DEFAULT);
460        assert_eq!(visibility("w"), elf::STV_DEFAULT, "a weak one is still a name others may use");
461        // The `static` one is local, and a local symbol's visibility means nothing either way,
462        // which is why the binding is what this asks about.
463        assert_eq!(visibility("s"), elf::STV_DEFAULT);
464    }
465
466    /// And the other direction: a name that did ask to be hidden is hidden, and a protected one is
467    /// protected.
468    ///
469    /// The half of tamnd/rucc#733 that the fix above left open. Saying `STV_DEFAULT` for everything
470    /// is right for everything nobody marked and wrong the moment something is marked, so the two
471    /// tests together are what says the field carries an answer rather than a constant.
472    ///
473    /// Both are asked of a function and of a variable, because they are added by two different
474    /// loops in `write` and a field one of them fills in is not a field the other one does.
475    #[test]
476    fn a_name_that_asked_to_be_hidden_is_hidden_and_a_protected_one_is_protected() {
477        let mut text = calling("puts");
478        for (index, (name, seen)) in
479            [("h", Visibility::Hidden), ("p", Visibility::Protected)].into_iter().enumerate()
480        {
481            let mut func = extent(name.to_owned(), 16 + index * 16, 1, Binding::Global);
482            func.visibility = seen;
483            text.funcs.push(func);
484        }
485        text.bytes.resize(49, 0x90);
486        let mut data = Data::default();
487        for (name, seen) in [("vh", Visibility::Hidden), ("vp", Visibility::Protected)] {
488            let mut object = variable(name, Place::Written);
489            object.visibility = seen;
490            data.objects.push(object);
491        }
492        let bytes = write(&text, &data, &[], &target()).expect("an object");
493        let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
494        let visibility = |name: &str| {
495            file.symbols()
496                .find(|s| s.name() == Ok(name))
497                .expect("the symbol")
498                .elf_symbol()
499                .st_visibility()
500        };
501        assert_eq!(visibility("h"), elf::STV_HIDDEN);
502        assert_eq!(visibility("p"), elf::STV_PROTECTED);
503        assert_eq!(visibility("vh"), elf::STV_HIDDEN, "a variable goes through a second loop");
504        assert_eq!(visibility("vp"), elf::STV_PROTECTED);
505        // The one thing a visibility must not disturb, since `st_info` and `st_other` are written
506        // in one go and the second was set after the first.
507        let h = file.symbols().find(|s| s.name() == Ok("h")).expect("the function");
508        assert!(h.is_global(), "hidden is about the dynamic linker and not about the binding");
509        assert_eq!(h.size(), 1, "and it is still a function of the length it was");
510    }
511
512    #[test]
513    fn a_name_this_file_does_not_define_is_left_for_the_linker_to_find() {
514        let bytes = write(&calling("puts"), &Data::default(), &[], &target()).expect("an object");
515        let file = object::File::parse(&bytes[..]).expect("a readable object");
516        let puts = file.symbols().find(|s| s.name() == Ok("puts")).expect("the callee");
517        assert!(puts.is_undefined(), "the file does not define it and must not claim to");
518    }
519
520    #[test]
521    fn a_call_asks_for_the_relocation_a_stub_may_answer_and_a_load_asks_for_the_one_that_may_not() {
522        for (reference, wanted) in [
523            (Reference::Call, elf::R_X86_64_PLT32),
524            (Reference::Data, elf::R_X86_64_PC32),
525            (Reference::Got, elf::R_X86_64_REX_GOTPCRELX),
526        ] {
527            let mut text = calling("puts");
528            text.relocs[0].kind = reference;
529            let bytes = write(&text, &Data::default(), &[], &target()).expect("an object");
530            let file = object::File::parse(&bytes[..]).expect("a readable object");
531            let section = file.section_by_name(".text").expect("a text section");
532            let (offset, reloc) = section.relocations().next().expect("one relocation");
533            assert_eq!(offset, 1);
534            assert_eq!(reloc.addend(), -4);
535            assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: wanted });
536        }
537    }
538
539    #[test]
540    fn a_name_wanted_twice_is_one_symbol_rather_than_two() {
541        let mut text = calling("puts");
542        text.relocs.push(Reloc {
543            at: 1,
544            symbol: "puts".to_owned(),
545            kind: Reference::Call,
546            addend: -4,
547        });
548        let bytes = write(&text, &Data::default(), &[], &target()).expect("an object");
549        let file = object::File::parse(&bytes[..]).expect("a readable object");
550        assert_eq!(file.symbols().filter(|s| s.name() == Ok("puts")).count(), 1);
551    }
552
553    #[test]
554    fn a_function_that_is_also_called_is_not_a_second_symbol() {
555        let text = calling("f");
556        let bytes = write(&text, &Data::default(), &[], &target()).expect("an object");
557        let file = object::File::parse(&bytes[..]).expect("a readable object");
558        let mut found = file.symbols().filter(|s| s.name() == Ok("f"));
559        let f = found.next().expect("the function");
560        assert!(!f.is_undefined(), "the file defines it");
561        assert!(found.next().is_none(), "and defines it once");
562    }
563
564    #[test]
565    fn the_marker_that_says_the_stack_is_not_executable_is_written() {
566        let bytes = write(&calling("puts"), &Data::default(), &[], &target()).expect("an object");
567        let file = object::File::parse(&bytes[..]).expect("a readable object");
568        let note = file.section_by_name(".note.GNU-stack").expect("the marker");
569        assert!(note.data().expect("no bytes").is_empty());
570    }
571
572    /// One variable of four bytes, in whichever section its own answer puts it.
573    fn variable(name: &str, place: Place) -> Object {
574        Object {
575            name: name.to_owned(),
576            bytes: if place == Place::Zero { Vec::new() } else { vec![1, 0, 0, 0] },
577            size: 4,
578            align: 4,
579            place,
580            binding: Binding::Global,
581            visibility: Visibility::Default,
582            relocs: Vec::new(),
583        }
584    }
585
586    /// A file of that one variable and nothing else.
587    fn holding(object: Object) -> Vec<u8> {
588        let data = Data { objects: vec![object] };
589        write(&Text::default(), &data, &[], &target()).expect("an object")
590    }
591
592    #[test]
593    fn what_a_variable_is_decides_which_section_it_goes_in() {
594        for (place, wanted) in [
595            (Place::Written, ".data"),
596            (Place::ReadOnly, ".rodata"),
597            (Place::RelocReadOnly { local: false }, ".data.rel.ro"),
598            (Place::RelocReadOnly { local: true }, ".data.rel.ro.local"),
599            (Place::Zero, ".bss"),
600            (Place::Named(".init_array".to_owned()), ".init_array"),
601        ] {
602            let bytes = holding(variable("x", place.clone()));
603            let file = object::File::parse(&bytes[..]).expect("a readable object");
604            let section = file.section_by_name(wanted).unwrap_or_else(|| panic!("{place:?}"));
605            assert_eq!(section.size(), 4, "{place:?}");
606            // The zero filled one is as long as it says and carries none of it, which is the
607            // whole reason the section exists.
608            let carried = section.data().expect("the bytes").len();
609            assert_eq!(carried, if place == Place::Zero { 0 } else { 4 }, "{place:?}");
610        }
611    }
612
613    /// Two variables that want `.data.rel.ro.local` end up in one section, not two of one name.
614    ///
615    /// The writer has no name of its own for that section, so it is added by hand, and asking for
616    /// it again makes a second section rather than handing back the first. SQLite has enough const
617    /// tables of function pointers in it to turn that into eighty odd sections in one object, each
618    /// with its own relocation section beside it, which is a pile of section headers describing
619    /// eight bytes apiece.
620    #[test]
621    fn every_variable_that_wants_the_local_relocated_section_shares_one() {
622        let place = Place::RelocReadOnly { local: true };
623        let data =
624            Data { objects: vec![variable("first", place.clone()), variable("second", place)] };
625        let bytes = write(&Text::default(), &data, &[], &target()).expect("an object");
626        let file = object::File::parse(&bytes[..]).expect("a readable object");
627        let named = file.sections().filter(|s| s.name() == Ok(".data.rel.ro.local")).count();
628        assert_eq!(named, 1, "one section holding both, not one each");
629    }
630
631    #[test]
632    fn a_variable_is_a_symbol_that_says_where_it_is_and_how_long_it_is() {
633        let mut data = Data { objects: vec![variable("first", Place::Written)] };
634        data.objects.push(Object { align: 16, ..variable("second", Place::Written) });
635        let bytes = write(&Text::default(), &data, &[], &target()).expect("an object");
636        let file = object::File::parse(&bytes[..]).expect("a readable object");
637        let second = file.symbols().find(|s| s.name() == Ok("second")).expect("the second one");
638        assert_eq!(second.kind(), SymbolKind::Data);
639        assert_eq!(second.size(), 4);
640        // Sixteen rather than four, because the second one asked for sixteen and the first one
641        // had already used four. Getting this wrong is a variable at an address it said it would
642        // never be at, which nothing downstream would notice until an aligned load faulted.
643        assert_eq!(second.address(), 16);
644    }
645
646    #[test]
647    fn the_linkage_a_variable_had_is_the_binding_the_symbol_gets() {
648        for (binding, global, weak) in [
649            (Binding::Global, true, false),
650            (Binding::Local, false, false),
651            (Binding::Weak, true, true),
652        ] {
653            let bytes = holding(Object { binding, ..variable("x", Place::Written) });
654            let file = object::File::parse(&bytes[..]).expect("a readable object");
655            let x = file.symbols().find(|s| s.name() == Ok("x")).expect("the variable");
656            assert_eq!(x.is_global(), global, "{binding:?}");
657            assert_eq!(x.is_weak(), weak, "{binding:?}");
658        }
659    }
660
661    #[test]
662    fn a_tentative_definition_asks_the_linker_for_space_rather_than_naming_any() {
663        let bytes = holding(Object { align: 8, ..variable("x", Place::Merged) });
664        let file = object::read::elf::ElfFile64::<Endianness>::parse(&bytes[..]).expect("readable");
665        let x = file.symbols().find(|s| s.name() == Ok("x")).expect("the variable");
666        assert!(x.is_common(), "the linker merges every definition of this name into one");
667        assert_eq!(x.size(), 4);
668        // What a common symbol records where an ordinary one records its address is what it wants
669        // to be aligned to, because it has no address yet. The reader deliberately answers nothing
670        // when asked for the address of one, so this is the field itself.
671        assert_eq!(x.address(), 0);
672        assert_eq!(x.elf_symbol().st_value(Endianness::Little), 8);
673    }
674
675    #[test]
676    fn an_address_in_an_image_is_the_address_and_not_a_distance_to_it() {
677        let object = Object {
678            bytes: vec![0; 8],
679            size: 8,
680            align: 8,
681            relocs: vec![Reloc {
682                at: 0,
683                symbol: "y".to_owned(),
684                kind: Reference::Address { bytes: 8 },
685                addend: 16,
686            }],
687            ..variable("p", Place::Written)
688        };
689        let bytes = holding(object);
690        let file = object::File::parse(&bytes[..]).expect("a readable object");
691        let section = file.section_by_name(".data").expect("a data section");
692        let (offset, reloc) = section.relocations().next().expect("one relocation");
693        assert_eq!(offset, 0);
694        assert_eq!(reloc.addend(), 16);
695        assert_eq!(reloc.flags(), RelocationFlags::Elf { r_type: elf::R_X86_64_64 });
696        let y = file.symbols().find(|s| s.name() == Ok("y")).expect("what it points at");
697        assert!(y.is_undefined(), "nothing here defines it and the linker is being asked for it");
698    }
699
700    /// Not a rewording of the case above: what is checked is the arithmetic between the two.
701    #[test]
702    fn a_relocation_counts_from_the_start_of_the_section_and_not_of_the_image_it_is_in() {
703        let mut data = Data { objects: vec![variable("first", Place::Written)] };
704        data.objects.push(Object {
705            bytes: vec![0; 16],
706            size: 16,
707            align: 8,
708            relocs: vec![Reloc {
709                at: 8,
710                symbol: "y".to_owned(),
711                kind: Reference::Address { bytes: 8 },
712                addend: 0,
713            }],
714            ..variable("second", Place::Written)
715        });
716        let bytes = write(&Text::default(), &data, &[], &target()).expect("an object");
717        let file = object::File::parse(&bytes[..]).expect("a readable object");
718        let section = file.section_by_name(".data").expect("a data section");
719        let (offset, _) = section.relocations().next().expect("one relocation");
720        // Eight into the second image, which starts eight in because the first one is four long
721        // and the second is eight aligned.
722        assert_eq!(offset, 16);
723    }
724
725    #[test]
726    fn a_second_name_is_a_second_symbol_at_the_first_one_s_address_and_no_second_image() {
727        let data = Data {
728            objects: vec![Object { binding: Binding::Local, ..variable("a", Place::Written) }],
729        };
730        let aliases = [Alias {
731            name: "b".to_owned(),
732            target: "a".to_owned(),
733            binding: Binding::Global,
734            visibility: Visibility::Default,
735        }];
736        let bytes = write(&Text::default(), &data, &aliases, &target()).expect("an object");
737        let file = object::File::parse(&bytes[..]).expect("a readable object");
738        let a = file.symbols().find(|s| s.name() == Ok("a")).expect("the variable");
739        let b = file.symbols().find(|s| s.name() == Ok("b")).expect("the second name");
740        assert_eq!(b.address(), a.address(), "the same place");
741        assert_eq!(b.size(), a.size());
742        assert_eq!(b.section_index(), a.section_index());
743        // The binding is the one thing the second name does not take from the first, which is
744        // what `extern int b __attribute__((alias("a")))` on a `static a` asks for.
745        assert!(a.is_local(), "the target was written `static`");
746        assert!(b.is_global(), "and the name given to it was not");
747        // Four bytes of image and not eight, since an alias is a name and not a copy.
748        assert_eq!(file.section_by_name(".data").expect("a data section").size(), 4);
749    }
750
751    #[test]
752    fn a_function_can_be_given_a_second_name_the_same_way_a_variable_can() {
753        let text = calling("puts");
754        let aliases = [Alias {
755            name: "g".to_owned(),
756            target: "f".to_owned(),
757            binding: Binding::Weak,
758            visibility: Visibility::Default,
759        }];
760        let bytes = write(&text, &Data::default(), &aliases, &target()).expect("an object");
761        let file = object::File::parse(&bytes[..]).expect("a readable object");
762        let f = file.symbols().find(|s| s.name() == Ok("f")).expect("the function");
763        let g = file.symbols().find(|s| s.name() == Ok("g")).expect("the second name");
764        assert_eq!(g.address(), f.address());
765        assert_eq!(g.size(), f.size());
766        assert_eq!(g.kind(), f.kind(), "a second name for a function is a function");
767        assert!(g.is_weak(), "so that a program may define the name itself instead");
768    }
769
770    /// The front end is what reports this as a program's mistake, so one arriving here is a bug
771    /// in this compiler and is said so rather than written as an undefined symbol.
772    #[test]
773    fn a_second_name_for_something_this_file_does_not_define_is_refused() {
774        let aliases = [Alias {
775            name: "b".to_owned(),
776            target: "a".to_owned(),
777            binding: Binding::Global,
778            visibility: Visibility::Default,
779        }];
780        let error = write(&Text::default(), &Data::default(), &aliases, &target())
781            .expect_err("nothing to point at");
782        assert!(matches!(error, Error::Refused { .. }), "{error:?}");
783    }
784
785    #[test]
786    fn a_platform_this_does_not_write_is_said_so_rather_than_written_as_elf() {
787        let text = calling("puts");
788        for triple in [
789            Triple::new(Arch::Aarch64, Os::Linux, Env::Gnu),
790            Triple::new(Arch::X86_64, Os::Darwin, Env::Gnu),
791        ] {
792            let error = write(&text, &Data::default(), &[], &TargetInfo::new(triple))
793                .expect_err("no writer");
794            assert!(matches!(error, Error::Format { .. }), "{error:?}");
795        }
796    }
797}