Skip to content

Commit

Permalink
basic addr2line
Browse files Browse the repository at this point in the history
  • Loading branch information
khuey committed Aug 16, 2016
1 parent 20e8bcd commit bbe232e
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ version = "0.6.0"
[dependencies]
byteorder = "0.5.3"
leb128 = "0.2.1"
getopts = "0.2"

[target.'cfg(target_os="linux")'.dev-dependencies]
elf = "0.0.9"
Expand Down
198 changes: 198 additions & 0 deletions examples/addr2line.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
extern crate gimli;
extern crate getopts;

use std::env;
use std::str::FromStr;

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 = match opts.parse(&args[1..]) {
Ok(m) => m,
Err(f) => { panic!(f.to_string()); }
};

let file_path = match matches.opt_str("e") {
Some(f) => f,
None => "a.out".to_string()
};

let file = obj::open(&file_path);
if obj::is_little_endian(&file) {
do_work::<gimli::LittleEndian>(&file, &matches);
} else {
do_work::<gimli::BigEndian>(&file, &matches);
}
}

fn do_work<Endian>(file: &obj::File, matches: &getopts::Matches)
where Endian: gimli::Endianity
{
let addrs: Vec<u64> = matches.free.iter().map(|x| u64::from_str_radix(x,16).expect("Failed to parse")).collect();

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>> = Vec::new();
dies.resize(addrs.len(), None); // XXXkhuey better way to do this?
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() {
match (*addr >= start, *addr < end) {
(true, true) => {
dies[i] = Some(arange.debug_info_offset());
},
_ => continue,
}
}
}

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 (index, die) in dies.iter().enumerate() {
let addr = addrs[index];
match *die {
None => println!("Found nothing"),
Some(d) => match debug_info.header_from_offset(d) {
Err(_) => continue,
Ok(h) => {
let abbrev = debug_abbrev.abbreviations(h.debug_abbrev_offset()).expect("Fail");
let mut entries = h.entries(&abbrev);
let (_, entry) = entries.next_dfs().expect("Fail").unwrap();
let value = entry.attr_value(gimli::DW_AT_stmt_list);
let offset = gimli::DebugLineOffset(match value {
Some(gimli::AttributeValue::Data(data)) if data.len() == 4 => {
Endian::read_u32(data) as u64
}
Some(gimli::AttributeValue::Data(data)) if data.len() == 8 => {
Endian::read_u64(data)
}
Some(gimli::AttributeValue::SecOffset(offset)) => offset,
_ => continue,
});
let header =
gimli::LineNumberProgramHeader::new(debug_line, offset, h.address_size());
if let Ok(header) = header {
let mut state_machine = gimli::StateMachine::new(&header);
while let Some(row) = state_machine.next_row()
.expect("Should parse and execute all rows in the line number program") {
if row.address() == addr {
let file = row.file().expect("WTF");
println!("{:?}:{} {}", file.path_name(),
row.line().expect("WTF"), file.directory_index());
break;
}
}
}
}
}
}
}
}

// 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,
}
}
}
16 changes: 16 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,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

0 comments on commit bbe232e

Please sign in to comment.