decompiler CLI

July 25, 2026 · View on GitHub

The decompiler command is a thin, LLM-friendly client over DecLib. You load a binary once (which spawns a headless decompiler server in the background) and then run quick inspection or mutation commands against it. Multiple binaries and backends can be loaded at the same time; each server is identified by a short ID.

image

This document is for humans; the short reference version used by LLM agents lives at declib/skills/decompiler/SKILL.md and can be installed with decompiler install-skill.


Table of contents


Install & setup

pip install declib
# Register DecLib plugins into every detected decompiler.
declib --install
# Or point the installer at one specific decompiler:
declib --single-decompiler-install binja "/Applications/Binary Ninja.app"

After pip install declib, two entry points are available:

  • declib — the existing management CLI (install plugins, run the server, etc.)
  • decompiler — the new LLM-facing CLI documented here.

Pick a backend you have available:

  • angr — pure Python, always available. Good for end-to-end testing and small/medium binaries.
  • ghidra — requires GHIDRA_INSTALL_DIR and uses PyGhidra.
  • binja — requires a Binary Ninja license.
  • ida — requires IDA Pro.
  • jadx — optional; requires the official JADX 1.5.6+ distribution and Java 17+. DecLib finds it via JADX_HOME or jadx on PATH. Check setup with decompiler backend status jadx --json. It uses stable JVM/Dex references and has dedicated class, method, field, resource, and manifest commands; see the JADX backend guide.

Quick start

# 1. Load a binary. The first call spawns a detached headless server.
decompiler load ./fauxware --backend angr
# id: 3308b81cf8 …

# 2. Poke around.
decompiler list_functions                    # enumerate every function first
decompiler decompile main                    # by name
decompiler disassemble 0x40071d              # by absolute address
decompiler xref_to authenticate              # every code+data reference
decompiler get_callers authenticate          # call-sites only (subset of xref_to)
decompiler xref_from main                    # what main calls
decompiler list_strings --filter 'pass|key'  # regex-filtered strings

# 3. Mutate the database.
decompiler rename func sub_400662 trampoline
decompiler rename var v2 auth_result --function main

# 4. Tear it down when you're done.
decompiler stop --all

How it works

┌─────────────┐      spawns       ┌─────────────────────────┐
│  decompiler │ ────────────────▶ │ declib --server (headless│
│     CLI     │   (first load)    │ decompiler + AF_UNIX    │
│             │                   │ socket)                 │
│             │ ◀─────────────────│                         │
└─────────────┘   every command   └─────────────────────────┘


~/.local/state/declib/servers/<id>.json  ← the shared registry

Each running server writes a small JSON descriptor (id, socket_path, binary_path, binary_hash, backend, pid, started_at) into a shared registry directory. The CLI reads the registry to figure out which server to talk to. Stale records (server exited, socket missing) are pruned on read. Run decompiler list --show-registry to print just the path.

Every subcommand except load, list, and install-skill accepts --id, --binary, and --backend to pick which server to target when you have more than one running.


Subcommand reference

load

Load a binary, starting a headless server if one isn't already running for it.

decompiler load <binary> [--backend {angr,ghidra,binja,ida,jadx}]
                         [--id SERVER_ID]
                         [--force | --replace]
                         [--timeout SECONDS]
                         [--project-dir PATH]
                         [--json]
  • --backend (default: angr) — which decompiler to use.
  • --id — explicit server ID; otherwise one is auto-generated.
  • --force — start an additional server even if one already matches this (binary, backend). Keeps the old server alive.
  • --replace — stop any existing server for this (binary, backend) first, then start a fresh one. Use this when you want to re-analyze from scratch.
  • --timeout SECONDS (default: 300) — maximum time to wait for the backend to register. Slow Ghidra or IDA analyses may need a larger value; use a smaller value in automation when a strict task timebox matters.
  • --project-dir PATH — where to keep the backend's project/database files (Ghidra project, IDA .id*/.til, etc.). Default: a per-binary directory under the user cache (<platformdirs cache>/declib/projects/<binary>-<hash>/), so analysis artifacts don't pollute the binary's directory. Pass --project-dir "" to disable the cache dir and let the backend drop files alongside the binary (legacy behavior).

