4. C/C++编译错误

C/C++编译错误是AOSP编译中最频繁的问题类型之一。头文件缺失、NDK版本冲突、ABI架构不匹配往往导致编译中断或运行时崩溃。本章从现象出发,通过结构化排查手段快速定位根因,并给出可落地的修复与预防策略。

C/C++编译错误排查故障树 C/C++编译错误 头文件缺失 NDK版本不兼容 ABI不匹配 现象: fatal error: xxx.h: No such file 原因: include路径缺失 / 依赖未声明 修复: 添加header_libs / include_dirs 现象: undefined reference to '__cxa_demangle' 原因: NDK r23 → r25 API符号变更 修复: 统一NDK版本 / 指定APP_STL 现象: cannot link against arm64-v8a .so for x86_64 原因: TARGET_ARCH与实际预编库ABI不一致 修复: 编译目标匹配预编库ABI / 提供多ABI支持

4.1 现象识别

三种错误的典型编译/链接错误信息如下表,可据此快速归类。

错误类别 典型错误消息(截取) 出现阶段
头文件缺失 fatal error: 'cutils/log.h' file not found 编译 (C/C++ 预处理)
NDK版本不兼容 undefined reference to '__cxa_guard_acquire' 或 cannot find -lstdc++_static 链接
ABI不匹配 skipping incompatible .../libfoo.so when searching for -lfoo 链接
💡 快速识别技巧
编译错误日志中若出现 No such file 或 file not found 基本指向头文件缺失;链接错误中出现 undefined reference 且与C++运行时符号相关,多半是NDK版本问题;skipping incompatible 则一定是ABI冲突。

4.2 数据采集

定位前需收集以下关键信息(记入排查笔记本):

  1. 完整编译日志:使用 make showcommands 或 m -j32 2>&1 | tee build.log 保留原始输出。
  2. 模块依赖声明:检查目标模块的 Android.bp 或 Android.mk 文件,重点查看 header_libs、shared_libs、static_libs。
  3. NDK/SDK版本:从 build/soong/cc/config/ 或 prebuilts/ndk/current/ 下确认使用的NDK版本。
  4. 目标ABI:lunch 选择的 TARGET_ARCH 与 TARGET_2ND_ARCH,以及预编译库的.so实际ABI(通过 file libfoo.so查看)。
  5. 编译环境变量:printenv | grep -E "NDK|ABI|ARCH"。
示例:采集到的关键日志片段
# 头文件缺失
In file included from frameworks/av/media/utils/MediaUtils.cpp:28:
frameworks/av/include/media/IMediaPlayer.h:30:10: fatal error: 'binder/IBinder.h' file not found

# NDK版本不兼容
external/libcxx/src/variant.cpp:125: error: undefined reference to '__cxa_demangle'
clang++: error: linker command failed with exit code 1

# ABI不匹配
prebuilts/arm64/libfoo.so: error: skipping incompatible when searching for -lfoo
target arm64-v8a, but prebuilt is for armeabi-v7a

4.3 原因定位(故障树+排查表)

使用下列决策表逐步缩小根因范围:

观察点 头文件缺失 NDK版本不兼容 ABI不匹配
错误信息包含文件路径? ✅ 是 ❌ 否(符号相关) ❌ 否(库相关)
涉及C++运行时符号? ❌ ✅ 是(如 __cxa_*、__gnu_*) ❌
预编译库后缀名与目标ABI不符? ❌ ❌ ✅ 是
模块未依赖必要的 header_libs? ✅ 常见 ❌ ❌
编译配置中 APP_STL 不一致? ❌ ✅ 常见 ❌
lunch 未正确选择 target? ❌ ❌ ✅ 常见

头文件缺失深度定位

  1. 确认缺失头文件属于哪个模块(查找 find . -name "xxx.h")。
  2. 检查目标模块的 Android.bp 是否包含该模块的 header_libs 或直接通过 include_dirs 引用。
  3. 验证依赖链:被依赖模块是否已 cc_library_headers 导出头文件。
⚠️ 常见陷阱
在AOSP中,许多头文件通过 header_libs 而非直接 include_dirs 提供。例如 libcutils 的头文件需要通过 header_libs: ["libcutils_headers"] 引入,而不是 -I直接路径。

