史上最优雅的NDK加载pass方案
字数 1975 2025-08-22 12:22:24

史上最优雅的NDK加载Pass方案教学文档

一、背景介绍

现有方案的问题

当前在Android NDK环境中实现代码保护技术(如OLLVM、Hikari)存在以下问题:

  1. 传统方案一:编译整个LLVM工程替换NDK中的toolchain

    • 需要编译整个LLVM工程,耗时耗资源
    • 对NDK有侵入性,可能影响原有功能
    • 维护困难
  2. 传统方案二:编译LLVM工程并移植为独立plugin

    • 仍需编译整个LLVM工程
    • 对NDK仍有侵入性
    • 维护成本比方案一低,但仍不够理想

本文提出的新方案优势

  1. 无需编译LLVM项目:节省大量时间和资源
  2. 无侵入性:不修改原有NDK环境
  3. 环境一致性:完全使用NDK自带环境,避免符号问题
  4. 依赖简单:仅需NDK环境,无需额外安装软件

二、环境准备

硬件/软件要求

  • 操作系统:Ubuntu 18.04或更高版本(任意Linux发行版均可)
  • NDK版本:r20(其他版本也可)
  • CMake:较高版本

文件准备

  1. 从AOSP获取缺失的头文件:

    • llvm.tar.gz:https://android.googlesource.com/platform/prebuilts/clang/host/linux-x86/+archive/refs/tags/ndk-r20/clang-r346389c/include/llvm.tar.gz
    • llvm-c.tar.gz:https://android.googlesource.com/platform/prebuilts/clang/host/linux-x86/+archive/refs/tags/ndk-r20/clang-r346389c/include/llvm-c.tar.gz
    • c++.tar.gz:https://android.googlesource.com/platform/prebuilts/clang/host/linux-x86/+archive/refs/tags/ndk-r20/clang-r346389c/include/c++.tar.gz
  2. 解压并放置头文件:

    • 建议将llvmllvm-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)

解决方案

  1. 确保使用NDK的clang作为编译器
  2. 添加-stdlib=libc++编译选项
  3. 包含正确的libc++头文件路径

五、跨平台注意事项

macOS平台问题

问题现象
加载Pass时出现符号缺失错误,如:

Symbol not found: __ZN4llvm12FunctionPass17assignPassManagerERNS_7PMStackENS_15PassManagerTypeE

根本原因
macOS的strip工具(特别是10.13/10.14版本)会错误地移除关键符号,导致加载失败。

临时解决方案
暂无完美解决方案,等待AOSP修复此问题。

Windows平台

目前方案不适用于Windows平台。

六、未来变化

NDK未来版本变化

  • NDK r21将移除.cmake文件
  • 届时需要从toolchain源码中获取这些文件

七、总结

方案优势总结

  1. 无需编译LLVM:直接使用NDK环境
  2. 环境纯净:不污染NDK原有文件
  3. 版本一致:确保Pass与NDK的LLVM版本完全匹配
  4. 维护简单:仅需维护Pass代码本身

完整工作流程

  1. 获取NDK和缺失的头文件
  2. 修改LLVMExports.cmake降低错误级别
  3. 编写正确的CMakeLists.txt
  4. 使用NDK的clang编译Pass
  5. 在Android项目中加载编译好的Pass

注意事项

  1. 确保使用libc++而非libstdc++
  2. macOS平台目前存在兼容性问题
  3. 未来NDK版本可能需要调整获取.cmake文件的方式

通过本方案,开发者可以最优雅地在NDK环境中加载自定义LLVM Pass,实现代码保护等功能,同时保持开发环境的简洁和可维护性。

史上最优雅的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.gz llvm-c.tar.gz :https://android.googlesource.com/platform/prebuilts/clang/host/linux-x86/+archive/refs/tags/ndk-r20/clang-r346389c/include/llvm-c.tar.gz c++.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 文件是否存在,需要修改: 文件位置: $NDK/toolchains/llvm/prebuilt/linux-x86_64/lib64/cmake/llvm/LLVMExports.cmake 2. 编写CMakeLists.txt 3. 编译Pass 四、加载Pass到Android项目 1. 修改build.gradle 2. 常见问题解决 问题:符号未找到 错误信息示例: 原因 :C++标准库版本不匹配 NDK使用 libc++ (命名空间 std::__1 ) 默认GCC编译使用 libstdc++ (命名空间 std::__cxx11 ) 解决方案 : 确保使用NDK的clang作为编译器 添加 -stdlib=libc++ 编译选项 包含正确的 libc++ 头文件路径 五、跨平台注意事项 macOS平台问题 问题现象 : 加载Pass时出现符号缺失错误,如: 根本原因 : 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,实现代码保护等功能,同时保持开发环境的简洁和可维护性。