Skip to content

Commit

Permalink
basic addr2line
Browse files Browse the repository at this point in the history
  • Loading branch information
khuey committed Aug 17, 2016
1 parent 20e8bcd commit a65de63
Show file tree
Hide file tree
Showing 5 changed files with 292 additions and 17 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ version = "0.6.0"
byteorder = "0.5.3"
leb128 = "0.2.1"

[dev-dependencies]
getopts = "0.2"

[target.'cfg(target_os="linux")'.dev-dependencies]
elf = "0.0.9"

Expand Down
208 changes: 208 additions & 0 deletions examples/addr2line.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
extern crate gimli;
extern crate getopts;

use std::env;

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();
let file_path = matches.opt_str("e").unwrap_or("a.out".to_string());
let file = obj::open(&file_path);
if obj::is_little_endian(&file) {
symbolicate::<gimli::LittleEndian>(&file, &matches);
} else {
symbolicate::<gimli::BigEndian>(&file, &matches);
}
}

fn parse_uint_from_hex_string(string: &str) -> u64
{
if string.len() > 2 {
if string.starts_with("0x") {
return u64::from_str_radix(&string[2..], 16).expect("Failed to parse address");
}
}

return u64::from_str_radix(string, 16).expect("Failed to parse address");
}

fn entries_for_addresses<Endian>(file: &obj::File, addrs: &Vec<u64>) -> Vec<Option<gimli::DebugInfoOffset>>
where Endian: gimli::Endianity
{
let aranges = obj::get_section(file, ".debug_aranges").expect("Can't addr2line with no aranges");
let aranges = gimli::DebugAranges::<Endian>::new(aranges);
let mut aranges = aranges.aranges();

let mut dies: Vec<Option<gimli::DebugInfoOffset>> = (0..addrs.len()).map(|_| None).collect();
while let Some(arange) = aranges.next_arange().expect("Should parse arange OK") {
let start = arange.start();
let end = start + arange.len();

for (i, addr) in addrs.iter().enumerate() {
if *addr >= start && *addr < end {
dies[i] = Some(arange.debug_info_offset());
}
}
}

dies
}

fn offset_for_entry<Endian>(abbrevs: &gimli::DebugAbbrev<Endian>, header: &gimli::UnitHeader<Endian>)
-> Option<gimli::DebugLineOffset>
where Endian: gimli::Endianity
{
let abbrev = abbrevs.abbreviations(header.debug_abbrev_offset()).expect("Fail");
let mut entries = header.entries(&abbrev);
let (_, entry) = entries.next_dfs().expect("Fail").unwrap();
match entry.attr_value(gimli::DW_AT_stmt_list) {
Some(gimli::AttributeValue::DebugLineRef(offset)) => Some(offset),
_ => None,
}
}

fn display_file<Endian>(header: &gimli::LineNumberProgramHeader<Endian>, row: &gimli::LineNumberRow<Endian>)
where Endian: gimli::Endianity
{
let file = row.file().expect("WTF");
println!("{}/{}:{}",
header.include_directories()[file.directory_index() as usize - 1].to_string_lossy(),
file.path_name().to_string_lossy(),
row.line().expect("WTF"));
}

fn symbolicate<Endian>(file: &obj::File, matches: &getopts::Matches)
where Endian: gimli::Endianity
{
let addrs: Vec<u64> = matches.free.iter().map(|x| parse_uint_from_hex_string(x)).collect();

let dies = entries_for_addresses::<Endian>(&file, &addrs);
let debug_info = obj::get_section(file, ".debug_info").expect("Can't addr2line with no debug_info");
let debug_info = gimli::DebugInfo::<Endian>::new(debug_info);
let debug_abbrev = obj::get_section(&file, ".debug_abbrev")
.expect("Does not have .debug_abbrev section");
let debug_abbrev = gimli::DebugAbbrev::<Endian>::new(debug_abbrev);
let debug_line = obj::get_section(file, ".debug_line").expect("Can't addr2line with no debug_line");
let debug_line = gimli::DebugLine::<Endian>::new(&debug_line);

for (die, addr) in dies.iter().zip(addrs.iter()) {
match *die {
None => println!("Found nothing"),
Some(d) => match debug_info.header_from_offset(d) {
Err(_) => println!("Couldn't get DIE header"),
Ok(h) => {
let offset = offset_for_entry(&debug_abbrev, &h).expect("No offset into .debug_lines!?");
let header =
gimli::LineNumberProgramHeader::new(debug_line, offset, h.address_size());
if let Ok(header) = header {
let mut state_machine = gimli::StateMachine::new(&header);
match state_machine.run_to_address(addr) {
Err(_) => println!("Failed to run line number program!"),
Ok(None) => println!("Failed to find matching line for {}", *addr),
Ok(Some(row)) => display_file(&header, &row),
}
}
}
}
}
}
}