NDK版本不兼容深度定位

  1. 获取当前编译使用的NDK版本:ls prebuilts/ndk/current 或查看 build/soong/cc/config/ndk_version.go。
  2. 检查模块的 Android.bp 中 sdk_version 或 ndk_version 是否显式指定,若未指定则使用默认值。
  3. 对比链接错误中的符号与NDK运行时库的导出符号表(nm -D libc++_shared.so | grep __cxa_demangle)。
  4. 确认 APP_STL 类型(c++_shared vs c++_static)与预置库一致。

ABI不匹配深度定位

  1. 执行 echo $TARGET_ARCH 和 echo $TARGET_2ND_ARCH。
  2. 对出错预编译库执行 file prebuilts/xxx/libfoo.so,确认ABI(如 ARM aarch64 vs ARM)。
  3. 检查模块声明:Android.bp 中的 arch: { arm64: { srcs: [...], }, } 是否只提供了单一ABI版本。
  4. 若链接 arm64 目标,却引用 arm 平台的预编译 .so,则报 skipping incompatible。

4.4 修复动作

针对三类根因给出明确修复步骤:

问题 修复方法 示例修改
头文件缺失 在模块的 Android.bp 中添加 header_libs 或直接 include_dirs
cc_binary {
    name: "my_binary",
    header_libs: [
        "libcutils_headers",
        "liblog_headers",
    ],
}
NDK版本不兼容 统一NDK版本:在 Android.bp 中指定 ndk_version 或全局修改 build/soong/cc/config/ndk_version.go;或调整 APP_STL 为一致类型
cc_library {
    name: "libfoo",
    sdk_version: "30",
    ndk_version: "25",
    stl: "c++_shared",
}
ABI不匹配 为目标ABI提供对应预编译库;或在 Android.bp 中使用 arch 分支分别指定不同ABI的源文件
arch: {
    arm64: { srcs: ["libfoo_arm64.a"] },
    arm:   { srcs: ["libfoo_arm.a"] },
}

4.5 复测

执行以下步骤验证修复:

  1. 清理目标模块:mm -B 或 m clean-。
  2. 重新编译:m -j32 并观察是否出现同样错误。
  3. 全面编译:若修复涉及公共头文件或NDK配置,建议执行 m -j32 全量编译确认无新增错误。
  4. 运行单元测试/集成测试:对于ABI修复,需在相应架构的真机或模拟器上测试 .so 加载与运行。
📌 复测小技巧
在 build.log 中搜索 error: 和 warning: 数量,与修复前对比;也可使用 m -j32 2>&1 | grep -E "^FAILED:|^ninja: build stopped" 快速判断是否通过。

4.6 预防措施

在团队开发中嵌入以下检查点,避免同类问题反复出现:

  • 头文件依赖可视化:使用 build/soong/scripts/dotdep.py 生成模块依赖图,确保所有 #include 的模块都显式声明在 header_libs 中。
  • NDK版本锁定:在 build/soong/cc/config/ndk_version.go 中固定NDK主版本,并在 BoardConfig.mk 或 ProductConfig.mk 中集中定义 NDK_APP_STL := c++_shared。
  • ABI前置校验:在提交预编译 .so 前,编写CI脚本检查其ABI是否与 lunch 目标一致。示例检查脚本:
# 检查预编译库ABI是否匹配指定目标
TARGET_ARCH=$(get_build_var TARGET_ARCH)
for lib in prebuilts/*.so; do
    ABI=$(file "$lib" | grep -oE 'ARM aarch64|ARM|Intel 80386|x86-64' | head -1)
    case "$TARGET_ARCH" in
        arm64) expected="ARM aarch64" ;;
        arm)   expected="ARM" ;;
        x86)   expected="Intel 80386" ;;
        x86_64)expected="x86-64" ;;
    esac
    if [ "$ABI" != "$expected" ]; then
        echo "ABI mismatch: $lib -> $ABI, expected $expected"
        exit 1
    fi
done
echo "All prebuilt libraries match target ABI."
✅ 预防检查清单(可集成到Code Review)
  • 每个 #include 是否有对应的 header_libs 声明?
  • 新增预编译库时是否在 Android.bp 中按 arch 区分?
  • NDK版本是否在项目中全局统一且与目标API级别兼容?
  • CI构建是否同时覆盖所有目标ABI(至少arm64和x86_64)?