史上最优雅的NDK加载pass方案
字数 1975 2025-08-22 12:22:24
史上最优雅的NDK加载Pass方案教学文档
一、背景介绍
现有方案的问题
当前在Android NDK环境中实现代码保护技术(如OLLVM、Hikari)存在以下问题:
-
传统方案一:编译整个LLVM工程替换NDK中的toolchain
- 需要编译整个LLVM工程,耗时耗资源
- 对NDK有侵入性,可能影响原有功能
- 维护困难
-
传统方案二:编译LLVM工程并移植为独立plugin
- 仍需编译整个LLVM工程
- 对NDK仍有侵入性
- 维护成本比方案一低,但仍不够理想
本文提出的新方案优势
- 无需编译LLVM项目:节省大量时间和资源
- 无侵入性:不修改原有NDK环境
- 环境一致性:完全使用NDK自带环境,避免符号问题
- 依赖简单:仅需NDK环境,无需额外安装软件
二、环境准备
硬件/软件要求
- 操作系统:Ubuntu 18.04或更高版本(任意Linux发行版均可)
- NDK版本:r20(其他版本也可)
- CMake:较高版本
文件准备
-
从AOSP获取缺失的头文件:
llvm.tar.gz:https://android.googlesource.com/platform/prebuilts/clang/host/linux-x86/+archive/refs/tags/ndk-r20/clang-r346389c/include/llvm.tar.gzllvm-c.tar.gz:https://android.googlesource.com/platform/prebuilts/clang/host/linux-x86/+archive/refs/tags/ndk-r20/clang-r346389c/include/llvm-c.tar.gzc++.tar.gz:https://android.googlesource.com/platform/prebuilts/clang/host/linux-x86/+archive/refs/tags/ndk-r20/clang-r346389c/include/c++.tar.gz
-
解压并放置头文件:
- 建议将
llvm和llvm-c放在NDK的include目录下 c++头文件建议放在独立目录(如/home/leadroyal/Android/Sdk/r346389c/include/)
- 建议将
三、编译Pass的详细步骤
1. 修改LLVMExports.cmake
NDK中的LLVMExports.cmake会检查.a文件是否存在,需要修改:
# 将FATAL_ERROR改为WARNING
- message(FATAL_ERROR "The imported target \"${target}\" references the file
+ message(WARNING "The imported target \"${target}\" references the file
文件位置:$NDK/toolchains/llvm/prebuilt/linux-x86_64/lib64/cmake/llvm/LLVMExports.cmake
2. 编写CMakeLists.txt
cmake_minimum_required(VERSION 3.4)
# 设置编译器为NDK自带的clang
set(CMAKE_C_COMPILER $ENV{NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang)
set(CMAKE_CXX_COMPILER $ENV{NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang++)
# 设置LLVM环境变量
if(NOT DEFINED ENV{LLVM_HOME})
message(FATAL_ERROR "$LLVM_HOME is not defined")
endif()
if(NOT DEFINED ENV{LLVM_DIR})
set(ENV{LLVM_DIR} $ENV{LLVM_HOME}/lib64/cmake/llvm)
endif()
# 查找LLVM包
find_package(LLVM REQUIRED CONFIG)
add_definitions(${LLVM_DEFINITIONS})
# 包含头文件
include_directories(${LLVM_INCLUDE_DIRS})
include_directories(/home/leadroyal/Android/Sdk/r346389c/include/c++/v1)
# 设置C++标准
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
# 添加子目录(Pass项目)
add_subdirectory(skeleton)
3. 编译Pass
export LLVM_HOME=$NDK/toolchains/llvm/prebuilt/linux-x86_64
mkdir build && cd build
cmake ..
cmake --build .
四、加载Pass到Android项目
1. 修改build.gradle
android {
externalNativeBuild {
cmake {
cppFlags "-Xclang -load -Xclang /path/to/your/libSkeletonPass.so"
}
}
}
2. 常见问题解决
问题:符号未找到
错误信息示例:
undefined symbol: _ZNK4llvm12FunctionPass17createPrinterPassERNS_11raw_ostreamERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
原因:C++标准库版本不匹配
- NDK使用
libc++(命名空间std::__1) - 默认GCC编译使用
libstdc++(命名空间std::__cxx11)
解决方案:
- 确保使用NDK的clang作为编译器
- 添加
-stdlib=libc++编译选项 - 包含正确的
libc++头文件路径
五、跨平台注意事项
macOS平台问题
问题现象:
加载Pass时出现符号缺失错误,如:
Symbol not found: __ZN4llvm12FunctionPass17assignPassManagerERNS_7PMStackENS_15PassManagerTypeE
根本原因:
macOS的strip工具(特别是10.13/10.14版本)会错误地移除关键符号,导致加载失败。
临时解决方案:
暂无完美解决方案,等待AOSP修复此问题。
Windows平台
目前方案不适用于Windows平台。
六、未来变化
NDK未来版本变化
- NDK r21将移除
.cmake文件 - 届时需要从toolchain源码中获取这些文件
七、总结
方案优势总结
- 无需编译LLVM:直接使用NDK环境
- 环境纯净:不污染NDK原有文件
- 版本一致:确保Pass与NDK的LLVM版本完全匹配
- 维护简单:仅需维护Pass代码本身
完整工作流程
- 获取NDK和缺失的头文件
- 修改
LLVMExports.cmake降低错误级别 - 编写正确的
CMakeLists.txt - 使用NDK的clang编译Pass
- 在Android项目中加载编译好的Pass
注意事项
- 确保使用
libc++而非libstdc++ - macOS平台目前存在兼容性问题
- 未来NDK版本可能需要调整获取
.cmake文件的方式
通过本方案,开发者可以最优雅地在NDK环境中加载自定义LLVM Pass,实现代码保护等功能,同时保持开发环境的简洁和可维护性。