这篇文章上次修改于 266 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
前言
stackplz在v3.0.3以及之前,为了实现堆栈回溯功能,用到了一个libstackplz.so
的预编译库,项目位于unwinddaemon
这个so的实现会依赖于libunwindstack
,这是参考定制bcc/ebpf在android平台上实现基于dwarf的用户态栈回溯后改进实现的,本质上是因为simpleperf是用libunwindstack
实现的
于是libstackplz
编译需要在AOSP的环境下完成,随着stackplz的更新,每次改动libstackplz
时就需要在AOSP下编译一次,如果代码写得有问题,又是等上好几分钟再来一次
如果是在已经编译过一次的情况下再次进行编译,使用mmm
不会从头编译一轮,但是AOSP的构建系统扫描代码的相当费时,即使很小的改动,在我的电脑上仍然要至少五六分钟的样子(记不太清了
最近终于在参考了以下两个项目之后,成功在CMAKE + NDK + 部分AOSP代码
的条件下实现了libstackplz
的编译,并且将所有需要的代码编译为单个so
实现
简单来说就是拉取必要的AOSP代码,编写CMakeLists.txt,配置NDK的环境,然后make即可
同步必要AOSP代码
这里选择了android14-release
分支,作为子模块添加
以下是init-submodules.sh的内容
BRANCH=android14-release
git submodule add -b "$BRANCH" https://android.googlesource.com/platform/art external/art
git submodule add -b "$BRANCH" https://android.googlesource.com/platform/system/libbase external/libbase
# git submodule add -b "$BRANCH" https://android.googlesource.com/platform/system/logging external/logging
git submodule add -b "$BRANCH" https://android.googlesource.com/platform/libnativehelper external/libnativehelper
git submodule add -b "$BRANCH" https://android.googlesource.com/platform/system/libprocinfo external/libprocinfo
git submodule add -b "$BRANCH" https://android.googlesource.com/platform/system/unwinding external/unwinding
git submodule add -b "$BRANCH" https://android.googlesource.com/platform/external/lzma external/lzma
git submodule add -b "$BRANCH" https://android.googlesource.com/platform/bionic external/bionic
这部分主要是参考的libunwindstack-standalone
这里logging
注释掉的原因是:最终编译是提取头文件 + 依赖ndk的log库,这个其实和libunwindstack中的做法差不多
也就是说如果想自己手动提取一下头文件,就同步,不然就不需要,用提取好的就行
另外提一下,如果想直接把log库静态编译到自己的so里面,依赖的其他模块还有点多,写起来可能很麻烦,我尝试了下还是放弃了
如果是自己写项目,执行了上面的命令,也就是init-submodules.sh
,接着就会拉取代码(记得上代理),一般情况下拉取了之后就会自己切好分支
以及这里需要补充的是,貌似没办法在git submodule
里面用--depth
,所以这个是同步的完整commit记录,所以下载的大小也不小,不过比起AOSP的整个代码还是小太多了
如果是拉取unwinddaemon,那么应该执行下面的命令(记得上代理),这样就会同步好代码
git submodule init
git submodule update --remote
这里额外提一下,有的地方会让你git submodule update
,不过可能会出现说某commit不存在之类的,一种可能是因为原项目子模块有修改,这样就和官方,这里也就是googlesource的commit不一样了,所以这个时候应该加上--remote
,把子模块的commit与官方保持一致
到这个时候,代码就算同步完成了
编写CMakeLists.txt
已经写好的CMakeLists.txt
我对cmake有一点了解但不多,所以还是选择参考libunwindstack,然后稍作改动以适应本项目需求
要编译之前已经写好的lib.cpp,最简单的写法就是下面这样
add_library(${PROJECT_NAME} SHARED lib.cpp)
当然接着就会出现各种错误,最直接的就是头文件找不到,于是挨个补齐头文件
target_include_directories(${PROJECT_NAME} PUBLIC ${EXTERNAL_SRC}/libbase/include)
target_include_directories(${PROJECT_NAME} PUBLIC ${EXTERNAL_SRC}/bionic/libc/kernel/uapi)
target_include_directories(${PROJECT_NAME} PUBLIC ${EXTERNAL_SRC}/unwinding/libunwindstack/include)
最开始编译出来发现文件很大,于是下面这样可以把产物做strip,生成的so就会很小了
SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-Wl,-s")
上面当然不是直接就能编译出来的,根据引用的头文件这些,就知道还需要其他依赖,主要是下面这些
target_link_libraries(${PROJECT_NAME} base procinfo lzma unwindstack log)
对于这些依赖,除了log库,其他的都是静态编译的,以base
为例,也就是设置好源代码,头文件目录,编译选项
add_library(base STATIC
"${EXTERNAL_SRC}/libbase/chrono_utils.cpp"
"${EXTERNAL_SRC}/libbase/file.cpp"
"${EXTERNAL_SRC}/libbase/hex.cpp"
"${EXTERNAL_SRC}/libbase/mapped_file.cpp"
"${EXTERNAL_SRC}/libbase/parsebool.cpp"
"${EXTERNAL_SRC}/libbase/parsenetaddress.cpp"
"${EXTERNAL_SRC}/libbase/logging.cpp"
"${EXTERNAL_SRC}/libbase/posix_strerror_r.cpp"
"${EXTERNAL_SRC}/libbase/process.cpp"
"${EXTERNAL_SRC}/libbase/stringprintf.cpp"
"${EXTERNAL_SRC}/libbase/strings.cpp"
"${EXTERNAL_SRC}/libbase/threads.cpp"
)
target_include_directories(base PUBLIC "${EXTERNAL_SRC}/libbase/include" "${SHIM_SRC}/liblog/include")
target_compile_options(base PRIVATE -Wno-c99-designator)
复制粘贴命令当然是不方便的,所以需要写一个编译脚本,也就是build.sh
,具体如下:
#/bin/bash
export ANDROID_NDK=/home/kali/Desktop/android-ndk-r25b
rm -r build
mkdir build && cd build
cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
-DANDROID=1 \
-DANDROID_ABI="arm64-v8a" \
-DANDROID_NDK=$ANDROID_NDK \
-DANDROID_PLATFORM=android-30 \
..
make
cd ..
其他
关于shim_files
在测试过程中,出现rustc_demangle.h
头文件说找不到,也就是unwinding/libunwindstack/Demangle.cpp
这个文件
rust什么的...暂时只好不管了,所以将这个文件提取出来,按照去掉了rust的部分,因为以前也没这个
为了不改动子模块的东西,于是把涉及到改动了的,都放到了shim_files
下面
- shim_files/libunwindstack/Demangle.cpp
另外还有下面这个,这里面的改动主要是struct sigaction
的初始化,原本的写法会有warning,强迫症怎么能忍
这个文件本身内容不算多,感觉也不会怎么变化,所以也改了拿出来放到shim_files
下面,这些文件也对应在CMakeLists.txt
- shim_files/libunwindstack/ThreadUnwinder.cpp
关于Android 10/ANDROID_PLATFORM=android-29
最近刚好遇到一个issue反馈,内核是5.10的,但是安卓版本是10,然而unwinddaemon本身一开始就是基于安卓13的aosp代码编译的,虽然Android.bp
中有设置min_sdk_version: "29",
,不过好像还是有些问题,于是堆栈功能用不了
既然cmake+ndk走通了,那就试试编译安卓10的版本,于是将ANDROID_PLATFORM
指定为android-29
进行编译
很快就会出现报错,提示如下:
error: '__android_log_set_default_tag' is unavailable: introduced in Android 30
这是因为相关的头文件中定义是下面这样的,也就是这个API是SDK 30即Android 11引入的,Android 10是没法用的
void __android_log_set_default_tag(const char* tag) __INTRODUCED_IN(30);
这个的解决办法,最合理的当然是找到具体的实现,自己适配一下
次之就是让它编译通过就行,比如返回void的就给个空函数,返回int的直接返回0或者1
cinit/libunwindstack有一个android_log_impl.cc可以作为参考,不过我尝试了下感觉还是怪麻烦的
秉承着尽可能不改动aosp里面的代码,我决定下面这样写,也就是让编译器以为Android 10上真有这些函数...
然后测试了下发现竟然不崩(也就是没有实际用到这些函数,所以这样也行吧...
#if __ANDROID_API__ >= 30
// 原来的代码
#else
void __android_log_set_default_tag(const char* tag);
#endif
总结
脱离AOSP的构建环境的核心其实就是做好Android.bp
到CMakeLists.txt
的转换,然后搞清楚依赖关系
当然这是对于简单引用一些AOSP里面的代码的情况,如果是太复杂,那也不好说...
有了这个经验之后,有一个用处就是,可以试试简单引用ArtMethod之类的头文件,然后可以比较方便编译出来,得到它们的偏移(大概
没有评论