For a newly started server, output includes id, socket_path, binary_path, backend, project_dir, log_path, and status. status is either started or already_loaded (the latter describes an existing server and does not add a new log_path).

Detached server stdout and stderr are written to log_path. If the child exits before registering, load fails immediately instead of waiting for the full timeout and prints the final 8 KiB of that log. A timeout reports the same path and bounded tail, which usually reveals a missing dependency, license failure, backend exception, or stale project problem.

list

Show all running decompiler servers.

decompiler list [--show-registry] [--json]

Text output:

ID           BACKEND  PID      BINARY
3308b81cf8   angr     57613    /…/fauxware
9d77ab8fd4   angr     57786    /…/posix_syscall

(registry: /Users/me/Library/Application Support/declib/servers)
  • --show-registry — print the registry directory and exit (useful for scripting manual cleanup).
  • --json emits {"registry_dir": "...", "servers": [...]}.

batch

Execute several structured CLI operations over one persistent server connection. This avoids a new Python process, registry lookup, and socket handshake for every operation.

decompiler batch --file operations.jsonl [--id ID | --binary PATH | --backend BACKEND] [--stop-on-error] [--json]
decompiler batch --stdin [--id ID | --binary PATH | --backend BACKEND] [--stop-on-error] [--json]

The input is JSON Lines: one object with a unique optional id and a CLI argument array per line.

{"id":"functions","argv":["list_functions","--filter","main|auth"]}
{"id":"main","argv":["decompile","main"]}
{"id":"header","argv":["read_memory","0","4","--format","hex"]}

Choose the server on the outer batch command. Operations inherit that target and must not contain their own --id, --binary, or --backend. Server lifecycle commands (load, list, stop, sync, and nested batch) and unstructured --raw output are rejected inside a batch; normal inspection and mutation commands, including save, are supported.

By default, output is compact JSONL: one result per completed operation and a final summary line. --json emits one pretty JSON envelope with results and summary. Each result includes the operation ID, command, exit status, duration, and parsed JSON result or error. Processing continues after an error unless --stop-on-error is set, and the batch exits nonzero if any completed operation failed.

stop

Stop one or all servers.

decompiler stop [--id SERVER_ID] [--binary PATH] [--all] [--save | --discard] [--json]

You must pass one of --id, --binary, or --all. By default the backend's native teardown applies (IDA discards, Ghidra persists). Pass --save to flush analysis to disk first (durable across a reload), or --discard to explicitly drop unsaved edits. --save and --discard are mutually exclusive.

save

Persist the backend's analysis to disk so renames, retypes, and comments survive a stop + reload of the same --project-dir.

decompiler save [--path PATH] [--id ID] [--binary PATH] [--backend BACKEND] [--json]

Each backend writes its native artifact: IDA a .i64/.idb, Ghidra its project, Binary Ninja a .bndb. angr is purely in-memory and has nothing to persist — save exits 2 (not implemented) there. On reload, DecLib reopens the saved database (IDA does so without re-analysis), so prior work is intact.

decompiler load ./fauxware --backend ida --project-dir ./proj
decompiler rename func authenticate auth_check
decompiler save                       # {"saved": true, "path": null}
decompiler stop --id <id>
decompiler load ./fauxware --backend ida --project-dir ./proj  # reopens saved .i64
decompiler list_functions --filter auth_check   # rename survived

list_functions

Enumerate every function in the loaded binary. This is usually the first thing you want on a new (possibly stripped) binary.

decompiler list_functions [--filter REGEX] [--id ID] [--binary PATH] [--backend BACKEND] [--json]

