DWARF¶
Introduction¶
DWARF debug information can be embedded directly within a binary (the default for ELF files) or stored in a separate, dedicated file.
When DWARF debug information is embedded within the binary, you can access it using the attribute. This attribute returns a object:
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
}
Additionally, the function can be used to load a DWARF file, whether it is embedded or standalone:
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");
Once loaded, you can use the API to interact with the debug information:
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);
In the case of an external DWARF file, you can bind this debug file to a using the function.
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);
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
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.
Generating C/C++ Definitions¶
DWARF functions, variables, types and compilation units can be turned back into a C/C++ definition thanks to the to_decl() function:
The generated output can be configured with a structure (e.g. to prefer C++ syntax or change the indentation):
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));
}
DWARF Editor¶
Editing Existing DWARF
LIEF does not currently support modifying an existing DWARF file.
LIEF provides a comprehensive high-level API for programmatically creating DWARF files. This works by using the interface, which can be instantiated using :
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);
Given this , you can create one or more entries, which own various , , and objects.
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.
API¶
You can find the documentation of the API for the different languages here:
Rust API: lief::dwarf