Skip to content

Commit

Permalink
Merge pull request #1 from jonhoo/master
Browse files Browse the repository at this point in the history
Clean up addr2line so it is a real library
  • Loading branch information
fitzgen committed Dec 4, 2016
2 parents 0331845 + 59bec17 commit 90038ab
Show file tree
Hide file tree
Showing 4 changed files with 420 additions and 229 deletions.
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

[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.")
.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>;

0 comments on commit 90038ab

Please sign in to comment.