
**IDA Pro in a terminal.** Listing, decompiler, graph — keyboard-first, mouse-capable.
`disasm` · `pseudocode` · `cfg` · `hex` · `strings` · `structs` · `traces` · `rpc`
---
```
┌ ida-tui ─────────────────────────────────────────────────────────────────────┐
│ .text:00002490 ; ---------- S U B R O U T I N E ---------- │
│ .text:00002490 main proc near │
│ .text:00002490 endbr64 │
│ .text:00002494 push rbp ; ← cursor │
│ .text:00002495 mov rbp, rsp │
│ .text:00002498 sub rsp, 0B0h ; `o` → 176 │
└──────────────────────────────────────────────────────────────────────────────┘
```
> **Status: personal project, actively hacked on.** No packaging, no versioning,
> no support. It assumes a licensed IDA Pro and a venv at `~/ida-venv`. Things
> move and break. Poke around; don't file expectations.
## Why
IDA's own UI is excellent and it is a GUI. This is for the times you're in a
terminal over SSH, in a tmux pane next to your notes, or driving RE from a
script — and still want the listing, Hex-Rays, and a real control-flow graph.
It is **not** a reimplementation of IDA. It's a frontend: IDA does the analysis,
this draws it.
## Install
Needs **Python ≥ 3.11**, **IDA Pro 9.4+ with idalib**, and
[ida-codemode](https://github.com/HexRaysSA/ida-codemode) checked out next to
this repo (`../ida-codemode`):
```sh
git clone https://github.com/HexRaysSA/ida-codemode ../ida-codemode
uv sync
```
Install the ida-codemode plugin in IDA too, if you want the TUI to attach to
databases you already have open in the GUI.
## Run
```sh
./ida-tui /path/to/binary # attach to a GUI session, or open a managed database
./ida-tui # attach, when exactly one database is registered
```
ida-tui never owns an IDA process. It takes a **lease**: a matching database open
in the IDA GUI is reused, otherwise Code Mode starts or shares a managed idalib
worker. Quitting drops the lease and leaves everyone else alone.
Headerless blobs have no format to detect — IDA falls back to x86 at address 0
and analyses nothing, so say what it is:
```sh
./ida-tui fw.bin --processor arm --base 0x8000000
```
`--base` is a real address. These apply only when a database is being **created**;
an existing IDB already records them. On ARM, `t` toggles ARM/Thumb decoding at
the cursor and `T` scans a vector table for Thumb entry points.
## Keys
| | |
|---|---|
| `enter` `escape` | follow / back |
| `tab` `F5` | listing ↔ pseudocode |
| `space` | control-flow graph (`z` zoom, `m` minimap, `J`/`K` walk edges) |
| `s` | split view — listing and pseudocode, cursor-synced |
| `g` `/` `?` | goto · search · search back |
| `x` `n` `y` `;` | xrefs · rename · retype · comment |
| `c` `d` `p` `u` `a` | make code · data · function · undefine · string |
| `o` `O` `B` | cycle this literal's format · reverse · opcode bytes |
| `\` `"` `ctrl+t` | hex · strings · structs |
| `ctrl+n` `ctrl+p` | symbol palette · command palette |
| `ctrl+s` `ctrl+l` `q` | save · reload as… · quit |
| `F1` | all of them |
## What's in it
**Listing** — one continuous IDA-style view: code, data and undefined runs
together, with IDA's own colour tags and per-operand marks. Line-virtualized, so
a 400 MB binary scrolls like a text file.
**Decompiler** — Hex-Rays pseudocode with syntax highlighting, per-line address
anchors, and rename/retype/comment that write back.
**Graph** (`space`) — the current function's basic blocks, laid out with a real
layered (Sugiyama) algorithm and routed edges: green taken, red fall-through,
blue unconditional, purple loop. The boxes hold the *same rows* as the listing,
so highlighting, renames and xrefs work inside them. Above 400 blocks it declines
and says so, because nothing readable comes out at that size.
→ [`docs/GRAPH_VIEW.md`](docs/GRAPH_VIEW.md)
**Split view** (`s`) — listing and pseudocode side by side. The focused pane
drives; the other highlights every instruction the current C line owns.
**Literal formats** (`o`) — hex → decimal → binary → char → offset, IDA's own
key. Only stops that change what you see are visited, so no press is a silent
no-op. The literal under the cursor is *marked*, and the mark is what changes —
it keeps up as the text reflows. Works on Hex-Rays' separate number formats too.
**Execution traces** — load a [Tenet](https://github.com/gaasedelen/tenet) trace
and move through time:
```sh
./ida-tui /path/to/binary --trace trace.0.log
```
`]`/`[` step, `}`/`{` step over. Both code views are painted with the execution
trail — including the pseudocode, since `decomp_map` knows which instructions
each C line covers. The dock shows registers and the stack *as of that instant*;
bytes the trace never saw print as `??`, not zeros. Trace addresses are rebased
onto the database automatically.
**RPC** — drive the live TUI from another process (agent-driven RE, livestreams):
```sh
./ida-tui /abs/path/bin --rpc /tmp/ida.sock
python -m idatui.drive where # terse-text helper
python -m idatui.drive pc main # pseudocode of main
python -m idatui.drive rename sub_5BE0 foo # goto + rename
```
→ [`docs/RPC.md`](docs/RPC.md)
**Splash** — the logo renders as a real image on terminals that speak the kitty
graphics protocol, `logo.ans` everywhere else. Support is detected by *asking the
terminal*, not by sniffing `$TERM` (under a multiplexer, every variable you'd
test is empty while the protocol works fine).
## Tests
```sh
python3 tests/run.py --fast # 302 checks, <1s, any python3 — between edits
python3 tests/run.py --list # what runs, and what needs IDA
python3 tests/run.py # 788 checks, ~2m — before a commit
```
Every suite declares `NEEDS_IDA`; `--fast` runs only the pure ones (stdlib, no
IDA, no database). The rest drive a headless Textual `Pilot` against a real
database. Iterate on one with `--only`:
```sh
~/ida-venv/bin/python tests/test_scenarios.py targets/echo --only hex,rename
```
Before optimising or debugging a slow run, read
[`.fastfeedback/SPEED.md`](.fastfeedback/SPEED.md) — per-suite timings, the known
flake, and the four ways a test here wastes minutes.
## Docs
- [`docs/RPC.md`](docs/RPC.md) — the RPC protocol, verb by verb
- [`docs/GRAPH_VIEW.md`](docs/GRAPH_VIEW.md) — how the graph is laid out
## Known sharp edges
- PLT/import stubs: following a libc symbol lands on `extrn X:near`, which
Hex-Rays can't decompile. No pretty-printing yet.
- Xref-select can land on the wrong line in the `x` popup.
- No "discard changes and quit": a managed worker saves when its last lease
closes, so the TUI can't promise your edits weren't persisted.
- A GUI user editing the same database won't invalidate our caches — Code Mode
has no change feed yet (see `docs/CODEMODE_UPSTREAM.md`).