// All cross platform / object file format compatibility stuff should
// be contained in the `obj` module. Each supported platform / object
// file format should implement the `obj` module with an identical
// interface, but with the `pub type File` changing as needed. Hooray
// duck typing!

#[cfg(target_os="linux")]
mod obj {
extern crate elf;
use std::path::Path;

/// The parsed object file type.
pub type File = elf::File;

/// Open and parse the object file at the given path.
pub fn open<P>(path: P) -> File
where P: AsRef<Path>
{
let path = path.as_ref();
elf::File::open_path(path).expect("Could not open file")
}

/// Get the contents of the section named `section_name`, if such
/// a section exists.
pub fn get_section<'a>(file: &'a File, section_name: &str) -> Option<&'a [u8]> {
file.sections
.iter()
.find(|s| s.shdr.name == section_name)
.map(|s| &s.data[..])
}

/// Return true if the file is little endian, false if it is big endian.
pub fn is_little_endian(file: &File) -> bool {
match file.ehdr.data {
elf::types::ELFDATA2LSB => true,
elf::types::ELFDATA2MSB => false,
otherwise => panic!("Unknown endianity: {}", otherwise),
}
}
}

#[cfg(target_os="macos")]
mod obj {
extern crate mach_o;

use std::ffi::CString;
use std::fs;
use std::io::Read;
use std::mem;
use std::path::Path;

pub type File = Vec<u8>;

pub fn open<P>(path: P) -> File
where P: AsRef<Path>
{
let mut file = fs::File::open(path).expect("Could not open file");
let mut buf = Vec::new();
file.read_to_end(&mut buf).expect("Could not read file");
buf
}

// Translate the "." prefix to the "__" prefix used by OSX/Mach-O, eg
// ".debug_info" to "__debug_info".
fn translate_section_name(section_name: &str) -> CString {
let mut name = Vec::with_capacity(section_name.len() + 1);
name.push(b'_');
name.push(b'_');
for ch in &section_name.as_bytes()[1..] {
name.push(*ch);
}
unsafe { CString::from_vec_unchecked(name) }
}

pub fn get_section<'a>(file: &'a File, section_name: &str) -> Option<&'a [u8]> {
let parsed = mach_o::Header::new(&file[..]).expect("Could not parse macho-o file");

let segment_name = CString::new("__DWARF").unwrap();
let section_name = translate_section_name(section_name);
parsed.get_section(&segment_name, &section_name).map(|s| s.data())
}

pub fn is_little_endian(file: &File) -> bool {
let parsed = mach_o::Header::new(&file[..]).expect("Could not parse macho-o file");

let bytes = [1, 0, 0, 0u8];
let int: u32 = unsafe { mem::transmute(bytes) };
let native_byteorder_is_little = int == 1;

match (native_byteorder_is_little, parsed.is_native_byteorder()) {
(true, b) => b,
(false, b) => !b,
}
}
}
20 changes: 20 additions & 0 deletions src/line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,26 @@ impl<'input, 'header, Endian> StateMachine<'input, 'header, Endian>
}
}
}

/// Parse and execute opcodes until we reach a row matching `addr`, the end of the program,
/// or an error.
pub fn run_to_address(&mut self, addr: &u64)
-> parser::ParseResult<Option<&LineNumberRow<'input, 'header, Endian>>> {
loop {
match self.next_row() {
Ok(Some(row)) => {
if row.address() == *addr {
// Can't return 'row' directly here because of rust-lang/rust#21906.
break;
}
},
Ok(None) => return Ok(None),
Err(err) => return Err(err),
};
}

return Ok(Some(&self.row));
}
}

/// A parsed line number program opcode.
Expand Down
63 changes: 59 additions & 4 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use abbrev::{DebugAbbrev, DebugAbbrevOffset, Abbreviations, Abbreviation, Attrib
use endianity::{Endianity, EndianBuf};
#[cfg(test)]
use endianity::LittleEndian;
use line::DebugLineOffset;
use std::cell::Cell;
use std::error;
use std::ffi;
Expand Down Expand Up @@ -317,6 +318,22 @@ impl<'input, Endian> DebugInfo<'input, Endian>
pub fn units(&self) -> UnitHeadersIter<'input, Endian> {
UnitHeadersIter { input: self.debug_info_section }
}

