Readable Reverse Engineering Is Built, Not Retrieved

Ghidra's decompiler becomes most useful after the static model has been checked, because readable output is a projection of that model rather than a recovered source file. This chapter focuses on the feedback loop among data-flow analysis, the listing, names, types, signatures, and structures. Understanding that loop lets the reader use markup to make reasoning explicit and reusable while still distinguishing an analyst's helpful annotation from evidence that a behavior was present in the original program.

The decompiler follows data flow, not source text

The Decompiler plug-in converts an individual binary function into a high-level C representation and keeps that view associated with the assembly listing as far as possible. Its work is not a textual translation from machine instructions into C. It analyzes how values move, how control paths interact, and how compiler conventions use registers and the stack. The resulting view is a reconstruction of relationships that may have been distributed across many instructions by compilation and optimization.

Data-flow analysis lets the decompiler reassemble expressions that were split into separate operations. It can follow a value as it moves from the stack into one register and then another, reducing the need to display every temporary storage location as a separate artificial variable. It can use calling conventions to reconstruct the form of function calls and reason about parameters. It can also propagate data types from known locations into local variables and infer structures from how a function uses offsets and components.

These capabilities explain both the power and the uncertainty of the output. A compiler may spread one source-level expression across instructions, reuse a register for different variables at different points, or optimize away the boundaries a reader expects to see. The decompiler tries to produce a coherent high-level representation from the surviving evidence. It can make a complicated function understandable, but its output is still constrained by function boundaries, p-code semantics, calling conventions, and the types already attached to the program.

The connection to the listing is therefore essential. The listing shows the decoded instructions, addresses, references, and markup that the decompiler is interpreting. The decompiler gathers those relationships into expressions and statements that are easier to reason about. If a high-level expression looks suspicious, the analyst can navigate back to the instructions that contribute to it. If the listing is corrected or a function signature changes, the decompiler can update the displayed function rather than leaving a stale, independent summary on screen.

A function's parameters illustrate the value of this connection. The machine code may place an argument in a register, move it to the stack, use it in a comparison, and pass a derived value to another call. The decompiler can present that chain as a parameter and an expression instead of a sequence of storage transfers. If the calling convention is wrong, however, the same operations may be assigned to the wrong parameters or return values. The readable form is useful because it compresses the data flow; it remains trustworthy only when the convention and the instructions agree.

Structure recovery works in a similar way.