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

Clean up addr2line so it is a real library #1

Merged
merged 2 commits into from Dec 4, 2016
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 3 additions & 2 deletions Cargo.toml
Expand Up @@ -7,14 +7,15 @@ license = "Apache-2.0/MIT"
name = "addr2line"
readme = "./README.md"
repository = "https://github.com/gimli-rs/addr2line"
version = "0.1.0"
version = "0.2.0"

[dependencies]
fallible-iterator = "0.1.3"
getopts = "0.2.14"
clap = "2.19.1"
gimli = "0.9.0"
memmap = "0.5.0"
object = "0.1.0"
owning_ref = { git = "https://github.com/Kimundi/owning-ref-rs" } # need OwningHandle
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a placeholder until the next release is cut? We won't be able to publish on crates.io with a git dependency :-/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct.


[features]
nightly = []
303 changes: 80 additions & 223 deletions src/bin/addr2line.rs
@@ -1,240 +1,97 @@
extern crate fallible_iterator;
extern crate gimli;
extern crate getopts;
extern crate memmap;
extern crate object;
#[macro_use]
extern crate clap;
extern crate addr2line;

use fallible_iterator::FallibleIterator;
use object::Object;
use std::env;
use std::fs;
use clap::{App, Arg};

fn main() {
let args: Vec<String> = env::args().collect();
let mut opts = getopts::Options::new();
opts.optopt("e",
"exe",
"Set the input file name (default is a.out)",
"<executable>");

let matches = opts.parse(&args[1..]).unwrap();
use std::path;

let file_path = matches.opt_str("e").unwrap_or("a.out".to_string());
let file = fs::File::open(&file_path).expect("Should open file");
let file = memmap::Mmap::open(&file, memmap::Protection::Read)
.expect("Should create a mmap for file");
let file = object::File::parse(unsafe { file.as_slice() });
#[cfg_attr(rustfmt, rustfmt_skip)]
const POSTSCRIPT: &'static str = "\
addr2line translates addresses into file names and line numbers.
Given an address in an executable or an offset in a section of a
relocatable object, it uses the debugging information to figure
out which file name and line number are associated with it.

let addrs = matches.free.iter().map(|x| parse_uint_from_hex_string(x)).collect();
By default, the format of the output is `FILENAME:LINENO', and
each input address generates one line of output. This behavior
can be modified by passing the -a option, which also includes
the address on its own line.

if file.is_little_endian() {
symbolicate::<gimli::LittleEndian>(&file, addrs);
} else {
symbolicate::<gimli::BigEndian>(&file, addrs);
}
}

fn parse_uint_from_hex_string(string: &str) -> u64 {
if string.len() > 2 && string.starts_with("0x") {
u64::from_str_radix(&string[2..], 16).expect("Failed to parse address")
} else {
u64::from_str_radix(string, 16).expect("Failed to parse address")
}
}
If the file name can not be determined, addr2line will print two
question marks in their place. If the line number can not be
determined, addr2line will print 0.
";

fn display_file<Endian>(unit: &Unit,
header: &gimli::LineNumberProgramHeader<Endian>,
row: &gimli::LineNumberRow)
where Endian: gimli::Endianity
{
let file = row.file(header).unwrap();
if let Some(directory) = file.directory(header) {
let directory = directory.to_string_lossy();
if !directory.starts_with("/") {
if let Some(comp_dir) = unit.comp_dir() {
print!("{}/", comp_dir.to_string_lossy());
fn main() {
let matches = App::new("addr2line")
.version(crate_version!())
.about("Convert addresses into line number/file name pairs.\n\
If no addresses are specified on the command line, they will be read from stdin.")
.arg(Arg::with_name("executable")
.short("e")
.long("exe")
.default_value("a.out")
.help("Specify the name of the executable for which addresses should be translated.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/name/path/ ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took the help text verbatim from the addr2line man page for consistency, but I'm happy to change it if you prefer path.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, disregard then.

.takes_value(true))
.arg(Arg::with_name("addresses")
.short("a")
.long("addresses")
.help("Display the address before the function name, file and line number \
information. The address is printed with a `0x' prefix to easily identify \
it."))
.arg(Arg::with_name("addr")
.multiple(true)
.index(1))
.after_help(POSTSCRIPT)
.get_matches();

let exe = matches.value_of("executable").unwrap_or("./a.out");
let debug = addr2line::Mapping::new(path::Path::new(&exe));

if let Err(e) = debug {
println!("addr2line: {:?}", e);
std::process::exit(1);
};
let debug = debug.unwrap();

let show_addrs = matches.is_present("addresses");
let one = |addr: &str| {
let addr = parse_uint_from_hex_string(addr);
if show_addrs {
use std::mem;
match mem::size_of::<usize>() {
8 => println!("0x{:016x}", addr),
_ => println!("0x{:08x}", addr),
}
}
print!("{}/", directory);
}
println!("{}:{}",
file.path_name().to_string_lossy(),
row.line().unwrap());
}

