mod cache;
mod library;
mod symbols;
use core::fmt;
use std::io;
use std::path::PathBuf;
use addr2line::LookupContinuation;
use addr2line::LookupResult;
use serde::Deserialize;
use serde::Serialize;
use self::cache::cache;
use self::library::Libraries;
use super::Pid;
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
pub struct Backtrace {
thread_id: Pid,
thread_name: Option<String>,
frames: Vec<Frame>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
pub struct PrettyBacktrace {
thread_id: Pid,
thread_name: Option<String>,
frames: Vec<PrettyFrame>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
pub struct Frame {
pub ip: u64,
pub is_signal: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
pub struct PrettyFrame {
frame: Frame,
symbol: Option<Symbol>,
locations: Vec<Location>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
pub struct Symbol {
pub name: String,
pub address: u64,
pub offset: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
pub struct Location {
file: PathBuf,
line: u32,
column: u32,
}
impl fmt::Display for Frame {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:#016x}: ???", self.ip)?;
if self.is_signal {
write!(f, " (in signal handler)")?;
}
Ok(())
}
}
impl fmt::Display for Location {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.column != 0 {
write!(f, "{}:{}:{}", self.file.display(), self.line, self.column)
} else {
write!(f, "{}:{}", self.file.display(), self.line)
}
}
}
impl fmt::Display for PrettyFrame {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(location) = self.locations.first() {
write!(f, "{} at {}", self.frame, location)
} else {
write!(f, "{}", self.frame)
}
}
}
impl fmt::Display for Symbol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} + {:#x}", self.name, self.offset)
}
}
impl Backtrace {
pub fn new(thread_id: Pid, frames: Vec<Frame>) -> Self {
Self {
thread_id,
thread_name: thread_name(thread_id).ok(),
frames,
}
}
pub fn pretty(&self) -> Result<PrettyBacktrace, anyhow::Error> {
let libraries = Libraries::new(self.thread_id)?;
let mut frames = Vec::new();
let mut cache = cache();
for frame in &self.frames {
let ip = frame.ip;
let mut locations = Vec::new();
let mut symbol = None;
if let Some((library, addr)) = libraries.ip_to_vaddr(ip) {
let symbols = cache.load(library)?;
let mut lookup_result = symbols.find_frames(addr);
if let Ok(mut source_frames) = loop {
match lookup_result {
LookupResult::Output(result) => break result,
LookupResult::Load {
load: _,
continuation,
} => {
lookup_result = continuation.resume(None);
}
}
} {
while let Ok(Some(f)) = source_frames.next() {
if let Some(loc) = f.location {
locations.push(Location {
file: loc.file.unwrap().into(),
line: loc.line.unwrap_or(0),
column: loc.column.unwrap_or(0),
});
}
}
}
if symbol.is_none() {
symbol = symbols.find_symbol(addr).map(|sym| Symbol {
name: addr2line::demangle_auto(sym.name().into(), None).into(),
address: sym.address(),
offset: addr + symbols.base_addr() - sym.address(),
});
}
}
frames.push(PrettyFrame {
frame: frame.clone(),
symbol,
locations,
});
}
Ok(PrettyBacktrace {
thread_id: self.thread_id,
thread_name: self.thread_name.clone(),
frames,
})
}
pub fn force_pretty(&self) -> PrettyBacktrace {
if let Ok(pretty) = self.pretty() {
return pretty;
}
let frames = self
.frames
.iter()
.map(|frame| PrettyFrame {
frame: frame.clone(),
symbol: None,
locations: Vec::new(),
})
.collect();
PrettyBacktrace {
thread_id: self.thread_id,
thread_name: self.thread_name.clone(),
frames,
}
}
pub fn iter(&self) -> impl Iterator<Item = &Frame> {
self.frames.iter()
}
pub fn thread_id(&self) -> Pid {
self.thread_id
}
pub fn thread_name(&self) -> Option<String> {
self.thread_name.clone()
}
}
impl PrettyBacktrace {
pub fn iter(&self) -> impl Iterator<Item = &PrettyFrame> {
self.frames.iter()
}
pub fn thread_name(&self) -> Option<String> {
self.thread_name.clone()
}
}
impl PrettyFrame {
pub fn symbol(&self) -> Option<&Symbol> {
self.symbol.as_ref()
}
}
impl IntoIterator for Backtrace {
type Item = Frame;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.frames.into_iter()
}
}
impl IntoIterator for PrettyBacktrace {
type Item = PrettyFrame;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.frames.into_iter()
}
}
impl From<Backtrace> for Vec<Frame> {
fn from(bt: Backtrace) -> Self {
bt.frames
}
}
impl fmt::Display for Backtrace {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let thread_name = self.thread_name();
let thread_name = thread_name.as_deref();
let thread_name = thread_name.unwrap_or("<unknown name>");
writeln!(
f,
"Stack trace for thread {} ({:?}):",
self.thread_id, thread_name
)?;
for frame in &self.frames {
writeln!(f, "{}", frame)?;
}
Ok(())
}
}
impl fmt::Display for PrettyBacktrace {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let thread_name = self.thread_name();
let thread_name = thread_name.as_deref();
let thread_name = thread_name.unwrap_or("<unknown name>");
writeln!(
f,
"Stack trace for thread {} ({:?}):",
self.thread_id, thread_name
)?;
for (i, frame) in self.frames.iter().enumerate() {
write!(f, "{:>4}: ", i)?;
match &frame.symbol {
Some(symbol) => writeln!(f, "{:#016x}: {:#}", frame.frame.ip, symbol)?,
None => writeln!(f, "{:#}", frame.frame)?,
}
for location in &frame.locations {
writeln!(f, " at {}", location)?;
}
}
Ok(())
}
}
fn thread_name(thread_id: Pid) -> io::Result<String> {
let mut name =
crate::process::launch_window::read_to_string(format!("/proc/{}/comm", thread_id))?;
assert_eq!(name.pop(), Some('\n'));
Ok(name)
}