Skip to content

Architecture

xollvm is a small set of new-pass-manager components wired around one idea: parse annotations once, cache them, then drive a deterministic per-function pipeline.

The mental model

  1. You annotate functions with "obf: ..." specs in the source.
  2. A module analysis parses every annotation into a cached map: Function → ObfuscationConfig.
  3. The module entry pass obfuscation runs:
    • module-only work (fmerge, then strenc) if enabled anywhere in the module,
    • a function driver that executes the topologically-sorted per-function pipeline.
  4. Optional: IR verification (-obf-verify), metrics, and reports.
flowchart TD
    subgraph Source
      A["Annotated C / C++<br/>annotate(obf: ...)"]
    end
    A -->|clang -emit-llvm| IR["LLVM IR + llvm.global.annotations"]
    IR --> MA["ObfuscationAnnotationAnalysis<br/>(module analysis, runs once)"]
    MA --> CACHE[(Function → ObfuscationConfig<br/>cached map)]
    IR --> MP["obfuscation<br/>(module entry pass)"]
    CACHE --> MP
    MP --> MOD["module-only: fmerge → strenc"]
    MP --> DRV["obfuscation-fn<br/>(per-function driver)"]
    DRV --> PIPE["topologically-ordered<br/>function pipeline"]
    PIPE --> OUT[Obfuscated IR]
    MP -.-> REP[(reports · metrics · seed manifest)]

The components

Component Kind Role
ObfuscationAnnotationAnalysis (obf-annotations) module analysis Parses llvm.global.annotations once into the cached Function → Config map.
FunctionObfContextAnalysis (function-obf-context) function analysis Exposes the resolved config + derived seeds to each function pass.
ObfuscationModulePass (obfuscation) module pass The entry point. Runs module-only passes, then the function driver.
ObfuscationFunctionDriverPass (obfuscation-fn) function pass Orders and runs the enabled per-function passes under the budget/gates.
ObfReportAnalysis (obf-report) module analysis Collects the JSON obfuscation map + CFG snapshots.
ObfDumpConfigPass (obf-dump-config) module pass Diagnostic: prints the resolved config + seeds.
ObfMetricsPass (obf-metrics) module pass Diagnostic: emits per-function JSONL metrics.

Why an analysis for the config?

Parsing annotations is done in a cached analysis, not repeated per pass. Every function pass queries the same resolved config, so ordering, seeds, and enablement are consistent and computed exactly once per module.

Two build shapes, one source

The same components ship as either a static extension linked into clang/opt, or a loadable -fpass-plugin — see Installation. Both register the identical pass/analysis names from a single source of truth (registration/ObfPasses.inc), so behaviour is identical regardless of how you build.

Where to go next