use crate::error::{Error, Result};
use gimli::{Dwarf, EndianSlice, RunTimeEndian, SectionId};
use memmap2::Mmap;
use object::{Object, ObjectSection};
use std::borrow::Cow;
use std::collections::HashMap;
use std::fs::File;
use std::path::Path;
use std::pin::Pin;
pub struct BinaryData {
pub mmap: Mmap,
}
pub type DwarfSlice<'a> = EndianSlice<'a, RunTimeEndian>;
pub struct DecompressedSections {
sections: HashMap<&'static str, Vec<u8>>,
}
impl DecompressedSections {
fn new() -> Pin<Box<Self>> {
Box::pin(Self { sections: HashMap::new() })
}
fn insert(self: &mut Pin<Box<Self>>, name: &'static str, data: Vec<u8>) {
unsafe { self.as_mut().get_unchecked_mut() }.sections.insert(name, data);
}
fn get(&self, name: &str) -> Option<&[u8]> {
self.sections.get(name).map(|v| v.as_slice())
}
}
pub struct LoadedDwarf<'a> {
pub dwarf: Dwarf<DwarfSlice<'a>>,
pub address_size: u8,
pub endian: RunTimeEndian,
_decompressed_sections: Pin<Box<DecompressedSections>>,
}
const DEBUG_SECTIONS: &[&str] = &[
"abbrev",
"addr",
"aranges",
"info",
"line",
"line_str",
"loc",
"loclists",
"ranges",
"rnglists",
"str",
"str_offsets",
"types",
];
impl BinaryData {
pub fn load(path: &Path) -> Result<Self> {
let file = File::open(path)?;
let mmap = unsafe { Mmap::map(&file)? };
Ok(Self { mmap })
}
pub fn load_dwarf(&self) -> Result<LoadedDwarf<'_>> {
let object = object::File::parse(&*self.mmap)?;
if !matches!(
object.format(),
object::BinaryFormat::Elf | object::BinaryFormat::MachO | object::BinaryFormat::Pe
) {
return Err(Error::UnsupportedFormat);
}
let endian =
if object.is_little_endian() { RunTimeEndian::Little } else { RunTimeEndian::Big };
let mut decompressed_sections = DecompressedSections::new();
for &base_name in DEBUG_SECTIONS {
let debug_name = format!(".debug_{}", base_name);
let zdebug_name = format!(".zdebug_{}", base_name);
for name in [&debug_name, &zdebug_name] {
if let Some(section) = object.section_by_name(name) {
if let Ok(Cow::Owned(vec)) = section.uncompressed_data() {
let static_name: &'static str = match name.as_str() {
n if n == debug_name => leak_section_name(&debug_name),
_ => leak_section_name(&zdebug_name),
};
decompressed_sections.insert(static_name, vec);
}
}
}
}
let decompressed_ptr = &*decompressed_sections as *const DecompressedSections;
let load_section = |id: SectionId| -> std::result::Result<DwarfSlice<'_>, gimli::Error> {
let section_name = id.name();
let zdebug_name = section_name.replace(".debug_", ".zdebug_");
let try_load = |name: &str| -> Option<&[u8]> {
let decompressed = unsafe { &*decompressed_ptr };
if let Some(slice) = decompressed.get(name) {
return Some(slice);
}
object.section_by_name(name).and_then(|s| s.uncompressed_data().ok()).and_then(
|data| match data {
Cow::Borrowed(b) => Some(b),
Cow::Owned(_) => None,
},
)
};
let slice = try_load(section_name).or_else(|| try_load(&zdebug_name)).unwrap_or(&[]);
Ok(EndianSlice::new(slice, endian))
};
let dwarf = Dwarf::load(load_section).map_err(|e| Error::Dwarf(e.to_string()))?;
let mut units = dwarf.units();
if units.next().map_err(|e| Error::Dwarf(e.to_string()))?.is_none() {
return Err(Error::NoDebugInfo);
}
Ok(LoadedDwarf {
dwarf,
address_size: if object.is_64() { 8 } else { 4 },
endian,
_decompressed_sections: decompressed_sections,
})
}
}
fn leak_section_name(name: &str) -> &'static str {
use std::sync::OnceLock;
static CACHE: OnceLock<std::sync::Mutex<HashMap<String, &'static str>>> = OnceLock::new();
let cache = CACHE.get_or_init(|| std::sync::Mutex::new(HashMap::new()));
let mut guard = cache.lock().unwrap_or_else(|e| e.into_inner());
if let Some(&cached) = guard.get(name) {
return cached;
}
let leaked: &'static str = Box::leak(name.to_string().into_boxed_str());
guard.insert(name.to_string(), leaked);
leaked
}