Skip to content

quiaingmuarua/learn_llvm

Repository files navigation

📘 Learn LLVM – 一个系统学习 LLVM 的实践项目

本项目旨在通过 实际动手写代码 的方式,系统性学习 LLVM,包括:

  • 通过编写一门简单语言来学习 LLVM 前端与 IR
  • 编写 LLVM Pass(含 OLLVM 混淆)来掌握 LLVM 中端优化技术
  • 通过各种小 demo 熟悉 LLVM API 与 IR 构造方式

这是一个分模块的学习型仓库,适合用于长期积累与完整掌握 LLVM 技术栈。


📁 项目结构

learn_llvm/
├── CMakeLists.txt          # 顶层 CMake,统一管理三大模块
├── build/                  # 构建输出目录(生成的 bin/lib 都在这里)
│
├── learn-demo/             # 零散的小实验、API 演示、IR 构造练习
│   ├── CMakeLists.txt
│   └── src/
│       └── demo_xxx.cpp
│
├── learn-lang/             # 通过“写一门语言”来学习 LLVM
│   ├── CMakeLists.txt
│   └── src/
│       └── xxx.cpp
│
└── learn-pass/             # 编写 LLVM Pass(含 OLLVM 混淆 Pass)
    ├── CMakeLists.txt
    └── src/
        └── pass_xxx.cpp

🎯 三大模块介绍

1️⃣ learn-demo:LLVM 小实验

这里用于快速测试 LLVM API,包含:

  • 构造 IR(IRBuilder)
  • 输出 .ll 文件
  • 使用模块、函数、基本块、指令
  • IR 变量、常量、函数调用等示例
  • 各种零碎功能验证(如 C++17 + LLVM API 的组合)

适合作为 LLVM 世界的实验田、沙盒区


2️⃣ learn-lang:从零写一门语言(Kaleidoscope 路线)

从最小语言开始,逐步实现:

  • 词法分析器(Lexer)
  • 语法分析器(Parser)
  • AST 结构
  • 生成 LLVM IR(IRBuilder)
  • 控制流(if / for)
  • 函数与外部调用
  • JIT 执行(ORC JIT)
  • 简单优化 Pass(常量折叠等)

最终目标: 实现一个可运行的小型语言解释器,支持表达式、函数、控制流。

这是学习 LLVM 前端与 IR 的最佳实践。


3️⃣ learn-pass:LLVM Pass + OLLVM 混淆

本模块实现 LLVM Pass(基于 new Pass Manager):

  • Hello Pass

  • 遍历函数/基本块/指令

  • 修改 IR

  • 常见 OLLVM 混淆 pass(集中在 learn-pass/src/Kotoamatsukami):

    • 控制流平坦化(CFF)
    • 虚假控制流(Bogus Control Flow)
    • 指令替换(Instruction Substitution)
    • 字符串/常量混淆
    • 其它辅助 Pass:ForObsSplitBasicBlockAddJunkCodeAntiDebugGVEncryptLoopen

未来可加入:

  • 反混淆(Deobfuscation)
  • 静态分析(数据流 / CFG / 循环分析)
  • SSA 相关操作

4️⃣ ndk_app:Android NDK 混淆集成

ndk_app/ 是一个完整的 Android NDK 示例工程,演示如何将 Kotoamatsukami 混淆 插件集成到 Android 原生库的构建流程中。

为什么不能直接用 -fpass-plugin

NDK 26 的 clang-17 是经 PGO+BOLT 优化的 Google 特供构建,与任何独立 LLVM 17 (Homebrew / apt)存在内部 ABI 差异,直接加载插件会在 PassBuilder 注册回调 时崩溃。

解决方案:两步 IR 流水线

native-lib.cpp
     │
     ▼ NDK clang (aarch64-linux-android24)
   .bc  ←── 保留完整的 Android ABI / sysroot 信息
     │
     ▼ host opt + Kotoamatsukami.{dylib,so}
  obf.bc  ←── IR 层 pass,不依赖目标 ABI
     │
     ▼ NDK clang (aarch64-linux-android24)
  libndk_example.so  ←── 最终 AArch64 Android 库

混淆强度(以 RC4 实现为基准)

Pass 组合 指令膨胀
bogus-control-flow 4.4x
bcf + substitution 20.4x
sbb + bcf + sub + flatten 40.0x
全量(默认) 54.6x

详见 docs/ndk-obfuscation.md


🛠️ 构建方式

本仓库当前推荐使用 LLVM 17。Linux 上可以直接用系统包;macOS 上建议使用 Homebrew 的 llvm@17,不要直接装最新 llvm,否则很容易拿到 LLVM 22+。

推荐方式:直接使用脚本

./build.sh
./build.sh test

build.sh 会自动探测 LLVM,并在 macOS 上优先选择 Homebrew llvm@17。 在 Linux / WSL 上会优先尝试系统包常见布局,例如 /usr/lib/llvm-17/bin/llvm-config,并保留 GNU 工具链优先级。 如果你需要显式覆盖,直接设置 LLVM_CONFIGLLVM_DIRCCCXX 即可。

