#![deny(rust_2018_idioms)]
#![deny(missing_docs)]
#![deny(warnings)]
#[macro_use]
extern crate failure;
use core::u16;
use std::{
collections::{BTreeMap, HashMap, HashSet},
io::Cursor,
};
#[cfg(feature = "tools")]
use std::{fs, path::Path};
use byteorder::{ReadBytesExt, LE};
use xmas_elf::{
header,
sections::SectionData,
symbol_table::{Entry, Type},
ElfFile,
};
#[derive(Clone, Debug)]
pub struct Functions<'a> {
pub have_32_bit_addresses: bool,
pub undefined: HashSet<&'a str>,
pub defined: BTreeMap<u64, Function<'a>>,
}
#[derive(Clone, Debug)]
pub struct Function<'a> {
names: Vec<&'a str>,
size: u64,
stack: Option<u64>,
}
impl<'a> Function<'a> {
pub fn names(&self) -> &[&'a str] {
&self.names
}
pub fn size(&self) -> u64 {
self.size
}
pub fn stack(&self) -> Option<u64> {
self.stack
}
}
fn is_tag(name: &str) -> bool {
name == "$a" || name == "$t" || name == "$d" || {
(name.starts_with("$a.") || name.starts_with("$d.") || name.starts_with("$t."))
&& name.splitn(2, '.').nth(1).unwrap().parse::<u64>().is_ok()
}
}
fn process_symtab_obj<'a, E>(
entries: &'a [E],
elf: &ElfFile<'a>,
) -> Result<
(
BTreeMap<u16, BTreeMap<u64, HashSet<&'a str>>>,
BTreeMap<u32, u16>,
),
failure::Error,
>
where
E: Entry,
{
let mut names: BTreeMap<_, BTreeMap<_, HashSet<_>>> = BTreeMap::new();
let mut shndxs = BTreeMap::new();
for (entry, i) in entries.iter().zip(0..) {
let name = entry.get_name(elf);
let shndx = entry.shndx();
let addr = entry.value() & !1;
let ty = entry.get_type();
if shndx != 0 {
shndxs.insert(i, shndx);
}
if ty == Ok(Type::Func)
|| (ty == Ok(Type::NoType)
&& name
.map(|name| !name.is_empty() && !is_tag(name))
.unwrap_or(false))
{
let name = name.map_err(failure::err_msg)?;
names
.entry(shndx)
.or_default()
.entry(addr)
.or_default()
.insert(name);
}
}
Ok((names, shndxs))
}
pub fn analyze_object(obj: &[u8]) -> Result<HashMap<&str, u64>, failure::Error> {
let elf = &ElfFile::new(obj).map_err(failure::err_msg)?;
if elf.header.pt2.type_().as_type() != header::Type::Relocatable {
bail!("object file is not relocatable")
}
let mut is_64_bit = false;
let (shndx2names, symtab2shndx) = match elf
.find_section_by_name(".symtab")
.ok_or_else(|| failure::err_msg("`.symtab` section not found"))?
.get_data(elf)
{
Ok(SectionData::SymbolTable32(entries)) => process_symtab_obj(entries, elf)?,
Ok(SectionData::SymbolTable64(entries)) => {
is_64_bit = true;
process_symtab_obj(entries, elf)?
}
_ => bail!("malformed .symtab section"),
};
let mut sizes = HashMap::new();
let mut sections = elf.section_iter();
while let Some(section) = sections.next() {
if section.get_name(elf) == Ok(".stack_sizes") {
let mut stack_sizes = Cursor::new(section.raw_data(elf));
let relocs: Vec<_> = match sections
.next()
.and_then(|section| section.get_data(elf).ok())
{
Some(SectionData::Rel32(rels)) if !is_64_bit => rels
.iter()
.map(|rel| rel.get_symbol_table_index())
.collect(),
Some(SectionData::Rela32(relas)) if !is_64_bit => relas
.iter()
.map(|rel| rel.get_symbol_table_index())
.collect(),
Some(SectionData::Rel64(rels)) if is_64_bit => rels
.iter()
.map(|rel| rel.get_symbol_table_index())
.collect(),
Some(SectionData::Rela64(relas)) if is_64_bit => relas
.iter()
.map(|rel| rel.get_symbol_table_index())
.collect(),
_ => bail!("expected a section with relocation information after `.stack_sizes`"),
};
for index in relocs {
let addr = if is_64_bit {
stack_sizes.read_u64::<LE>()?
} else {
u64::from(stack_sizes.read_u32::<LE>()?)
};
let stack = leb128::read::unsigned(&mut stack_sizes).unwrap();
let shndx = symtab2shndx[&index];
let entries = shndx2names
.get(&(shndx as u16))
.unwrap_or_else(|| panic!("section header with index {} not found", shndx));
assert!(sizes
.insert(
*entries
.get(&addr)
.unwrap_or_else(|| panic!(
"symbol with address {} not found at section {} ({:?})",
addr, shndx, entries
))
.iter()
.next()
.unwrap(),
stack
)
.is_none());
}
if stack_sizes.position() != stack_sizes.get_ref().len() as u64 {
bail!(
"the number of relocations doesn't match the number of `.stack_sizes` entries"
);
}
}
}
Ok(sizes)
}
fn process_symtab_exec<'a, E>(
entries: &'a [E],
elf: &ElfFile<'a>,
) -> Result<(HashSet<&'a str>, BTreeMap<u64, Function<'a>>), failure::Error>
where
E: Entry + core::fmt::Debug,
{
let mut defined = BTreeMap::new();
let mut maybe_aliases = BTreeMap::new();
let mut undefined = HashSet::new();
for entry in entries {
let ty = entry.get_type();
let value = entry.value();
let size = entry.size();
let name = entry.get_name(&elf);
if ty == Ok(Type::Func) {
let name = name.map_err(failure::err_msg)?;
if value == 0 && size == 0 {
undefined.insert(name);
} else {
defined
.entry(value)
.or_insert(Function {
names: vec![],
size,
stack: None,
})
.names
.push(name);
}
} else if ty == Ok(Type::NoType) {
if let Ok(name) = name {
if !is_tag(name) {
maybe_aliases.entry(value).or_insert(vec![]).push(name);
}
}
}
}
for (value, alias) in maybe_aliases {
if let Some(sym) = defined.get_mut(&(value | 1)) {
sym.names.extend(alias);
} else if let Some(sym) = defined.get_mut(&(value & !1)) {
sym.names.extend(alias);
}
}
Ok((undefined, defined))
}
pub fn analyze_executable(elf: &[u8]) -> Result<Functions<'_>, failure::Error> {
let elf = &ElfFile::new(elf).map_err(failure::err_msg)?;
let mut have_32_bit_addresses = false;
let (undefined, mut defined) = if let Some(section) = elf.find_section_by_name(".symtab") {
match section.get_data(elf).map_err(failure::err_msg)? {
SectionData::SymbolTable32(entries) => {
have_32_bit_addresses = true;
process_symtab_exec(entries, elf)?
}
SectionData::SymbolTable64(entries) => process_symtab_exec(entries, elf)?,
_ => bail!("malformed .symtab section"),
}
} else {
(HashSet::new(), BTreeMap::new())
};
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);
while cursor.position() < end {
let address = if have_32_bit_addresses {
u64::from(cursor.read_u32::<LE>()?)
} else {
cursor.read_u64::<LE>()?
};
let stack = leb128::read::unsigned(&mut cursor)?;
if let Some(sym) = defined.get_mut(&(address | 1)) {
sym.stack = Some(stack);
} else if let Some(sym) = defined.get_mut(&(address & !1)) {
sym.stack = Some(stack);
} else {
unreachable!()
}
}
}
Ok(Functions {
have_32_bit_addresses,
defined,
undefined,
})
}
#[cfg(feature = "tools")]
#[doc(hidden)]
pub fn run_exec(exec: &Path, obj: &Path) -> Result<(), failure::Error> {
let exec = &fs::read(exec)?;
let obj = &fs::read(obj)?;
let stack_sizes = analyze_object(obj)?;
let symbols = analyze_executable(exec)?;
if symbols.have_32_bit_addresses {
println!("address\t\tstack\tname");
for (addr, sym) in symbols.defined {
let stack = sym
.names()
.iter()
.filter_map(|name| stack_sizes.get(name))
.next();
if let (Some(name), Some(stack)) = (sym.names().first(), stack) {
println!(
"{:#010x}\t{}\t{}",
addr,
stack,
rustc_demangle::demangle(name)
);
}
}
} else {
println!("address\t\t\tstack\tname");
for (addr, sym) in symbols.defined {
let stack = sym
.names()
.iter()
.filter_map(|name| stack_sizes.get(name))
.next();
if let (Some(name), Some(stack)) = (sym.names().first(), stack) {
println!(
"{:#018x}\t{}\t{}",
addr,
stack,
rustc_demangle::demangle(name)
);
}
}
}
Ok(())
}
#[cfg(feature = "tools")]
#[doc(hidden)]
pub fn run(path: &Path) -> Result<(), failure::Error> {
let bytes = &fs::read(path)?;
let elf = &ElfFile::new(bytes).map_err(failure::err_msg)?;
if elf.header.pt2.type_().as_type() == header::Type::Relocatable {
let symbols = analyze_object(bytes)?;
if symbols.is_empty() {
bail!("this object file contains no stack usage information");
}
println!("stack\tname");
for (name, stack) in symbols {
println!("{}\t{}", stack, rustc_demangle::demangle(name));
}
Ok(())
} else {
let symbols = analyze_executable(bytes)?;
if symbols
.defined
.values()
.all(|symbol| symbol.stack().is_none())
{
bail!("this executable contains no stack usage information");
}
if symbols.have_32_bit_addresses {
println!("address\t\tstack\tname");
for (addr, sym) in symbols.defined {
if let (Some(name), Some(stack)) = (sym.names().first(), sym.stack()) {
println!(
"{:#010x}\t{}\t{}",
addr,
stack,
rustc_demangle::demangle(name)
);
}
}
} else {
println!("address\t\t\tstack\tname");
for (addr, sym) in symbols.defined {
if let (Some(name), Some(stack)) = (sym.names().first(), sym.stack()) {
println!(
"{:#018x}\t{}\t{}",
addr,
stack,
rustc_demangle::demangle(name)
);
}
}
}
Ok(())
}
}