DWARF debug information can be embedded directly within a binary (the default for ELF files) or stored in a separate, dedicated file.
import lief
elf = lief.ELF.parse("/bin/with_debug")
if debug_info := elf.debug_info:
assert isinstance(debug_info, lief.dwarf.DebugInfo)
print(f"DWARF Debug handler: {debug_info}")
auto elf = LIEF::ELF::Parser::parse("/bin/with_debug");
if (const LIEF::DebugInfo* info = elf->debug_info()) {
assert(LIEF::dwarf::DebugInfo::classof(info) && "Wrong debug type");
const auto& dwarf_dbg = static_cast<const LIEF::dwarf::DebugInfo&>(*info);
}
let elf = lief::elf::Binary::parse("/bin/ls").unwrap();
if let Some(lief::DebugInfo::Dwarf(dwarf)) = elf.debug_info() {
// DWARF debug info
}
import lief
dbg: lief.dwarf.DebugInfo | None = lief.dwarf.load("/bin/with_debug")
dbg: lief.dwarf.DebugInfo | None = lief.dwarf.load("external_dwarf")
dbg: lief.dwarf.DebugInfo | None = lief.dwarf.load("debug.dwo")
auto dbg = LIEF::dwarf::load("/bin/with_debug");
dbg = LIEF::dwarf::load("external_dwarf");
dbg = LIEF::dwarf::load("debug.dwo");
let dbg = lief::dwarf::load("/bin/with_debug");
let dbg = lief::dwarf::load("external_dwarf");
let dbg = lief::dwarf::load("debug.dwo");
dbg: lief.dwarf.DebugInfo
for compilation_unit in dbg.compilation_units:
print(compilation_unit.producer)
for func in compilation_unit.functions:
print(func.name, func.linkage_name, func.address)
for var in compilation_unit.variables:
print(var.name, var.address)
for ty in compilation_unit.types:
print(ty.name, ty.size)
dbg.find_function("_ZNSi4peekEv")
dbg.find_function("std::basic_istream<char, std::char_traits<char> >::peek()")
dbg.find_function(0x137A70)
dbg.find_variable("_ZNSt12out_of_rangeC1EPKc")
dbg.find_variable("std::out_of_range::out_of_range(char const*)")
dbg.find_variable(0x2773A0)
dbg.find_type("my_type_t")
std::unique_ptr<LIEF::dwarf::DebugInfo> dbg;
for (const LIEF::dwarf::CompilationUnit& CU : dbg->compilation_units()) {
log(LEVEL::INFO, "Producer: {}", CU.producer());
for (const LIEF::dwarf::Function& func : CU.functions()) {
log(LEVEL::INFO, "name={}, linkage={}, address={}", func.name(),
func.linkage_name(), std::to_string(func.address().value_or(0)));
}
for (const LIEF::dwarf::Variable& var : CU.variables()) {
log(LEVEL::INFO, "name={}, address={}", var.name(),
std::to_string(var.address().value_or(0)));
}
for (const LIEF::dwarf::Type& ty : CU.types()) {
log(LEVEL::INFO, "name={}, size={}", ty.name().value_or(""),
std::to_string(ty.size().value_or(0)));
}
}
dbg->find_function("_ZNSi4peekEv");
dbg->find_function("std::basic_istream<char, std::char_traits<char> >::peek()");
dbg->find_function(0x137a70);
dbg->find_variable("_ZNSt12out_of_rangeC1EPKc");
dbg->find_variable("std::out_of_range::out_of_range(char const*)");
dbg->find_variable(0x2773a0);
let path: &Path = some_path;
let dbg = lief::dwarf::load(path).unwrap_or_else(|| {
process::exit(1);
});
for cu in dbg.compilation_units() {
println!("Producer: {}", cu.producer());
for func in cu.functions() {
println!(
"name={}, linkage={}, address={}",
func.name(),
func.linkage_name(),
func.address().unwrap_or(0)
);
}
for var in cu.variables() {
println!(
"name={}, address={}",
var.name(),
var.address().unwrap_or(0)
);
}
for ty in cu.types() {
println!(
"name={}, size={}",
ty.name().unwrap_or("".to_string()),
ty.size().unwrap_or(0)
);
}
}
dbg.function_by_name("_ZNSi4peekEv");
dbg.function_by_name("std::basic_istream<char, std::char_traits<char> >::peek()");
dbg.function_by_addr(0x137a70);
dbg.variable_by_name("_ZNSt12out_of_rangeC1EPKc");
dbg.variable_by_name("std::out_of_range::out_of_range(char const*)");
dbg.variable_by_addr(0x137a70);
Here’s an example:
binary: lief.Binary
dbg = binary.load_debug_info("/home/romain/dev/LIEF/some.dwo")
std::unique_ptr<LIEF::Binary> binary;
binary->load_debug_info("/home/romain/dev/LIEF/some.dwo");
let bin: &mut dyn lief::generic::Binary = some_bin;
let path = PathBuf::from("/home/romain/dev/LIEF/some.dwo");
bin.load_debug_info(&path);
binary: lief.Binary
binary.load_debug_info("/home/romain/dev/LIEF/some.dwo")
# The location (address/size) of `my_function` is defined in some.dwo
for inst in binary.disassemble("my_function"):
print(inst)
std::unique_ptr<LIEF::Binary> binary;
binary->load_debug_info("/home/romain/dev/LIEF/some.dwo");
// The location (address/size) of `my_function` is defined in some.dwo
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("/home/romain/dev/LIEF/some.dwo");
bin.load_debug_info(&path);
// The location (address/size) of `my_function` is defined in some.dwo
for inst in bin.disassemble_symbol("my_function") {
println!("{inst}");
}
Additionally, you may also want to explore the BinaryNinja and Ghidra DWARF export plugins, which generate debug information based on the analyses performed by these frameworks.
DWARF functions, variables, types and compilation units can be turned back into a C/C++ definition thanks to the to_decl() function:
dbg = lief.dwarf.load("/bin/with_debug")
func = dbg.find_function("main")
print(func.to_decl())
opt = lief.DeclOpt()
opt.is_cpp = True
opt.indentation = 4
for cu in dbg.compilation_units:
# Emit the definition of the functions of the compilation unit
print(cu.to_decl(opt))
auto dbg = LIEF::dwarf::load("/bin/with_debug");
std::unique_ptr<LIEF::dwarf::Function> func = dbg->find_function("main");
std::cout << func->to_decl() << '\n';
LIEF::DeclOpt opt;
opt.is_cpp(true).indentation(4);
for (const LIEF::dwarf::CompilationUnit& CU : dbg->compilation_units()) {
std::cout << CU.to_decl(opt) << '\n';
}
let dbg = lief::dwarf::load("/bin/with_debug").unwrap();
if let Some(func) = dbg.function_by_name("main") {
println!("{}", func.to_decl());
}
let opt = lief::DeclOpt {
is_cpp: true,
indentation: 4,
..Default::default()
};
for cu in dbg.compilation_units() {
println!("{}", cu.to_decl_with_opt(&opt));
}
Editing Existing DWARF
LIEF does not currently support modifying an existing DWARF file.
pe = lief.PE.parse("demo.exe")
assert isinstance(pe, lief.PE.Binary)
editor = lief.dwarf.Editor.from_binary(pe)
std::unique_ptr<LIEF::PE::Binary> pe = LIEF::PE::Parser::parse("demo.exe");
std::unique_ptr<LIEF::dwarf::Editor> editor =
LIEF::dwarf::Editor::from_binary(*pe);
let path: &Path = some_path;
let mut bin = lief::pe::Binary::parse(path).unwrap();
let editor = lief::dwarf::Editor::from_binary(&mut bin);
editor: lief.dwarf.Editor
unit = editor.create_compilation_unit()
unit.set_producer("LIEF")
func = unit.create_function("hello")
func.set_address(0x123)
struct_ptr = unit.create_structure("my_struct_t").pointer_to()
assert isinstance(struct_ptr, lief.dwarf.editor.PointerType)
func.set_return_type(struct_ptr)
var = func.create_stack_variable("local_var")
var.set_stack_offset(8)
editor.write("/tmp/out.debug")
std::unique_ptr<LIEF::dwarf::Editor> editor;
std::unique_ptr<LIEF::dwarf::editor::CompilationUnit> unit =
editor->create_compilation_unit();
unit->set_producer("LIEF");
std::unique_ptr<LIEF::dwarf::editor::Function> func =
unit->create_function("hello");
func->set_address(0x123);
func->set_return_type(*unit->create_structure("my_struct_t")->pointer_to());
std::unique_ptr<LIEF::dwarf::editor::Variable> var =
func->create_stack_variable("local_var");
var->set_stack_offset(8);
editor->write("/tmp/out.debug");
let editor: &mut lief::dwarf::Editor = some_editor;
let mut unit = editor.create_compile_unit().unwrap();
unit.set_producer("LIEF");
let mut func = unit.create_function("hello").unwrap();
func.set_address(0x123);
func.set_return_type(&unit.create_structure("my_struct_t").pointer_to());
let mut var = func.create_stack_variable("local_var");
var.set_stack_offset(8);
editor.write("/tmp/out.debug");
BinaryNinja & Ghidra
This feature is provided as a plugin for BinaryNinja and Ghidra.
You can find the documentation of the API for the different languages here:
Rust API: lief::dwarf