Text output:

ADDR         SIZE     NAME
0x540        6        __libc_start_main
0x71d        184      main
0x664        184      authenticate
...

JSON output is a list of {"addr": int, "size": int, "name": str, "addr_hex": str}.

decompile

Decompile a function to pseudocode.

decompiler decompile <target> [--raw | --map-lines --json] \
  [--lines START:END ... | --grep REGEX ... [--context N] [--ignore-case]] \
  [--max-chars N] [--output PATH [--force-output]] \
  [--id ID] [--binary PATH] [--backend BACKEND]

<target> is a function name or address (hex/decimal, lifted or absolute — see Address formats).

  • --raw — print the decompilation text directly, skipping all wrapping. Useful at a terminal when --json's escaped \ns are unreadable.
  • --map-lines --json — ask the backend to map pseudocode lines to the corresponding lifted instruction addresses. The JSON response adds a deterministic line_map list. Each record has line, sorted integer addrs, and matching addrs_hex values. A line may map to several instructions, and some pseudocode lines may have no mapping.
  • --lines START:END — return only a 1-based inclusive pseudocode line range. Repeat it for multiple ranges; START: and :END are accepted.
  • --grep REGEX — return lines matching a regular expression. Repeat it to match any expression. --context N adds surrounding lines and --ignore-case changes all patterns to case-insensitive matching. --grep and --lines are mutually exclusive.
  • --max-chars N — cap the selected pseudocode at exactly N characters. Metadata reports whether truncation occurred.
  • --output PATH — write the selected pseudocode to a UTF-8 file and return only its absolute path, byte count, and SHA-256 digest instead of sending the body to stdout. Existing files are protected unless --force-output is supplied.

Default text output is the decompilation. JSON output includes addr, addr_hex, decompiler, and text, plus line_map when requested. When any shaping option is used, JSON also includes total_chars, total_lines, selected_chars, output_chars, output_lines, source_ranges, matched_lines, truncated, and bounded. Line mappings are restricted to selected source lines. --output replaces text with an output metadata object, keeping large pseudocode out of agent context.

{
  "addr": 1821,
  "addr_hex": "0x71d",
  "decompiler": "ida",
  "text": "int main(...) { ... }",
  "line_map": [
    {"line": 4, "addrs": [1849], "addrs_hex": ["0x739"]}
  ]
}

Examples for a very large function:

# Inspect a known source region without returning the other 10,000 lines.
decompiler decompile processClientInput --lines 1450:1700 --json

# Find two concepts and include eight lines of context around every match.
decompiler decompile processClientInput \
  --grep 'firmware|readlog' --context 8 --ignore-case --json

# Materialize once for later local rg/sed work; stdout contains metadata only.
decompiler decompile processClientInput --output /tmp/processClientInput.c --json

Error messages distinguish three failure modes:

  • target not found — function name/address doesn't resolve.
  • not a function start — address resolves, but isn't a function boundary. Exit 1.
  • decompiler engine failed — address is a known function start, but the backend gave up. Exit 1.

disassemble

Disassemble a function to text assembly.

decompiler disassemble <target> [--raw] [--id ID] [--binary PATH] [--backend BACKEND] [--json]

Same error semantics and --raw flag as decompile.

xref_to

Every reference to target — code AND data.

decompiler xref_to <target> [--decompile] [--id ID] [--binary PATH] [--backend BACKEND] [--json]

<target> can be:

  • a function name or address — resolves to function xrefs (who calls this function),
  • a raw address that isn't a function start — resolves via the backend's address-level reference table (useful for globals, jump table entries, etc.),
  • a string literal — looked up via list_strings first, then queried as a raw-address xref. Great for "who reads this constant?".

Each row has a kind field (Function, GlobalVariable, …) so you can tell code refs from data refs. The JSON payload also carries target_kind (function, address, or string) so callers can tell which resolution path fired.

  • --decompile — ask the backend to decompile first. On Ghidra this surfaces additional references (e.g. globals pulled in through the HighFunction's global symbol map).

When you want only call-sites, reach for get_callers instead.

xref_from

Functions that target calls (its callees).

decompiler xref_from <target> [--id ID] [--binary PATH] [--backend BACKEND] [--json]

Implementation note: this prefers the backend's call-graph. If the call-graph is unavailable it falls back to scanning the function's disassembly for call 0x… instructions.

rename

Rename a function or a local variable.

# Rename a function.
decompiler rename func <old_name_or_addr> <new_name> [--id ID] [--json]

# Rename a local variable inside a function.
decompiler rename var <old_var_name> <new_var_name> --function <func_name_or_addr> [--id ID] [--json]

The CLI exits 1 if the rename didn't actually change anything (the response's success field is authoritative).

comment

Get, set, append, delete, or list comments (annotations), keyed by address.

decompiler comment set <addr> <text> [--decompiled] [--id ID] [--json]
decompiler comment append <addr> <text> [--decompiled] [--id ID] [--json]
decompiler comment get <addr> [--id ID] [--json]
decompiler comment delete <addr> [--id ID] [--json]
decompiler comment list [--filter REGEX] [--id ID] [--json]

--decompiled attaches the comment to the decompiler/pseudocode view instead of the disassembly. set replaces any existing comment; append concatenates. get exits 1 when there is no comment at the address.

decompiler comment set 0x71d "parses argv, calls authenticate"
decompiler comment append 0x71d "SOSNEAKY is the backdoor password"
decompiler comment get 0x71d
# {"addr": 1821, "decompiled": false, "comment": "parses argv, calls authenticate\nSOSNEAKY is the backdoor password", "addr_hex": "0x71d"}
decompiler comment list --filter backdoor --json

Backend notes: IDA accepts comments only at addresses inside a function; Ghidra and Binary Ninja are more permissive. angr implements comment writes but not reads/enumeration, so get/list return nothing there. Use save (or stop --save) to persist comments across a reload.

global

List, read, rename, or retype global variables.

decompiler global list [--filter REGEX] [--id ID] [--json]
decompiler global get <addr> [--id ID] [--json]
decompiler global rename <addr> <new_name> [--id ID] [--json]
decompiler global retype <addr> <new_type> [--id ID] [--json]
decompiler global list --filter 'key|flag'
# ADDR         SIZE   TYPE                 NAME
# 0x4040       8      char *               g_key
decompiler global rename 0x4040 g_secret_key
decompiler global retype 0x4040 "char[32]"

rename works everywhere globals are enumerated; retype is fully supported on IDA and Binary Ninja, best-effort on Ghidra. angr has no global-variable store, so its global commands return nothing.

signature

Get or set a function's full signature (return type + argument types/names).

decompiler signature get <func> [--id ID] [--json]
decompiler signature set <func> "<C prototype>" [--id ID] [--json]
decompiler signature get main
# int main(int argc, char **argv)
decompiler signature set main "int main(int argc, char **argv, char **envp)"

IDA applies the whole prototype atomically via SetType (it can also change the parameter count). Ghidra and Binary Ninja retype/rename the parameters they already recognize. angr sets argument names but not types.

list_strings

List strings the decompiler's own string detector has identified in the binary.

decompiler list_strings [--filter REGEX]
                        [--min-length N]
                        [--id ID] [--binary PATH] [--backend BACKEND] [--json]
  • --filter REGEX — only return strings matching the regex.
  • --min-length N — drop strings shorter than N characters (default 4).

Text output is 0x<addr>\t<string> per line. JSON output is a list of {"addr", "addr_hex", "string"} entries.

Fidelity caveat. This command only returns what the decompiler itself surfaced — it does not second-guess the backend or supplement with a file-level scan. Backend string detection quality varies (angr < ghidra < ida); angr in particular misses most of .rodata. If the output looks thin, cross-check with an external tool before concluding a string isn't in the binary:

