Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace clap with bpaf #255

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ compiler_builtins = { version = '0.1.2', optional = true }

[dev-dependencies]
memmap2 = "0.5.5"
clap = "3.1.6"
bpaf = { version = "0.5.6", default-features = false, features = ["autocomplete"] }
backtrace = "0.3.13"
findshlibs = "0.10"
rustc-test = "0.3"
Expand Down
291 changes: 159 additions & 132 deletions examples/addr2line.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
extern crate addr2line;
extern crate clap;
extern crate bpaf;
extern crate fallible_iterator;
extern crate gimli;
extern crate memmap2;
Expand All @@ -8,45 +8,25 @@ extern crate typed_arena;

use std::borrow::Cow;
use std::fs::File;
use std::io::{BufRead, Lines, StdinLock, Write};
use std::path::Path;
use std::io::{BufRead, Write};
use std::path::{Path, PathBuf};

use clap::{Arg, Command, Values};
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> {
fn parse_uint_from_hex_string(string: &str) -> Result<u64, std::num::ParseIntError> {
if string.len() > 2 && string.starts_with("0x") {
u64::from_str_radix(&string[2..], 16).ok()
u64::from_str_radix(&string[2..], 16)
} else {
u64::from_str_radix(string, 16).ok()
}
}

enum Addrs<'a> {
Args(Values<'a>),
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)
u64::from_str_radix(string, 16)
}
}

fn print_loc(loc: Option<&Location>, basenames: bool, llvm: bool) {
if let Some(ref loc) = loc {
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())
Expand Down Expand Up @@ -108,68 +88,104 @@ fn find_name_from_symbols<'a>(
symbols.get(probe).map(|x| x.name())
}

struct Options {
exe: PathBuf,
sup: Option<PathBuf>,
do_functions: bool,
do_inlines: bool,
pretty: bool,
print_addrs: bool,
basenames: bool,
demangle: bool,
llvm: bool,
addrs: Vec<Option<u64>>,
}

fn options() -> Options {
use bpaf::*;

let print_addrs = short('a')
.long("addresses")
.help("Display the address before the function name, file and line number information.")
.switch();

let exe = short('e')
.long("exe")
.help("Specify the name of the executable for which addresses should be translated.")
.argument_os("filename")
.map(PathBuf::from);

let sup = long("sup")
.help("Path to supplementary object file.")
.argument_os("filename")
.map(PathBuf::from)
.optional();

let do_functions = short('f')
.long("functions")
.help("Display function names as well as file and line number information.")
.switch();

let pretty = short('p')
.long("pretty-print")
.help("Make the output more human friendly: each location are printed on one line.")
.switch();

let do_inlines = short('i')
.long("inlines")
.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.",
)
.switch();

let basenames = short('s')
.long("basenames")
.help("Display only the base of each file name.")
.switch();

let demangle = short('C')
.long("demangle")
.help(
"Demangle function names.
Specifying a specific demangling style (like GNU addr2line) is not supported. (TODO)",
)
.switch();

let llvm = short('L')
.long("llvm")
.help("Display output in the same format as llvm-symbolizer.")
.switch();

let addrs = positional("addrs")
.help("Addresses to use instead of reading from stdin.")
.map(|s| parse_uint_from_hex_string(&s).ok())
.many();

construct!(Options {
print_addrs,
do_functions,
exe,
sup,
pretty,
demangle,
basenames,
do_inlines,
llvm,
addrs,
})
.to_options()
.descr("A fast addr2line Rust port")
.version(env!("CARGO_PKG_VERSION"))
.run()
}

fn main() {
let matches = Command::new("addr2line")
.version("0.1")
.about("A fast addr2line Rust port")
.args(&[
Arg::new("exe")
.short('e')
.long("exe")
.value_name("filename")
.help(
"Specify the name of the executable for which addresses should be translated.",
)
.required(true),
Arg::new("sup")
.long("sup")
.value_name("filename")
.help("Path to supplementary object file."),
Arg::new("functions")
.short('f')
.long("functions")
.help("Display function names as well as file and line number information."),
Arg::new("pretty").short('p').long("pretty-print").help(
"Make the output more human friendly: each location are printed on one line.",
),
Arg::new("inlines").short('i').long("inlines").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").help(
"Display the address before the function name, file and line number information.",
),
Arg::new("basenames")
.short('s')
.long("basenames")
.help("Display only the base of each file name."),
Arg::new("demangle").short('C').long("demangle").help(
"Demangle function names. \
Specifying a specific demangling style (like GNU addr2line) is not supported. \
(TODO)"
),
Arg::new("llvm")
.long("llvm")
.help("Display output in the same format as llvm-symbolizer."),
Arg::new("addrs")
.takes_value(true)
.multiple_occurrences(true)
.help("Addresses to use instead of reading from stdin."),
])
.get_matches();
let opts = options();

