DWARF


Introduction

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}")
Additionally, the function can be used to load a DWARF file, whether it is embedded or standalone:
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")
Once loaded, you can use the API to interact with the debug information:
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")

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")
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:
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)

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 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 :
import lief

pe = lief.PE.parse("demo.exe")

editor: lief.dwarf.Editor = lief.dwarf.Editor.from_binary(pe)
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")

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:

Python API

C++ API

Rust API: lief::dwarf