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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/name/path/ ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I took the help text verbatim from the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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>; |
There was a problem hiding this comment.
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 :-/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct.