Disassembler


Introduction

LIEF extended exposes a user-friendly API to disassemble code in different places of executable formats for the following architectures: x86/x86-64, ARM, AArch64, RISC-V, Mips, PowerPC, eBPF.

import lief

elf = lief.ELF.parse("/bin/hello")

inst: lief.assembly.Instruction = ...

for inst in elf.disassemble(0x400120):
    print(inst)
From a design perspective, the disassembler returns a lazy iterator which outputs a instance when it evaluates the instruction at the address associated with the iterator’s position.

Thus, when calling elf.disassemble_address(0x400), nothing is disassembled until the iterator is processed.

An instruction is represented by the object: which is extended by the following objects for each supported architecture:

In Python, one can check the effective type of a lief.assembly.Instruction with isinstance(...):

inst: lief.assembly.Instruction = ...

if isinstance(inst, lief.assembly.riscv.Instruction):
   opcode: lief.assemble.riscv.OPCODE = inst.opcode

In C++, downcasting can be done using the function: LIEF::assembly::Instruction::as():

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();
}

In Rust, instructions are represented by the enum lief::assembly::Instructions. Thus, you can write:

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

Note

You can also check the assembler documentation here: Assembler

Use Cases

DWARF Function

Warning

is only working 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 .instructions:
    print(inst)

Dyld Shared Cache

import lief

dyld_cache: lief.dsc.DylibSharedCache = lief.dsc.load("macos-15.0.1/")

for inst in dyld_cache:
    print(inst)

Technical Details

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

Compared to Capstone, LIEF uses a mainstream LLVM version without any modification on 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.

Compared to Nyxstone’s disassembler, LLVM is hidden from the public API which means 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 the other projects is that it does not expose a standalone API to disassemble arbitrary code. The disassembler is bound to the object from which the API is exposed (, , , …).

Python API

C++ API

Rust API: lief::assembly