ffi.disasm
x86-64 disassembly utilities backed by Capstone. All addresses are Lua numbers (doubles representing 64-bit pointers). Capstone is initialised lazily on first use; if initialisation fails, functions return an empty table or nil rather than raising an error.
Functions
ffi.disasm.at(address, count?)
Disassembles up to count instructions starting at address and returns them as an array. Each element is an instruction table with the following fields:
| Field | Type | Description |
|---|---|---|
address | number | Virtual address of this instruction. |
mnemonic | string | Instruction mnemonic (e.g. "mov", "call"). |
op_str | string | Operand string as printed by Capstone (e.g. "rax, qword ptr [rip + 0x1234]"). |
size | number | Byte length of the instruction. |
bytes | table | Array of number — the raw bytes of the instruction (1-indexed). |
operands | table | Array of operand tables (see below). |
Each operand table always contains type ("reg", "imm", "mem", or "unknown") and size (number of bytes the operand accesses). Additional fields depend on type:
"reg":reg(string) — register name."imm":imm(number) — immediate value."mem":base(string, optional),index(string, optional),scale(number),disp(number).
Parameters
| Name | Type | Description |
|---|---|---|
address | number | Starting address to disassemble from. |
count? | number | Maximum number of instructions to decode. Defaults to |
Returns
| Type | Description |
|---|---|
table | Array of instruction tables. Empty if Capstone failed to initialise or could not decode any instructions. |
ffi.disasm.at_bytes(address, byte_count)
Disassembles as many complete instructions as fit within byte_count bytes starting at address. Unlike at, the limit is measured in bytes rather than instruction count, which pairs naturally with disasm.size. Returns an array of instruction tables with the same shape as at.
Parameters
| Name | Type | Description |
|---|---|---|
address | number | Starting address to disassemble from. |
byte_count | number | Maximum number of bytes to consume. |
Returns
| Type | Description |
|---|---|
table | Array of instruction tables. Empty if Capstone failed to initialise or could not decode any instructions. |
ffi.disasm.pull_global(address)
Decodes the single instruction at address and, if it is a RIP-relative mov of the form mov <reg>, qword ptr [rip + offset], resolves and returns the absolute target address. This is the standard compiler pattern for loading the address of a global variable. Returns nil if the instruction is not that pattern, if disassembly failed, or if Capstone could not be initialised.
Parameters
| Name | Type | Description |
|---|---|---|
address | number | Address of the instruction to inspect. |
Returns
| Type | Description |
|---|---|
number? | Resolved global address, or |
ffi.disasm.pull_all_globals(address, max_size?)
Scans up to max_size instructions starting at address and collects the resolved target address of every RIP-relative mov <reg>, qword ptr [rip + offset] instruction found (the pattern used by the compiler to access global variables).
Scanning stops early if an INT3 padding byte is encountered (indicating the end of the function body). Unlike size, this function does not stop at ret or jmp, because globals can be loaded after such instructions (e.g. into a return register in the function epilogue).
Returns an array of resolved addresses in the order they were found.
Parameters
| Name | Type | Description |
|---|---|---|
address | number | Starting address to scan from. |
max_size? | number | Maximum number of instructions to examine. Defaults to |
Returns
| Type | Description |
|---|---|
table | Array of |
ffi.disasm.pull_calls(address, max_size?)
Scans up to max_size instructions starting at address and collects the resolved target address of every call instruction found.
Both direct (call imm) and RIP-relative indirect (call qword ptr [rip + offset]) forms are resolved to absolute addresses. Other indirect forms (e.g. call rax) are skipped because the target cannot be determined statically.
Scanning stops early at the first INT3 padding byte. This is useful for finding thunk addresses of virtual-function calls, where hooking the thunk's inner target is more practical than hooking the thunk itself.
Returns an array of resolved call-target addresses in encounter order.
Parameters
| Name | Type | Description |
|---|---|---|
address | number | Starting address to scan from. |
max_size? | number | Maximum number of instructions to examine. Defaults to |
Returns
| Type | Description |
|---|---|
table | Array of |
ffi.disasm.size(address)
Returns the byte size of the function that contains address.
Primary method: RtlLookupFunctionEntry reads the .pdata RUNTIME_FUNCTION table, which covers all non-leaf functions on x86-64 Windows.
Fallback: For leaf functions (which have no .pdata entry), Capstone linearly sweeps up to 0x1000 bytes and stops at the first run of INT3 padding bytes.
A lone INT3 surrounded by non-INT3 instructions is treated as a live __debugbreak() or assertion trap, not as padding — only a INT3 immediately after a ret/jmp, or two or more consecutive INT3s, triggers the boundary cut.
Returns nil if neither method produces a result.
Parameters
| Name | Type | Description |
|---|---|---|
address | number | Any address within the function to measure (typically the function entry point). |
Returns
| Type | Description |
|---|---|
number? | Size of the function in bytes, or |
ffi.disasm.autosig(address, size)
Copies size bytes from address and auto-masks them into a byte-pattern signature string with wildcard (?) bytes in place of relocatable values (e.g. relative offsets in call/jmp instructions). The resulting string is suitable for use with pattern scanners. Returns nil if the masking process produces an empty signature.
Parameters
| Name | Type | Description |
|---|---|---|
address | number | Starting address of the region to signature. |
size | number | Number of bytes to read and mask. |
Returns
| Type | Description |
|---|---|
string? | The auto-generated signature string, or |