use std::borrow::Cow;
use std::ffi::OsStr;
use std::fs::File;
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::path::PathBuf;
use addr2line::gimli;
use addr2line::gimli::EndianSlice;
use addr2line::gimli::RunTimeEndian as Endian;
use memmap2::Mmap;
use object::Object as _;
use object::ObjectSegment;
use object::SymbolMapName;
use typed_arena::Arena;
type SectionStash = Arena<Vec<u8>>;
struct Context<'mmap> {
dwarf: addr2line::Context<EndianSlice<'mmap, Endian>>,
symbol_map: object::SymbolMap<SymbolMapName<'mmap>>,
base_addr: u64,
_object: object::File<'mmap>,
}
pub struct Symbols {
_mmap: Mmap,
context: Context<'static>,
symbol_data: SectionStash,
}
unsafe impl Sync for Symbols {}
enum Either<A, B> {
A(A),
B(B),
}
impl Symbols {
fn load_with<F>(mmap: Mmap, func: F) -> anyhow::Result<Self>
where
F: for<'a> FnOnce(&'a [u8], &'a SectionStash) -> anyhow::Result<Context<'a>>,
{
Self::load_either_with(mmap, move |mmap, symbol_data| {
let context = func(mmap, symbol_data)?;
Ok(Either::B(context))
})
}
fn load_either_with<F>(mmap: Mmap, func: F) -> anyhow::Result<Self>
where
F: for<'a> FnOnce(&'a [u8], &'a SectionStash) -> anyhow::Result<Either<Self, Context<'a>>>,
{
let symbol_data = SectionStash::new();
let context = match func(&mmap, &symbol_data)? {
Either::A(symbols) => return Ok(symbols),
Either::B(context) => context,
};
let context = unsafe { core::mem::transmute::<Context<'_>, Context<'static>>(context) };
Ok(Self {
_mmap: mmap,
context,
symbol_data,
})
}
fn load_path_with<F>(path: &Path, func: F) -> anyhow::Result<Self>
where
F: for<'a> FnOnce(&'a [u8], &'a SectionStash) -> anyhow::Result<Context<'a>>,
{
let mmap = mmap_path(path)?;
Self::load_with(mmap, func)
}
fn load_either_path_with<F>(path: &Path, func: F) -> anyhow::Result<Self>
where
F: for<'a> FnOnce(&'a [u8], &'a SectionStash) -> anyhow::Result<Either<Self, Context<'a>>>,
{
let mmap = mmap_path(path)?;
Self::load_either_with(mmap, func)
}
pub fn load(path: &Path) -> Result<Self, anyhow::Error> {
Self::load_either_path_with(path, |mmap, symbol_data| {
let object = object::File::parse(mmap)?;
let debuglink = object
.gnu_debuglink()?
.map(|(data, _)| Path::new(OsStr::from_bytes(data)))
.and_then(|debuglink| locate_debuglink(path, debuglink));
if let Some(path) = debuglink {
return Self::load_debug(&path).map(Either::A);
}
let context = Context::load(object, symbol_data)?;
Ok(Either::B(context))
})
}
fn load_debug(path: &Path) -> anyhow::Result<Self> {
Self::load_path_with(path, |mmap, symbol_data| {
let object = object::File::parse(mmap)?;
let context = Context::load(object, symbol_data)?;
Ok(context)
})
}
pub fn base_addr(&self) -> u64 {
self.context.base_addr
}
pub fn find_frames(
&self,
probe: u64,
) -> addr2line::LookupResult<
impl addr2line::LookupContinuation<
Output = Result<
addr2line::FrameIter<'_, EndianSlice<'static, Endian>>,
gimli::read::Error,
>,
>,
> {
self.context.dwarf.find_frames(probe + self.base_addr())
}
pub fn find_symbol(&self, probe: u64) -> Option<SymbolMapName<'_>> {
self.context
.symbol_map
.before(probe + self.base_addr())
.copied()
}
pub fn bytes_used(&self) -> usize {
self.symbol_data.len()
}
}
impl<'mmap> Context<'mmap> {
fn load(object: object::File<'mmap>, symbol_data: &'mmap SectionStash) -> anyhow::Result<Self> {
let base_addr = object.segments().next().map_or(0, |seg| seg.address());
let endian = if object.is_little_endian() {
Endian::Little
} else {
Endian::Big
};
let dwarf = {
let mut symbol_loader = |id| load_file_section(id, &object, endian, symbol_data);
gimli::Dwarf::load(&mut symbol_loader)?
};
let symbol_map = object.symbol_map();
let context = Context {
dwarf: addr2line::Context::from_dwarf(dwarf)?,
symbol_map,
base_addr,
_object: object,
};
Ok(context)
}
}
fn mmap_path(path: &Path) -> io::Result<Mmap> {
let _open = crate::process::launch_window::TransientOpen::begin();
let f = File::open(path)?;
unsafe { Mmap::map(&f) }
}
fn load_file_section<'a, 'b>(
id: gimli::SectionId,
file: &object::File<'a>,
endian: gimli::RunTimeEndian,
data: &'b SectionStash,
) -> object::Result<gimli::EndianSlice<'b, gimli::RunTimeEndian>>
where
'a: 'b,
{
use object::Object;
use object::ObjectSection;
match file.section_by_name(id.name()) {
Some(section) => match section.uncompressed_data()? {
Cow::Borrowed(bytes) => Ok(gimli::EndianSlice::new(bytes, endian)),
Cow::Owned(bytes) => Ok(gimli::EndianSlice::new(data.alloc(bytes), endian)),
},
None => Ok(gimli::EndianSlice::new(&[][..], endian)),
}
}
fn locate_debuglink(elf_path: &Path, debuglink: &Path) -> Option<PathBuf> {
const DEBUG_PATH: &str = "/usr/lib/debug";
let elf_path = elf_path.canonicalize().ok()?;
let parent = elf_path.parent()?;
let path = parent.join(debuglink);
if path != elf_path && path.is_file() {
return Some(path);
}
let mut path = parent.join(".debug");
path.push(debuglink);
if path != elf_path && path.is_file() {
return Some(path);
}
let mut path = Path::new(DEBUG_PATH).join(parent.strip_prefix("/").unwrap());
path.push(debuglink);
if path.is_file() {
return Some(path);
}
None
}