let arena_data = Arena::new();

let do_functions = matches.is_present("functions");
let do_inlines = matches.is_present("inlines");
let pretty = matches.is_present("pretty");
let print_addrs = matches.is_present("addresses");
let basenames = matches.is_present("basenames");
let demangle = matches.is_present("demangle");
let llvm = matches.is_present("llvm");
let path = matches.value_of("exe").unwrap();

let file = File::open(path).unwrap();
let file = File::open(opts.exe).unwrap();
let map = unsafe { memmap2::Mmap::map(&file).unwrap() };
let object = &object::File::parse(&*map).unwrap();

Expand All @@ -184,7 +200,7 @@ fn main() {
};

let sup_map;
let sup_object = if let Some(sup_path) = matches.value_of("sup") {
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())
Expand All @@ -203,85 +219,96 @@ fn main() {

let ctx = Context::from_dwarf(dwarf).unwrap();

let stdin = std::io::stdin();
let addrs = matches
.values_of("addrs")
.map(Addrs::Args)
.unwrap_or_else(|| Addrs::Stdin(stdin.lock().lines()));
let addrs: Box<dyn Iterator<Item = Option<u64>>> = if opts.addrs.is_empty() {
Box::new(
std::io::stdin()
.lock()
.lines()
.map(|s| parse_uint_from_hex_string(&s.unwrap()).ok()),
)
} else {
Box::new(opts.addrs.iter().copied())
};

for probe in addrs {
if print_addrs {
let addr = probe.unwrap_or(0);
if llvm {
for mprobe in addrs {
let probe = match mprobe {
Some(probe) => probe,
None => {
println!("??:0");
pacak marked this conversation as resolved.
Show resolved Hide resolved
continue;
}
};
if opts.print_addrs {
let addr = probe;
if opts.llvm {
print!("0x{:x}", addr);
} else {
print!("0x{:016x}", addr);
}
if pretty {
if opts.pretty {
print!(": ");
} else {
println!();
}
}

if do_functions || do_inlines {
if opts.do_functions || opts.do_inlines {
let mut printed_anything = false;
if let Some(probe) = probe {
let mut frames = ctx.find_frames(probe).unwrap().enumerate();
while let Some((i, frame)) = frames.next().unwrap() {
if pretty && i != 0 {
print!(" (inlined by) ");

let mut frames = ctx.find_frames(probe).unwrap().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 do_functions {
if let Some(func) = frame.function {
print_function(
func.raw_name().ok().as_ref().map(AsRef::as_ref),
func.language,
demangle,
);
} else {
let name = find_name_from_symbols(&symbols, probe);
print_function(name, None, demangle);
}

if pretty {
print!(" at ");
} else {
println!();
}
if opts.pretty {
print!(" at ");
} else {
println!();
}
}

print_loc(frame.location.as_ref(), basenames, llvm);
print_loc(frame.location.as_ref(), opts.basenames, opts.llvm);

printed_anything = true;
printed_anything = true;

if !do_inlines {
break;
}
if !opts.do_inlines {
break;
}
}

if !printed_anything {
if do_functions {
let name = probe.and_then(|probe| find_name_from_symbols(&symbols, probe));
print_function(name, None, demangle);
if opts.do_functions {
let name = find_name_from_symbols(&symbols, probe);
print_function(name, None, opts.demangle);

if pretty {
if opts.pretty {
print!(" at ");
} else {
println!();
}
}

print_loc(None, basenames, llvm);
print_loc(None, opts.basenames, opts.llvm);
}
} else {
let loc = probe.and_then(|probe| ctx.find_location(probe).unwrap());
print_loc(loc.as_ref(), basenames, llvm);
let loc = ctx.find_location(probe).unwrap();
print_loc(loc.as_ref(), opts.basenames, opts.llvm);
}

if llvm {
if opts.llvm {
println!();
}
std::io::stdout().flush().unwrap();
Expand Down