Skip to content

The Decompilation Book

By Hugo Matousek, SeungHyun Sung, and Zhuo Zhang.

Compilers turn source code into machine code. This book is about the much messier trip back.

About This Book

We call this a book, although technically it is a series of blog posts. It was inspired by The Fuzzing Book, which is a great place to begin learning fuzzing. We want to do something similar for decompilation: start from the beginning, write real code, and explain what happens between a binary and the pseudo-C shown on your screen.

In 2026, it is hard to avoid the claim that decompilation, and perhaps cybersecurity in general, will soon become an AI job. Speaking only for myself here (Zhuo), I think there is some truth to that.

While developing SRE-Bench, we watched GPT-6 Astra solve almost every challenge in SRE-bench's first version. That was impressive. It was also slightly painful for us as benchmark builders.

We are not surrendering, of course. We are already working on a harder version. But the result did leave me thinking about a different problem:

What happens if AI becomes so good at reverse engineering that nobody wants to learn the basics anymore?

The same thing may happen to C and C++ programming, or even to compiler construction. But anyone who still wants to understand these topics can draw on decades of books, courses, and examples. Decompilation has far fewer beginner-friendly resources that walk through the entire process step by step.

That is why we built tiny-dec, and why Hugo, SeungHyun, and I started writing this series around it. Even if AI handles almost all reverse engineering two years from now, curious people should still have a place to learn how a decompiler actually works.

A Decompiler Is a Compiler in Reverse (Almost)

Compiler courses are everywhere. Decompiler courses are not.

A compiler turns C into machine code. Along the way, it discards information that the processor does not need: variable names, source-level types, loops, and much of the program's original structure.

A decompiler starts at the other end and tries to reconstruct what disappeared.

That sounds like compilation in reverse. Almost. The problem is that the compiler does not save a copy of everything it removed. That information is gone, so the decompiler has to infer what it can from the machine instructions that remain.

Here is the whole problem in one small example:

COMPILATION

x = y;    →  sw x10, -12(x8)  →  0xfea42a23
original C    generated instruction  encoded binary


DECOMPILATION

0xfea42a23  →  sw x10, -12(x8)  →  local_12_4 = a0;
encoded binary   decoded instruction   recovered C

Nothing in the binary says that the stack location shown as local_12_4 should be treated as a variable, let alone that its original name was x. It also does not tell us the variable's type, whether x8 is being used as a frame pointer, or whether the function returns a struct.

The decompiler has to infer all of this.

The resulting pseudo-C is not the original source code. It is a reconstruction. Sometimes it is excellent. Sometimes it is wrong in ways that are surprisingly difficult to notice. Every variable, type, expression, and control structure comes from an analysis or heuristic, and any of them can be wrong.

In this book, we will build those analyses one by one, in the same order that a decompiler uses them. The implementation is written in Python, and tiny-dec is intentionally small. You should be able to read the whole codebase in an afternoon.

Why Learn Decompilation?

If you work with binaries, you probably already use a decompiler. You open Ghidra or IDA, read the pseudo-C, and make decisions from it:

  • This loop is bounded.
  • This buffer access is safe.
  • This function contains the check I need to patch.

Or perhaps you rarely open a decompiler yourself. Instead, you watch Codex invoke Binary Ninja, inspect the pseudo-C, trace the program, and find a bug.

Either way, a human or an AI is making decisions based on decompiler output.

This matters because pseudo-C can be wrong. A type may be inferred from a single use. One stack slot may be split into two variables. The control flow may be rearranged into code that looks reasonable but bears little resemblance to the original source.

Knowing what came directly from the binary and what the decompiler guessed is the difference between reading its output and blindly trusting it. The quickest way to develop that instinct is to build a decompiler yourself and catch it making bad guesses.

This book is also for compiler people.

A compiler begins with rich source-level information and carefully lowers it into machine code (with soundness guarantee). A decompiler begins with the leftovers. You have to recover structure using incomplete evidence, heuristics, and plenty of uncertainty. The result can be useful without being sound or unique.

Messy? Absolutely. But that is also what makes decompilation fun. Welcome to the messier side of program analysis.

What Do Existing Resources Skip?

Most material about decompilation falls into two groups:

  1. Tool tutorials show you where to click in Ghidra or which keyboard shortcut renames a variable. They teach you how to operate the tool, but not how the decompiler underneath it works.

  2. Research papers explain individual techniques precisely, but often assume that you already understand SSA, lattices, compiler intermediate representations, and the heuristics used in reverse engineering.

Very little walks through everything between "here are some bytes" and "here is some C".

This series tries to do exactly that.

It is built around tiny-dec, an educational decompiler written in Python. It translates RV32I binaries into C through a sequence of stages. You can run every stage separately and inspect its input and output.

The chapters explain the code, and the code keeps us from hand-waving past the difficult parts.

How Does the Series Work?

The chapters follow the data from the input binary to the final C code. Each chapter focuses on one stage and does three things:

  1. It explains what the stage needs to accomplish and why the problem is harder than it may first appear.
  2. It walks through the corresponding tiny-dec module using a real binary.
  3. It ends with a short quiz to make sure the main idea actually stuck.

We chose RV32I, the 32-bit base integer instruction set of RISC-V, as our target architecture. It is small: fixed-width instructions, clean encodings, and around forty base instructions. This lets us teach the complete pipeline without spending the first thirty chapters explaining instruction prefixes.

At the same time, RV32I is expressive enough for ordinary integer programs; Floating point can wait ;)

Will you encounter x86-64 or Arm more often in the real world? Almost certainly. But starting with either one would bury the main ideas under decades of architectural complexity. It is easier to learn the pipeline first and add that complexity later.

If you can read Python and C, you are ready. We will introduce assembly and compiler theory as we need them.

Start Here with Post 0

This series is being written in the open

We publish chapters as we finish them, and we may continue editing them afterward. The later stages of the pipeline are still being written.

If something is unclear, incorrect, or missing, please tell us. The thumbs at the end of each chapter and the feedback button in the corner both reach us directly. You can also select any passage to comment on that specific sentence.

Nothing you submit will be shown publicly, so please be honest. We can take it.