-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
215 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 §ion_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, §ion_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, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters