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 withOBF_MODULE_PASSand add the id toisModuleOnlyPassId.
Checklist before "done"¶
-
verifyFunction/verifyModulepasses after your transform. -
PreservedAnalysesreturnsall()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-configshows 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.