Skip to content

Adding a pass

A new obfuscator pass (function or module) must hit seven wiring touch-points. Miss one and the pass either builds but never runs, or runs but is unreachable via annotation. Model a new pass on an existing shipped one (constenc and fmerge are good templates).

The seven touch-points

flowchart TD
    S["1. Sources<br/>YourPass.cpp/.h"] --> C["2. CMakeLists.txt"]
    C --> P["3. PassIds.h<br/>(canonical id + aliases)"]
    P --> R["4. ObfPasses.inc<br/>(register pass + analyses)"]
    R --> CFG["5. ObfuscationConfig<br/>(parse + store params)"]
    CFG --> PIPE["6. ObfuscationPipeline<br/>(topological position + conflicts)"]
    PIPE --> T["7. Runtime tests<br/>(utils/cases + gates)"]
# File What to add
1 YourPass.cpp / include/llvm/Transforms/Obfuscator/YourPass.h The pass itself — a PassInfoMixin with a run() that transforms IR and reports its result.
2 CMakeLists.txt Add the new source so it compiles into the obfuscator library.
3 include/llvm/Transforms/Obfuscator/PassIds.h Add the canonical id to allCanonicalPassIds(), a normalizePassId branch for aliases, and (if module-only) isModuleOnlyPassId.
4 registration/ObfPasses.inc Register the pass name (and any new analysis) for both build modes.
5 ObfuscationConfig.* Parse and store the pass's annotation parameters into the config.
6 ObfuscationPipeline.* Declare the pass's position in the topological order and any conflicts.
7 utils/cases/ + utils/gates/ A runtime test case + an IR feature-gate asserting the transform appeared.

Function vs module pass

  • Function pass — the common case. Runs inside the per-function pipeline in topological order, under the budget and gates. Register with OBF_FUNCTION_PASS (or leave it to the driver, following existing passes).
  • Module pass — runs before the function pipeline (like fmerge/strenc). Register with OBF_MODULE_PASS and add the id to isModuleOnlyPassId.

Checklist before "done"

  • verifyFunction / verifyModule passes after your transform.
  • PreservedAnalyses returns all() only when nothing changed.
  • All randomness comes from the provided RNG / deriveSeed — reproducible under a fixed seed.
  • No dependence on hash-map iteration order; stable-key sorting where order matters.
  • A skip reason is reported when the pass declines a function (not a silent no-op).
  • obf-dump-config shows the pass enabled with your parsed parameters.
  • The runtime suite is green: python utils/obf_runtime_tests.py --build-dir <build> --filter yourpass.

Add a case in utils/cases/ and a gate in utils/gates/ so the new pass is covered by the suite (and, if it makes a resistance claim, a resilience-bench case too).

See Internals for what each touch-point file actually does.