#![deny(missing_docs)]
#![deny(warnings)]
extern crate byteorder;
extern crate either;
#[macro_use]
extern crate failure;
extern crate leb128;
#[cfg(feature = "tools")]
extern crate rustc_demangle;
extern crate xmas_elf;
use std::{collections::HashMap, io::Cursor, u32};
#[cfg(feature = "tools")]
use std::{fs::File, io::Read, path::Path};
use byteorder::{ReadBytesExt, LE};
use either::Either;
use xmas_elf::{
sections::{SectionData, SectionHeader},
symbol_table::{Entry, Type},
ElfFile,
};
#[derive(Debug)]
pub struct Function<'a, A> {
address: Option<A>,
names: Vec<&'a str>,
size: u64,
stack: Option<u64>,
}
impl<'a, A> Function<'a, A> {
pub fn address(&self) -> Option<A>
where
A: Copy,
{
self.address
}
pub fn names(&self) -> &[&'a str] {
&self.names
}
pub fn size(&self) -> u64 {
self.size
}
pub fn stack(&self) -> Option<u64> {
self.stack
}
}
pub fn analyze(
elf: &[u8],
) -> Result<Either<Vec<Function<u32>>, Vec<Function<u64>>>, failure::Error> {
let elf = ElfFile::new(elf).map_err(failure::err_msg)?;
let mut all_names = HashMap::new();
let mut undefs = vec![];
let mut maybe_aliases = HashMap::new();
let mut is_64_bit = false;
if let Some(section) = elf.find_section_by_name(".symtab") {
match section.get_data(&elf).map_err(failure::err_msg)? {
SectionData::SymbolTable32(entries) => {
for entry in entries {
let ty = entry.get_type();
let value = entry.value();
let size = entry.size();
let name = entry.get_name(&elf).map_err(failure::err_msg)?;
if ty == Ok(Type::Func) {
if value == 0 && size == 0 {
undefs.push(name);
} else {
all_names
.entry(value)
.or_insert((vec![], size))
.0
.push(name);
}
} else if ty == Ok(Type::NoType) {
maybe_aliases.entry(value).or_insert(vec![]).push(name);
}
}
}
SectionData::SymbolTable64(entries) => {
is_64_bit = true;
for entry in entries {
let ty = entry.get_type();
let value = entry.value();
let size = entry.size();
let name = entry.get_name(&elf).map_err(failure::err_msg)?;
if ty == Ok(Type::Func) {
if value == 0 && size == 0 {
undefs.push(name);
} else {
all_names
.entry(value)
.or_insert((vec![], size))
.0
.push(name);
}
} else if ty == Ok(Type::NoType) {
maybe_aliases.entry(value).or_insert(vec![]).push(name);
}
}
}
_ => bail!("malformed .symtab section"),
}
}
for (value, alias) in maybe_aliases {
if let Some((names, _)) = all_names.get_mut(&value) {
names.extend(alias);
}
}
if let Some(stack_sizes) = elf.find_section_by_name(".stack_sizes") {
let data = stack_sizes.raw_data(&elf);
let end = data.len() as u64;
let mut cursor = Cursor::new(data);
match stack_sizes {
SectionHeader::Sh32(..) => {
let mut funs = vec![];
while cursor.position() < end {
let address = cursor.read_u32::<LE>()?;
let (mut names, size) = all_names
.remove(&(u64::from(address)))
.or_else(|| all_names.remove(&(u64::from(address) + 1)))
.expect("UNREACHABLE");
let stack = Some(leb128::read::unsigned(&mut cursor)?);
names.sort();
funs.push(Function {
address: Some(address),
stack,
names,
size,
});
}
funs.sort_by(|a, b| b.stack().cmp(&a.stack()));
for (address, (mut names, size)) in all_names {
names.sort();
funs.push(Function {
address: Some(address as u32),
stack: None,
names,
size,
});
}
if !undefs.is_empty() {
funs.push(Function {
address: None,
stack: None,
names: undefs,
size: 0,
});
}
Ok(Either::Left(funs))
}
SectionHeader::Sh64(..) => {
let mut funs = vec![];
while cursor.position() < end {
let address = cursor.read_u64::<LE>()?;
let (mut names, size) = all_names
.remove(&address)
.or_else(|| all_names.remove(&(address + 1)))
.expect("UNREACHABLE");
let stack = Some(leb128::read::unsigned(&mut cursor)?);
names.sort();
funs.push(Function {
address: Some(address),
stack,
names,
size,
});
}
funs.sort_by(|a, b| b.stack().cmp(&a.stack()));
for (address, (mut names, size)) in all_names {
names.sort();
funs.push(Function {
address: Some(address),
stack: None,
names,
size,
});
}
if !undefs.is_empty() {
funs.push(Function {
address: None,
stack: None,
names: undefs,
size: 0,
});
}
Ok(Either::Right(funs))
}
}
} else if is_64_bit {
let mut funs = all_names
.into_iter()
.map(|(address, (mut names, size))| {
names.sort();
Function {
address: Some(address),
stack: None,
names,
size,
}
})
.collect::<Vec<_>>();
if !undefs.is_empty() {
funs.push(Function {
address: None,
stack: None,
names: undefs,
size: 0,
});
}
Ok(Either::Right(funs))
} else {
let mut funs = all_names
.into_iter()
.map(|(address, (mut names, size))| {
names.sort();
Function {
address: Some(address as u32),
stack: None,
names,
size,
}
})
.collect::<Vec<_>>();
if !undefs.is_empty() {
funs.push(Function {
address: None,
stack: None,
names: undefs,
size: 0,
});
}
Ok(Either::Left(funs))
}
}
#[cfg(feature = "tools")]
#[doc(hidden)]
pub fn run<P>(path: P) -> Result<(), failure::Error>
where
P: AsRef<Path>,
{
let mut bytes = vec![];
File::open(path)?.read_to_end(&mut bytes)?;
let funs = analyze(&bytes)?;
match funs {
Either::Left(funs) => {
println!("address\t\tstack\tname");
for fun in funs {
if let (Some(name), Some(stack), Some(addr)) =
(fun.names().first(), fun.stack(), fun.address())
{
println!(
"{:#010x}\t{}\t{}",
addr,
stack,
rustc_demangle::demangle(name)
);
}
}
}
Either::Right(funs) => {
println!("address\t\t\tstack\tname");
for fun in funs {
if let (Some(name), Some(stack), Some(addr)) =
(fun.names().first(), fun.stack(), fun.address())
{
println!(
"{:#018x}\t{}\t{}",
addr,
stack,
rustc_demangle::demangle(name)
);
}
}
}
}
Ok(())
}