BeamSpy

CI Hex.pm Docs

objdump, strings, and readelf for the BEAM VM

BeamSpy is a BEAM file analysis tool that provides commands for inspecting compiled BEAM modules.

Features

Installation

Add beam_spy to your list of dependencies in mix.exs:

def deps do
[
{:beam_spy, "~> 0.2.0"}
]
end

As a CLI tool

git clone https://github.com/QuinnWilton/beam_spy
cd beam_spy
mix deps.get && mix escript.build

This creates a beam_spy executable in the project directory.

Quick Start

# Show module metadata
./beam_spy info Enum
# List exported functions, filtered by name
./beam_spy exports lists --filter=map
# Disassemble with source interleaving
./beam_spy disasm GenServer -f "handle_call*" --source
# Generate a call graph as SVG
./beam_spy callgraph Enum --format=dot | dot -Tsvg -o graph.svg

Module Resolution

All commands accept either a file path or a module name:

# Module names (resolved automatically via code path)
./beam_spy info Enum # Elixir module
./beam_spy info lists # Erlang module
./beam_spy info GenServer # Standard library
# Direct file paths
./beam_spy info ./my_module.beam
./beam_spy info /path/to/module.beam

Commands Reference

Command Purpose
atoms Extract atom table
exports List exported functions
imports List imported functions
info Show module metadata
chunks List/inspect BEAM chunks
disasm Disassemble bytecode
callgraph Build function call graph

atoms

Extract the atom table from a BEAM file.

./beam_spy atoms Enum
./beam_spy atoms Enum --filter=map
./beam_spy atoms Enum --format=json

Options:

Option Short Description
--format -o Output format: text, json
--filter -F Filter pattern (substring, re:regex, or glob:pattern)

exports

List exported functions from a module.

./beam_spy exports lists
./beam_spy exports Enum --filter=map
./beam_spy exports Enum --plain # One per line (for piping)

Options:

Option Short Description
--format -o Output format: text, json
--filter -F Filter pattern
--plain Output plain text (one per line)

imports

List imported functions from a module.

./beam_spy imports GenServer
./beam_spy imports GenServer --group # Group by module

Options:

Option Short Description
--format -o Output format: text, json
--filter -F Filter pattern
--group -g Group imports by module

info

Show module metadata.

./beam_spy info Enum

Output includes:

chunks

List BEAM file chunks or dump raw chunk data.

./beam_spy chunks Enum # List all chunks
./beam_spy chunks Enum --raw AtU8 # Hex dump of atom table

Options:

Option Short Description
--format -o Output format: text, json
--raw -r Hex dump of specific chunk (e.g., AtU8)

Common chunk IDs:

Chunk Description
AtU8 Atom table (UTF-8)
Code Bytecode
StrT String table
ImpT Import table
ExpT Export table
FunT Lambda/fun table
LitT Literal table (compressed)
Dbgi Debug info
Docs Documentation
Line Line number table

disasm

Disassemble BEAM bytecode into human-readable assembly.

./beam_spy disasm lists -f "reverse/1"
./beam_spy disasm Enum -f "map*" --source
./beam_spy disasm GenServer --format=json

Options:

Option Short Description
--format -o Output format: text, json
--function -f Filter to specific function (supports globs)
--source -S Interleave source code with disassembly

Function filter patterns:

callgraph

Build a function call graph.

./beam_spy callgraph Enum
./beam_spy callgraph Enum --format=dot | dot -Tsvg -o graph.svg
./beam_spy callgraph Enum --format=json

Options:

Option Short Description
--format -o Output format: text, json, dot

Assembler Syntax Reference

The disasm command outputs BEAM bytecode in a human-readable assembler syntax. This section documents the syntax so you can understand the output.

Registers

BEAM uses several register types:

Type Syntax Purpose
X registers x(0), x(1), ... Arguments, return values, and temporaries
Y registers y(0), y(1), ... Stack frame slots (preserved across calls)
Float registers fr(0), fr(1), ... Floating-point operations
Labels f(19), f(23), ... Branch targets (failure labels)

X registers are the primary working registers. x(0) through x(N-1) hold function arguments on entry. x(0) holds the return value.

Y registers are stack slots allocated with allocate. They persist across function calls and must be deallocated before returning.

Atoms

Atoms are displayed with Elixir-style colon prefix:

:ok
:error
:handle_call
Enum # Module atoms may omit the colon

External Function References

External function calls use the format :module:function/arity:

:erlang:error/1
:lists:reverse/2
Enum:map/2

Lists

Lists use square bracket notation:

[] # Empty list
[x(0), x(1)] # List of registers
[:ok, x(0)] # Mixed list

Map Operations

Map instructions use special syntax:

get_map_elements - Extract map values:

get_map_elements f(425), x(0), [step => x(5), last => x(4), first => x(3)]

