# Ghidra Decompiler → SSA P-Code Integration Research (Phase 0) > Generated 2026-05-31. Phase 0 of the "vendor Ghidra decompiler" plan. Docs-first: > understand the engine's API, source layout, and build before writing any glue. Sources are > the Ghidra repo (NationalSecurityAgency/ghidra) and angr/pypcode (a lift-only vendor we > mirror for the build approach). ## Decision recap - **Vendor** Ghidra's **full decompiler** C++ engine (Apache-2.0) at a **pinned commit** — we control the version, so no ABI skew (the failure mode of the r2ghidra plugin). Consume its **SSA high P-Code** (Funcdata). Ghidra does data-flow/SSA/variable recovery; fold does Z3 path-reasoning + findings via the existing `SafetyObserver::*Ir` hooks. - **Pin:** tag `Ghidra_12.1_build` (Ghidra 12.1, released 2026-05-13). Record the exact 40-char SHA in `third_party/ghidra-decompiler/VENDOR.md` at clone time. - **License:** Apache-2.0 — vendorable; carry upstream `LICENSE` + a `NOTICE` listing the vendored tree, commit SHA, and modifications (build glue only). ## 1. P-Code semantics (what we consume) P-Code is a register-transfer IL; **every architecture lowers to the same op set**, so the encoder is written once. The opcode enum is `OpCode` in `opcodes.hh` (`CPUI_*`). Two levels: - **Raw P-Code** — straight from SLEIGH lifting; per-instruction, no data-flow. (This is all pypcode exposes.) - **High P-Code (SSA)** — after the decompiler's analysis actions run on a `Funcdata`: SSA form (each Varnode written once), dead code removed, variables/types recovered, def-use explicit. **This is what we want** — it adds `CPUI_MULTIEQUAL` (SSA phi), `CPUI_INDIRECT`, `CPUI_PTRADD`/`CPUI_PTRSUB`, `CPUI_CAST` over the raw set. **Varnode** = (address space, offset, size). Spaces we care about: `const` (immediates → Z3 `bv_val`), `register`, `unique` (temporaries), `ram` (memory), plus the stack space. A Varnode in `const` space with offset 0 is the literal zero our current const-hazard pass hunts for — but here it falls out of SSA naturally. **Core ops fold maps to `SafetyObserver` hooks (the "~25-op core"):** | P-Code op | fold hook | Finding | |-----------|-----------|---------| | `CPUI_INT_SDIV` / `INT_UDIV` / `INT_SREM` / `INT_REM` | `onDivision` / `onBinaryArith(Div)` | div-by-zero (CWE-369) when divisor SAT==0 under path | | `CPUI_LOAD` / `CPUI_STORE` | `onPointerDerefIr` | null-deref (CWE-476) when addr Varnode SAT==0 | | `CPUI_INT_ADD` / `INT_SUB` / `INT_MULT` | `onBinaryArith` | int-overflow (CWE-190); reuse source guard heuristics to avoid every-op FP | | `CPUI_CALL` / `CPUI_CALLIND` | `onCallExprIr` / `onLibraryCall` | free/malloc/strcpy models → UAF/buffer via existing observers | | `CPUI_CBRANCH` | (path predicate) | real path-sensitivity → covers `div_by_zero_variable` / `null_deref_conditional` | | `CPUI_COPY` / `INT_ZEXT` / `INT_SEXT` / `SUBPIECE` / `PIECE` | value propagation in encoder | — | | `CPUI_MULTIEQUAL` (phi) | Z3 ite/merge at block joins | SSA join | | `CPUI_INT_EQUAL` / `INT_SLESS` / `INT_LESS` … | branch conditions | path constraints | Reference: `Ghidra/Features/Decompiler/src/decompile/cpp/opcodes.hh` and the rendered P-Code reference `Ghidra/Features/Decompiler/src/main/doc/pcoderef.xml`. ## 2. Decompiler C++ embedding API (the glue surface) Confine all of this to `src/binary/ghidra_lifter.cpp` (Ghidra types never leak into `fold::ir`, per the M30 `*_clang.cpp` convention). - **`LoadImage`** (`loadimage.hh`) — abstract byte source. Subclass it to serve a function's bytes from r2 (r2 gives us `arch/bits/endian`, the function vaddr+size, and raw bytes via `p8`/`prj`). Implement `loadFill(uint1*, int4, const Address&)` + `getArchType`/`adjustVma`. - **`SleighArchitecture` / `Architecture`** (`sleigh_arch.hh`, `architecture.hh`) — owns the translator, spec databases, and the action pipeline. Built from a spec id; specs are loaded via **`DocumentStorage`** (`xml.hh`) reading the `.ldefs`/`.pspec`/`.cspec` and the compiled `.sla`. Call `arch->init(store)` to bring up sleigh + types + the default action set. - **Decompile a function:** obtain a `Funcdata*` for the entry address from the architecture's scope, then run the analysis pipeline: `arch->allacts.getCurrent()->reset(*fd); arch->allacts.getCurrent()->perform(*fd);` After `perform`, `fd` holds **high (SSA) P-Code**. Iterate ops via `fd->beginOpAll()` / `fd->endOpAll()` (each `PcodeOp` has `getOpcode()`, `getOut()`, `getIn(i)` Varnodes) and basic blocks via `fd->getBasicBlocks()`. The canonical example is the interactive console `consolemain.cc` (the `decomp_opt` binary) and the doxygen "Building a Project" page. - **Thread-safety:** the decompiler uses process/global state (it was designed to run as a one-function-at-a-time child process, `ghidra_process`). Treat an `Architecture` as **not thread-safe** → one instance per fold worker thread, or serialize lifting. ## 3. Source layout + what to vendor From `Ghidra/Features/Decompiler/src/decompile/cpp/Makefile` (authoritative file lists): - **`CORE`** (lifting/serialization base): `xml marshal space float address pcoderaw translate opcodes globalcontext` - **`SLEIGH`** (the lifter): `sleigh pcodeparse pcodecompile sleighbase slghsymbol slghpatexpress slghpattern semantics context slaformat compression filemanage` - **`DECCORE`** (the full decompiler / SSA engine — this is the delta over pypcode's lift-only vendor): `capability architecture options graph cover block cast typeop database cpool comment stringmanage modelrules fspec action loadimage grammar varnode op type variable varmap jumptable emulate emulateutil flow userop expression multiprecision funcdata funcdata_block funcdata_op funcdata_varnode unionresolve pcodeinject heritage prefersplit rangeutil ruleaction subflow blockaction merge double transform constseq bitfield coreaction condexe override dynamic crc32 prettyprint printlanguage printc printjava memstate opbehavior paramid signature` - SSA-relevant: **`heritage`** (SSA construction), **`funcdata*`** (the SSA container), `action`/`ruleaction`/`coreaction` (the simplification rules), `merge`, `flow`, `blockaction`, `jumptable`. - **Targets:** vendor builds **`libdecomp.a`** (= CORE + DECCORE + SLEIGH) and links it into fold. (`libsla.a` = CORE + SLEIGH only is the lift-only library; not enough for SSA.) `sleigh` (the spec compiler) is also built to produce `.sla`. - **bison/flex generated files:** `grammar.cc`←`grammar.y`, `xml.cc`←`xml.y`, `pcodeparse.cc`←`pcodeparse.y`, `slghparse.cc`/`.hh`←`slghparse.y`, `slghscan.cc`←`slghscan.l`, `ruleparse.cc`/`.hh`←`ruleparse.y`. → **Pre-generate these at vendor time and check the `.cc`/`.hh` in**, so fold's build needs no bison/flex (matches the "trade runtime-debugging for up-front work" goal). - **Processor specs:** `Ghidra/Processors//data/languages/*.{slaspec,ldefs,pspec,cspec,sinc}`. Compile `.slaspec`→`.sla` once with the vendored `sleigh` binary and **check the `.sla` + `.ldefs`/`.pspec`/`.cspec` in** under `third_party/ghidra-decompiler/specs//`. Vendor only the arches we target (below) to keep the tree small. ## 4. Target architectures (match corpus + bug-bounty reality) `x86` (32) + `x86-64`, `AARCH64` (corpus is arm64 Mach-O), `ARM` (LE/BE), `MIPS` (LE/BE). These cover the CTF corpus and the common closed-source targets. More arches are just more checked-in `.sla` files later — no code change. ## 5. Build approach (meson) - `third_party/ghidra-decompiler/` holds the pinned `cpp/` sources (with pre-generated parser/lexer `.cc`/`.hh`) + `specs//*.sla` + `LICENSE` + `NOTICE` + `VENDOR.md` (commit SHA + the exact upstream paths + the pre-generation/compile commands used). - meson: a static-lib target `libghidra_decompiler.a` over the CORE+DECCORE+SLEIGH `.cc` set (a `files()` list mirroring the Makefile vars); C++17, `-DLOCAL_ZLIB`/bundled zlib as pypcode does, define `-DFOLD_HAVE_GHIDRA=1`. Gate behind a `-Dghidra=auto|enabled|disabled` option mirroring the existing `radare2` option (`meson.build:35`), so builds without it fall back to the shipped r2-disasm passes. - Smoke: `tools/pcode_dump.cpp` links the lib, lifts one function from a corpus dylib, prints SSA P-Code ops — validates vendor + spec loading before any fold integration. ## 6. Reference: pypcode (mirror its build mechanics, not its scope) `angr/pypcode` `CMakeLists.txt` compiles `SLEIGH_COMMON` (= our CORE+SLEIGH, lift-only: `address compression context float globalcontext marshal opcodes pcodecompile pcodeparse pcoderaw semantics slaformat sleigh sleighbase slghpatexpress slghpattern slghsymbol space translate xml`), builds the `sleigh` compiler from `+ filemanage slgh_compile slghparse slghscan`, and runs `sleigh -a ` at build time to compile `.sla`. We extend the file set with `DECCORE` (for SSA) and **pre-compile `.sla` at vendor time** instead of at fold build time. pypcode is Apache-2.0/BSD and a good build reference. ## 7. Open items resolved in later phases - Exact pinned SHA for `Ghidra_12.1_build` → recorded in `VENDOR.md` at clone (Phase 1). - Whether to also expose decompiled **C** (printc) for a future decompile→Clang→source-detector path → out of scope now; `printc` is in DECCORE so it's available for free later. - Per-function decompile cost vs the `binary_backend` test budget → measured in Phase 1 with `pcode_dump`; cap/deadline designed in Phase 4.