ZhangZhuoSJTU/tiny-dec
ZhangZhuoSJTU/tiny-dec has 249 GitHub stars and 12 forks. GitHot measured 0 stars in 24 hours, 0 in 7 days, and 0 in 15 days. Last checked 2026-11-12 15:48 UTC. Last push 2026-05-27.
A tiny educational decompiler that helps people understand how decompilation works.
实测星标增长
- 24小时
- 0
- 7天
- 0
- 15天
- 0
- 1个月
- 正在收集历史
各周期内的星标净变化,周期相互重叠,并非每日新增。缺失的测量保持空白。星标代表关注度,不代表软件质量。
README.md
原文
Learn decompilation by building one from scratch.
19 stages. Every step inspectable. From raw RISC-V bytes to readable C.
RV32I: the simplest real ISA, so the focus stays on decompilation, not instruction encoding.
[!TIP] This project is inspired by the amazing open-source efforts behind angr, radare2, and Ghidra. Thanks for all the open-source work that makes projects like this possible.
[!NOTE] This is a teaching tool, not a production decompiler. The pipeline is deliberately factored into many small, isolated stages so that each concept can be studied independently. A real-world decompiler (Ghidra, IDA, angr, Binary Ninja) would fuse, reorder, or iterate these passes for performance and precision. We trade that efficiency for clarity: if you can follow each stage here, you will understand what the production tools are doing under the hood.
Learn with Our AI Tutor
We ship tiny-dec as a Claude Code plugin. An AI tutor assesses your level, walks you through each pipeline stage, quizzes you, and lets you experiment with real binaries, all inside your terminal.
See the Pipeline in Action

