1use crate::{
4 elf,
5 glibc::{Kind, Snapshot},
6 version::Version,
7};
8use std::{
9 collections::{BTreeMap, BTreeSet},
10 fmt::Write as _,
11};
12
13#[derive(Debug, Eq, PartialEq)]
15pub struct Sources {
16 pub assembly: String,
18 pub versions: String,
20 pub selected: elf::Metadata,
22}
23
24pub fn render_glibc(
28 snapshot: &Snapshot,
29 library: &str,
30 reference: &elf::Metadata,
31) -> Result<Sources, String> {
32 snapshot.release.validate_release()?;
33 let soname = glibc_soname(library).ok_or("unsupported glibc library identity")?;
34 if reference.soname != soname {
35 return Err(format!("expected SONAME {soname}"));
36 }
37 let expected = snapshot
38 .libraries
39 .get(library)
40 .ok_or("missing library ABI metadata")?;
41 let public = elf::Metadata {
42 soname: reference.soname.clone(),
43 symbols: reference
44 .symbols
45 .iter()
46 .filter(|s| {
47 matches!(s.visibility, 0 | 3)
48 && s.version
49 .as_deref()
50 .and_then(|v| v.strip_prefix("GLIBC_"))
51 .is_some_and(|v| v.parse::<Version>().is_ok())
52 })
53 .cloned()
54 .collect(),
55 };
56 if public.symbols.len() != expected.len() {
57 return Err("ELF/snapshot public version membership differs".into());
58 }
59 let mut actual = BTreeMap::new();
60 for s in &public.symbols {
61 if actual
62 .insert((s.name.as_str(), s.version.as_deref()), s)
63 .is_some()
64 {
65 return Err("duplicate ELF public definition".into());
66 }
67 }
68 let mut expected_keys = BTreeSet::new();
69 for entry in expected {
70 if !expected_keys.insert((entry.name.as_str(), entry.version)) {
71 return Err("duplicate ABI definition".into());
72 }
73 let version = format!("GLIBC_{}", entry.version);
74 let s = actual
75 .get(&(entry.name.as_str(), Some(version.as_str())))
76 .ok_or_else(|| format!("{}@{version}: missing ELF definition", entry.name))?;
77 let matches = match entry.kind {
78 Kind::Function => matches!(s.symbol_type, 2 | 10),
79 Kind::Object(size) => s.symbol_type == 1 && s.size == u64::from(size),
80 Kind::Tls(size) => s.symbol_type == 6 && s.size == u64::from(size),
81 };
82 if !matches {
83 return Err(format!(
84 "{}@{version}: ELF/snapshot kind or size differs",
85 entry.name
86 ));
87 }
88 }
89 let names: BTreeSet<_> = expected.iter().map(|s| s.name.as_str()).collect();
90 render(&public, &names.into_iter().collect::<Vec<_>>())
91}
92
93pub fn glibc_soname(library: &str) -> Option<&'static str> {
95 Some(match library {
96 "c" => "libc.so.6",
97 "m" => "libm.so.6",
98 "ld" => "ld-linux-x86-64.so.2",
99 "resolv" => "libresolv.so.2",
100 "pthread" => "libpthread.so.0",
101 "dl" => "libdl.so.2",
102 "rt" => "librt.so.1",
103 "util" => "libutil.so.1",
104 _ => return None,
105 })
106}
107
108pub fn glibc_table(snapshot: &Snapshot, lib64: &std::path::Path) -> Result<String, String> {
114 let mut table = String::new();
115 for library in crate::glibc::LIBRARIES {
116 let soname = glibc_soname(library).expect("known library");
117 let reference = elf::Metadata::load(&lib64.join(soname))?;
118 let text = render_glibc(snapshot, library, &reference)
119 .map_err(|e| format!("{soname}: {e}"))?
120 .selected
121 .to_tsv();
122 let rows = if table.is_empty() {
123 &text[..]
124 } else {
125 text.split_once('\n').map_or("", |(_, rows)| rows)
126 };
127 table.push_str(rows);
128 }
129 Ok(table)
130}
131
132pub fn glibc_from_table(
134 snapshot: &Snapshot,
135 table: &str,
136) -> Result<Vec<(&'static str, Sources)>, String> {
137 let libraries = elf::Metadata::from_tsv(table)?;
138 if libraries.len() != crate::glibc::LIBRARIES.len() {
139 return Err("the library table must have exactly the eight glibc libraries".into());
140 }
141 crate::glibc::LIBRARIES
142 .iter()
143 .map(|&library| {
144 let soname = glibc_soname(library).expect("known library");
145 let reference = libraries
146 .iter()
147 .find(|m| m.soname == soname)
148 .ok_or_else(|| format!("the library table lacks {soname}"))?;
149 let sources =
150 render_glibc(snapshot, library, reference).map_err(|e| format!("{soname}: {e}"))?;
151 Ok((library, sources))
152 })
153 .collect()
154}
155
156fn identifier(name: &str) -> bool {
157 let mut bytes = name.bytes();
158 bytes
159 .next()
160 .is_some_and(|b| b.is_ascii_alphabetic() || b == b'_')
161 && bytes.all(|b| b.is_ascii_alphanumeric() || b == b'_')
162 && !name.starts_with("__abi_stub_")
163 && name.len() <= 1024
164}
165
166struct DataRange {
167 start: u64,
168 end: u64,
169 alignment: u64,
170 section: u16,
171 kind: u8,
172}
173
174fn data_layout(
175 symbols: &[elf::Symbol],
176) -> Result<(Vec<DataRange>, BTreeMap<usize, usize>), String> {
177 let mut by_section = BTreeMap::<(u16, u8), Vec<usize>>::new();
178 for (index, s) in symbols
179 .iter()
180 .enumerate()
181 .filter(|(_, s)| matches!(s.symbol_type, 1 | 6))
182 {
183 let alignment = s.section_alignment.max(1);
184 if alignment > 4096 || !alignment.is_power_of_two() {
185 return Err("unsupported data/TLS section alignment".into());
186 }
187 if s.size == 0 || s.size > 16 * 1024 * 1024 {
188 return Err("unsupported zero or oversized data/TLS symbol".into());
189 }
190 s.value
191 .checked_add(s.size)
192 .ok_or("ELF data range overflow")?;
193 by_section
194 .entry((s.section, s.symbol_type))
195 .or_default()
196 .push(index);
197 }
198 let mut groups: Vec<DataRange> = Vec::new();
199 let mut membership = BTreeMap::new();
200 for ((section, kind), mut indexes) in by_section {
201 indexes.sort_by_key(|&index| (symbols[index].value, symbols[index].size));
202 let mut active: Option<usize> = None;
203 for index in indexes {
204 let s = &symbols[index];
205 let end = s.value + s.size; let group = match active {
207 Some(group) if s.value < groups[group].end => {
208 groups[group].end = groups[group].end.max(end);
209 groups[group].alignment =
210 groups[group].alignment.max(s.section_alignment.max(1));
211 group
212 }
213 _ => {
214 let group = groups.len();
215 groups.push(DataRange {
216 start: s.value,
217 end,
218 alignment: s.section_alignment.max(1),
219 section,
220 kind,
221 });
222 active = Some(group);
223 group
224 }
225 };
226 if groups[group].end - groups[group].start > 16 * 1024 * 1024 {
227 return Err("data/TLS backing range exceeds 16 MiB".into());
228 }
229 membership.insert(index, group);
230 }
231 }
232 Ok((groups, membership))
233}
234
235pub fn render(reference: &elf::Metadata, names: &[&str]) -> Result<Sources, String> {
240 render_versions(reference, names, "GLIBC_", false)
241}
242
243pub fn render_versions(
248 reference: &elf::Metadata,
249 names: &[&str],
250 prefix: &str,
251 chained: bool,
252) -> Result<Sources, String> {
253 if names.is_empty() || names.len() > 10000 || reference.symbols.len() > 100000 {
254 return Err("stub requires 1..10000 names and at most 100000 ELF definitions".into());
255 }
256 let requested: BTreeSet<_> = names.iter().copied().collect();
257 if requested.len() != names.len() || requested.iter().any(|name| !identifier(name)) {
258 return Err("stub names must be unique C identifiers outside the reserved prefix".into());
259 }
260 let mut selected = elf::Metadata {
261 soname: reference.soname.clone(),
262 symbols: reference
263 .symbols
264 .iter()
265 .filter(|symbol| requested.contains(symbol.name.as_str()))
266 .cloned()
267 .collect(),
268 };
269 selected.symbols.sort();
270 let observed: BTreeSet<_> = selected.symbols.iter().map(|s| s.name.as_str()).collect();
271 if observed != requested {
272 return Err("requested stub symbol is absent from ELF evidence".into());
273 }
274 let mut keys = BTreeSet::new();
275 let mut defaults = BTreeSet::new();
276 let mut nodes = BTreeMap::<Version, BTreeSet<String>>::new();
277 let mut spellings = BTreeMap::<Version, String>::new();
278 let mut assembly = String::from("# Experimental link-only library; function bodies trap.\n");
279 let mut storage = BTreeMap::new();
280 let mut ranges = BTreeMap::<u16, BTreeMap<u64, u64>>::new();
281 let mut allocated = 0u64;
282 let (data_ranges, data_membership) = data_layout(&selected.symbols)?;
283 let mut emitted_data = BTreeSet::new();
284 for (index, symbol) in selected.symbols.iter().enumerate() {
285 if !matches!(symbol.binding, 1 | 2)
286 || !matches!(symbol.visibility, 0 | 3)
287 || !matches!(symbol.symbol_type, 1 | 2 | 6 | 10)
288 || symbol.section == 0
289 || symbol.section >= 0xff00
290 {
291 return Err(format!(
292 "{}: unsupported binding, visibility, type, or section",
293 symbol.name
294 ));
295 }
296 let alignment = symbol.section_alignment.max(1);
297 if alignment > 4096 || !alignment.is_power_of_two() {
298 return Err(format!("{}: unsupported alignment", symbol.name));
299 }
300 let version = symbol
301 .version
302 .as_deref()
303 .ok_or("unversioned stub definitions unsupported")?;
304 let numeric: Version = version
305 .strip_prefix(prefix)
306 .ok_or_else(|| format!("stub requires numeric {prefix} versions"))?
307 .parse()?;
308 if prefix == "GLIBC_" && format!("GLIBC_{numeric}") != version {
310 return Err("noncanonical GLIBC version".into());
311 }
312 if *spellings
313 .entry(numeric)
314 .or_insert_with(|| version.to_string())
315 != version
316 {
317 return Err(format!("{version}: one version spelled two ways"));
318 }
319 let default = symbol
320 .default_version
321 .ok_or("missing default-version evidence")?;
322 if !keys.insert((&symbol.name, version)) || (default && !defaults.insert(&symbol.name)) {
323 return Err("duplicate definition or multiple default stub versions".into());
324 }
325 nodes
326 .entry(numeric)
327 .or_default()
328 .insert(symbol.name.clone());
329 let key = (
330 symbol.section,
331 symbol.value,
332 symbol.symbol_type,
333 symbol.size,
334 );
335 let emitted_size = if matches!(symbol.symbol_type, 2 | 10) {
336 2
337 } else {
338 symbol.size
339 };
340 if emitted_size == 0 || emitted_size > 16 * 1024 * 1024 {
341 return Err(format!(
342 "{}: unsupported zero or oversized storage",
343 symbol.name
344 ));
345 }
346 let old_storage = if symbol.size == 0 {
347 None
348 } else {
349 storage.get(&key).copied()
350 };
351 let location = if let Some(&group) = data_membership.get(&index) {
352 let range = &data_ranges[group];
353 if emitted_data.insert(group) {
354 let bytes = range.end - range.start;
355 let location = ranges.entry(range.section).or_default();
356 let overlaps_before = location
357 .range(..=range.start)
358 .next_back()
359 .is_some_and(|(_, &end)| end > range.start);
360 let overlaps_after = location
361 .range(range.start..)
362 .next()
363 .is_some_and(|(&start, _)| start < range.end);
364 if overlaps_before || overlaps_after {
365 return Err("incompatible overlapping storage types".into());
366 }
367 location.insert(range.start, range.end);
368 allocated = allocated
369 .checked_add(bytes + range.alignment - 1)
370 .ok_or("stub allocation overflow")?;
371 if allocated > 64 * 1024 * 1024 {
372 return Err("stub storage budget exceeds 64 MiB".into());
373 }
374 let section = if range.kind == 6 {
375 ".section .tbss,\"awT\",@nobits"
376 } else {
377 ".section .bss,\"aw\",@nobits"
378 };
379 writeln!(
380 assembly,
381 "{section}\n.balign {}\n.Ldata{group}:\n .zero {bytes}",
382 range.alignment
383 )
384 .expect("String write");
385 }
386 format!(".Ldata{group}+{}", symbol.value - range.start)
387 } else {
388 let slot = if let Some(slot) = old_storage {
389 slot
390 } else {
391 let end = symbol
393 .value
394 .checked_add(symbol.size)
395 .ok_or("ELF symbol range overflow")?;
396 let location = ranges.entry(symbol.section).or_default();
397 let overlaps_before = location
398 .range(..=symbol.value)
399 .next_back()
400 .is_some_and(|(_, &old_end)| old_end > symbol.value);
401 let overlaps_after = location
402 .range(symbol.value..)
403 .next()
404 .is_some_and(|(&start, _)| start < end);
405 if symbol.size != 0 && (overlaps_before || overlaps_after) {
406 return Err(format!(
407 "{}@{version}: partially overlapping symbol ranges unsupported at {}:{:#x}+{}",
408 symbol.name, symbol.section, symbol.value, symbol.size
409 ));
410 }
411 if symbol.size != 0 {
412 location.insert(symbol.value, end);
413 }
414 allocated = allocated
415 .checked_add(emitted_size + alignment - 1)
416 .ok_or("stub allocation overflow")?;
417 if allocated > 64 * 1024 * 1024 {
418 return Err("stub storage budget exceeds 64 MiB".into());
419 }
420 let slot = index;
421 if symbol.size != 0 {
422 storage.insert(key, slot);
423 }
424 let section = match symbol.symbol_type {
425 1 => ".section .bss,\"aw\",@nobits",
426 6 => ".section .tbss,\"awT\",@nobits",
427 _ => ".text",
428 };
429 writeln!(assembly, "{section}\n.balign {alignment}\n.Lstorage{slot}:")
430 .expect("String write");
431 if matches!(symbol.symbol_type, 2 | 10) {
432 assembly.push_str(" ud2\n");
433 } else {
434 writeln!(assembly, " .zero {emitted_size}").expect("String write");
435 }
436 slot
437 };
438 format!(".Lstorage{slot}")
439 };
440 let private = format!("__abi_stub_{index}");
441 let binding = if symbol.binding == 2 {
442 ".weak"
443 } else {
444 ".globl"
445 };
446 let kind = match symbol.symbol_type {
447 1 => "object",
448 6 => "tls_object",
449 10 => "gnu_indirect_function",
450 _ => "function",
451 };
452 writeln!(assembly, "{binding} {private}\n.set {private},{location}\n.type {private},@{kind}\n.size {private},{emitted_size}").expect("String write");
453 if symbol.visibility == 3 {
454 writeln!(assembly, ".protected {private}").expect("String write");
455 }
456 let separator = if default { "@@" } else { "@" };
457 writeln!(
458 assembly,
459 ".symver {private},{}{separator}{version}",
460 symbol.name
461 )
462 .expect("String write");
463 }
464 assembly.push_str(".section .note.GNU-stack,\"\",@progbits\n");
465 let mut versions = String::new();
466 let mut previous: Option<&str> = None;
467 for (index, (version, names)) in nodes.iter().enumerate() {
468 let spelled = &spellings[version];
469 writeln!(versions, "{spelled} {{\n global:").expect("String write");
470 for name in names {
471 writeln!(versions, " {name};").expect("String write");
472 }
473 if index == 0 {
474 versions.push_str(" local: *;\n");
475 }
476 match previous {
477 Some(parent) if chained => writeln!(versions, "}} {parent};").expect("String write"),
478 _ => versions.push_str("};\n"),
479 }
480 previous = Some(spelled);
481 }
482 Ok(Sources {
483 assembly,
484 versions,
485 selected,
486 })
487}