Disassembler

Introduction

LIEF Extended provides a user-friendly API for disassembling code within various parts of executable formats for the following architectures: x86/x86-64, ARM, AArch64, RISC-V, MIPS, PowerPC, and eBPF.

Disassemble a binary

elf: lief.ELF.Binary

for inst in elf.disassemble(0x400120):
    print(inst)
From a design perspective, the disassembler returns a lazy iterator, yielding a instance as it evaluates the instruction at each address.

Consequently, when calling elf.disassemble_address(0x400), no disassembly occurs until the iterator is advanced.

Inspect instructions and operands

Instructions are represented by the object, which is extended by architecture-specific objects:
In Python, use pattern matching or isinstance(...) to select an architecture-specific :
inst: lief.assembly.Instruction

match inst:
    case lief.assembly.riscv.Instruction():
        opcode: lief.assembly.riscv.OPCODE = inst.opcode
In C++, use to access the architecture-specific instruction:
std::unique_ptr<LIEF::assembly::Instruction> inst;

if (const auto* riscv_inst = inst->as<LIEF::assembly::riscv::Instruction>()) {
  LIEF::assembly::riscv::OPCODE opcode = riscv_inst->opcode();
}
pub fn check_opcode(some_inst: &lief::assembly::Instructions) {
    let inst: &lief::assembly::Instructions = some_inst;

    if let lief::assembly::Instructions::RiscV(riscv) = inst {
        println!("{:?}", riscv.opcode());
    }
}

Note

You can also check the assembler documentation here: Assembler

For the x86/x86-64 and AArch64 architectures, you can also iterate over an instruction’s operands:

import lief
macho: lief.MachO.Binary

for inst in macho.disassemble(0x400120):
    print(inst)
    # Check inst properties
    if inst.is_branch:
        print(f"Resolved: {inst.branch_target}")

    for idx, operand in enumerate(inst.operands):
        match operand:
            case lief.assembly.aarch64.operands.Register():
                print(f"op[{idx}]: REG - {operand.value}")
            case lief.assembly.aarch64.operands.Memory():
                print(f"op[{idx}]: MEM - {operand.base}")
            case lief.assembly.aarch64.operands.PCRelative():
                print(f"op[{idx}]: PCR - {operand.value}")
            case lief.assembly.aarch64.operands.Immediate():
                print(f"op[{idx}]: IMM - {operand.value}")

See the architecture-specific Python and C++ references for operand types and instruction properties. To write a patch using assembly text, continue with the assembler guide.

x86/x86-64

On x86/x86-64, also exposes an API to inspect and rewrite the LOCK prefix of an instruction:
import lief
inst: lief.assembly.x86.Instruction

if inst.has_lock_prefix or inst.is_atomic:
    print(f"{inst} is atomic")
elif inst.is_lockable and (locked := inst.lock()) is not None:
    print(f"atomic version of {inst}: {locked}")

Use Cases

DWARF Function

Warning

only works if the DWARF debug info is embedded in the binary. This is the default behavior for ELF binaries, but this is not the case for Mach-O .dSYM files.
import lief
elf = lief.ELF.parse("/bin/hello")

main = elf.debug_info.find_function("main")

for inst in main.instructions:
    print(inst)

Dyld Shared Cache

dyld_cache: lief.dsc.DyldSharedCache

for inst in dyld_cache.disassemble(0x1886F4A44):
    print(inst)

COFF Support

For more details, please check the COFF Disassembler section

In-Memory Disassembler

The disassembler is also available for analyzing code directly in the memory of the running process. This functionality is exposed through the runtime API , as detailed in the Runtime Memory documentation.

Technical Details

The disassembler is based on LLVM’s MC layer, which is known to be efficient and accurate for disassembling code. This LLVM MC layer is already used by other projects like capstone or, more recently, Nyxstone.

Compared to Capstone, LIEF uses a mainstream LLVM version with limited modifications to the MC layer. On the other hand, it does not expose a C API, supports fewer architectures than Capstone, and does not expose a standalone API.

Note

The current LLVM version is 22.x.

Unlike Nyxstone’s disassembler, LIEF hides LLVM from the public API, meaning that LLVM does not need to be installed on the system. On the other hand, it does not expose a standalone API.

The major difference between LIEF’s disassembler and other projects is that it does not expose a standalone API for disassembling arbitrary code. The disassembler is bound to the object from which the API is exposed (, , , etc.).

API

Python API

C++ API

Rust API: lief::assembly