1. 背景
Google Play 商店对上传的 App 强制引入了 支持 16 KB 页面大小 (Page Size) 的合规要求。在开启了 16KB 页面大小的现代化 Android 设备(或 Android 15/16 模拟器)上,传统按 4KB 对齐编译的原生二进制或 .so 库会直接因错位导致进程崩溃。
在许多需要运行“端侧独立 ADB”的应用(如无线调试工具 LADB 等)中,核心功能依赖一个运行在 Android 设备内部(arm64-v8a 架构)的 ADB 客户端。面对该合规要求,现有的第三方预编译产物基本都无法通过审核。
本文记录了从现有二进制文件的逆向分析,到基于 AOSP Android 16 (Baklava) 主线源码 进行 16KB 页面对齐、静态链接交叉编译的完整修复过程。
2. 现有原生库 libadb.so 的来源溯源
在盲目重新编译前,需要先查明当前项目(以 LADB 为例)中使用的 libadb.so 究竟来自何方。
2.1 链路追踪
- 初始定位:LADB 项目将该文件作为原生库放置在
app/src/main/jniLibs/arm64-v8a/libadb.so路径下。 - 社区线索:查阅该项目的历史 Issue #87,作者透露该二进制文件并非自主编译,而是引自 Termux 社区的第三方 ADB 移植项目。
- 源头锁定:最终确认其源自 rendiix/termux-adb-fastboot 仓库下的
binary/arm64-v8a/bin/adb。
2.2 哈希验证
我们在本地对两个仓库的对应文件进行了 sha256sum 校验:
che2n3jigw@1302-family:~/LADB/app/src/main/jniLibs/arm64-v8a$ sha256sum \
~/termux-adb-fastboot/binary/arm64-v8a/bin/adb \
~/LADB/app/src/main/jniLibs/arm64-v8a/libadb.so
47ea035fa5ed57f6149a2b025bbbd4b21584c355c05d0400416804715e4c12de /home/che2n3jigw/termux-adb-fastboot/binary/arm64-v8a/bin/adb
47ea035fa5ed57f6149a2b025bbbd4b21584c355c05d0400416804715e4c12de /home/che2n3jigw/LADB/app/src/main/jniLibs/arm64-v8a/libadb.so
结论:哈希值完全一致。这证实了现有方案是将 Termux 容器里运行的 adb 可执行文件更改为了 .so 后缀,直接塞进 Android 项目使用。
3. 现有二进制文件的 ELF 结构缺陷分析
为了确认能否通过工具(如 elfedit)直接修补该文件,对其进行逆向属性分析。
3.1 基础元数据分析
che2n3jigw@1302-family:~/tmp$ file adb
adb: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /system/bin/linker64, BuildID[sha1]=8924de13d112db8a19f4e0e2a05661d402e69279, stripped
文件是一个标准的 64 位 ARM(aarch64)PIE 可执行文件,请求了 Android 的 /system/bin/linker64 动态链接器。
3.2 内存布局与 4KB 对齐限制
使用 readelf -l 查看其程序头(Program Headers)的内存段映射布局:
che2n3jigw@1302-family:~/tmp$ readelf -l adb
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
...
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000195f04 0x0000000000195f04 R 0x1000
LOAD 0x0000000000195f40 0x0000000000196f40 0x0000000000196f40
0x00000000002f7490 0x00000000002f7490 R E 0x1000
- 缺陷:类型为
LOAD的可加载段,其Align(对齐系数)全部固定为0x1000(即 4KB)。这导致其无法在 16KB 分页的 Android 系统上完成虚拟内存映射。
3.3 动态链接与 RUNPATH 污染
通过 readelf -d 查看其动态节区(Dynamic Section):
che2n3jigw@1302-family:~/tmp$ readelf -d adb
Dynamic section at offset 0x4a9938 contains 30 entries:
Tag Type Name/Value
0x000000000000001d (RUNPATH) Library runpath: [/data/data/com.termux/files/usr/lib]
0x0000000000000001 (NEEDED) Shared library: [libm.so]
...
- 缺陷:由于是在 Termux 环境下编译,链接器被注入了
RUNPATH项,指向 Termux 的私有沙盒路径[/data/data/com.termux/files/usr/lib]。如果普通 App 独立拉起此二进制文件,会存在不稳定的隐患。
结论:由于存在 4KB 对齐硬伤与 RUNPATH 污染,无法直接修补二进制,必须回归 AOSP 源码层重新交叉编译。
4. 基于 AOSP 主线的 Soong 构建配置魔改
ADB 源码深度耦合 AOSP 基础组件(如 libbase、liblog、libcutils、libcrypto 等),无法单独脱离大仓编译。
4.1 AOSP 源码拉取
sudo apt-get install git-core gnupg flex bison build-essential zip curl zlib1g-dev libc6-dev-i386 x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig
sudo apt-get update && sudo apt-get install repo
cd ~ && mkdir aosp && cd aosp
repo init --partial-clone -b main -u https://android.googlesource.com/platform/manifest
repo sync -c -j8
4.2 魔改 Soong 构建配置(Android.bp)
AOSP 默认只为 Host 端编译 ADB 客户端,为设备端只编译服务端(adbd)。为了编译出运行在 Android 手机上的静态化 ADB 客户端,在 packages/modules/adb/Android.bp 尾部纯新增以下配置:
cc_binary {
name: "adb_android",
defaults: ["adb_binary_android_defaults"],
host_supported: false,
device_supported: true,
compile_multilib: "64", // 锁定 64位架构 (arm64-v8a)
}
cc_defaults {
name: "adb_binary_android_defaults",
stl: "libc++_static", // 强制静态链接 C++ 运行时
defaults: ["adb_android_defaults"],
srcs: [
"client/adb_client.cpp",
"client/commandline.cpp",
"client/file_sync_client.cpp",
"client/main.cpp",
"client/line_printer.cpp",
"shell_service_protocol.cpp",
],
static_libs: [
"libadb_crypto",
"libadb_android",
"libadb_android_protos",
"libadb_protos",
"libadb_sysdeps",
"libadb_tls_connection",
"libapp_processes_protos_android_full",
"libbase",
"libbrotli",
"libcrypto_static",
"libcrypto_utils",
"libcutils",
"liblog",
"liblz4",
"libprotobuf-cpp-full-ndk",
"libssl",
"libutils",
"libz",
"libziparchive",
"libzstd",
],
}
cc_defaults {
name: "adb_android_defaults",
cflags: [
"-Wall", "-Wextra", "-Werror",
"-Wexit-time-destructors",
"-Wno-non-virtual-dtor",
"-Wno-unused-parameter",
"-Wno-missing-field-initializers",
"-Wthread-safety", "-Wvla",
"-DADB_HOST=1", // 即使在设备端运行,也保持 Host 客户端的行为逻辑
"-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION=1",
],
cpp_std: "experimental",
use_version_lib: true,
}
libadb_android_srcs = [
"adb.cpp", "adb_io.cpp", "adb_listeners.cpp", "adb_trace.cpp",
"adb_unique_fd.cpp", "adb_utils.cpp", "apacket_reader.cpp",
"fdevent/fdevent.cpp", "services.cpp", "sockets.cpp", "socket_spec.cpp",
"sysdeps/env.cpp", "sysdeps/errno.cpp", "transport.cpp", "transport_fd.cpp", "types.cpp",
]
cc_library_static {
name: "libadb_android",
device_supported: true,
compile_multilib: "64",
defaults: ["adb_android_defaults"],
srcs: libadb_android_srcs + [
"client/auth.cpp",
"sysdeps_unix.cpp",
"sysdeps/posix/network.cpp",
"fdevent/fdevent_poll.cpp",
"fdevent/fdevent_epoll.cpp",
"client/transport_emulator.cpp",
],
generated_headers: ["platform_tools_version"],
static_libs: [
"libadb_crypto", "libadb_tls_connection", "libadb_protos",
"libadb_android_protos", "libbase", "libcrypto_utils",
"libcrypto_static", "libcutils", "liblog", "libprotobuf-cpp-lite", "libutils"
],
}
同时,为了支持上述配置中的 Protobuf 组件静态链接,在 packages/modules/adb/proto/Android.bp 中修改或补充:
cc_library_static {
name: "libadb_android_protos",
defaults: ["adb_android_protos_defaults"],
static_libs: ["libprotobuf-cpp-full-ndk"],
proto: {
export_proto_headers: true, // 确保向外部依赖导出 .pb.h 头文件
type: "full",
},
}
cc_defaults {
name: "adb_android_protos_defaults",
cflags: ["-Wall", "-Wextra", "-Wthread-safety", "-Werror"],
compile_multilib: "64",
srcs: [":adb_android_proto"],
visibility: ["//packages/modules/adb:__subpackages__"],
stl: "libc++_static",
apex_available: ["com.android.adbd"],
}
filegroup {
name: "adb_android_proto",
srcs: ["adb_host.proto"],
visibility: ["//packages/modules/adb:__subpackages__", "//tools/asuite"],
}
cc_library_static {
name: "libapp_processes_protos_android_full",
defaults: ["libapp_processes_protos_defaults"],
proto: {
export_proto_headers: true,
type: "full",
},
}
4.3 源码功能裁剪
由于强行将 Client 逻辑在 Android 设备端进行编译,部分与底层系统冲突的代码(例如部分特定平台的主机断言)需要手动删减。具体修补明细已开源供参考:GitHub Commit - 源码功能删减明细。
5. 编译执行与技术指标验证
在 AOSP 根目录下初始化构建环境并执行编译:
source build/envsetup.sh
lunch aosp_arm64-trunk_staging-userdebug
m adb_android
编译成功后,产物路径为:out/target/product/generic_arm64/obj/EXECUTABLES/adb_android_intermediates/adb_android。
5.1 端侧运行验证
将产物推送至目标设备(pearl)进行基础测试:
pearl:/data/local/tmp $ ./adb_android version
Android Debug Bridge version 1.0.41
Version 36.0.1-che2n3jigw-84023d7
Installed as /data/local/tmp/adb_android
Running on Linux 5.10.236-android12-9-00003-gfb24cf99ad97-ab14313284 (aarch64)
ADB 核心引擎版本识别为 v36.0.1,带上了预期的定制 Git 提交哈希 84023d7,运行正常。
5.2 16KB 页面对齐验证 (readelf -l)
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
...
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x000000000011cbc8 0x000000000011cbc8 R 0x4000
LOAD 0x0000000000120000 0x0000000000120000 0x0000000000120000
0x00000000002c38d0 0x00000000002c38d0 R E 0x4000
所有 LOAD 段的 Align 参数全部成功变更为 0x4000(即 16KB 对齐),符合 Google Play 的合规标准。
5.3 RUNPATH 污染验证 (readelf -d)
Dynamic section at offset 0x3f8c08 contains 27 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libc.so]
0x0000000000000001 (NEEDED) Shared library: [libm.so]
0x0000000000000001 (NEEDED) Shared library: [libdl.so]
输出中旧产物的 RUNPATH 已彻底消除。NEEDED 仅保留了 Android 最基础的三个标准系统库,不再与任何第三方容器环境强绑定。
6. 项目集成指南
该产物同时解决了 16KB 页面对齐合规性与环境硬编码污染问题。如果需要在 Android App 项目中集成:
- 将编译生成的二进制文件
adb_android重命名为libadb.so。 - 放入工程的
app/src/main/jniLibs/arm64-v8a/目录下。 - 在代码中通过拉起本地进程的方式(如
Runtime.getRuntime().exec指向应用私有目录下的libadb.so)即可正常调用。
相关代码修改与预编译二进制产物已上传至 GitHub,有相同上架合规需求的同学可自行参考或下载。 github.com/che2n3jigw/…