strings -a -n 4 ./target       # classic strings(1)
rabin2 -z ./target             # radare2, structured output
readelf -p .rodata ./target    # ELF-specific, per section

Once you've located a string that way you can feed its address back into the CLI via decompiler xref_to 0x... or decompiler decompile 0x....

get_callers

Functions that contain a call to target — a strict subset of xref_to.

decompiler get_callers <target> [--id ID] [--binary PATH] [--backend BACKEND] [--json]

Unlike xref_to, this never returns globals or other data refs. Rows are always of kind Function.

eval / exec (UNSAFE backend scripting)

An escape hatch for backend-specific work the abstracted API doesn't cover. This executes arbitrary Python inside the backend process — not portable, not sandboxed.

decompiler eval "<expression>" [--id ID] [--json]
decompiler exec "<code>" [--id ID] [--json]
decompiler exec --file <path.py> [--id ID] [--json]

deci is the live DecompilerInterface; the backend's own API is reachable through it (idaapi importable, deci.flat_api on Ghidra, deci.project on angr, deci.bv on Binary Ninja). eval returns the expression's repr; exec captures stdout and the value of a variable named result. Errors print a traceback and exit non-zero.

decompiler eval "deci.name"
decompiler exec "print(hex(deci.binary_base_addr))"
decompiler exec --file ./analysis.py

patch

Apply, inspect, list, or revert byte patches.

decompiler patch set <addr> <hex> [--id ID] [--json]
decompiler patch get <addr> [--id ID] [--json]
decompiler patch delete <addr> [--id ID] [--json]
decompiler patch list [--id ID] [--json]
decompiler patch set 0x401200 "9090"     # NOP two bytes
decompiler patch list
decompiler patch delete 0x401200         # revert (IDA)

patch set is implemented on IDA, Ghidra, and Binary Ninja; angr has no user-patch store. Patch tracking (get/list/delete/revert) is IDA-only for now — other backends apply the patch but don't record it. Use save to persist.

define and undefine

Repair analysis: create functions, disassemble code, define data, or clear a region back to undefined bytes.

decompiler define function <addr> [--id ID] [--json]
decompiler define code <addr> [--id ID] [--json]
decompiler define data <addr> [--type C_TYPE | --size N] [--json]
decompiler undefine <addr> [--size N] [--json]
decompiler define function 0x401200      # create a missed function
decompiler define data 0x4040 --type int
decompiler undefine 0x401200 --size 32   # removes a function starting here

Supported on IDA, Ghidra, and Binary Ninja. angr has no define/undefine primitives and returns exit 2. A realistic repair is undefinedefine codedefine function.

search and imports

Find byte/string/instruction patterns and enumerate imported symbols.

decompiler search bytes <hex> [--max N] [--id ID] [--json]
decompiler search string <text> [--encoding ENC] [--max N] [--json]
decompiler search instruction <regex> [--max N] [--json]
decompiler imports [--filter REGEX] [--id ID] [--json]
decompiler search bytes "7f454c46"          # -> 0x0 (ELF magic at the base)
decompiler search string "SOSNEAKY"
decompiler search instruction "call.*authenticate"
decompiler imports --filter 'puts|read'

search bytes/string use each backend's native memory search (IDA, Ghidra, Binary Ninja); angr lacks one and returns exit 2. search instruction is client-side (greps disassembly), so it works everywhere but disassembles as it goes — keep --max modest. imports is implemented on IDA, angr, and Binary Ninja; Ghidra returns not implemented.

read (typed) and address semantics

read_memory returns raw bytes; read decodes them. Decoding happens client-side over read_memory, so all three subcommands work on every backend.

