Skip to content

Commit

Permalink
Add basic support for PDB.
Browse files Browse the repository at this point in the history
Closes #17
  • Loading branch information
nico-abram authored Dec 20, 2021
1 parent c750c00 commit 9b3e078
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 9 deletions.
30 changes: 30 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ multimap = { version = "0.8", default-features = false }
pico-args = "0.4.2"
term_size = "0.3.1"
binfarce = "0.2.1"
pdb = "0.7.0"

[dependencies.regex]
version = "1.3"
Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ Inspired by [google/bloaty](/~https://github.com/google/bloaty).

**Note:** supports ELF (Linux, BSD), Mach-O (macOS) and PE (Windows) binaries.

**Note:** Windows MSVC target is not supported. See [#17](/~https://github.com/RazrFalcon/cargo-bloat/issues/17).

**Note:** WASM is not supported. Prefer [twiggy](/~https://github.com/rustwasm/twiggy) instead.

### Install
Expand Down
165 changes: 158 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ enum Error {
NoArtifacts,
UnsupportedFileFormat(path::PathBuf),
ParsingError(binfarce::ParseError),
PdbError(pdb::Error),
}

impl From<binfarce::ParseError> for Error {
Expand All @@ -84,6 +85,12 @@ impl From<binfarce::UnexpectedEof> for Error {
}
}

impl From<pdb::Error> for Error {
fn from(e: pdb::Error) -> Self {
Error::PdbError(e)
}
}

impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Expand Down Expand Up @@ -121,6 +128,9 @@ impl fmt::Display for Error {
Error::ParsingError(ref e) => {
write!(f, "parsing failed cause '{}'", e)
}
Error::PdbError(ref e) => {
write!(f, "error parsing pdb file cause '{}'", e)
}
}
}
}
Expand Down Expand Up @@ -855,24 +865,165 @@ fn collect_macho_data(data: &[u8]) -> Result<Data, Error> {
Ok(d)
}

fn collect_pe_data(path: &path::Path, data: &[u8]) -> Result<Data, Error> {
let (symbols, text_size) = pe::parse(data)?.symbols()?;
fn collect_pdb_data(pdb_path: &path::Path, text_size: u64) -> Result<Data, Error> {
use pdb::FallibleIterator;

// `pe::parse` will return zero symbols for an executable built with MSVC.
if symbols.is_empty() {
eprintln!("Warning: MSVC target is not supported.");
return Err(Error::UnsupportedFileFormat(path.to_owned()))
let file = std::fs::File::open(&pdb_path).map_err(|_| Error::OpenFailed(pdb_path.to_owned()))?;
let mut pdb = pdb::PDB::open(file)?;

let dbi = pdb.debug_information()?;
let symbol_table = pdb.global_symbols()?;
let address_map = pdb.address_map()?;

let mut out_symbols = Vec::new();

// Collect the PublicSymbols
let mut public_symbols = Vec::new();

let mut symbols = symbol_table.iter();
while let Ok(Some(symbol)) = symbols.next() {
match symbol.parse() {
Ok(pdb::SymbolData::Public(data)) => {
if data.code || data.function {
public_symbols.push((data.offset, data.name.to_string().into_owned()));
}
}
_ => {}
}
}

let mut modules = dbi.modules()?;
while let Some(module) = modules.next()? {
let info = match pdb.module_info(&module)? {
Some(info) => info,
None => continue,
};
let mut symbols = info.symbols()?;
while let Some(symbol) = symbols.next()? {
if let Ok(pdb::SymbolData::Public(data)) = symbol.parse() {
if data.code || data.function {
public_symbols.push((data.offset, data.name.to_string().into_owned()));
}
}
}
}

let cmp_offsets = |a: &pdb::PdbInternalSectionOffset, b: &pdb::PdbInternalSectionOffset| {
a.section.cmp(&b.section).then(a.offset.cmp(&b.offset))
};
public_symbols.sort_unstable_by(|a, b| cmp_offsets(&a.0, &b.0));

// Now find the Procedure symbols in all modules
// and if possible the matching PublicSymbol record with the mangled name
let mut handle_proc = |proc: pdb::ProcedureSymbol| {
let mangled_symbol = public_symbols
.binary_search_by(|probe| {
let low = cmp_offsets(&probe.0, &proc.offset);
let high = cmp_offsets(&probe.0, &(proc.offset + proc.len));

use std::cmp::Ordering::*;
match (low, high) {
// Less than the low bound -> less
(Less, _) => Less,
// More than the high bound -> greater
(_, Greater) => Greater,
_ => Equal,
}
})
.ok()
.map(|x| &public_symbols[x]);

let demangled_name = proc.name.to_string().into_owned();
out_symbols.push((
proc.offset.to_rva(&address_map),
proc.len as u64,
demangled_name,
mangled_symbol,
));
};

let mut symbols = symbol_table.iter();
while let Ok(Some(symbol)) = symbols.next() {
if let Ok(pdb::SymbolData::Procedure(proc)) = symbol.parse() {
handle_proc(proc);
}
}
let mut modules = dbi.modules()?;
while let Some(module) = modules.next()? {
let info = match pdb.module_info(&module)? {
Some(info) => info,
None => continue,
};

let mut symbols = info.symbols()?;

while let Some(symbol) = symbols.next()? {
if let Ok(pdb::SymbolData::Procedure(proc)) = symbol.parse() {
handle_proc(proc);
}
}
}

let d = Data {
symbols,
symbols: out_symbols
.into_iter()
.filter_map(|(address, size, unmangled_name, mangled_name)| {
if let Some(address) = address {
Some(SymbolData {
name: mangled_name
.map(|(_, mangled_name)| {
binfarce::demangle::SymbolName::demangle(mangled_name)
})
// Assume the Symbol record name is unmangled if we didn't find one
.unwrap_or(binfarce::demangle::SymbolName {
complete: unmangled_name.clone(),
trimmed: unmangled_name.clone(),
crate_name: None,
kind: binfarce::demangle::Kind::V0,
}),
address: address.0 as u64,
size,
})
} else {
None
}
})
.collect(),
file_size: 0,
text_size,
};

Ok(d)
}

fn collect_pe_data(path: &path::Path, data: &[u8]) -> Result<Data, Error> {
let (symbols, text_size) = pe::parse(data)?.symbols()?;

// `pe::parse` will return zero symbols for an executable built with MSVC.
if symbols.is_empty() {
let pdb_path = {
let file_name = if let Some(file_name) = path.file_name() {
if let Some(file_name) = file_name.to_str() {
file_name.replace("-", "_")
} else {
return Err(Error::OpenFailed(path.to_owned()));
}
} else {
return Err(Error::OpenFailed(path.to_owned()));
};
path.with_file_name(file_name).with_extension("pdb")
};

collect_pdb_data(&pdb_path, text_size)
} else {
Ok(Data {
symbols,
file_size: 0,
text_size,
})
}
}

struct Methods {
has_filter: bool,
filter_out_size: u64,
Expand Down

0 comments on commit 9b3e078

Please sign in to comment.