Skip to content

Internals

For contributors modifying the framework itself. This is the machinery behind Architecture.

The annotation cache

ObfuscationAnnotationAnalysis (obf-annotations) is a module analysis. It walks llvm.global.annotations once, parses each "obf: ..." spec, and builds a cached Function → ObfuscationConfig map. Because it's an analysis, the result is computed once and shared by every downstream pass — parsing never repeats.

FunctionObfContextAnalysis (function-obf-context) is the per-function view: it exposes the resolved config plus the derived seeds to each function pass, so a pass never re-parses or re-derives.

The config type

ObfuscationConfig (in ObfuscationConfig.*) holds per-pass enablement and parameters. Parsing rules (keys, quoting, booleans) and merging semantics (additive enablement, last-wins params) live here. When you add a pass, this is where its parameters get parsed and stored — one of the seven wiring touch-points.

The driver & ordering

ObfuscationModulePass (obfuscation) is the entry pass. It runs module-only passes (fmerge, then strenc), then invokes the function driver ObfuscationFunctionDriverPass (obfuscation-fn).

The driver resolves the pipeline order in ObfuscationPipeline.* — a topological sort with conflict enforcement (e.g. vm + flattening is rejected). Two CLI overrides exist (-obf-pipeline-ordering, -obf-pipeline-ordering-ann), both of which still run the conflict checks. When you add a pass, its position in the topological order is declared here.

Seed derivation

Seeds cascade base → module → function → pass, each level a stable hash of the level above plus a stable key (module id, function name, canonical pass id). Passes must draw randomness from the provided RNG / deriveSeed — never from random_device directly, never from hash-map iteration order — or determinism breaks. See Determinism & seeds.

Registration

registration/ObfPasses.inc is the single source of truth for registered pass/analysis names, consumed by registration/PluginEntry.cpp for both build modes. It registers analyses too, not just passes — the driver queries FunctionObfContextAnalysis and the module pass queries ObfReportAnalysis, so a registration that omits the analyses asserts at runtime.

Reporting

ObfReportAnalysis (obf-report) collects the JSON obfuscation map and per-pass CFG snapshots. A pass reports its status/deltas/skip-reason through this, which is what surfaces in the HTML viewer and the report JSON. Emitting a skip reason (rather than silently doing nothing) is expected when a pass declines a function.

Gotcha: erasing annotated functions

fmerge erases merged originals. Any pass or analysis that reads llvm.global.annotations must account for a function's annotation use being consumed and the function possibly no longer existing after merge — handle the use/erase ordering carefully or you'll dereference a dead Function*.

Deeper reference

The repository's docs/DEV.md and docs/VM.md carry the exhaustive internals (VM engine builders, ISA extension points, the ADec registry). This page is the map; those are the territory.