A bird's-eye view of all 19 stages. Explore interactively at tiny-dec.zzhang.xyz.
Quick Start
Learn with the AI Tutor
Install Claude Code, then:
# 1. Add the marketplace
/plugin marketplace add ZhangZhuoSJTU/tiny-dec
# 2. Install the plugin
/plugin install tiny-dec@tiny-dec
# 3. Reload to activate
/reload-plugins
Type /tiny-dec:teach-decompilation to start.
Install from Source
# System dependencies (Ubuntu/Debian)
$ sudo apt-get install -y clang lld llvm binutils build-essential python3 python3-pip
# Python dependencies
$ pip install poetry
$ poetry install
# Build RV32I fixture binaries for tests
$ ./scripts/build_fixtures.sh
Decompile
# Full decompilation to C (default)
$ poetry run tiny-dec decompile ./binary.elf
# Target a specific function
$ poetry run tiny-dec decompile ./binary.elf --func parse_record
# Stop at any intermediate stage and look inside
$ poetry run tiny-dec decompile ./binary.elf --stage ssa --func main
# Inspect binary metadata
$ poetry run tiny-dec info ./binary.elf
Every stage is a window into the pipeline:
loader → decode → pcode → disasm → ir → simplify → dataflow → ssa
→ calls → stack → memory → scalar_types → aggregate_types → variables
→ range → interproc → structuring → c_lowering → c
Test
$ poetry run pytest # all 500 tests
$ poetry run ruff check . # lint
$ poetry run mypy tiny_dec # type check
Example Output
Raw RISC-V instructions go in. Readable C comes out:
| Assembly (RV32I) | Decompiled Output |
|---|---|
|
|
Recovered: struct layout, pointer parameter, while loop, array indexing with field access, and local variables.
It handles more than loops and structs:
// Switch recovery from equality-ladder CFG patterns
static int32_t dispatch(uint32_t arg_x10_4, uint32_t arg_x11_4) {
uint32_t local_24_4;
uint32_t local_12_4;
local_24_4 = arg_x10_4;
switch (arg_x10_4) {
case 0:
local_12_4 = arg_x11_4 + 1;
break;
case 1:
local_12_4 = arg_x11_4 + 4;
break;
case 2:
local_12_4 = arg_x11_4 << 1;
break;
case 3:
local_12_4 = arg_x11_4 - 3;
break;
default:
local_12_4 = -1;
break;
}
return local_12_4;
}
// Interprocedural analysis with prototype inference
static int32_t main(void) {
int32_t local_16_4;
int32_t local_12_4;
local_12_4 = 7;
local_16_4 = helper(local_12_4);
return local_16_4 - 2;
}
static int32_t helper(int32_t arg_x10_4) {
return (arg_x10_4 << 1) + arg_x10_4 + 1;
}
The Pipeline
19 stages, three phases. Each stage produces a typed, deterministic artifact you can inspect with --stage. The stages are split for learning purposes; a production decompiler would combine several of these into fewer, more tightly coupled passes.
Phase 1: Frontend (Bytes to IR)
| # | Stage | What it does |
|---|---|---|
| 00 | Loader | Parse ELF structure, resolve symbols, find main. The loader is deliberately shallow: it delegates all ELF parsing to pwntools and uses simple heuristics for main discovery, since binary format parsing is not the educational focus of this project. |
| 01 | Decode | Decode RV32I instruction words into structured objects |
| 02 | P-code Lift | Lift each instruction into semantic p-code operations |
| 03 | Disassembly | Recursive traversal to build basic blocks and CFG |
| 04 | IR Containers | Wrap functions and programs into durable typed containers |
Phase 2: Analysis
| # | Stage | What it does |
|---|---|---|
| 05 | Simplify | Constant folding, identity elimination, temporary forwarding |
| 06 | Dataflow | Forward intraprocedural constant propagation |
| 07 | SSA | Dominance-frontier SSA with memory versioning |
| 08 | Calls | Call classification, ABI argument/return snapshots |
| 09 | Stack | Frame layout recovery, stack slot identification |
| 10 | Memory | Partition recovery for stack, global, and pointer accesses |
| 11 | Scalar Types | Type inference: bool, int, pointer, word |
| 12 | Aggregate Types | Struct layout recovery from pointer arithmetic patterns |
| 13 | Variables | Group SSA values and memory slots into named variables |
| 14 | Range | Interval analysis and branch predicate refinement |
| 15 | Interproc | Cross-function prototype inference and summaries |
Phase 3: Backend (Structure and Emit)
| # | Stage | What it does |
|---|---|---|
| 16 | Structuring | Recover if / while / switch from CFG topology |
| 17 | C Lowering | Lower to C-like IR with typed declarations and expressions |
| 18 | C Render | Emit readable C with precedence-aware formatting |
Project Layout
tiny_dec/
├── loader/ ELF loading and symbol resolution
├── decode/ RV32I instruction decoder
├── ir/ P-code semantics and IR containers
├── disasm/ Recursive disassembler and CFG builder
├── analysis/
│ ├── simplify/ Canonical IR cleanup
│ ├── dataflow/ Constant propagation
│ ├── ssa/ SSA construction
│ ├── calls/ Call modeling and ABI facts
│ ├── stack/ Stack frame recovery
│ ├── memory/ Memory partitioning
│ ├── types/ Scalar and aggregate type recovery
│ ├── highvars/ Variable recovery
│ ├── range/ Range and predicate analysis
│ └── interproc/ Interprocedural summaries
├── structuring/ Control-flow structuring
├── c_emit/ C lowering and rendering
├── pipeline/ End-to-end decompilation driver
└── cli.py Command-line interface
tests/
├── fixtures/
│ ├── src/ 13 C programs (loops, structs, calls, switches, ...)
│ └── bin/ 39 cross-compiled RV32I ELF binaries
└── posts/
└── post_00/ ... post_18/ One test suite per pipeline stage
Why Is It Built This Way?
Explicit over clever. Every intermediate representation is a concrete, typed Python dataclass. No hidden state, no magic.
Deterministic output. Every stage has a text renderer that produces identical output given identical input. This is both the debugging surface and the test oracle.
One stage, one job. Each stage solves exactly one problem and passes a well-defined artifact to the next. You can stop the pipeline at any point and inspect what it produced.
Conservative analysis. The pipeline never invents information. When evidence is ambiguous, it preserves the raw form rather than guessing. Residual raw<...> expressions in the output are honest about what the analysis couldn't resolve.
This is a teaching tool, not a production decompiler. The pipeline is deliberately factored into many small, isolated stages so that each concept can be studied independently. A real-world decompiler (Ghidra, IDA, angr, Binary Ninja) would fuse, reorder, or iterate these passes for performance and precision. We trade that efficiency for clarity: if you can follow each stage here, you will understand what the production tools are doing under the hood.
Fixture Binaries
13 C programs compiled to RV32I ELF at three optimization levels (39 binaries total, with strict no-compressed-instruction verification):
| Variant | Flags | Purpose |
|---|---|---|
*_O0_nopie |
-O0 -fno-pie |
Unoptimized, predictable stack layout |
*_O2_nopie |
-O2 -fno-pie |
Optimized, tests register allocation recovery |
*_O2_pie |
-O2 -fpie |
Position-independent, tests GOT/PLT handling |
Rebuild with ./scripts/build_fixtures.sh (requires clang, lld, llvm with RISC-V support).
Further Reading
Actively-maintained resources that go deeper into the concepts behind each pipeline stage.