DWARF


Introduction

DWARF debug information can be included directly in the binary (which is the default behavior for ELF binaries) or stored in a separate dedicated file.

When the DWARF debug information is embedded within the binary, you can access it using the following attribute: . This attribute returns a :
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, we can use the function: to load a DWARF file, regardless of whether it is embedded or not:
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")
At this point, one can use all the API exposed in on the instantiated debug info:
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 want to check out the BinaryNinja and Ghidra DWARF export plugin which can generate debug information based on the analyses performed by these frameworks.

DWARF Editor

Editing Existing DWARF

Currently, LIEF does not support modifying an existing DWARF file

LIEF provides a comprehensive high-level API to create DWARF files programmatically. This works by using the interface that 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