# r2ghidra: Ghidra's decompiler running inside radare2

> r2ghidra embeds Ghidra's decompiler in radare2 as a plugin, so you get Ghidra quality decompilation inside a tool that opens a binary in seconds rather than a suite that wants a project. The interesting part now is the dead code handling, which reports which p-code operations Ghidra eliminated and, with an option set, keeps them visible.

**radareorg/r2ghidra** — Native Ghidra Decompiler for r2

- Repository: https://github.com/radareorg/r2ghidra
- Website: https://www.radare.org/
- Stars: 566 · Forks: 84
- Language: C++
- License: NOASSERTION
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/radareorg-r2ghidra

## The decompiler half of Ghidra, without Ghidra

r2ghidra is an integration of the Ghidra decompiler for radare2, and the important architectural fact is stated in the second sentence of the description. It is solely based on the decompiler part of Ghidra, which is written entirely in C++, so Ghidra itself is not required at all and the plugin can be built self-contained.

That distinction is what makes the project useful rather than a curiosity. Ghidra is a suite, and a suite has opinions: a project, a database, a program tree, a scripting language, a code browser. radare2 is the opposite, a tool you point at a file and get an answer. Taking only the decompiler and dropping it into radare2 gives you the analysis quality of Ghidra with the turnaround of a hex editor.

The practical consequence is that you get Ghidra's decompilation for a function you are already looking at, inside a session where you have been stepping through disassembly, and you do not have to import the binary into a Ghidra project first. The project was presented at r2con 2019 as part of the Cutter talk, so it has been part of the radare2 ecosystem for years rather than being a new arrival.

The build reflects the C++ core. The repository top level has Makefile.acr, configure.acr and a preconfigure script for the ACR build system, meson.build for Meson, and a make.bat alongside configure.bat for Windows. There are three processor list files, ghidra-processors.txt.all, ghidra-processors.txt.default and ghidra-processors.txt.x86arm, which is how the plugin decides which Sleigh languages to load. The ghidra/ directory holds the vendored decompiler sources, r2plugin/ and iaito-plugin/ hold the two plugin front ends, with src/, sys/, test/ and subprojects/ around them.

The licence is a custom one that GitHub could not classify, with LICENSE.md and a LESSER.md beside a COPYING file, so the terms are worth reading before you vendor it into a commercial toolchain.

## Installing it, and the build dependencies that hint at the scope

Installation is through r2pm, the radare2 package manager:

```
r2pm -U
r2pm -ci r2ghidra
```

By default r2pm installs into your home directory, and the -g flag uses the system-wide installation instead.

The build dependencies list is where the scope of the port becomes clear. You need radare2, preferably from git, and for distribution builds you need to ensure the -dev package is also installed. You need pkg-config, which is how the build system finds libraries and include files. You need either acr and make or meson and ninja, and the documentation is relaxed about which: pick the build system you like, since all of them are maintained and working. You need a C++ compiler, which the list names as msvc, g++ or clang++, basically a C++ compiler and a C compiler. And you need git and python3, which are needed to clone and prepare ghidra-native.

That last dependency is the one that explains what the plugin is doing. Ghidra's processor specifications are not plain C++ source, they are SLEIGH definitions, and compiling them into the plugin requires a step that fetches and prepares the Ghidra processor modules. A build that clones something during the build is a build that can fail on a network problem, which is worth knowing before you start.

The troubleshooting advice is brief and useful. If the build fails, read the error message and act accordingly, and the documentation notes that r2pm should handle the PKG_CONFIG_PATH automatically in any case.

Portability is listed as working on Termux on Android arm64, macOS and iOS, GNU/Linux, Windows and FreeBSD on x86-64. The presence of Termux and iOS in that list is unusual for a reversing plugin and tells you the native build is not tied to a desktop Linux assumption.

## The pdg command family, and what each output is for

Using it is two commands. You type af to analyse the function and then pdg to invoke the decompiler, and the help output lists the whole family:

```
[0x100001060]> pdg?
Usage: pdg  # Native Ghidra decompiler plugin
| pdg           # Decompile current function with the Ghidra decompiler
| pdg*          # Decompiled code is returned to r2 as comment
| pdga          # Side by side two column disasm and decompilation
| pdgd          # Dump the debug XML Dump
| pdgj          # Dump the current decompiled function as JSON
| pdgo          # Decompile current function side by side with offsets
| pdgp          # Switch to RAsm and RAnal plugins driven by SLEIGH from Ghidra
| pdgs          # Display loaded Sleigh Languages
| pdgsd N       # Disassemble N instructions with Sleigh and print pcode
| pdgss         # Display automatically matched Sleigh Language ID
| pdgw          # Write recovered variable types, names and signatures to r2
| pdgx          # Dump the XML of the current decompiled function
```

The variations matter more than they look. pdg* returns the decompiled code as an r2 comment, which is how you get it into a script or a note. pdga and pdgo give you a side by side view of disassembly and decompilation, with and without offsets, which is the view you actually use to check the decompiler's work. pdgj and pdgx dump the function as JSON and as XML respectively, and those are the machine readable forms. pdgs, pdgss and pdgsd expose the Sleigh layer directly, so you can list the loaded processor languages, see which one was automatically matched, and disassemble N instructions with Sleigh and print the p-code.

pdgp is the odd one out and the most interesting. It switches to the RAsm and RAnal plugins driven by SLEIGH from Ghidra, which means the disassembly itself comes from the Ghidra processor definitions rather than from radare2's own analysis. For a platform where radare2's disassembly is wrong, that is a much better answer than a wrong decompilation of a wrong disassembly.

And pdgw writes back into radare2 rather than out of it, updating existing variables and the current function's signature. That is the feature that makes the plugin worth the build effort, because a decompiler that produces names you have to re-enter by hand saves you less than you would hope.

