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");
if let Some(lief::DebugInfo::Dwarf(dwarf)) = elf.debug_info() {
// DWARF debug info
}
import lief
dbg: lief.dwarf.DebugInfo = lief.dwarf.load("/bin/with_debug")
dbg: lief.dwarf.DebugInfo = lief.dwarf.load("external_dwarf")
dbg: lief.dwarf.DebugInfo = lief.dwarf.load("debug.dwo")
auto dbg = LIEF::dwarf::load("/bin/with_debug");
auto dbg = LIEF::dwarf::load("external_dwarf");
auto 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");
import lief
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")
for (std::unique_ptr<LIEF::dwarf::CompilationUnit> CU : dbg->compilation_units()) {
log(LEVEL::INFO, "Producer: {}", CU->producer());
for (std::unique_ptr<LIEF::dwarf::Function> func : CU->functions()) {
log(LEVEL::INFO, "name={}, linkage={}, address={}",
func->name(), func->linkage_name(), func->address().value_or(0));
}
for (std::unique_ptr<LIEF::dwarf::Variable> var : CU->variables()) {
log(LEVEL::INFO, "name={}, address={}", var->name(), var->address().value_or(0));
}
for (std::unique_ptr<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_function("std::out_of_range::out_of_range(char const*)");
dbg->find_function(0x2773a0);
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:
import lief
binary: lief.Binary = ... # Can be an ELF/PE/Mach-O [...]
dbg: lief.DebugInfo = binary.load_debug_info("/home/romain/dev/LIEF/some.dwo")
std::unique_ptr<LIEF::Binary> binary; // Can be an ELF/PE/Mach-O
binary->load_debug_info("/home/romain/dev/LIEF/some.dwo");
bin: &mut dyn lief::generic::Binary = ...;
let path = PathBuf::from("/home/romain/dev/LIEF/some.dwo");
bin.load_debug_info(&path);
import lief
binary: lief.Binary = ... # Can be an ELF/PE/Mach-O [...]
dbg: lief.DebugInfo = 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; // Can be an ELF/PE/Mach-O
binary->load_debug_info("/home/romain/dev/LIEF/some.dwo");
// The location (address/size) of `my_function` is defined in some.dwo
for (std::unique_ptr<LIEF::asm::Instruction> inst : binary->disassemble("my_function")) {
std::cout << *inst << '\n';
}
bin: &mut dyn lief::generic::Binary = ...;
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.
Editing Existing DWARF
LIEF does not currently support modifying an existing DWARF file.
import lief
pe = lief.PE.parse("demo.exe")
editor: lief.dwarf.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 mut bin = lief::pe::Binary::parse(&path).unwrap();
let editor = lief::dwarf::Editor::from_binary(&mut bin);
unit: lief.dwarf.editor.CompilationUnit = editor.create_compilation_unit()
unit.set_producer("LIEF")
func: lief.dwarf.editor.Function = unit.create_function("hello")
func.set_address(0x123)
func.set_return_type(
unit.create_structure("my_struct_t").pointer_to()
)
var: lief.dwarf.editor.Variable = func.create_stack_variable("local_var")
var.set_stack_offset(8)
editor.write("/tmp/out.debug")
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 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