Comparative Evaluation of Intermediate Representations for Hardware Architecture Abstraction Capability
This project compares intermediate representations (IR) produced by eight backends (pyghidra/P-code, angr/VEX, radare2/ESIL, Binary Ninja LLIL/MLIL/HLIL, RetDec/LLVM IR, IDA Pro/Hex-Rays microcode) across multiple CPU architectures, for a set of metrics (cost, expansion ratio, temporaries, robustness, agnosticism, nesting depth, SSA operations, verbosity). Built for a Master's thesis (TFM) evaluating how architecture-agnostic each backend's IR actually is.
The pipeline runs in two phases:
- Lifting (
pipeline.run_lifting) — lift every (binary, backend) pair and write each backend's raw IR + per-function status toresults/(or the specified path). - Metrics (
pipeline.metrics.run_metrics) — readresults/(or the specified path), compute the metric blocks underpipeline/metrics/blocks/, and write aggregated JSON/CSV per backend.
pipeline/
run_lifting.py phase 1 orchestrator
common.py shared helpers: BinKit filename parsing, binary discovery, IR dump helpers
native_classify.py classifies native (disassembled) instructions into arithmetic/control/memory/other
binja_il_classify.py BNIL-specific IR classification/traversal helpers shared by the 3 binja backends
elf_bytes.py reads raw instruction bytes straight from the ELF, independent of any backend
backends/ one standalone CLI script per backend
pyghidra_lift.py
angr_lift.py
r2_lift.py
binja_llil_lift.py
binja_mlil_lift.py
binja_hlil_lift.py
retdec_lift.py
ida_lift.py
metrics/
run_metrics.py phase 2 orchestrator
registry.py @register_block registration used by every file under blocks/
formulas.py shared aggregate-stats helpers (median/IQR/etc.)
blocks/ metric scripts
expansion_ratio.py
temporaries.py
nesting_depth.py
ssa_operations.py
cost.py
robustness.py
agnosticism.py
results/ default output path (gitignored)
requirements.txt pinned Python dependencies
- Python 3.12 (pipeline was developed/run against 3.12.3).
- A virtual environment is recommended:
If you don't activate the venv, pass
python3 -m venv IRanalysis source IRanalysis/bin/activate pip install -r requirements.txt--python IRanalysis/bin/python3torun_lifting.pyinstead (see Usage below) — each backend runs as its own subprocess using whichever Python executable you point it at.
Four of the eight backends are not pip packages and must be installed and licensed separately. The pipeline will error clearly (per-backend, per-binary, not aborting the whole run) if one isn't reachable.
| Backend | Requirement |
|---|---|
pyghidra |
A Ghidra install, with either GHIDRA_INSTALL_DIR set to point at it, or Ghidra discoverable some other way pyghidra supports. Requires a JDK (developed against Temurin 21). |
r2 |
The radare2 CLI on PATH (r2pipe just talks to it over a pipe). |
binja_llil / binja_mlil / binja_hlil |
A licensed Binary Ninja install with its headless Python API installed into your interpreter (via Binary Ninja's own installer/install_api.py) — commercial license required for headless/API use. |
ida |
A licensed IDA Pro install with idalib activated: from the IDA install directory, pip install idalib/python, then run that install's py-activate-idalib.py once. import idapro must happen before any other ida_* import, which is why this backend runs as its own subprocess. |
retdec |
The retdec-decompiler CLI, found via --retdec-bin, the RETDEC_BIN env var, or PATH (checked in that order). |
angr and pyelftools (used directly by pipeline/elf_bytes.py and the
robustness metric block) are ordinary pip dependencies and need no extra setup.
A prebuilt image provides the environment for the four free/open-source
backends (pyghidra, angr, r2, retdec) — Ghidra, radare2, RetDec,
Temurin JDK, and the pinned Python dependencies. Binary Ninja and IDA Pro
are not included (both require a paid, per-seat commercial license —
see the table above); run those two separately on a licensed host.
The image ships tools only, not this repo's code — mount your own checkout
of pipeline/ in at run time so git pull alone picks up code updates,
no image rebuild needed:
docker pull ghcr.io/gitdebaro/multi-arch-ir-analysis:latest
# or build locally from the Dockerfile:
# docker build -t multi-arch-ir-analysis .
docker run --rm -it ghcr.io/gitdebaro/multi-arch-ir-analysis:latestBinaries are expected to follow the BinKit naming convention:
<project>-<pver>_<compiler>-<cver>_<arch>_<bits>_<opt>_<name>.<ext>
(e.g. coreutils-8.29_gcc-6.4.0_x86_32_O1_ls.elf), staged under
binaries/<tool>/<arch>/<bits>/<opt>/<filename>. pipeline/common.py
parses arch/bits/opt/compiler straight from the filename; binaries that
don't match the pattern are still lifted, just without that metadata.
The binaries folder and all its content, Binkit subset with the 9 compiler versions (4.2 GB unzipped), can be obtained in the following link:
https://drive.google.com/file/d/1jMf_w0qf2cdomKUgRsNWDYZPOEe9tblF/view?usp=sharing
# Everything under binaries/, all 8 backends
python -m pipeline.run_lifting --binaries-dir binaries
# A subset: one binary, two backends
python -m pipeline.run_lifting --binary binaries/ls/x86/32/O0/coreutils-8.29_gcc-8.2.0_x86_32_O0_ls.elf \
--backends r2,angr
# Filter by architecture/bits/optimization level (comma-separated, matched against the BinKit filename)
python -m pipeline.run_lifting --binaries-dir binaries --arch arm,x86 --bits 32 --opt O0,O2Useful flags:
--results-dir (default ./results)
--max-parallel (concurrent binary x backend subprocesses, default = CPU count)
--max-mem-mb (soft scheduling budget)
--timeout (per-run subprocess timeout, default 1800s)
--skip-existing / --resume (for continuing a large corpus run across sessions — see manifest.json / pause_state.json under --results-dir)
Run `python -m pipeline.run_lifting --help` for thefull list.
python -m pipeline.metrics.run_metrics --results-dir results
# or just a subset of metric blocks:
python -m pipeline.metrics.run_metrics --results-dir results --blocks cost,robustnessReads results/manifest.json (written by phase 1) plus each binary's
metadata.json, then runs every registered block in
pipeline/metrics/blocks/ (or only --blocks) and writes
<outdir>/<block>.json + <block>.csv (default outdir:
<results-dir>/metrics).
Under --results-dir (default results/):
manifest.json— one entry per (binary, backend) run: status, output dir, timing/rusagestats.<binary_stem>/<backend>/— per-run output:functions.json— every functionrun_liftingfound for that binary.lift_records.json— one record per attempted function:status("ok"/"error"/"skipped"), IR-size counts, and on"error", the exception type/message.summary.json— counts (num_lifted_ok/_error/_skipped),duration_s,fatal_errorif the whole run aborted.whole_binary.<level>.txt— concatenated per-function IR text, address-ordered.