1use std::path::Path;
14use std::sync::Arc;
15
16use color_eyre::Result;
17
18use crate::error_display::{FileError, in_file};
19use object::{Object, ObjectSection, ObjectSymbol, SectionFlags, SymbolFlags, SymbolSection};
20use polars::prelude::*;
21
22use crate::model_files::MetaValue;
23use crate::sqlite::Table;
24use crate::text_formats::Detail;
25
26pub(crate) const READER: crate::readers::Reader = crate::readers::Reader {
28 scan,
29 signatures: &[crate::readers::Signature {
31 says: |head, _| looks_like(head),
32 kind: crate::readers::Kind::Magic,
33 trusted: crate::readers::Trusted {
34 listing: false,
35 tables: true,
36 ..crate::readers::EVERYWHERE
37 },
38 }],
39 tables: Some(|_| Ok(tables())),
40 ..crate::readers::BASE
41};
42
43pub const MAGIC: &[u8; 4] = b"\x7fELF";
45
46pub const SYMBOLS: &str = "symbols";
48pub const SECTIONS: &str = "sections";
49
50const MAX_SYMBOLS: usize = 10_000_000;
52
53const SHF_WRITE: u64 = 0x1;
55const SHF_ALLOC: u64 = 0x2;
56
57pub fn looks_like(head: &[u8]) -> bool {
59 head.starts_with(MAGIC)
60}
61
62pub fn tables() -> Vec<Table> {
64 let table = |name: &str, columns: &[&str]| Table {
65 name: name.to_string(),
66 kind: "table".to_string(),
67 internal: false,
68 columns: columns
69 .iter()
70 .map(|c| (c.to_string(), String::new()))
71 .collect(),
72 };
73 vec![
74 table(
75 SYMBOLS,
76 &["name", "addr", "size", "kind", "bind", "section", "region"],
77 ),
78 table(
79 SECTIONS,
80 &["name", "addr", "size", "flags", "kind", "region"],
81 ),
82 ]
83}
84
85fn region(flags: u64) -> Option<&'static str> {
88 match (flags & SHF_ALLOC != 0, flags & SHF_WRITE != 0) {
89 (false, _) => None,
90 (true, true) => Some("ram"),
91 (true, false) => Some("flash"),
92 }
93}
94
95fn flags_text(flags: u64) -> String {
98 const LETTERS: [(u64, char); 11] = [
99 (0x1, 'W'),
100 (0x2, 'A'),
101 (0x4, 'X'),
102 (0x10, 'M'),
103 (0x20, 'S'),
104 (0x40, 'I'),
105 (0x80, 'L'),
106 (0x100, 'O'),
107 (0x200, 'G'),
108 (0x400, 'T'),
109 (0x800, 'C'),
110 ];
111 LETTERS
112 .iter()
113 .filter(|(bit, _)| flags & bit != 0)
114 .map(|(_, c)| *c)
115 .collect()
116}
117
118fn sh_flags(flags: SectionFlags) -> u64 {
119 match flags {
120 SectionFlags::Elf { sh_flags } => sh_flags,
121 _ => 0,
122 }
123}
124
125fn symbol_kind(st_info: u8) -> &'static str {
127 match st_info & 0xf {
128 0 => "notype",
129 1 => "object",
130 2 => "func",
131 3 => "section",
132 4 => "file",
133 5 => "common",
134 6 => "tls",
135 10 => "ifunc",
136 _ => "other",
137 }
138}
139
140fn symbol_bind(st_info: u8) -> &'static str {
142 match st_info >> 4 {
143 0 => "local",
144 1 => "global",
145 2 => "weak",
146 10 => "unique",
147 _ => "other",
148 }
149}
150
151pub fn demangle(name: &str) -> String {
154 match rustc_demangle::try_demangle(name) {
155 Ok(demangled) => format!("{demangled:#}"),
156 Err(_) => name.to_string(),
157 }
158}
159
160pub struct Elf {
162 pub symbols: DataFrame,
163 pub sections: DataFrame,
164 pub detail: Detail,
165 pub left_out: usize,
167}
168
169pub fn read(data: &[u8]) -> std::result::Result<Elf, String> {
171 if !looks_like(data) {
172 return Err("not an ELF file: no \\x7fELF at the start".into());
173 }
174 let file = object::File::parse(data).map_err(|e| format!("not a readable ELF file: {e}"))?;
175
176 let mut section_names: Vec<String> = Vec::new();
177 let mut section_flags: Vec<u64> = Vec::new();
178 let (mut s_name, mut s_addr, mut s_size, mut s_flags, mut s_kind, mut s_region) = (
179 Vec::new(),
180 Vec::new(),
181 Vec::new(),
182 Vec::new(),
183 Vec::new(),
184 Vec::new(),
185 );
186 let (mut flash, mut ram) = (0u64, 0u64);
187 for section in file.sections() {
188 let index = section.index().0;
189 if section_names.len() <= index {
190 section_names.resize(index + 1, String::new());
191 section_flags.resize(index + 1, 0);
192 }
193 let name = section.name().unwrap_or_default().to_string();
194 let flags = sh_flags(section.flags());
195 let place = region(flags);
196 match place {
197 Some("ram") => ram = ram.saturating_add(section.size()),
198 Some(_) => flash = flash.saturating_add(section.size()),
199 None => {}
200 }
201 section_names[index] = name.clone();
202 section_flags[index] = flags;
203 s_name.push(name);
204 s_addr.push(section.address());
205 s_size.push(section.size());
206 s_flags.push(flags_text(flags));
207 s_kind.push(format!("{:?}", section.kind()).to_ascii_lowercase());
208 s_region.push(place);
209 }
210
211 let mut symbols: Vec<_> = file.symbols().collect();
213 if symbols.is_empty() {
214 symbols = file.dynamic_symbols().collect();
215 }
216 let total = symbols.len();
217 symbols.truncate(MAX_SYMBOLS);
218 let rows = symbols.len();
219 let (mut name, mut addr, mut size, mut kind, mut bind, mut section, mut place) = (
220 Vec::with_capacity(rows),
221 Vec::with_capacity(rows),
222 Vec::with_capacity(rows),
223 Vec::with_capacity(rows),
224 Vec::with_capacity(rows),
225 Vec::with_capacity(rows),
226 Vec::with_capacity(rows),
227 );
228 for symbol in &symbols {
229 let st_info = match symbol.flags() {
230 SymbolFlags::Elf { st_info, .. } => st_info,
231 _ => 0,
232 };
233 name.push(demangle(symbol.name().unwrap_or_default()));
234 addr.push(symbol.address());
235 size.push(symbol.size());
236 kind.push(symbol_kind(st_info));
237 bind.push(symbol_bind(st_info));
238 let (in_section, in_region) = match symbol.section() {
239 SymbolSection::Section(index) => (
240 section_names.get(index.0).cloned(),
241 section_flags.get(index.0).copied().and_then(region),
242 ),
243 SymbolSection::Undefined => (Some("UND".to_string()), None),
244 SymbolSection::Absolute => (Some("ABS".to_string()), None),
245 SymbolSection::Common => (Some("COMMON".to_string()), None),
246 _ => (None, None),
247 };
248 section.push(in_section);
249 place.push(in_region);
250 }
251 let symbols = df!(
252 "name" => name,
253 "addr" => addr,
254 "size" => size,
255 "kind" => kind,
256 "bind" => bind,
257 "section" => section,
258 "region" => place,
259 )
260 .map_err(|e| e.to_string())?;
261 let sections = df!(
262 "name" => s_name,
263 "addr" => s_addr,
264 "size" => s_size,
265 "flags" => s_flags,
266 "kind" => s_kind,
267 "region" => s_region,
268 )
269 .map_err(|e| e.to_string())?;
270
271 let group = crate::numfmt::group_chrome;
272 let mut lines = vec![
273 format!(
274 "Class: {}, {} endian",
275 if file.is_64() { "64-bit" } else { "32-bit" },
276 if file.is_little_endian() {
277 "little"
278 } else {
279 "big"
280 }
281 ),
282 format!("Machine: {:?}", file.architecture()),
283 format!("Type: {:?}", file.kind()),
284 format!("Entry: 0x{:x}", file.entry()),
285 format!(
286 "Flash: {} bytes in loaded, unwritten sections",
287 group(usize::try_from(flash).unwrap_or(usize::MAX))
288 ),
289 format!(
290 "RAM: {} bytes in written sections",
291 group(usize::try_from(ram).unwrap_or(usize::MAX))
292 ),
293 format!("Symbols: {}", group(total)),
294 ];
295 if total > rows {
296 lines.push(format!("The first {} symbols are read.", group(rows)));
297 }
298 let list = crate::text_formats::capped_list(
299 (0..sections.height()).map(|i| {
300 let get = |c: &str| sections.column(c).ok().and_then(|c| c.get(i).ok());
301 let text = format!(
302 "0x{:x} {} bytes {}",
303 get("addr")
304 .and_then(|v| v.extract::<u64>())
305 .unwrap_or_default(),
306 get("size")
307 .and_then(|v| v.extract::<u64>())
308 .unwrap_or_default(),
309 get("flags")
310 .and_then(|v| v.get_str().map(str::to_string))
311 .unwrap_or_default()
312 );
313 let name = get("name")
314 .and_then(|v| v.get_str().map(str::to_string))
315 .unwrap_or_default();
316 (name, MetaValue::Text(text))
317 }),
318 sections.height(),
319 );
320 Ok(Elf {
321 symbols,
322 sections,
323 detail: Detail {
324 tab: crate::text_formats::tab(crate::FileFormat::Elf),
325 lines,
326 list_title: "Sections",
327 list,
328 first: false,
329 ..Default::default()
330 },
331 left_out: total - rows,
332 })
333}
334
335pub fn open(path: &Path, wanted: Option<&str>) -> Result<(LazyFrame, crate::members::Opened)> {
338 let tables = tables();
339 let picked = match wanted {
340 None => SYMBOLS.to_string(),
341 Some(_) => match crate::members::pick(tables.clone(), wanted, path, "")? {
342 crate::sqlite::Pick::One(table) => table.name,
343 crate::sqlite::Pick::Several(_) => SYMBOLS.to_string(),
344 },
345 };
346 let bytes = crate::fixed_records::Bytes::map(path).map_err(|e| in_file(path, e.into()))?;
347 let elf = read(bytes.as_slice()).map_err(|e| FileError::new(path, e))?;
348 let mut notes = Vec::new();
349 if elf.left_out > 0 {
350 notes.push(crate::text_formats::note(
351 format!(
352 "{} symbols left out: past the first {}",
353 crate::numfmt::group_chrome(elf.left_out),
354 crate::numfmt::group_chrome(MAX_SYMBOLS)
355 ),
356 "the symbol table".to_string(),
357 ));
358 }
359 let df = if picked == SECTIONS {
360 elf.sections
361 } else {
362 elf.symbols
363 };
364 Ok((
365 df.lazy(),
366 crate::members::Opened {
367 window: None,
368 detail: Some(Arc::new(elf.detail)),
369 other_tables: crate::members::others(&tables, &picked),
370 notes,
371 units: Vec::new(),
372 indexing: None,
373 numbering: None,
374 },
375 ))
376}
377
378fn scan(input: crate::readers::ScanIn<'_>) -> Result<crate::scan::Scan> {
380 let (lf, opened) = open(input.path(), input.options.table.as_deref())?;
381 input.report.opened = Some(Arc::new(opened));
382 Ok(lf.into())
383}
384
385#[cfg(test)]
386pub(crate) mod tests {
387 use super::*;
388
389 #[test]
391 fn errors_name_the_file() {
392 crate::readers::bad_input::each_names_its_file(
393 crate::FileFormat::Elf,
394 &[
395 ("text.elf", b"hello there", "Not an ELF file"),
396 (
397 "cut.elf",
398 b"\x7fELF\x02\x01\x01\0",
399 "Not a readable ELF file",
400 ),
401 ],
402 );
403 }
404
405 type SectionHeader = (u32, u32, u64, u64, u64, u64, u32, u32, u64, u64);
407
408 pub(crate) fn tiny() -> Vec<u8> {
412 let shstr = b"\0.text\0.rodata\0.data\0.bss\0.symtab\0.strtab\0.shstrtab\0";
413 let name_at = |n: &[u8]| {
414 shstr
415 .windows(n.len())
416 .position(|w| w == n)
417 .expect("a section name") as u32
418 };
419 let strtab =
420 b"\0main\0TABLE\0counter\0buffer\0_ZN4core3fmt5write17h0123456789abcdefE\0weak_hook\0";
421 let str_at = |n: &[u8]| {
422 strtab
423 .windows(n.len())
424 .position(|w| w == n)
425 .expect("a symbol name") as u32
426 };
427 let syms: Vec<(u32, u64, u64, u8, u16)> = vec![
429 (0, 0, 0, 0, 0),
430 (str_at(b"main\0"), 0x1000, 64, 0x12, 1),
431 (str_at(b"TABLE\0"), 0x2000, 256, 0x11, 2),
432 (str_at(b"counter\0"), 0x3000, 4, 0x11, 3),
433 (str_at(b"buffer\0"), 0x3010, 1024, 0x01, 4),
434 (
435 str_at(b"_ZN4core3fmt5write17h0123456789abcdefE\0"),
436 0x1040,
437 128,
438 0x12,
439 1,
440 ),
441 (str_at(b"weak_hook\0"), 0x10c0, 8, 0x22, 1),
442 ];
443 let mut symtab = Vec::new();
444 for (name, value, size, info, shndx) in &syms {
445 symtab.extend(name.to_le_bytes());
446 symtab.push(*info);
447 symtab.push(0);
448 symtab.extend(shndx.to_le_bytes());
449 symtab.extend(value.to_le_bytes());
450 symtab.extend(size.to_le_bytes());
451 }
452 let text = vec![0xc3u8; 0xc8];
453 let rodata = vec![1u8; 256];
454 let data = vec![2u8; 16];
455 let mut body = Vec::new();
457 let mut place = |bytes: &[u8]| {
458 let at = 64 + body.len() as u64;
459 body.extend_from_slice(bytes);
460 while body.len() % 8 != 0 {
461 body.push(0);
462 }
463 at
464 };
465 let text_at = place(&text);
466 let rodata_at = place(&rodata);
467 let data_at = place(&data);
468 let symtab_at = place(&symtab);
469 let strtab_at = place(strtab);
470 let shstr_at = place(shstr);
471 let shoff = 64 + body.len() as u64;
472 let sections: Vec<SectionHeader> = vec![
473 (0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
474 (
475 name_at(b".text\0"),
476 1,
477 0x6,
478 0x1000,
479 text_at,
480 text.len() as u64,
481 0,
482 0,
483 16,
484 0,
485 ),
486 (
487 name_at(b".rodata\0"),
488 1,
489 0x2,
490 0x2000,
491 rodata_at,
492 256,
493 0,
494 0,
495 8,
496 0,
497 ),
498 (name_at(b".data\0"), 1, 0x3, 0x3000, data_at, 16, 0, 0, 8, 0),
499 (
500 name_at(b".bss\0"),
501 8,
502 0x3,
503 0x3010,
504 data_at + 16,
505 1024,
506 0,
507 0,
508 8,
509 0,
510 ),
511 (
512 name_at(b".symtab\0"),
513 2,
514 0,
515 0,
516 symtab_at,
517 symtab.len() as u64,
518 6,
519 1,
520 8,
521 24,
522 ),
523 (
524 name_at(b".strtab\0"),
525 3,
526 0,
527 0,
528 strtab_at,
529 strtab.len() as u64,
530 0,
531 0,
532 1,
533 0,
534 ),
535 (
536 name_at(b".shstrtab\0"),
537 3,
538 0,
539 0,
540 shstr_at,
541 shstr.len() as u64,
542 0,
543 0,
544 1,
545 0,
546 ),
547 ];
548 let mut out = Vec::new();
549 out.extend(MAGIC);
550 out.extend([2, 1, 1, 0]);
551 out.extend([0u8; 8]);
552 out.extend(2u16.to_le_bytes()); out.extend(62u16.to_le_bytes()); out.extend(1u32.to_le_bytes());
555 out.extend(0x1000u64.to_le_bytes()); out.extend(0u64.to_le_bytes()); out.extend(shoff.to_le_bytes());
558 out.extend(0u32.to_le_bytes());
559 out.extend(64u16.to_le_bytes());
560 out.extend(56u16.to_le_bytes());
561 out.extend(0u16.to_le_bytes());
562 out.extend(64u16.to_le_bytes());
563 out.extend((sections.len() as u16).to_le_bytes());
564 out.extend(7u16.to_le_bytes()); out.extend(body);
566 for (name, kind, flags, addr, offset, size, link, info, align, entsize) in sections {
567 out.extend(name.to_le_bytes());
568 out.extend(kind.to_le_bytes());
569 out.extend(flags.to_le_bytes());
570 out.extend(addr.to_le_bytes());
571 out.extend(offset.to_le_bytes());
572 out.extend(size.to_le_bytes());
573 out.extend(link.to_le_bytes());
574 out.extend(info.to_le_bytes());
575 out.extend(align.to_le_bytes());
576 out.extend(entsize.to_le_bytes());
577 }
578 out
579 }
580
581 #[test]
582 fn symbols_with_their_section_and_region() {
583 let elf = read(&tiny()).unwrap();
584 let s = &elf.symbols;
585 let text = |c: &str| -> Vec<Option<String>> {
586 s.column(c)
587 .unwrap()
588 .str()
589 .unwrap()
590 .iter()
591 .map(|v| v.map(str::to_string))
592 .collect()
593 };
594 let names = text("name");
595 let at = |n: &str| names.iter().position(|v| v.as_deref() == Some(n)).unwrap();
596 assert!(names.contains(&Some("core::fmt::write".to_string())));
597 let region = text("region");
598 let section = text("section");
599 let kind = text("kind");
600 let bind = text("bind");
601 assert_eq!(region[at("main")].as_deref(), Some("flash"));
602 assert_eq!(region[at("TABLE")].as_deref(), Some("flash"));
603 assert_eq!(region[at("counter")].as_deref(), Some("ram"));
604 assert_eq!(section[at("buffer")].as_deref(), Some(".bss"));
605 assert_eq!(region[at("buffer")].as_deref(), Some("ram"));
606 assert_eq!(kind[at("main")].as_deref(), Some("func"));
607 assert_eq!(bind[at("buffer")].as_deref(), Some("local"));
608 assert_eq!(bind[at("weak_hook")].as_deref(), Some("weak"));
609 let sizes = s.column("size").unwrap().u64().unwrap();
610 assert_eq!(sizes.get(at("buffer")), Some(1024));
611 let flags: Vec<_> = elf
612 .sections
613 .column("flags")
614 .unwrap()
615 .str()
616 .unwrap()
617 .iter()
618 .map(|v| v.unwrap_or_default().to_string())
619 .collect();
620 assert!(flags.contains(&"AX".to_string()) && flags.contains(&"WA".to_string()));
621 assert!(
622 elf.detail
623 .lines
624 .iter()
625 .any(|l| l.starts_with("RAM: 1,040 bytes")),
626 "{:?}",
627 elf.detail.lines
628 );
629 }
630
631 #[test]
632 fn garbage_is_refused() {
633 assert!(read(b"\x7fELF\x02\x01\x01").is_err());
634 assert!(read(b"MZ").is_err());
635 let mut cut = tiny();
636 cut.truncate(cut.len() - 100);
637 assert!(read(&cut).is_err());
638 }
639}