手动构建

在项目根目录执行以下命令:

cd learn_llvm

cmake -S . -B build \
  -DLLVM_DIR=$(llvm-config --cmakedir) \
  -G Ninja
cmake --build build

CLion

推荐直接使用仓库根目录的 CMakePresets.json

在 CLion 里:

  1. 打开 Settings > Build, Execution, Deployment > CMake
  2. 新建或选择一个 Profile
  3. Load CMake Presets 打开,或直接选择 preset
  4. Apple Silicon 机器选择 macos-homebrew-llvm17-arm64
  5. Intel Mac 选择 macos-homebrew-llvm17-intel

如果你已经手动在 CLion 里填过错误的 LLVM_DIR,先执行一次 Tools > CMake > Reset Cache and Reload Project

macOS 推荐准备步骤

xcode-select --install
brew install cmake ninja llvm@17

如果你希望手动指定 LLVM 17:

LLVM17_PREFIX=$(brew --prefix llvm@17)
cmake -S . -B build \
  -DLLVM_DIR="$(${LLVM17_PREFIX}/bin/llvm-config --cmakedir)" \
  -DCMAKE_C_COMPILER="${LLVM17_PREFIX}/bin/clang" \
  -DCMAKE_CXX_COMPILER="${LLVM17_PREFIX}/bin/clang++" \
  -G Ninja
cmake --build build

构建完成后,所有可执行文件会统一放在:

learn_llvm/build/bin/

例如:

bin/demo_hello_ir
bin/lang_repl
bin/pass_hello
bin/test_pass_hello
bin/test_koto_loopen
bin/test_koto_passes

插件产物位于 build/lib/。Linux 下通常是 .so,macOS 下通常是 .dylib


📌 开发方式说明

增加新的 demo:

learn-demo/src/demo_new.cpp

CMake 会自动检测并生成新的可执行程序。


增加新的语言模块文件:

learn-lang/src/*.cpp

同样无需修改 CMake,会自动构建。


增加新的 Pass:

learn-pass/src/pass_xxx.cpp

以后加入插件式 Pass 时,我也可以帮你扩展支持 -fpass-plugin=xxx.so 的结构。


🧪 测试说明

项目中已经集成了一批 GoogleTest 用例如下:

可执行文件 说明
bin/test_pass_hello 覆盖 hello_pass_lib 中 Hello/Junk/SimpleObf/Flatten Pass 的基础行为
bin/test_koto_loopen 验证 Kotoamatsukami::Loopen Pass 是否会注入 Kotoamatsukami_quick_pow 助手函数
bin/test_koto_passes 批量测试 ForObsBogusControlFlowSplitBasicBlockAddJunkCode 等混淆 Pass

运行方式:

# 构建后直接运行某个测试
./build/bin/test_koto_passes

# 只运行 Loopen 测试
./build/bin/test_koto_loopen --gtest_filter='*Loopen*'

这些测试会在运行时为 Kotoamatsukami 生成临时配置文件(位于 /tmp/koto_test_config_*.json),互不干扰。实际使用插件时,可手动编辑仓库根目录的 Kotoamatsukami.config 来启用/禁用指定的混淆 Pass。


🚀 Roadmap / 未来计划

✔ learn-demo

  • 基本 IR 构造(完成)
  • 循环/分支 IR 例子
  • 读取/打印 .ll/.bc
  • 使用 PassManager 对单个模块优化

✔ learn-lang

  • 表达式解析
  • 变量与作用域
  • 函数定义
  • 控制流
  • ORC JIT
  • 简单优化
  • REPL 调试工具

✔ learn-pass

  • Hello Pass(完成)
  • 打印 CFG、Dominator Tree
  • 控制流平坦化 Pass
  • 字符串加密
  • 指令替换
  • 虚假控制流
  • 反混淆

✅ 持续集成

GitHub Actions(.github/workflows/ci.yml)会在 pushpull_request 触发,包含三个 job:

Job 说明
build-and-test 标准构建 + ctest
build-and-test-sanitized ASAN + UBSAN 构建 + ctest
ndk-obfuscation-smoke 安装 NDK 26、构建插件、跑两步 IR 混淆、验证 .so 导出 JNI 符号

本地若想与 CI 对齐:

# LLVM 构建与测试
./build.sh test

# NDK 混淆 smoke test(需要先 ./build.sh)
cmake -S ndk_app/app/src/main/cpp -B /tmp/ndk-ci-test \
  -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake \
  -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-24 -G Ninja
cmake --build /tmp/ndk-ci-test
llvm-nm -D /tmp/ndk-ci-test/libndk_example.so | grep “T Java_”

补充说明:当前仓库已经在 macOS + Homebrew llvm@17 环境下验证通过 ctest


About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors