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¶
- You annotate functions with
"obf: ..."specs in the source. - A module analysis parses every annotation into a cached map:
Function → ObfuscationConfig. - The module entry pass
obfuscationruns:- module-only work (
fmerge, thenstrenc) if enabled anywhere in the module, - a function driver that executes the topologically-sorted per-function pipeline.
- module-only work (
- 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¶
- Annotation model — the input side of the diagram above.
- Pipeline & ordering — how the function pipeline is ordered.
- Internals — for contributors extending the framework.