## Dead code reporting, and the caveats the documentation is careful about

The most substantial recent work in the plugin is the dead code handling, and the documentation is unusually careful to say what the numbers do and do not mean.

pdgx includes the native p-code operation's is_dead boolean on its seqnum element, after the existing space, offset and uniq identity attributes. The value comes from PcodeOp::isDead() for that exact operation, and the documentation is explicit that it is not a reachability, path-feasibility or runtime-execution claim. The function AST contains operations retained in native basic blocks, not an inventory of all operations eliminated during decompilation, and older exports without the attribute leave the state unknown.

pdgj includes a pcode_ops array alongside the existing code and annotations, with each entry carrying the operation's space, offset, uniq, opcode number and native is_dead boolean. Those are the same retained basic-block operations exported by pdgx, so their dead state is normally false.

The option that changes this is r2ghidra.deadcode, which defaults to false so that Ghidra eliminates unused computations as usual. Setting it to true retains unused computations in the decompiled C and the p-code exports, so an assignment overwritten before its value is used can remain visible. The documentation notes the output can include extra temporaries, register flags and stack bookkeeping, that other simplifications and unreachable-block removal still run, and that this is not an unoptimised instruction listing.

The subtlety is in the two-pass behaviour. With the option enabled, JSON and XML decompilation runs analysis twice, first with normal elimination to capture native isDead()==true records, then with unused computations preserved for display. The same operation identity can therefore appear as live in pcode_ops and removed in eliminated_ops, because the two sections describe different analysis passes. The worked example is given: for mov eax,1; mov eax,42; ret, the C retains the first assignment and eliminated_ops records its removal by normal elimination.

The limits are stated just as precisely. Removed-operation records cover operations captured after initial lifting, their opcode is the opcode at capture time, they contain identity and state metadata without references to deleted operands, and rewrites and moves can also retire operations, so these records do not prove that an instruction is unreachable or never executes. Operations created and removed later in analysis are not included, and on an analysis restart the capture is replaced with the new pass.

## Type recovery written back, and the processor alias rules

pdgw is the writeback path, and the documentation explains what it does with the types it recovered, including the cases where it deliberately stays vague.

It updates existing variables and the current function's signature. Unknown 1-, 2-, 4- and 8-byte scalar types are written as fixed-width unsigned integers, which preserves their size without claiming that signedness was recovered. A signature is written only when all exported types keep the recovered ABI storage, while individually safe argument types can still be written. Local arrays retain their element types and dimensions without treating array bounds as proof that adjacent stack writes are unobservable, and existing named composite types are preserved. Unsupported types are skipped, and a second write reports no changes once the recovered types have converged.

The two write kinds are controlled separately, with r2ghidra.write.vars and r2ghidra.write.sig for the variables and the signature. The split exists because the safety condition is different for each: a variable whose type is uncertain can still be written as an unsigned integer of the right width, whereas a function signature has to be ABI-correct as a whole or it is wrong, and a wrong signature in radare2 produces a wrong calling convention for every call site in the function.

The other configuration knob is the processor selection. With e asm.arch=r2ghidra, asm.cpu accepts r2 aliases such as arm, ppc, java, msp430 and sh. The aliases and the short Ghidra processor names are case-insensitive, so ArM still selects ARM or AArch64.

That last detail is worth a moment, because it is a small compatibility surface with a long tail. When the plugin drives radare2's own disassembly through Ghidra's SLEIGH definitions, the mapping between radare2 architecture names and Ghidra processor names has to be right, and a case-insensitive alias is what stops a typo in the alias from silently selecting the wrong processor language.

The three processor list files at the repository root are the mechanism behind that, and they suggest the plugin ships a curated default set, a full set and a combined x86 and ARM set.

## Conclusion

r2ghidra suits an analyst who wants Ghidra's decompiler output without adopting Ghidra's project model, particularly when working inside radare2 or Cutter on a quick look at a binary. It does not suit you if you want a full reversing environment, because the plugin is solely the decompiler half of Ghidra and leaves the rest of the stack to radare2. Before you rely on the dead code reporting, read the caveats in the documentation rather than the feature list, because the is_dead flag is explicitly not a reachability claim and the eliminated operation records are not an instruction patch list.

## FAQ

### Does r2ghidra require Ghidra to be installed?

No. It is solely based on the decompiler part of Ghidra, which is written entirely in C++, so Ghidra itself is not required at all and the plugin can be built self-contained.

### How do I install r2ghidra?

Through r2pm, with r2pm -U followed by r2pm -ci r2ghidra. By default r2pm installs into your home directory, and the -g flag uses the system-wide installation.

### How do I decompile a function with r2ghidra?

Type af to analyse the function and then pdg to invoke the decompiler. Variants include pdg* to return the decompiled code as an r2 comment, pdga and pdgo for a side by side disassembly and decompilation view, pdgj and pdgx for JSON and XML dumps, and pdgs, pdgss and pdgsd to work with the Sleigh layer directly.

### What does the is_dead flag in the r2ghidra p-code export mean?

It comes from PcodeOp::isDead() for that exact operation, and it is explicitly not a reachability, path-feasibility or runtime-execution claim. The AST contains operations retained in native basic blocks, and the eliminated operation records are not an instruction patch list.

## Sources

- [Issues](https://github.com/radareorg/r2ghidra/issues)
- [Project website](https://www.radare.org/)
- [radareorg/r2ghidra on GitHub](https://github.com/radareorg/r2ghidra)
- [README](https://github.com/radareorg/r2ghidra/blob/master/README.md)
- [Releases](https://github.com/radareorg/r2ghidra/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/radareorg-r2ghidra
