1use color_eyre::Result;
14
15use std::sync::Arc;
16
17use crate::formats::columns::{Builder, Cell, Kind};
18use object::{Object, ObjectSection, ObjectSymbol, SectionFlags, SymbolFlags, SymbolSection};
19use polars::prelude::*;
20
21use crate::formats::model_files::MetaValue;
22use crate::formats::sqlite::Table;
23use crate::formats::text_formats::Detail;
24
25pub(crate) const READER: crate::formats::readers::Reader = crate::formats::readers::Reader {
27 scan,
28 signatures: &[crate::formats::readers::Signature {
30 says: |head, _| looks_like(head),
31 kind: crate::formats::readers::Kind::Magic,
32 trusted: crate::formats::readers::Trusted {
33 listing: false,
34 tables: true,
35 ..crate::formats::readers::EVERYWHERE
36 },
37 }],
38 tables: Some(|_| Ok(tables())),
39 ..crate::formats::readers::BASE
40};
41
42pub const MAGIC: &[u8; 4] = b"\x7fELF";
44
45pub const SYMBOLS: &str = "symbols";
47pub const SECTIONS: &str = "sections";
48
49const SHF_WRITE: u64 = 0x1;
51const SHF_ALLOC: u64 = 0x2;
52
53pub fn looks_like(head: &[u8]) -> bool {
55 head.starts_with(MAGIC)
56}
57
58pub fn tables() -> Vec<Table> {
60 let symbols = ["name", "addr", "size", "kind", "bind", "section", "region"];
61 let sections = ["name", "addr", "size", "flags", "kind", "region"];
62 vec![
63 Table::plain(SYMBOLS, "table", symbols),
64 Table::plain(SECTIONS, "table", sections),
65 ]
66}
67
68fn region(flags: u64) -> Option<&'static str> {
71 match (flags & SHF_ALLOC != 0, flags & SHF_WRITE != 0) {
72 (false, _) => None,
73 (true, true) => Some("ram"),
74 (true, false) => Some("flash"),
75 }
76}
77
78fn flags_text(flags: u64) -> String {
81 const LETTERS: [(u64, char); 11] = [
82 (0x1, 'W'),
83 (0x2, 'A'),
84 (0x4, 'X'),
85 (0x10, 'M'),
86 (0x20, 'S'),
87 (0x40, 'I'),
88 (0x80, 'L'),
89 (0x100, 'O'),
90 (0x200, 'G'),
91 (0x400, 'T'),
92 (0x800, 'C'),
93 ];
94 LETTERS
95 .iter()
96 .filter(|(bit, _)| flags & bit != 0)
97 .map(|(_, c)| *c)
98 .collect()
99}
100
101fn sh_flags(flags: SectionFlags) -> u64 {
102 match flags {
103 SectionFlags::Elf { sh_flags } => sh_flags,
104 _ => 0,
105 }
106}
107
108fn symbol_kind(st_info: u8) -> &'static str {
110 match st_info & 0xf {
111 0 => "notype",
112 1 => "object",
113 2 => "func",
114 3 => "section",
115 4 => "file",
116 5 => "common",
117 6 => "tls",
118 10 => "ifunc",
119 _ => "other",
120 }
121}
122
123fn symbol_bind(st_info: u8) -> &'static str {
125 match st_info >> 4 {
126 0 => "local",
127 1 => "global",
128 2 => "weak",
129 10 => "unique",
130 _ => "other",
131 }
132}
133
134pub fn demangle(name: &str) -> String {
137 match rustc_demangle::try_demangle(name) {
138 Ok(demangled) => format!("{demangled:#}"),
139 Err(_) => name.to_string(),
140 }
141}
142
143pub struct Elf {
145 pub symbols: DataFrame,
146 pub sections: DataFrame,
147 pub detail: Detail,
148 pub left_out: usize,
150}
151
152pub fn read(data: &[u8]) -> std::result::Result<Elf, String> {
154 if !looks_like(data) {
155 return Err("not an ELF file: no \\x7fELF at the start".into());
156 }
157 let file = object::File::parse(data).map_err(|e| format!("not a readable ELF file: {e}"))?;
158
159 let mut section_names: Vec<Option<Arc<str>>> = Vec::new();
161 let mut section_flags: Vec<u64> = Vec::new();
162 let mut sections = Builder::new(&[
163 ("name", Kind::Str),
164 ("addr", Kind::U64),
165 ("size", Kind::U64),
166 ("flags", Kind::Str),
167 ("kind", Kind::Str),
168 ("region", Kind::Label),
169 ]);
170 let (mut flash, mut ram) = (0u64, 0u64);
171 for section in file.sections() {
172 let index = section.index().0;
173 if section_names.len() <= index {
174 section_names.resize(index + 1, Some(Arc::from("")));
175 section_flags.resize(index + 1, 0);
176 }
177 let name = section.name().unwrap_or_default();
178 let flags = sh_flags(section.flags());
179 let place = region(flags);
180 match place {
181 Some("ram") => ram = ram.saturating_add(section.size()),
182 Some(_) => flash = flash.saturating_add(section.size()),
183 None => {}
184 }
185 section_names[index] = Some(name.into());
186 section_flags[index] = flags;
187 sections.push([
188 Cell::Str(Some(name.to_string())),
189 Cell::U64(Some(section.address())),
190 Cell::U64(Some(section.size())),
191 Cell::Str(Some(flags_text(flags))),
192 Cell::Str(Some(format!("{:?}", section.kind()).to_ascii_lowercase())),
193 Cell::Label(place),
194 ]);
195 }
196
197 let mut symbols: Vec<_> = file.symbols().collect();
199 if symbols.is_empty() {
200 symbols = file.dynamic_symbols().collect();
201 }
202 let total = symbols.len();
203 symbols.truncate(crate::limits::get().elf_symbols);
204 let rows = symbols.len();
205 let mut table = Builder::new(&[
206 ("name", Kind::Str),
207 ("addr", Kind::U64),
208 ("size", Kind::U64),
209 ("kind", Kind::Label),
210 ("bind", Kind::Label),
211 ("section", Kind::Shared),
212 ("region", Kind::Label),
213 ]);
214 let pseudo = |name: &str| Some(Arc::<str>::from(name));
215 for symbol in &symbols {
216 let st_info = match symbol.flags() {
217 SymbolFlags::Elf { st_info, .. } => st_info,
218 _ => 0,
219 };
220 let (in_section, in_region) = match symbol.section() {
221 SymbolSection::Section(index) => (
222 section_names.get(index.0).cloned().flatten(),
223 section_flags.get(index.0).copied().and_then(region),
224 ),
225 SymbolSection::Undefined => (pseudo("UND"), None),
226 SymbolSection::Absolute => (pseudo("ABS"), None),
227 SymbolSection::Common => (pseudo("COMMON"), None),
228 _ => (None, None),
229 };
230 table.push([
231 Cell::Str(Some(demangle(symbol.name().unwrap_or_default()))),
232 Cell::U64(Some(symbol.address())),
233 Cell::U64(Some(symbol.size())),
234 Cell::Label(Some(symbol_kind(st_info))),
235 Cell::Label(Some(symbol_bind(st_info))),
236 Cell::Shared(in_section),
237 Cell::Label(in_region),
238 ]);
239 }
240 let symbols = table.take().map_err(|e| e.to_string())?;
241 let sections = sections.take().map_err(|e| e.to_string())?;
242
243 let group = crate::numfmt::group_chrome;
244 let mut lines = vec![
245 format!(
246 "Class: {}, {} endian",
247 if file.is_64() { "64-bit" } else { "32-bit" },
248 if file.is_little_endian() {
249 "little"
250 } else {
251 "big"
252 }
253 ),
254 format!("Machine: {:?}", file.architecture()),
255 format!("Type: {:?}", file.kind()),
256 format!("Entry: 0x{:x}", file.entry()),
257 format!(
258 "Flash: {} bytes in loaded, unwritten sections",
259 group(usize::try_from(flash).unwrap_or(usize::MAX))
260 ),
261 format!(
262 "RAM: {} bytes in written sections",
263 group(usize::try_from(ram).unwrap_or(usize::MAX))
264 ),
265 format!("Symbols: {}", group(total)),
266 ];
267 if total > rows {
268 lines.push(format!("The first {} symbols are read.", group(rows)));
269 }
270 let list = crate::formats::text_formats::capped_list(
271 (0..sections.height()).map(|i| {
272 let get = |c: &str| sections.column(c).ok().and_then(|c| c.get(i).ok());
273 let text = format!(
274 "0x{:x} {} bytes {}",
275 get("addr")
276 .and_then(|v| v.extract::<u64>())
277 .unwrap_or_default(),
278 get("size")
279 .and_then(|v| v.extract::<u64>())
280 .unwrap_or_default(),
281 get("flags")
282 .and_then(|v| v.get_str().map(str::to_string))
283 .unwrap_or_default()
284 );
285 let name = get("name")
286 .and_then(|v| v.get_str().map(str::to_string))
287 .unwrap_or_default();
288 (name, MetaValue::Text(text))
289 }),
290 sections.height(),
291 );
292 Ok(Elf {
293 symbols,
294 sections,
295 detail: Detail {
296 tab: crate::formats::text_formats::tab(crate::FileFormat::Elf),
297 lines,
298 list_title: "Sections",
299 list,
300 first: false,
301 ..Default::default()
302 },
303 left_out: total - rows,
304 })
305}
306
307fn scan(input: crate::formats::readers::ScanIn<'_>) -> Result<crate::loading::scan::Scan> {
309 let path = input.path();
310 let wanted = input.options.table.as_deref();
311 let tables = tables();
312 let picked = match wanted {
313 None => SYMBOLS.to_string(),
314 Some(_) => match crate::formats::members::pick(tables.clone(), wanted, path, "")? {
315 crate::formats::sqlite::Pick::One(table) => table.name,
316 crate::formats::sqlite::Pick::Several(_) => SYMBOLS.to_string(),
317 },
318 };
319 let bytes = crate::formats::fixed_records::Bytes::map(path)?;
320 let elf = read(bytes.as_slice()).map_err(|e| color_eyre::eyre::eyre!(e))?;
321 let mut notes = Vec::new();
322 if elf.left_out > 0 {
323 notes.push(crate::limits::left_out(
324 &format!("{} symbols", crate::numfmt::group_chrome(elf.left_out)),
325 crate::limits::get().elf_symbols,
326 "elf_symbols",
327 ));
328 }
329 let df = if picked == SECTIONS {
330 elf.sections
331 } else {
332 elf.symbols
333 };
334 let opened = crate::formats::members::Opened::for_table(
335 elf.detail,
336 &tables,
337 &picked,
338 notes,
339 "the symbol table",
340 );
341 Ok(opened.scan(input, df.lazy()))
342}
343
344#[cfg(test)]
345mod tests {
346 use super::*;
347
348 #[test]
350 fn errors_name_the_file() {
351 crate::formats::readers::bad_input::each_names_its_file(
352 crate::FileFormat::Elf,
353 &[
354 ("text.elf", b"hello there", "Not an ELF file"),
355 (
356 "cut.elf",
357 b"\x7fELF\x02\x01\x01\0",
358 "Not a readable ELF file",
359 ),
360 ],
361 );
362 }
363
364 #[test]
365 fn symbols_with_their_section_and_region() {
366 let elf = read(&crate::tests::fixtures::elf()).unwrap();
367 let s = &elf.symbols;
368 let text = |c: &str| -> Vec<Option<String>> {
369 s.column(c)
370 .unwrap()
371 .str()
372 .unwrap()
373 .iter()
374 .map(|v| v.map(str::to_string))
375 .collect()
376 };
377 let names = text("name");
378 let at = |n: &str| names.iter().position(|v| v.as_deref() == Some(n)).unwrap();
379 assert!(names.contains(&Some("core::fmt::write".to_string())));
380 let region = text("region");
381 let section = text("section");
382 let kind = text("kind");
383 let bind = text("bind");
384 assert_eq!(region[at("main")].as_deref(), Some("flash"));
385 assert_eq!(region[at("TABLE")].as_deref(), Some("flash"));
386 assert_eq!(region[at("counter")].as_deref(), Some("ram"));
387 assert_eq!(section[at("buffer")].as_deref(), Some(".bss"));
388 assert_eq!(region[at("buffer")].as_deref(), Some("ram"));
389 assert_eq!(kind[at("main")].as_deref(), Some("func"));
390 assert_eq!(bind[at("buffer")].as_deref(), Some("local"));
391 assert_eq!(bind[at("weak_hook")].as_deref(), Some("weak"));
392 let sizes = s.column("size").unwrap().u64().unwrap();
393 assert_eq!(sizes.get(at("buffer")), Some(1024));
394 let flags: Vec<_> = elf
395 .sections
396 .column("flags")
397 .unwrap()
398 .str()
399 .unwrap()
400 .iter()
401 .map(|v| v.unwrap_or_default().to_string())
402 .collect();
403 assert!(flags.contains(&"AX".to_string()) && flags.contains(&"WA".to_string()));
404 assert!(
405 elf.detail
406 .lines
407 .iter()
408 .any(|l| l.starts_with("RAM: 1,040 bytes")),
409 "{:?}",
410 elf.detail.lines
411 );
412 }
413
414 #[test]
415 fn garbage_is_refused() {
416 assert!(read(b"\x7fELF\x02\x01\x01").is_err());
417 assert!(read(b"MZ").is_err());
418 let mut cut = crate::tests::fixtures::elf();
419 cut.truncate(cut.len() - 100);
420 assert!(read(&cut).is_err());
421 }
422}