3. 环境搭建:NDK环境配置、交叉编译工具链、addr2line与objdump工具安装
好,咱们正式开始动手。这一章我带你把吃饭的家伙事儿全装上。
做Native层的问题定位,说白了就是跟二进制文件打交道。你手里没工具,就像医生没有听诊器,光靠猜可不行。我个人习惯先把环境搭得干干净净,后面排查问题才能一气呵成。
3.1 NDK环境配置
NDK是什么?它就是Android上写C/C++代码的“编译器全家桶”。没有它,你的.so文件就编不出来。
第一步:下载NDK
我建议你直接用Android Studio自带的SDK Manager下载。路径很简单:
Tools -> SDK Manager -> SDK Tools -> 勾选 NDK (Side by side) -> Apply
嗯,这里要注意。新版Studio默认下载的是最新版NDK。但如果你维护的是老项目,可能会遇到兼容性问题。我曾经在一个项目里,因为NDK版本太高,导致编译出来的so在Android 5.0上直接崩溃。后来老老实实切回r21版本才搞定。
第二步:配置环境变量
下载完别急着用。你得让系统知道NDK在哪。我个人习惯在 ~/.bashrc 或 ~/.zshrc 里加上:
export ANDROID_NDK_HOME=$HOME/Library/Android/sdk/ndk/25.2.9519653
export PATH=$PATH:$ANDROID_NDK_HOME
配置完记得 source ~/.bashrc 让它生效。然后你敲一下 ndk-build --version,如果能打印出版本号,说明环境通了。
ln -s ndk-21 ndk-current,切换时只改链接指向就行。
3.2 交叉编译工具链
交叉编译,说白了就是在你的电脑上编译出能在手机上跑的程序。手机是ARM架构,电脑是x86架构,这中间需要一个“翻译官”。
NDK从r19版本开始,推荐使用独立的工具链。你不需要手动去下载什么gcc-arm-linux-androideabi,NDK里全给你准备好了。
获取工具链的方式:
$ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py \
--arch arm64 \
--api 29 \
--install-dir ./my-toolchain
这条命令会在当前目录生成一个 my-toolchain 文件夹。里面包含了编译器、链接器、头文件等等。
你想想看,有了这个工具链,你就可以在命令行里直接编译C代码了:
./my-toolchain/bin/aarch64-linux-android29-clang main.c -o main
生成的 main 文件,直接push到手机上就能跑。我在早期做嵌入式开发时,经常用这个方法来验证一些底层算法,比每次都跑Android Studio快多了。
3.3 addr2line与objdump工具安装
这两个工具,是破解Native Crash的“左膀右臂”。
addr2line:把地址变成代码行号
当你的app发生Native Crash时,logcat里会打印一串地址,比如:
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
backtrace:
#00 pc 0001a3b4 /data/app/lib/arm64/libnative-lib.so
这个 0001a3b4 是什么?是内存地址。你直接看它,啥也看不出来。但用addr2line一转换,就能定位到具体的源文件和行号。
安装方式:
addr2line就在NDK工具链里。你不需要额外安装。找到它:
$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-addr2line
注意,不同操作系统路径不同。Linux上是 linux-x86_64,Windows上是 windows-x86_64。
使用示例:
llvm-addr2line -f -e libnative-lib.so 0001a3b4
输出结果:
_Z7crashMePv
/home/user/project/jni/crash.cpp:15
你看,一下子就定位到了 crash.cpp 的第15行。函数名是 crashMe。嗯,这里要注意,你的so文件必须带符号表(编译时不要strip),否则addr2line也救不了你。
objdump:反汇编利器
有时候光看行号还不够。比如你怀疑是编译器优化导致的问题,或者想看看某段代码生成的汇编指令。这时候objdump就派上用场了。
常用命令:
# 查看so文件的段信息
llvm-objdump -h libnative-lib.so
# 反汇编指定函数
llvm-objdump -d libnative-lib.so --disassemble-symbols=_Z7crashMePv
# 查看动态符号表
llvm-objdump -T libnative-lib.so
我个人最常用的是 -d 参数。有一次排查一个死锁问题,代码逻辑怎么看都没毛病。反汇编一看,发现某个全局变量被编译器优化到了寄存器里,导致多线程访问时数据不一致。这种问题,光看源码是看不出来的。
- addr2line用于将地址转换为文件名和行号
- objdump用于反汇编和查看二进制文件结构
- 两个工具都集成在NDK的llvm工具链中
- 一定要保留符号表(编译时加 -g 选项)
3.4 验证环境是否可用
工具都装好了,咱们来做个快速验证。写一个会崩溃的C程序:
// test_crash.c
#include <stdio.h>
void crash_me() {
int *p = NULL;
*p = 42; // 故意解引用空指针
}
int main() {
crash_me();
return 0;
}
用交叉编译工具链编译:
aarch64-linux-android29-clang -g test_crash.c -o test_crash
然后push到手机上运行:
adb push test_crash /data/local/tmp/
adb shell /data/local/tmp/test_crash
你会看到类似这样的输出:
Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
backtrace:
#00 pc 00000734 /data/local/tmp/test_crash
记下这个地址 00000734,然后用addr2line:
llvm-addr2line -f -e test_crash 00000734
如果输出是 crash_me 和 test_crash.c:4,恭喜你,环境搭建成功了!
alias a2l='$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-addr2line'。这样每次敲命令能省不少时间。
好了,环境搭完了。下一章咱们就开始真正分析Crash日志。你把这些工具准备好,后面会省很多力气。