decompiler read int <addr> [--size N] [--signed] [--endian little|big] [--json]
decompiler read string <addr> [--max-len N] [--encoding ENC] [--json]
decompiler read struct <addr> <struct-name> [--endian little|big] [--json]
decompiler read int 0x4040 --size 4 --endian big      # -> {"value": 2130640198, ...}
decompiler read string 0x8e0                           # -> "Welcome to ..."
decompiler create-type "struct Point { int x; int y; }"
decompiler read struct 0x4050 Point
# struct Point @ 0x4050 (size 8):
#   +0x0   x                int               = 5  (05000000)
#   +0x4   y                int               = 10 (0a000000)

Address forms. Every address argument (here and in read_memory, comment, global, xref_*, ...) accepts a lifted offset (0x2320), an absolute loaded address (0x402320), or decimal. The CLI rebases absolute addresses to the backend's lifted form, so both refer to the same byte instead of the backend double-adding the image base. An address at or above the image base is treated as absolute; a smaller one is already lifted.

backend status

Check whether an optional backend runtime is installed without starting a decompiler server:

decompiler backend status jadx [--json]

The JSON response reports the bridge JAR, Java runtime, official JADX JAR and detected versions. It exits successfully when the backend is ready and exits 1 with a reasons list when setup is incomplete.

install-skill

Copy the bundled Agent Skill into a supported agent skill directory so Claude Code or Codex learns how to drive the CLI.

decompiler install-skill [names ...] [--agent claude|codex|all] [--dest DIR] [--force] [--json]

With no names, every bundled skill is installed. By default the installer uses Codex when CODEX_* environment variables are present, otherwise Claude. Use --agent codex, --agent claude, repeated --agent flags, or --agent all to choose explicitly. Claude installs under ~/.claude/skills; Codex installs under $CODEX_HOME/skills when CODEX_HOME is set, otherwise ~/.codex/skills.

Use --dest to copy the skill somewhere else, and --force to overwrite an existing directory. --json emits a well-formed JSON payload suitable for piping through jq.


Server selection

When more than one server is running, the inspection/mutation commands need to know which one to talk to. Narrow with any combination of:

  • --id <SERVER_ID> — exact match.
  • --binary <PATH> — match by binary path (resolved to an absolute path).
  • --backend <angr|ghidra|binja|ida|jadx> — match by backend.

If zero servers match, the CLI errors out and tells you to run decompiler load. If multiple match, it prints a disambiguation list:

Multiple servers match. Specify --id to disambiguate:
  3308b81cf8  backend=angr  binary=/…/fauxware
  9d77ab8fd4  backend=angr  binary=/…/posix_syscall

JSON output

Pass --json on any subcommand to get a structured payload suitable for downstream parsing. This is the recommended mode for scripts and LLM callers. Every JSON payload that mentions an address provides both addr (integer, lifted) and addr_hex (hex string, also lifted), so you can copy either form without re-formatting:

decompiler list_functions --filter '^main$' --json
# [{"addr": 1821, "size": 184, "name": "main", "addr_hex": "0x71d"}]

decompiler xref_to authenticate --json
# {"addr": 1636, "direction": "to",
#  "xrefs": [{"kind": "Function", "addr": 1821, "name": "main", "addr_hex": "0x71d"}, ...],
#  "addr_hex": "0x664"}

For decompile/disassemble output, JSON wraps the text in a text field with escaped newlines. At a terminal this is awkward; pass --raw instead:

decompiler decompile main --raw         # prints the pseudocode directly
decompiler disassemble 0x71d --raw      # prints assembly directly

Exit codes

Every CLI command uses these exit codes:

CodeMeaning
0Success.
1User-visible error — target not found, rename didn't apply, decompile failed, etc. Most failure modes unify to 1 so that shell && chaining works cleanly.
2The requested capability is not implemented for the selected backend (e.g. save on angr). Distinct from 1 so scripts can branch on "unsupported" vs "failed".

Argparse-level errors (unknown subcommand, missing required argument) also exit with Python's standard argparse code 2.


Running multiple binaries at once

