PDB¶
Introduction¶
Unlike DWARF debug information, PDB debug information is always stored externally from the original binary. Nevertheless, the original binary keeps the path of the PDB file in the attribute.
Based on this fact, tries to instantiate a object using this file path. If it fails, it returns
nullptr or None.You can also instantiate a object using :
pe: lief.PE.Binary
if (debug_info := pe.debug_info) is not None:
assert isinstance(debug_info, lief.pdb.DebugInfo)
print(f"PDB Debug handler: {debug_info}")
# Or you can load the PDB directly:
pdb = lief.pdb.load("some.pdb")
std::unique_ptr<LIEF::PE::Binary> pe;
if (const LIEF::DebugInfo* info = pe->debug_info()) {
assert(LIEF::pdb::DebugInfo::classof(info) && "Wrong DebugInfo type");
const auto& pdb = static_cast<const LIEF::pdb::DebugInfo&>(*info);
}
// Or loading directly the pdb file
std::unique_ptr<LIEF::pdb::DebugInfo> pdb = LIEF::pdb::load("some.pdb");
let pe: &lief::pe::Binary = some_pe;
if let Some(lief::DebugInfo::Pdb(pdb)) = pe.debug_info() {
// PDB debug info
}
let pdb = lief::pdb::load("some.pdb");
At this point, the PDB instance () can be used to explore the PDB debug information:
pdb: lief.pdb.DebugInfo
print(f"arg={pdb.age}, guid={pdb.guid}")
for sym in pdb.public_symbols:
print(f"name={sym.name}, section={sym.section_name}, RVA={sym.RVA}")
for ty in pdb.types:
if isinstance(ty, lief.pdb.types.Class):
print(f"Class[name]={ty.name}")
for cu in pdb.compilation_units:
print(f"module={cu.module_name}")
for src in cu.sources:
print(f" - {src}")
for func in cu.functions:
print(
f"name={func.name}, section={func.section_name}, RVA={func.RVA}, code_size={func.code_size}"
)
std::unique_ptr<LIEF::pdb::DebugInfo> pdb;
log(Level::Info, "age={}, guid={}", std::to_string(pdb->age()), pdb->guid());
for (const LIEF::pdb::PublicSymbol& symbol : pdb->public_symbols()) {
log(Level::Info, "name={}, section={}, RVA={}", symbol.name(),
symbol.section_name(), std::to_string(symbol.RVA()));
}
for (const LIEF::pdb::Type& ty : pdb->types()) {
if (LIEF::pdb::types::Class::classof(&ty)) {
const auto* clazz = ty.as<LIEF::pdb::types::Class>();
log(Level::Info, "Class[name]={}", clazz->name().value_or(""));
}
}
for (const LIEF::pdb::CompilationUnit& CU : pdb->compilation_units()) {
log(Level::Info, "module={}", CU.module_name());
for (const std::string& src : CU.sources()) {
log(Level::Info, " - {}", src);
}
for (const LIEF::pdb::Function& func : CU.functions()) {
log(Level::Info, "name={}, section={}, RVA={}, code size={}", func.name(),
func.section_name(), std::to_string(func.RVA()),
std::to_string(func.code_size()));
}
}
let pdb = lief::pdb::load(path).unwrap_or_else(|| {
process::exit(1);
});
println!("age={}, guid={}", pdb.age(), pdb.guid());
for symbol in pdb.public_symbols() {
println!(
"name={}, section={}, RVA={}",
symbol.name(),
symbol.section_name().unwrap_or("".to_string()),
symbol.rva()
);
}
for ty in pdb.types() {
if let lief::pdb::Type::Class(clazz) = ty {
println!("Class[name]={}", clazz.name().unwrap_or_default());
}
}
for cu in pdb.compilation_units() {
println!("module={}", cu.module_name());
for src in cu.sources() {
println!(" - {}", src);
}
for func in cu.functions() {
println!(
"name={}, section={}, RVA={}, code_size={}",
func.name(),
func.section_name(),
func.rva(),
func.code_size()
);
}
}
You can also use the function to bind a PDB file to an existing :
binary: lief.Binary
dbg = binary.load_debug_info(r"C:\Users\romain\LIEF.pdb")
std::unique_ptr<LIEF::Binary> binary;
binary->load_debug_info(R"(C:\Users\romain\LIEF.pdb)");
let bin: &mut dyn lief::generic::Binary = some_bin;
let path = PathBuf::from("C:\\Users\\romain\\LIEF.pdb");
bin.load_debug_info(&path);
Note that can also attach an external DWARF file to a PE binary, even though this is not a typical use case. For instance, the BinaryNinja and Ghidra DWARF export plugins can generate a DWARF file for a PE binary based on analyses performed by these frameworks.
This external loading API is useful for adding debug information that might not already be present in the binary. For instance, the function can leverage this additional debug information to disassemble functions defined in the debug file previously loaded:
binary: lief.Binary
dbg = binary.load_debug_info(r"C:\Users\romain\LIEF.pdb")
# The location (address/size) of `my_function` is defined in LIEF.pdb
for inst in binary.disassemble("my_function"):
print(inst)
std::unique_ptr<LIEF::Binary> binary;
binary->load_debug_info(R"(C:\Users\romain\LIEF.pdb)");
// The location (address/size) of `my_function` is defined in LIEF.pdb
for (const LIEF::assembly::Instruction& inst :
binary->disassemble("my_function"))
{
std::cout << inst << '\n';
}
let bin: &mut dyn lief::generic::Binary = some_bin;
let path = PathBuf::from("C:\\Users\\romain\\LIEF.pdb");
bin.load_debug_info(&path);
// The location (address/size) of `my_function` is defined in LIEF.pdb
for inst in bin.disassemble_symbol("my_function") {
println!("{inst}");
}
Generating C/C++ Definitions¶
PDB types, functions and compilation units can be turned into C/C++ definitions using the to_decl() function:
The generated output can be configured with a structure:
pdb: lief.pdb.DebugInfo
opt = lief.DeclOpt()
opt.is_cpp = True
for ty in pdb.types:
print(ty.to_decl(opt))
for cu in pdb.compilation_units:
# Emit the definition of the functions of the compilation unit
print(cu.to_decl(opt))
for func in cu.functions:
print(func.to_decl(opt))
std::unique_ptr<LIEF::pdb::DebugInfo> pdb;
LIEF::DeclOpt opt;
opt.is_cpp(true);
for (const LIEF::pdb::Type& ty : pdb->types()) {
std::cout << ty.to_decl(opt) << '\n';
}
for (const LIEF::pdb::CompilationUnit& CU : pdb->compilation_units()) {
std::cout << CU.to_decl(opt) << '\n';
for (const LIEF::pdb::Function& func : CU.functions()) {
std::cout << func.to_decl(opt) << '\n';
}
}
let pdb: &lief::pdb::DebugInfo = some_pdb;
let opt = lief::DeclOpt {
is_cpp: true,
..Default::default()
};
for ty in pdb.types() {
println!("{}", ty.to_decl_with_opt(&opt));
}
for cu in pdb.compilation_units() {
println!("{}", cu.to_decl_with_opt(&opt));
for func in cu.functions() {
println!("{}", func.to_decl_with_opt(&opt));
}
}
API¶
You can find the documentation of the API for the different languages here:
Rust API: lief::pdb