318 lines
10 KiB
Rust
318 lines
10 KiB
Rust
|
use std::borrow::Cow;
|
||
|
use std::fs::File;
|
||
|
use std::io::{BufRead, Lines, StdinLock, Write};
|
||
|
use std::path::{Path, PathBuf};
|
||
|
|
||
|
use clap::{Arg, ArgAction, Command};
|
||
|
use fallible_iterator::FallibleIterator;
|
||
|
use object::{Object, ObjectSection, SymbolMap, SymbolMapName};
|
||
|
use typed_arena::Arena;
|
||
|
|
||
|
use addr2line::{Context, Location};
|
||
|
|
||
|
fn parse_uint_from_hex_string(string: &str) -> Option<u64> {
|
||
|
if string.len() > 2 && string.starts_with("0x") {
|
||
|
u64::from_str_radix(&string[2..], 16).ok()
|
||
|
} else {
|
||
|
u64::from_str_radix(string, 16).ok()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
enum Addrs<'a> {
|
||
|
Args(clap::parser::ValuesRef<'a, String>),
|
||
|
Stdin(Lines<StdinLock<'a>>),
|
||
|
}
|
||
|
|
||
|
impl<'a> Iterator for Addrs<'a> {
|
||
|
type Item = Option<u64>;
|
||
|
|
||
|
fn next(&mut self) -> Option<Option<u64>> {
|
||
|
let text = match *self {
|
||
|
Addrs::Args(ref mut vals) => vals.next().map(Cow::from),
|
||
|
Addrs::Stdin(ref mut lines) => lines.next().map(Result::unwrap).map(Cow::from),
|
||
|
};
|
||
|
text.as_ref()
|
||
|
.map(Cow::as_ref)
|
||
|
.map(parse_uint_from_hex_string)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn print_loc(loc: Option<&Location<'_>>, basenames: bool, llvm: bool) {
|
||
|
if let Some(loc) = loc {
|
||
|
if let Some(ref file) = loc.file.as_ref() {
|
||
|
let path = if basenames {
|
||
|
Path::new(Path::new(file).file_name().unwrap())
|
||
|
} else {
|
||
|
Path::new(file)
|
||
|
};
|
||
|
print!("{}:", path.display());
|
||
|
} else {
|
||
|
print!("??:");
|
||
|
}
|
||
|
if llvm {
|
||
|
print!("{}:{}", loc.line.unwrap_or(0), loc.column.unwrap_or(0));
|
||
|
} else if let Some(line) = loc.line {
|
||
|
print!("{}", line);
|
||
|
} else {
|
||
|
print!("?");
|
||
|
}
|
||
|
println!();
|
||
|
} else if llvm {
|
||
|
println!("??:0:0");
|
||
|
} else {
|
||
|
println!("??:0");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn print_function(name: Option<&str>, language: Option<gimli::DwLang>, demangle: bool) {
|
||
|
if let Some(name) = name {
|
||
|
if demangle {
|
||
|
print!("{}", addr2line::demangle_auto(Cow::from(name), language));
|
||
|
} else {
|
||
|
print!("{}", name);
|
||
|
}
|
||
|
} else {
|
||
|
print!("??");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn load_file_section<'input, 'arena, Endian: gimli::Endianity>(
|
||
|
id: gimli::SectionId,
|
||
|
file: &object::File<'input>,
|
||
|
endian: Endian,
|
||
|
arena_data: &'arena Arena<Cow<'input, [u8]>>,
|
||
|
) -> Result<gimli::EndianSlice<'arena, Endian>, ()> {
|
||
|
// TODO: Unify with dwarfdump.rs in gimli.
|
||
|
let name = id.name();
|
||
|
match file.section_by_name(name) {
|
||
|
Some(section) => match section.uncompressed_data().unwrap() {
|
||
|
Cow::Borrowed(b) => Ok(gimli::EndianSlice::new(b, endian)),
|
||
|
Cow::Owned(b) => Ok(gimli::EndianSlice::new(arena_data.alloc(b.into()), endian)),
|
||
|
},
|
||
|
None => Ok(gimli::EndianSlice::new(&[][..], endian)),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn find_name_from_symbols<'a>(
|
||
|
symbols: &'a SymbolMap<SymbolMapName<'_>>,
|
||
|
probe: u64,
|
||
|
) -> Option<&'a str> {
|
||
|
symbols.get(probe).map(|x| x.name())
|
||
|
}
|
||
|
|
||
|
struct Options<'a> {
|
||
|
do_functions: bool,
|
||
|
do_inlines: bool,
|
||
|
pretty: bool,
|
||
|
print_addrs: bool,
|
||
|
basenames: bool,
|
||
|
demangle: bool,
|
||
|
llvm: bool,
|
||
|
exe: &'a PathBuf,
|
||
|
sup: Option<&'a PathBuf>,
|
||
|
}
|
||
|
|
||
|
fn main() {
|
||
|
let matches = Command::new("addr2line")
|
||
|
.version(env!("CARGO_PKG_VERSION"))
|
||
|
.about("A fast addr2line Rust port")
|
||
|
.max_term_width(100)
|
||
|
.args(&[
|
||
|
Arg::new("exe")
|
||
|
.short('e')
|
||
|
.long("exe")
|
||
|
.value_name("filename")
|
||
|
.value_parser(clap::value_parser!(PathBuf))
|
||
|
.help(
|
||
|
"Specify the name of the executable for which addresses should be translated.",
|
||
|
)
|
||
|
.required(true),
|
||
|
Arg::new("sup")
|
||
|
.long("sup")
|
||
|
.value_name("filename")
|
||
|
.value_parser(clap::value_parser!(PathBuf))
|
||
|
.help("Path to supplementary object file."),
|
||
|
Arg::new("functions")
|
||
|
.short('f')
|
||
|
.long("functions")
|
||
|
.action(ArgAction::SetTrue)
|
||
|
.help("Display function names as well as file and line number information."),
|
||
|
Arg::new("pretty").short('p').long("pretty-print")
|
||
|
.action(ArgAction::SetTrue)
|
||
|
.help(
|
||
|
"Make the output more human friendly: each location are printed on one line.",
|
||
|
),
|
||
|
Arg::new("inlines").short('i').long("inlines")
|
||
|
.action(ArgAction::SetTrue)
|
||
|
.help(
|
||
|
"If the address belongs to a function that was inlined, the source information for \
|
||
|
all enclosing scopes back to the first non-inlined function will also be printed.",
|
||
|
),
|
||
|
Arg::new("addresses").short('a').long("addresses")
|
||
|
.action(ArgAction::SetTrue)
|
||
|
.help(
|
||
|
"Display the address before the function name, file and line number information.",
|
||
|
),
|
||
|
Arg::new("basenames")
|
||
|
.short('s')
|
||
|
.long("basenames")
|
||
|
.action(ArgAction::SetTrue)
|
||
|
.help("Display only the base of each file name."),
|
||
|
Arg::new("demangle").short('C').long("demangle")
|
||
|
.action(ArgAction::SetTrue)
|
||
|
.help(
|
||
|
"Demangle function names. \
|
||
|
Specifying a specific demangling style (like GNU addr2line) is not supported. \
|
||
|
(TODO)"
|
||
|
),
|
||
|
Arg::new("llvm")
|
||
|
.long("llvm")
|
||
|
.action(ArgAction::SetTrue)
|
||
|
.help("Display output in the same format as llvm-symbolizer."),
|
||
|
Arg::new("addrs")
|
||
|
.action(ArgAction::Append)
|
||
|
.help("Addresses to use instead of reading from stdin."),
|
||
|
])
|
||
|
.get_matches();
|
||
|
|
||
|
let arena_data = Arena::new();
|
||
|
|
||
|
let opts = Options {
|
||
|
do_functions: matches.get_flag("functions"),
|
||
|
do_inlines: matches.get_flag("inlines"),
|
||
|
pretty: matches.get_flag("pretty"),
|
||
|
print_addrs: matches.get_flag("addresses"),
|
||
|
basenames: matches.get_flag("basenames"),
|
||
|
demangle: matches.get_flag("demangle"),
|
||
|
llvm: matches.get_flag("llvm"),
|
||
|
exe: matches.get_one::<PathBuf>("exe").unwrap(),
|
||
|
sup: matches.get_one::<PathBuf>("sup"),
|
||
|
};
|
||
|
|
||
|
let file = File::open(opts.exe).unwrap();
|
||
|
let map = unsafe { memmap2::Mmap::map(&file).unwrap() };
|
||
|
let object = &object::File::parse(&*map).unwrap();
|
||
|
|
||
|
let endian = if object.is_little_endian() {
|
||
|
gimli::RunTimeEndian::Little
|
||
|
} else {
|
||
|
gimli::RunTimeEndian::Big
|
||
|
};
|
||
|
|
||
|
let mut load_section = |id: gimli::SectionId| -> Result<_, _> {
|
||
|
load_file_section(id, object, endian, &arena_data)
|
||
|
};
|
||
|
|
||
|
let sup_map;
|
||
|
let sup_object = if let Some(sup_path) = opts.sup {
|
||
|
let sup_file = File::open(sup_path).unwrap();
|
||
|
sup_map = unsafe { memmap2::Mmap::map(&sup_file).unwrap() };
|
||
|
Some(object::File::parse(&*sup_map).unwrap())
|
||
|
} else {
|
||
|
None
|
||
|
};
|
||
|
|
||
|
let symbols = object.symbol_map();
|
||
|
let mut dwarf = gimli::Dwarf::load(&mut load_section).unwrap();
|
||
|
if let Some(ref sup_object) = sup_object {
|
||
|
let mut load_sup_section = |id: gimli::SectionId| -> Result<_, _> {
|
||
|
load_file_section(id, sup_object, endian, &arena_data)
|
||
|
};
|
||
|
dwarf.load_sup(&mut load_sup_section).unwrap();
|
||
|
}
|
||
|
|
||
|
let mut split_dwarf_loader = addr2line::builtin_split_dwarf_loader::SplitDwarfLoader::new(
|
||
|
|data, endian| {
|
||
|
gimli::EndianSlice::new(arena_data.alloc(Cow::Owned(data.into_owned())), endian)
|
||
|
},
|
||
|
Some(opts.exe.clone()),
|
||
|
);
|
||
|
let ctx = Context::from_dwarf(dwarf).unwrap();
|
||
|
|
||
|
let stdin = std::io::stdin();
|
||
|
let addrs = matches
|
||
|
.get_many::<String>("addrs")
|
||
|
.map(Addrs::Args)
|
||
|
.unwrap_or_else(|| Addrs::Stdin(stdin.lock().lines()));
|
||
|
|
||
|
for probe in addrs {
|
||
|
if opts.print_addrs {
|
||
|
let addr = probe.unwrap_or(0);
|
||
|
if opts.llvm {
|
||
|
print!("0x{:x}", addr);
|
||
|
} else {
|
||
|
print!("0x{:016x}", addr);
|
||
|
}
|
||
|
if opts.pretty {
|
||
|
print!(": ");
|
||
|
} else {
|
||
|
println!();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if opts.do_functions || opts.do_inlines {
|
||
|
let mut printed_anything = false;
|
||
|
if let Some(probe) = probe {
|
||
|
let frames = ctx.find_frames(probe);
|
||
|
let frames = split_dwarf_loader.run(frames).unwrap();
|
||
|
let mut frames = frames.enumerate();
|
||
|
while let Some((i, frame)) = frames.next().unwrap() {
|
||
|
if opts.pretty && i != 0 {
|
||
|
print!(" (inlined by) ");
|
||
|
}
|
||
|
|
||
|
if opts.do_functions {
|
||
|
if let Some(func) = frame.function {
|
||
|
print_function(
|
||
|
func.raw_name().ok().as_ref().map(AsRef::as_ref),
|
||
|
func.language,
|
||
|
opts.demangle,
|
||
|
);
|
||
|
} else {
|
||
|
let name = find_name_from_symbols(&symbols, probe);
|
||
|
print_function(name, None, opts.demangle);
|
||
|
}
|
||
|
|
||
|
if opts.pretty {
|
||
|
print!(" at ");
|
||
|
} else {
|
||
|
println!();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
print_loc(frame.location.as_ref(), opts.basenames, opts.llvm);
|
||
|
|
||
|
printed_anything = true;
|
||
|
|
||
|
if !opts.do_inlines {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !printed_anything {
|
||
|
if opts.do_functions {
|
||
|
let name = probe.and_then(|probe| find_name_from_symbols(&symbols, probe));
|
||
|
print_function(name, None, opts.demangle);
|
||
|
|
||
|
if opts.pretty {
|
||
|
print!(" at ");
|
||
|
} else {
|
||
|
println!();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
print_loc(None, opts.basenames, opts.llvm);
|
||
|
}
|
||
|
} else {
|
||
|
let loc = probe.and_then(|probe| ctx.find_location(probe).unwrap());
|
||
|
print_loc(loc.as_ref(), opts.basenames, opts.llvm);
|
||
|
}
|
||
|
|
||
|
if opts.llvm {
|
||
|
println!();
|
||
|
}
|
||
|
std::io::stdout().flush().unwrap();
|
||
|
}
|
||
|
}
|