put_map_assoc/put_map_exact - Create/update maps:

put_map_assoc f(0), x(1), x(2), 2, %{key: x(3), other: x(4)}

Allocation Tuples

Stack allocation uses compact notation:

alloc(w:2) # Allocate 2 words
alloc(w:1, fn:1) # 1 word + 1 fun slot
alloc(w:2, fl:1) # 2 words + 1 float slot

Labels

Labels mark branch targets and function entry points:

label 19:
func_info :lists, :reverse, 1
label 20: # Entry point (from function header)
test :is_nonempty_list, f(23), [x(0)]

The number in f(x) in test instructions refers to the label to jump to on failure.

Opcode Categories

BeamSpy categorizes opcodes for syntax highlighting:

Category Opcodes Description
call call, call_ext, call_only, call_fun2, apply, bif1, gc_bif2 Function invocation
control label, jump, select_val, is_* tests Control flow
data move, swap, get_list, put_list, get_tuple_element Data manipulation
stack allocate, deallocate, test_heap, trim Stack management
return return Function return
exception try, catch, raise, build_stacktrace Exception handling
error func_info, badmatch, case_end Error generation
message send, receive, wait, timeout Message passing
binary bs_get_*, bs_match, bs_create_bin Binary operations
float fadd, fsub, fmul, fdiv, fconv Floating-point
meta line, executable_line Metadata

Common Instruction Patterns

Function entry:

label 19:
func_info :lists, :reverse, 1 # Error info (called on pattern match failure)
label 20: # Actual entry point

Type tests:

test :is_nonempty_list, f(23), [x(0)] # Test x(0), jump to label 23 on failure
test :is_nil, f(21), [x(2)]
test :is_eq_exact, f(425), [x(2), Range]

List operations:

get_list x(0), x(1), x(2) # x(1) = hd(x(0)), x(2) = tl(x(0))
put_list x(1), [], x(1) # x(1) = [x(1) | []]
put_list x(0), x(1), x(0) # x(0) = [x(0) | x(1)]

Stack management:

allocate 4, 6 # Allocate 4 Y slots, preserve 6 X regs
test_heap 4, 2 # Ensure 4 words heap space, preserve 2 X regs
deallocate 1 # Free 1 Y slot
trim 3, 1 # Remove 3 Y slots, keep 1

Function calls:

call 2, {Enum, :map_range, 4} # Local call
call_ext 3, :Elixir.Enum:reduce/3 # External call
call_only 2, {Enum, :"-map/2-...", 2} # Tail call (local)
call_ext_only 2, :lists:reverse/2 # Tail call (external)

Example: lists:reverse/1

function reverse/1 (entry: 20)
────────────────────────────────────────────────────────
line 15
label 19:
func_info :lists, :reverse, 1
label 20:
test :is_nonempty_list, f(23), [x(0)]
get_list x(0), x(1), x(2)
test :is_nonempty_list, f(22), [x(2)]
get_list x(2), x(0), x(2)
test :is_nil, f(21), [x(2)]
test_heap 4, 2
put_list x(1), [], x(1)
put_list x(0), x(1), x(0)
return
label 21:
test_heap 4, 3
put_list x(1), [], x(1)
put_list x(0), x(1), x(1)
move x(2), x(0)
line 16
call_ext_only 2, :lists:reverse/2
label 22:
test :is_nil, f(19), [x(2)]
return
label 23:
test :is_nil, f(19), [x(0)]
return

This shows the optimized implementation:

Source Interleaving

With --source, BeamSpy shows source code alongside bytecode:

function map/2 (entry: 420)
────────────────────────────────────────────────────────
1687 │ def map(enumerable, fun)
│ label 419:
│ func_info Enum, :map, 2
│ label 420:
│ test :is_list, f(421), [x(0)]
│ call_only 2, {Enum, :"-map/2-lists^map/1-1-", 2}

Format:

Distant references:

When bytecode references lines far from the current function (e.g., inlined code), BeamSpy shows a compact reference:

→ map_range (line 4539)
│ call_fun 1

This indicates the bytecode came from the map_range function at line 4539.

Common Options

These options are available on all subcommands:

Option Short Description
--theme -t Color theme to use (default: default)
--paging Paging mode: auto, always, never

The --list-themes flag is available at the top level to list available themes.

Environment Variables

Output Formats

Most commands support multiple output formats:

./beam_spy exports Enum --format=json | jq '.[] | select(.arity == 2)'
./beam_spy callgraph Enum --format=dot | dot -Tpng -o graph.png

Themes

BeamSpy supports color themes defined in TOML format. Themes are stored in priv/themes/.

# List available themes
./beam_spy --list-themes
# Use a specific theme
./beam_spy disasm Enum --theme=monokai

Theme files define colors for:

License

MIT