Inertia is an angr-based workspace for decompiling and comparing 16-bit x86 DOS programs. It targets real-mode .EXE, .COM, raw blobs, and compiler/listing sidecars from DOS-era toolchains.
Correctness comes before pretty C. When evidence is weak, the tools prefer low-level output, visible refusals, or explicit fallback reports over guessed source.
The repo is tested against the angr stack pinned in pyproject.toml.
git submodule update --init --recursive
python3.11 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -m pip install -e .
python -m pip install -e ".[test]"The root ./decompile.py wrapper re-execs through ./.venv/bin/python when that virtualenv exists.
Main entry points:
./decompile.pypython -m inertia_decompiler.cli- installed script:
decompile-x86-16
Common runs:
./decompile.py PROGRAM.EXE
./decompile.py PROGRAM.COM
./decompile.py PROGRAM.EXE --addr 0x11423 --timeout 30
./decompile.py LISTING.COD --proc _main --proc-kind NEAR
./decompile.py blob.bin --blob --base-addr 0x1000 --entry-point 0x1000Useful options:
--addr ADDR: decompile one function by linear address.--proc NAME: extract one procedure from a.CODlisting.--max-functions N: cap whole-binary output.--timeout SEC: bound analysis for a function or run.--window BYTES: bound CFG recovery around a target address.--c-target portable-flat|msc-dos: choose generated C target helpers.--api-style modern|dos|raw|pseudo|service|msc|compiler: choose helper naming style.--show-asm: print the first lifted block before C.--trace-c-stages: print labeled generated-C snapshots.--dump-layers --dump-layer-dir DIR: write per-stage C artifacts.--function-discovery-backend auto|angr|rizin|hybrid: choose whole-binary discovery.--signature-catalog PATH: apply a PAT catalog built from.pat,.obj, and.lib.-qorINERTIA_BRIEF=1: reduce progress and diagnostics.
Tail validation can be enabled for decompiler correctness checks:
INERTIA_ENABLE_TAIL_VALIDATION=1 ./decompile.py SORTDEMO.EXEWhen a run is slow, capture compact telemetry:
INERTIA_OTEL_SPANS=1 \
INERTIA_OTEL_SPAN_FILE=angr_platforms/.cache/otel.trace.txt \
./decompile.py PROGRAM.EXE --addr 0x11423See reference/telemetry.md for trace formats and OTLP export.
Supported executable inputs:
.COM- DOS MZ
.EXE - 16-bit NE
.EXEat smoke level .BIN/.RAWblobs.CODlistings as direct procedure inputs
Metadata sidecars are optional but useful:
.CODand.LST: procedure ranges, labels, local names, and source-backed hints..MAP: public symbols and layout.- CodeView NB00/NB02/NB04/NB08/NB09: debug names, procedures, stack variables, source files, line maps, type names, and member records where supported.
- TDInfo: Borland/Turbo Debugger symbols, stack/register/constant symbols, compact type descriptors, structs, unions, and enums.
.pat,.obj,.lib: library signature matching.
Inspect embedded debug metadata directly:
./dump_debug_info.py PROGRAM.EXEMore format detail lives in CODEVIEW_SUPPORT.md, DOS_COMPILER_SUPPORT.md, NE_WIN16_SUPPORT.md, and NE_LOADER_INTEGRATION_VERIFIED.md.
Build one deduplicated catalog from FLAIR .pat, OMF .obj, and OMF .lib inputs:
python scripts/build_signature_catalog.py signature_catalogs/ QLINK/ \
--output signature_catalogs/local.patUse it during decompilation:
./decompile.py PROGRAM.EXE --signature-catalog signature_catalogs/local.patBuild a shareable all-compilers bundle when the local compiler archive exists:
python scripts/build_compiler_catalog_bundle.pyReport likely compiler/runtime matches for a binary:
python scripts/report_compiler_matches.py PROGRAM.EXE \
--catalog signature_catalogs/all_compilers_catalog_bundle.zipAdd --compilers-only for a short ranked compiler list.
The compiler matcher can also score likely Microsoft C 5.1 flag combinations when profile data is available:
python scripts/build_msc51_flag_profiles.py \
--cod-dir deep \
--output signature_catalogs/msc51_flag_profiles.json
python scripts/report_compiler_matches.py PROGRAM.EXE \
--catalog signature_catalogs/all_compilers_catalog_bundle.zip \
--detect-flags-msc51 \
--msc51-flag-profiles signature_catalogs/msc51_flag_profiles.jsonThe decompiler also has an internal MS C 5.1 local-variable hash diagnostic. It checks whether recovered local names and BP offsets fit the compiler's 16-bucket allocation pattern; this is used as evidence, not as a source of guessed names.
./z3func.py is the main static and concrete function-comparison tool. ./dosunit.py exposes the same flow for compatibility.
Typical static SSA comparison:
./z3func.py discover --exe original.exe --map original.map --out /tmp/orig.functions.json
./z3func.py discover --exe rebuilt.exe --map rebuilt.map --out /tmp/rebuilt.functions.json
./z3func.py make-mapping \
--oracle-functions /tmp/orig.functions.json \
--candidate-functions /tmp/rebuilt.functions.json \
--out /tmp/mapping.json
./z3func.py ssa \
--exe original.exe \
--functions /tmp/orig.functions.json \
--cache-dir .cache/dosunit \
--out /tmp/orig.ssa.json
./z3func.py ssa \
--exe rebuilt.exe \
--functions /tmp/rebuilt.functions.json \
--cache-dir .cache/dosunit \
--out /tmp/rebuilt.ssa.json
./z3func.py compare-ssa \
--oracle-ssa /tmp/orig.ssa.json \
--candidate-ssa /tmp/rebuilt.ssa.json \
--mapping /tmp/mapping.json \
--out /tmp/ssa.results.json
./z3func.py report-failures \
--results /tmp/ssa.results.json \
--out /tmp/ssa.failures.mdFor large programs, prefer the batched comparator:
./z3func.py compare-ssa-batched \
--oracle-ssa /tmp/orig.ssa.json \
--candidate-ssa /tmp/rebuilt.ssa.json \
--oracle-index-ssa /tmp/orig.ssa.json \
--candidate-index-ssa /tmp/rebuilt.ssa.json \
--mapping /tmp/mapping.json \
--out-dir /tmp/ssa-batches \
--out /tmp/ssa-batches/aggregate.jsonOther useful comparator subcommands:
complexity: classify which functions are good bounded-Z3 targets.regions/compare-regions: compare lifter-backed operand/effect summaries.compare-ssa-abi: prove ABI-visible equivalence from an ABI manifest.gen-vectors,record-oracle,compare: concrete-vector comparison through the runtime backend.compare-data: compare loaded MZ data ranges after relocation.summarize: print result rollups.
Start an angr-backed DOS GDB remote server:
python -m inertia_decompiler.debug_dos PROGRAM.EXE --host 127.0.0.1 --port 1234Connect with the Textual TUI:
python -m inertia_decompiler.gdb_tui --host 127.0.0.1 --port 1234 --arch x86_16The debugger exposes 16-bit registers, segment registers, flags, memory, breakpoints, and stepping through the GDB remote protocol.
Decompile a .COD corpus into sibling .dec files:
python scripts/decompile_cod_dir.py cod --timeout 20 --max-memory-mb 1024Useful filters include --cod-file, --proc-name, --skip-existing, and --write-tail-validation-baseline.
Compare discovery engines:
python scripts/compare_discovery_backends.py PROGRAM.EXE \
--backends all \
--json-output /tmp/discovery.jsonRun the curated development gate:
make decompiler-check PYTHON=./.venv/bin/pythonRun the MS C tiny pipeline examples:
make test-pipeline PYTHON=./.venv/bin/pythonOptional native speedups are available for selected pure-Python modules:
python -m pip install ".[mypyc]"
python scripts/build_mypyc.py build_ext --inplaceIf mypyc is unavailable, normal .py execution is unchanged.
The core pipeline is:
IR -> Alias -> Widening -> Types -> Structuring -> Rewrite
The frontend loads DOS real-mode programs through the in-tree x86-16 angr platform, loader, lifter, SimOS, and sidecar parsers. Recovery tries to prove storage identity first, then widen split byte/word values, recover typed memory and stack objects, structure control flow, and finally perform cleanup-only C rewrites.
Generated C targets either portable flat helpers or MS C DOS helpers. Segmented memory is represented through helpers such as SEG_U8, SEG_U16, SEG_PTR, and MK_FP; raw (seg << 4) + off arithmetic is an internal execution detail, not normal output.
Tail validation compares semantic effects after late-stage cleanup. Validation failures are reported as failures rather than silently converted into prettier C.
z3func.py ssa lifts bounded VEX or AIL regions into compact SSA. The comparator observes ABI-selected registers, stack effects, memory stores, direct control-flow successors, direct calls, and optional composed acyclic regions.
compare-ssa first applies cheap equivalence checks such as byte-identical code, compact-SSA equality, mapped direct-call targets, and proven region facts. It then asks Z3 for a counterexample where observable outputs differ. Unsupported helpers, hard guarded memory, oversized slices, symbolic loop paths beyond the bound, and unknown call effects become structured refusals.
compare-ssa-batched runs the same proof in child processes with full SSA indexes available for call-target lookup, keeping whole-program comparisons memory-bounded.
Function signature evidence comes from callsite materialization, stack-push recovery, sidecar/debug metadata, known helper/runtime models, library signature matches, and ABI manifests. The decompiler uses that evidence to distinguish near/far calls, stack arguments, register returns, preserved/clobbered registers, and pointer-vs-value argument classes.
When exact signatures are not proven, output should keep an honest fallback prototype instead of filling guessed arguments or rewriting call bodies late.
The signature pipeline accepts existing FLAIR .pat files and Microsoft OMF .obj / .lib inputs.
For OMF inputs, the parser extracts module blobs, public names, fixup references, segment bytes, module lengths, tail bytes, source path, and compiler provenance. Each module is converted to PAT-style pattern data, then signature_catalog.py deduplicates modules by pattern bytes, length, public names, referenced names, and tail bytes.
At match time, the loaded binary image is scanned with either the portable Python regex backend or Hyperscan when available. Matches become code labels, code ranges, library-function classifications, and probable compiler names. Unsupported archive formats are detected defensively and skipped instead of crashing.
Contributor and agent-specific rules are intentionally not in this README. See AGENTS.md, reference/agent-rules.md, reference/decompiler-fix-plan.md, and the other files under reference/.