decompiler load ./my-binary                # id=abc1234
decompiler load ./my-binary-2              # id=def5678

decompiler list
# ID           BACKEND  PID      BINARY
# abc1234...   angr     4213     .../my-binary
# def5678...   angr     4217     .../my-binary-2
#
# (registry: /…/declib/servers)

# Target by ID …
decompiler decompile main --id abc1234

# … or by binary path.
decompiler decompile main --binary ./my-binary-2

# Restart a server cleanly (stop existing, spawn fresh):
decompiler load ./my-binary --replace

# Run an additional server alongside the existing one:
decompiler load ./my-binary --force

# Tear them all down.
decompiler stop --all

You can even mix backends on the same binary — add --force to load to launch a second server for the same file:

decompiler load ./bin --backend ghidra
decompiler load ./bin --backend angr --force
decompiler decompile main --binary ./bin --backend ghidra
decompiler decompile main --binary ./bin --backend angr

Address formats

DecLib normalizes addresses to a lifted form (relative to the binary's base address), so artifacts stay stable across decompilers. The CLI, though, accepts whatever is natural for the user:

  • 0x71d, 1821 — lifted
  • 0x40071d — absolute (base + lifted)
  • main — symbol name

The CLI converts on the fly. The returned addr fields in JSON output are always lifted, which matches what the server's artifact dictionaries use. addr_hex is the same value as a hex string for convenience.


Library-level API

Everything the CLI does is also available as a library — useful when you want to chain operations or integrate DecLib into a larger tool:

from declib.api.decompiler_client import DecompilerClient

# Pick a running server out of the shared registry.
client = DecompilerClient.discover_from_registry(binary_path="./fauxware")

for addr, func in client.functions.items():
    if func.name == "main":
        print(client.decompile(addr).text)
        print(client.disassemble(addr))
        for caller in client.get_callers(addr):
            print(caller.addr, caller.name)

The three APIs added to power the CLI are also usable directly through DecompilerInterface (headless/embedded) and DecompilerClient (remote):

  • list_strings(filter: str | None = None) -> list[tuple[int, str]]
  • get_callers(target: Function | int | str) -> list[Function]
  • disassemble(addr: int) -> str | None

Backends currently implementing them: angr and Ghidra. IDA and Binary Ninja fall back to the default implementations.


Troubleshooting

No running decompiler server matches … You haven't loaded the binary yet. Run decompiler load <binary> --backend <backend> first, or use decompiler list to see what's already running.

Multiple servers match. Specify --id to disambiguate Two servers match your filters. Either pass --id with one of the printed IDs, or narrow with --binary/--backend.

Timed out waiting … for server … to start. The detached server process didn't come up in time (default 5 minutes). Check backend prerequisites:

  • Ghidra: GHIDRA_INSTALL_DIR must be set.
  • IDA/Binary Ninja: their Python bindings must be importable.
  • angr: should just work.

No function starts at 0x… The address is valid in the binary but doesn't correspond to the first byte of any known function. Use decompiler list_functions to find a valid start. (Prior to v2 this was reported with the same error as "decompiler engine failed"; they're now distinct.)

Rename reports success: False (exit 1) The old name was not found in the function (e.g. it was already renamed, or you targeted the wrong function).

list_strings looks thin This is expected on angr (and can happen on Ghidra for stripped binaries) — list_strings returns only what the decompiler itself identified. Use an external scanner to see every ASCII constant in the file, then feed the address back into xref_to / decompile:

strings -a -n 4 ./target
rabin2 -z ./target
readelf -p .rodata ./target

Server-side logs Spawned servers have their stdout/stderr sent to /dev/null. If you're debugging server startup, start one by hand in a foreground terminal:

declib --server --headless --decompiler angr --binary-path ./bin --server-id my-srv

That will print log output to the terminal, and the CLI in another terminal can still drive it via decompiler decompile main --id my-srv.