/// Get the UnitHeader located at offset from this .debug_info section.
///
///
pub fn header_from_offset(&self, offset: DebugInfoOffset) -> ParseResult<UnitHeader<'input, Endian>> {
let offset = offset.0 as usize;
if self.debug_info_section.len() < offset {
return Err(Error::UnexpectedEof);
}

let input = self.debug_info_section.range_from(offset..);
match parse_unit_header(input) {
Ok((_, header)) => Ok(header),
Err(e) => Err(e)
}
}
}

/// An iterator over the compilation- and partial-units of a section.
Expand Down Expand Up @@ -1243,17 +1260,52 @@ impl<'input, 'abbrev, 'unit, Endian> DebuggingInformationEntry<'input, 'abbrev,
}
}

/// Find the first attribute in this entry which has the given name,
/// and return its value. Returns `Ok(None)` if no attribute is found.
pub fn attr_value(&self, name: constants::DwAt) -> Option<AttributeValue<'input>> {
fn attr_value_internal<F>(&self, name: constants::DwAt, func: F) -> Option<AttributeValue<'input>>
where F: Fn(constants::DwAt, AttributeValue<'input>) -> AttributeValue<'input>
{
let mut attrs = self.attrs();
while let Ok(Some(attr)) = attrs.next() {
if attr.name() == name {
return Some(attr.value());
return Some(func(name, attr.value()));
}
}
None
}

/// Run some common fixups to present DWARF attributes in more useful forms.
fn prettify_attr_value(name: constants::DwAt, value: AttributeValue<'input>) -> AttributeValue<'input> {
match name {
constants::DW_AT_stmt_list => {
let offset = DebugLineOffset(match value {
AttributeValue::Data(data) if data.len() == 4 => {
Endian::read_u32(data) as u64
}
AttributeValue::Data(data) if data.len() == 8 => {
Endian::read_u64(data)
}
AttributeValue::SecOffset(offset) => offset,
otherwise => return otherwise,
});
AttributeValue::DebugLineRef(offset)
},
_ => value,
}
}

/// Find the first attribute in this entry which has the given name,
/// and return its value, without any attempt to clean it up. Returns
/// `Ok(None)` if no attribute is found.
pub fn attr_value_raw(&self, name: constants::DwAt) -> Option<AttributeValue<'input>> {
self.attr_value_internal(name, |_, x| x)
}

/// Find the first attribute in this entry which has the given name,
/// and return its value, after running `prettify_attr_value` on it.
/// Returns `Ok(None)` if no attribute is found.
pub fn attr_value(&self, name: constants::DwAt) -> Option<AttributeValue<'input>> {
self.attr_value_internal(name,
DebuggingInformationEntry::<'input, 'abbrev, 'unit, Endian>::prettify_attr_value)
}
}

/// The value of an attribute in a `DebuggingInformationEntry`.
Expand Down Expand Up @@ -1298,6 +1350,9 @@ pub enum AttributeValue<'input> {
/// different compilation unit from the current one.
DebugInfoRef(DebugInfoOffset),

/// An offset into the `.debug_lines` section.
DebugLineRef(DebugLineOffset),

/// An offset into the `.debug_types` section.
DebugTypesRef(DebugTypesOffset),

Expand Down
15 changes: 2 additions & 13 deletions tests/parse_self.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
extern crate byteorder;
extern crate gimli;

use byteorder::ByteOrder;
use gimli::{AttributeValue, DebugAbbrev, DebugAranges, DebugInfo, DebugLine, DebugLineOffset,
use gimli::{AttributeValue, DebugAbbrev, DebugAranges, DebugInfo, DebugLine,
DW_AT_stmt_list, LineNumberProgramHeader, LittleEndian, StateMachine};
use std::env;
use std::fs::File;
Expand Down Expand Up @@ -71,17 +70,7 @@ fn test_parse_self_debug_line() {
let unit_entry = cursor.current()
.expect("Should have a root entry");

if let Some(value) = unit_entry.attr_value(DW_AT_stmt_list) {
// rustc generates DWARF 2 by default, which doesn't have
// DW_FORM_sec_offset, so instead we get DW_FORM_data4
// values. Because of this, we have to turn the data into an offset
// manually.
let offset = match value {
AttributeValue::Data(data) => LittleEndian::read_u32(data),
otherwise => panic!("unexpected value form: {:?}", otherwise),
};
let offset = DebugLineOffset(offset as u64);

if let Some(AttributeValue::DebugLineRef(offset)) = unit_entry.attr_value(DW_AT_stmt_list) {
let header = LineNumberProgramHeader::new(debug_line, offset, unit.address_size())
.expect("should parse line number program header");

Expand Down

0 comments on commit a65de63

Please sign in to comment.