Skip to main content

rucc_object/
carry.rs

1//! A section of bytes put into a finished object, and read back out of one.
2//!
3//! What `-flto` keeps beside the machine code, which is the module the code was made from. The
4//! writers underneath are done with the file by the time that is known to be wanted, and the link
5//! that reads it back is handed objects it did not write, so both halves work on the bytes of a
6//! finished file rather than on the writer's tables.
7//!
8//! ELF only, and only little endian, which is every ELF target this compiler writes. A file in
9//! any other format is left alone and has nothing in it to find, and an object nothing was put in
10//! links the ordinary way, which is the answer a link with no module to read has to give anyway.
11
12/// `SHT_PROGBITS`, bytes that mean whatever the section's name says they mean.
13const PROGBITS: u64 = 1;
14
15/// `SHF_EXCLUDE`, which tells the linker to leave the section out of what it writes. The module is
16/// for the link to read, and an executable has no use for it.
17const EXCLUDE: u64 = 0x8000_0000;
18
19/// The first section index that means something other than an index. A file with that many
20/// sections says how many in its first header instead, which nothing here writes.
21const RESERVED: u64 = 0xff00;
22
23/// Where the parts of a file's header that matter here are, which is in different places in the
24/// two classes because every field after the first few is a different width.
25struct Layout {
26    /// Whether the file is a 64 bit one.
27    wide: bool,
28    /// `e_shoff`, where the section headers start.
29    table: usize,
30    /// `e_shentsize`, how far apart they are.
31    each: usize,
32    /// `e_shnum`, how many there are.
33    count: usize,
34    /// `e_shstrndx`, which one holds the names of all of them.
35    names: usize,
36}
37
38impl Layout {
39    /// The layout of `bytes`, or nothing for a file that is not a little endian ELF object or
40    /// whose headers are not all inside it.
41    fn of(bytes: &[u8]) -> Option<Layout> {
42        if bytes.get(..4)? != b"\x7fELF" || *bytes.get(5)? != 1 {
43            return None;
44        }
45        let wide = match bytes.get(4)? {
46            1 => false,
47            2 => true,
48            _ => return None,
49        };
50        let (table, each, count, names) = if wide {
51            (get(bytes, 0x28, 8)?, 0x3a, 0x3c, 0x3e)
52        } else {
53            (get(bytes, 0x20, 4)?, 0x2e, 0x30, 0x32)
54        };
55        let layout = Layout {
56            wide,
57            table: usize::try_from(table).ok()?,
58            each: usize::try_from(get(bytes, each, 2)?).ok()?,
59            count: usize::try_from(get(bytes, count, 2)?).ok()?,
60            names: usize::try_from(get(bytes, names, 2)?).ok()?,
61        };
62        let size = if wide { 64 } else { 40 };
63        let end = layout.count.checked_mul(layout.each)?.checked_add(layout.table)?;
64        (layout.each == size && layout.names < layout.count && end <= bytes.len()).then_some(layout)
65    }
66
67    /// Where the `nth` header starts.
68    fn header(&self, nth: usize) -> usize {
69        self.table + nth * self.each
70    }
71
72    /// Where `sh_offset` and `sh_size` are in a header, and how wide each is.
73    fn place(&self) -> (usize, usize, usize) {
74        if self.wide { (24, 32, 8) } else { (16, 20, 4) }
75    }
76
77    /// Where a section's contents are in the file, checked to be inside it.
78    fn contents(&self, bytes: &[u8], nth: usize) -> Option<std::ops::Range<usize>> {
79        let (offset, size, width) = self.place();
80        let at = self.header(nth);
81        let from = usize::try_from(get(bytes, at + offset, width)?).ok()?;
82        let to = from.checked_add(usize::try_from(get(bytes, at + size, width)?).ok()?)?;
83        (to <= bytes.len()).then_some(from..to)
84    }
85
86    /// What the `nth` section is called.
87    fn name<'a>(&self, bytes: &'a [u8], nth: usize) -> Option<&'a [u8]> {
88        let strings = &bytes[self.contents(bytes, self.names)?];
89        let from = usize::try_from(get(bytes, self.header(nth), 4)?).ok()?;
90        let rest = strings.get(from..)?;
91        Some(&rest[..rest.iter().position(|byte| *byte == 0)?])
92    }
93}
94
95/// The little endian number `width` bytes long at `at`, or nothing past the end of the file.
96fn get(bytes: &[u8], at: usize, width: usize) -> Option<u64> {
97    let field = bytes.get(at..at.checked_add(width)?)?;
98    Some(field.iter().rev().fold(0, |sum, &byte| sum << 8 | u64::from(byte)))
99}
100
101/// Writes `value` as a little endian number `width` bytes long at `at`.
102fn put(bytes: &mut [u8], at: usize, width: usize, value: u64) {
103    bytes[at..at + width].copy_from_slice(&value.to_le_bytes()[..width]);
104}
105
106/// Puts `payload` into `object` as a section called `name`, which the linker leaves out of what it
107/// writes. Whether it did, which it does not for a file that is not little endian ELF or that has
108/// too many sections to add one to.
109///
110/// Everything goes on the end. The new section header table is the old one with one more header
111/// after it, so no section changes its index and nothing that points at one by number has to be
112/// told, and the names of the sections are copied there too with the new one after them, since
113/// the old table of names has no room. The old copies of both are left where they were, which is
114/// bytes nothing points at any more.
115pub fn attach(object: &mut Vec<u8>, name: &str, payload: &[u8]) -> bool {
116    let Some(layout) = Layout::of(object) else { return false };
117    let Some(strings) = layout.contents(object, layout.names) else { return false };
118    if layout.count == 0 || layout.count as u64 + 1 >= RESERVED || name.contains('\0') {
119        return false;
120    }
121    let mut names = object[strings].to_vec();
122    let named = names.len() as u64;
123    names.extend_from_slice(name.as_bytes());
124    names.push(0);
125    let mut headers = object[layout.table..layout.header(layout.count)].to_vec();
126
127    let contents = object.len();
128    object.extend_from_slice(payload);
129    let strings = object.len();
130    object.extend_from_slice(&names);
131    object.resize(object.len().next_multiple_of(8), 0);
132    let table = object.len();
133
134    // The table of names is somewhere else now and longer.
135    let (offset, size, width) = layout.place();
136    let at = layout.names * layout.each;
137    put(&mut headers, at + offset, width, strings as u64);
138    put(&mut headers, at + size, width, names.len() as u64);
139    // And the new header, whose fields are in the same order in both classes and only some of
140    // them wider in the 64 bit one: name, type, flags, address, offset, size, link, info,
141    // alignment and the size of an entry.
142    let word = if layout.wide { 8 } else { 4 };
143    let fields = [
144        (4, named),
145        (4, PROGBITS),
146        (word, EXCLUDE),
147        (word, 0),
148        (word, contents as u64),
149        (word, payload.len() as u64),
150        (4, 0),
151        (4, 0),
152        (word, 1),
153        (word, 0),
154    ];
155    for (width, value) in fields {
156        headers.extend_from_slice(&value.to_le_bytes()[..width]);
157    }
158    object.extend_from_slice(&headers);
159
160    let (shoff, shnum) = if layout.wide { (0x28, 0x3c) } else { (0x20, 0x30) };
161    put(object, shoff, word, table as u64);
162    put(object, shnum, 2, layout.count as u64 + 1);
163    true
164}
165
166/// The contents of the section called `name` in `object`, or nothing when there is no such
167/// section or the file is not one [`attach`] could have put it in.
168#[must_use]
169pub fn carried<'a>(object: &'a [u8], name: &str) -> Option<&'a [u8]> {
170    let layout = Layout::of(object)?;
171    let nth = (0..layout.count).find(|&nth| layout.name(object, nth) == Some(name.as_bytes()))?;
172    Some(&object[layout.contents(object, nth)?])
173}
174
175#[cfg(test)]
176mod tests {
177    use super::{attach, carried};
178    use object::elf;
179    use object::write::{Object, Relocation, StandardSection, Symbol, SymbolSection};
180    use object::{
181        Architecture, BinaryFormat, Endianness, Object as _, ObjectSection as _, ObjectSymbol as _,
182        RelocationFlags, SectionFlags, SectionKind, SymbolFlags, SymbolKind, SymbolScope,
183    };
184
185    /// A small object with a function in it that calls another, so that there is a symbol table,
186    /// a relocation section pointing at it by index and a table of names to move.
187    fn object(architecture: Architecture, call: elf::RelocationType) -> Vec<u8> {
188        let mut obj = Object::new(BinaryFormat::Elf, architecture, Endianness::Little);
189        let text = obj.section_id(StandardSection::Text);
190        let at = obj.append_section_data(text, &[0xe8, 0, 0, 0, 0, 0xc3], 16);
191        obj.add_symbol(Symbol {
192            name: b"f".to_vec(),
193            value: at,
194            size: 6,
195            kind: SymbolKind::Text,
196            scope: SymbolScope::Linkage,
197            weak: false,
198            section: SymbolSection::Section(text),
199            flags: SymbolFlags::None,
200        });
201        let g = obj.add_symbol(Symbol {
202            name: b"g".to_vec(),
203            value: 0,
204            size: 0,
205            kind: SymbolKind::Text,
206            scope: SymbolScope::Unknown,
207            weak: false,
208            section: SymbolSection::Undefined,
209            flags: SymbolFlags::None,
210        });
211        obj.add_relocation(
212            text,
213            Relocation {
214                offset: at + 1,
215                symbol: g,
216                addend: -4,
217                flags: RelocationFlags::Elf { r_type: call },
218            },
219        )
220        .expect("the relocation is one the writer takes");
221        obj.write().expect("the object is written")
222    }
223
224    /// A section's name and contents.
225    type Section = (String, Vec<u8>);
226
227    /// A symbol's name and the index of the section it is in.
228    type Named = (String, Option<usize>);
229
230    /// What `object` reads in a file: each section's name and contents, and each symbol's name and
231    /// section index, which is everything adding a section must leave as it was. The table of
232    /// names is the one section that does change, and its contents are the names read here.
233    fn read(bytes: &[u8]) -> (Vec<Section>, Vec<Named>, usize) {
234        let file = object::File::parse(bytes).expect("the file still parses");
235        let sections = file
236            .sections()
237            .filter(|s| s.name().ok() != Some(".shstrtab"))
238            .map(|s| (s.name().unwrap().to_string(), s.data().unwrap().to_vec()))
239            .collect();
240        let symbols = file
241            .symbols()
242            .map(|s| (s.name().unwrap().to_string(), s.section_index().map(|i| i.0)))
243            .collect();
244        let relocs = file.sections().map(|s| s.relocations().count()).sum();
245        (sections, symbols, relocs)
246    }
247
248    #[test]
249    fn a_section_put_in_is_read_back_and_nothing_else_moves() {
250        for (architecture, call) in [
251            (Architecture::X86_64, elf::R_X86_64_PLT32),
252            (Architecture::I386, elf::R_386_PC32),
253            (Architecture::Aarch64, elf::R_AARCH64_CALL26),
254        ] {
255            let before = object(architecture, call);
256            let mut after = before.clone();
257            assert!(attach(&mut after, ".rucc.lto", b"the module"), "{architecture:?}");
258            assert_eq!(carried(&after, ".rucc.lto"), Some(&b"the module"[..]), "{architecture:?}");
259            assert_eq!(carried(&after, ".rucc.other"), None);
260            assert_eq!(carried(&before, ".rucc.lto"), None);
261
262            let (old, symbols, relocs) = read(&before);
263            let (mut new, moved, kept) = read(&after);
264            let added = new.pop().expect("the new section is the last one");
265            assert_eq!(added, (".rucc.lto".to_string(), b"the module".to_vec()));
266            assert_eq!(new, old, "{architecture:?}: every other section is as it was");
267            assert_eq!(moved, symbols, "{architecture:?}: every symbol is where it was");
268            assert_eq!(kept, relocs, "{architecture:?}: and so is every relocation");
269
270            let file = object::File::parse(&*after).unwrap();
271            let section = file.section_by_name(".rucc.lto").unwrap();
272            assert_eq!(section.kind(), SectionKind::Other, "{architecture:?}: not loaded");
273            let SectionFlags::Elf { sh_type, sh_flags } = section.flags() else {
274                panic!("not ELF")
275            };
276            assert_eq!(
277                (sh_type, sh_flags),
278                (elf::SHT_PROGBITS, elf::SHF_EXCLUDE),
279                "{architecture:?}"
280            );
281        }
282    }
283
284    #[test]
285    fn a_file_that_is_not_elf_is_left_alone() {
286        let mut obj = Object::new(BinaryFormat::Coff, Architecture::X86_64, Endianness::Little);
287        let text = obj.section_id(StandardSection::Text);
288        obj.append_section_data(text, &[0xc3], 16);
289        let before = obj.write().unwrap();
290        let mut after = before.clone();
291        assert!(!attach(&mut after, ".rucc.lto", b"the module"));
292        assert_eq!(after, before);
293        assert_eq!(carried(&after, ".rucc.lto"), None);
294
295        // Nor is anything read out of a file that is not an object, or of one cut short.
296        assert_eq!(carried(b"!<arch>\n", ".rucc.lto"), None);
297        let mut whole = object(Architecture::X86_64, elf::R_X86_64_PLT32);
298        assert!(attach(&mut whole, ".rucc.lto", b"the module"));
299        for len in [0, 4, 16, 64, whole.len() / 2, whole.len() - 1] {
300            assert_eq!(carried(&whole[..len], ".rucc.lto"), None, "{len} bytes");
301        }
302    }
303}