fn symbolicate<Endian>(file: &object::File, addrs: Vec<u64>)
where Endian: gimli::Endianity
{
let debug_info = file.get_section(".debug_info")
.expect("Can't addr2line without .debug_info");
let debug_info = gimli::DebugInfo::<Endian>::new(debug_info);
let debug_abbrev = file.get_section(".debug_abbrev")
.expect("Can't addr2line without .debug_abbrev");
let debug_abbrev = gimli::DebugAbbrev::<Endian>::new(debug_abbrev);
let debug_line = file.get_section(".debug_line")
.expect("Can't addr2line without .debug_line");
let debug_line = gimli::DebugLine::<Endian>::new(debug_line);
let debug_ranges = file.get_section(".debug_ranges").unwrap_or(&[]);
let debug_ranges = gimli::DebugRanges::<Endian>::new(debug_ranges);
let debug_str = file.get_section(".debug_str").unwrap_or(&[]);
let debug_str = gimli::DebugStr::<Endian>::new(debug_str);

let mut units = Vec::new();
let mut headers = debug_info.units();
while let Some(header) = headers.next().expect("Couldn't get DIE header") {
if let Some(unit) = Unit::parse(&debug_abbrev, &debug_ranges, &debug_str, &header) {
units.push(unit);
if let Some((file, lineno)) = debug.locate(addr) {
println!("{}:{}", file, lineno);
} else {
println!("??:0")
}
}
};

for addr in addrs {
find_address(debug_line, &units, addr);
}
}
if let Some(addrs) = matches.values_of("addr") {
for addr in addrs {
one(addr);
}
} else {
use std::io;
use std::io::prelude::*;

fn find_address<Endian>(debug_line: gimli::DebugLine<Endian>, units: &[Unit], addr: u64)
where Endian: gimli::Endianity
{
let mut current = None;
for unit in units {
if unit.contains_address(addr) {
if let Ok(mut rows) = unit.line_rows(debug_line) {
while let Ok(Some((header, row))) = rows.next_row() {
if row.address() > addr {
if let Some(ref row) = current {
display_file(unit, header, row);
return;
}
break;
}
if row.end_sequence() {
current = None;
} else {
current = Some(row.clone());
}
}
}
let stdin = io::stdin();
for line in stdin.lock().lines() {
let addr = line.unwrap();
one(&*addr);
}
}
println!("Failed to find matching line for {}", addr);
}

// TODO: most of this should be moved to the main library.
struct Unit<'input> {
address_size: u8,
ranges: Vec<gimli::Range>,
line_offset: gimli::DebugLineOffset,
comp_dir: Option<&'input std::ffi::CStr>,
comp_name: Option<&'input std::ffi::CStr>,
}

impl<'input> Unit<'input> {
fn parse<Endian>(debug_abbrev: &gimli::DebugAbbrev<Endian>,
debug_ranges: &gimli::DebugRanges<Endian>,
debug_str: &gimli::DebugStr<'input, Endian>,
header: &gimli::CompilationUnitHeader<'input, Endian>)
-> Option<Unit<'input>>
where Endian: gimli::Endianity
{
let abbrev = header.abbreviations(*debug_abbrev).expect("Fail");
let mut entries = header.entries(&abbrev);
let (_, entry) = entries.next_dfs()
.expect("Should parse first entry OK")
.expect("And first entry should exist!");
assert_eq!(entry.tag(), gimli::DW_TAG_compile_unit);

let ranges = if let Some(ranges) =
Self::parse_noncontiguous_ranges(entry,
debug_ranges,
header.address_size()) {
ranges
} else if let Some(range) = Self::parse_contiguous_range(entry) {
vec![range]
} else {
return None;
};

let line_offset = match entry.attr_value(gimli::DW_AT_stmt_list) {
Some(gimli::AttributeValue::DebugLineRef(offset)) => offset,
_ => return None,
};
let comp_dir = entry.attr(gimli::DW_AT_comp_dir)
.and_then(|attr| attr.string_value(debug_str));
let comp_name = entry.attr(gimli::DW_AT_name)
.and_then(|attr| attr.string_value(debug_str));

Some(Unit {
address_size: header.address_size(),
ranges: ranges,
line_offset: line_offset,
comp_dir: comp_dir,
comp_name: comp_name,
})
}

// This must be checked before `parse_contiguous_range`.
fn parse_noncontiguous_ranges<Endian>(entry: &gimli::DebuggingInformationEntry<Endian>,
debug_ranges: &gimli::DebugRanges<Endian>,
address_size: u8)
-> Option<Vec<gimli::Range>>
where Endian: gimli::Endianity
{
let offset = match entry.attr_value(gimli::DW_AT_ranges) {
Some(gimli::AttributeValue::DebugRangesRef(offset)) => offset,
_ => return None,
};
let base_address = match entry.attr_value(gimli::DW_AT_low_pc) {
Some(gimli::AttributeValue::Addr(addr)) => addr,
_ => 0,
};
let ranges = debug_ranges.ranges(offset, address_size, base_address)
.expect("Range offset should be valid");
Some(ranges.collect().expect("Should parse ranges"))
}

fn parse_contiguous_range<Endian>(entry: &gimli::DebuggingInformationEntry<Endian>)
-> Option<gimli::Range>
where Endian: gimli::Endianity
{
debug_assert!(entry.attr_value(gimli::DW_AT_ranges).is_none());

let low_pc = match entry.attr_value(gimli::DW_AT_low_pc) {
Some(gimli::AttributeValue::Addr(addr)) => addr,
_ => return None,
};

let high_pc = match entry.attr_value(gimli::DW_AT_high_pc) {
Some(gimli::AttributeValue::Addr(addr)) => addr,
Some(gimli::AttributeValue::Udata(size)) => low_pc.wrapping_add(size),
None => low_pc.wrapping_add(1),
_ => return None,
};

// TODO: convert to error
assert!(low_pc < high_pc);
Some(gimli::Range {
begin: low_pc,
end: high_pc,
})
}

fn contains_address(&self, address: u64) -> bool {
self.ranges.iter().any(|range| address >= range.begin && address < range.end)
}

fn line_rows<Endian>(&self,
debug_line: gimli::DebugLine<'input, Endian>)
-> gimli::Result<gimli::StateMachine<'input, Endian>>
where Endian: gimli::Endianity
{
let header = try!(debug_line.header(self.line_offset,
self.address_size,
self.comp_dir,
self.comp_name));
Ok(header.rows())
}

fn comp_dir(&self) -> Option<&std::ffi::CStr> {
self.comp_dir
fn parse_uint_from_hex_string(string: &str) -> u64 {
if string.len() > 2 && string.starts_with("0x") {
u64::from_str_radix(&string[2..], 16).expect("Failed to parse address")
} else {
u64::from_str_radix(string, 16).expect("Failed to parse address")
}
}
15 changes: 15 additions & 0 deletions src/error.rs
@@ -0,0 +1,15 @@
//! Holds custom error types and `Result` wrapper for addr2line

use std::io;

#[derive(Debug)]
/// Set of possible errors from constructing and using a `Mapping`.
pub enum MappingError {
/// The given executable path could not be opened.
BadPath(io::Error),
/// The given executable did not contain sufficient information to enable address translation.
MissingDebugInfo(&'static str),
}

/// Wrapper for `Result` whose error is a `MappingError`.
pub type MappingResult<T> = Result<T, MappingError>;