Contributing to Kagura¶
Getting Started¶
git clone https://github.com/ykus4/kagura.git
cd kagura
bash build.sh
cd build && ctest --output-on-failure
build.sh works on macOS and Linux. It auto-detects LLVM via llvm-config,
brew --prefix llvm and the usual /usr/lib/llvm-<N> locations; override with
LLVM_PREFIX=/path/to/llvm bash build.sh. Extra arguments are forwarded to
CMake:
Adding a Pass¶
include/kagura/PassRegistry.def is the single source of truth for the pass
list. One row there generates the cl::opt enable flag and its extern
declaration (Options.cpp / Options.h), the named-pass parsing callback, the
OptimizerLast auto-pipeline entry (Plugin.cpp), the JSON policy key
(ConfigLoader.cpp) and a link smoke test (tests/CMakeLists.txt). Do not
hand-register a pass in any of those — they are all X-macro expansions of the
.def.
- Add the pass declaration to
include/kagura/Passes/<Category>.h, matching thelib/Transforms/subdirectory you are about to put the source in - Implement in
lib/Transforms/<Category>/YourPass.cpp, including onlykagura/Passes/<Category>.h— not thePasses.humbrella - Add one row to
include/kagura/PassRegistry.def: KAGURA_FN_PASS(Flag, "kagura-x", "Description", YourPass())for a function pass, orKAGURA_MOD_PASS(...)for a module pass.Flagis thekagura::opt::symbol name; the row's position sets its position in the auto-injected pipeline.KAGURA_INFRA_PASS("kagura-x", YourPass())only for infrastructure passes that are not driven by a plain bool flag — those still need a hand-written injection point inPlugin.cpp.KAGURA_TUNING(...)for a numeric parameter the pass reads.- Add a C source to
tests/pass-inputs/ - Add a
kagura_add_pass_test()entry intests/CMakeLists.txt - Add a FileCheck test in
tests/lit/<your-pass>.ll - If the pass should be on by default for FAST / BALANCED / STRONG, add rows to
lib/Transforms/Profiles.defand runscripts/ci/gen-profiles.py. Do not editintegration/profiles/*.json— they are generated and CI checks them.
There is nothing to add to lib/Transforms/CMakeLists.txt: the source list and
the unity-build groups are globbed per directory. A new category directory
does need a line in KAGURA_PASS_DIRS there.
Pass Guidelines¶
- Use
PassInfoMixin(New Pass Manager only — no legacy pass support) - Skip declarations:
if (F.isDeclaration()) return PA; - Check
shouldObfuscate(F, "passname", defaultEnabled)fromUtils.hto respect per-function annotations - Skip functions with exception handling when the pass cannot handle EH:
if (hasExceptionHandling(F)) return PA; - Use
kagura::PRNGfromUtils.hfor all randomness; respect-kagura-seed - Keep
isRequired()returningfalsefor all obfuscation passes - Use
kagura::getModuleTriple(M)(notM.getTargetTriple()directly) for LLVM 17–22 compatibility
Runtime Library¶
runtime/ is organised by platform:
| Directory | Contents |
|---|---|
runtime/core/ |
Cross-platform: AES, secure zeroing, device key, VM interpreter |
runtime/anti_debug/ |
ptrace / Frida / breakpoint / hook / emulator detection (POSIX) |
runtime/ios/ |
Darwin: jailbreak detection, Mach-O integrity, ObjC/Swift helpers |
runtime/android/ |
Bionic / Linux: JNI, Play Integrity, seccomp, APK / ELF integrity |
runtime/windows/ |
Win32: ETW detection, PE integrity, tamper response |
runtime/game/ |
Anti-cheat helpers (IL2CPP, UE4, protected values) |
If your pass needs runtime support, add a .c file to the right subdirectory
and register it in runtime/CMakeLists.txt. Declare any runtime functions in
an extern "C" block in the pass file.
The build manifests under integration/ select runtime sources by
directory, never by file name, so moving a file within runtime/ does not
require touching them. Please keep it that way — an earlier flat-to-nested
reorganisation silently broke the SwiftPM, Bazel, CocoaPods and Android NDK
manifests because they enumerated individual .c files.
Tests¶
- Pass-level IR tests (
tests/pass-inputs/+tests/CMakeLists.txt): each file is compiled to bitcode and run throughoptto verify the pass executes without crashing - FileCheck lit tests (
tests/lit/): verify specific IR transformations using.llinputs with; CHECK:directives
All tests must pass across LLVM 17, 18, 19, 21, and 22.
To run only the FileCheck tests:
To run the differential tests (obfuscated vs. plain output comparison):
Code Style¶
- Follow LLVM coding conventions (camelCase for functions, PascalCase for types)
- File header comment format:
- No
using namespace std - No
LLVM_VERSION_MAJORguards for thegetTargetTriple()API — usekagura::getModuleTriple()instead
Pull Requests¶
- One pass or feature per PR
- Include a FileCheck test (
.ll) that verifies the pass transformation - Run
./scripts/ci/differential-test.shlocally and confirm no regressions - CI must be green before merge
Release Process¶
Releases are published from the main branch. On GitHub:
- Create a new Release tag (e.g.,
v0.2.0) from the GitHub UI. - The
release.ymlworkflow triggers automatically and uploads pre-built binaries for: - macOS arm64 × LLVM 21 and 22
- Linux x86_64 × LLVM 19, 21, and 22
- A source tarball (
kagura-<version>-source.tar.gz) is also attached.
License¶
By contributing, you agree that your contributions will be licensed under the MIT License.