1const PROGBITS: u64 = 1;
14
15const EXCLUDE: u64 = 0x8000_0000;
18
19const RESERVED: u64 = 0xff00;
22
23struct Layout {
26 wide: bool,
28 table: usize,
30 each: usize,
32 count: usize,
34 names: usize,
36}
37
38impl Layout {
39 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 fn header(&self, nth: usize) -> usize {
69 self.table + nth * self.each
70 }
71
72 fn place(&self) -> (usize, usize, usize) {
74 if self.wide { (24, 32, 8) } else { (16, 20, 4) }
75 }
76
77 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 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
95fn 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
101fn 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
106pub 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 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 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#[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 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 type Section = (String, Vec<u8>);
226
227 type Named = (String, Option<usize>);
229
230 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 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}