修复 Google Play 合规问题:Android 设备端纯净 ADB 客户端编译复盘

6 阅读7分钟

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 链路追踪

  1. 初始定位:LADB 项目将该文件作为原生库放置在 app/src/main/jniLibs/arm64-v8a/libadb.so 路径下。
  2. 社区线索:查阅该项目的历史 Issue #87,作者透露该二进制文件并非自主编译,而是引自 Termux 社区的第三方 ADB 移植项目。
  3. 源头锁定:最终确认其源自 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 基础组件(如 libbaselibloglibcutilslibcrypto 等),无法单独脱离大仓编译。

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 项目中集成:

  1. 将编译生成的二进制文件 adb_android 重命名为 libadb.so
  2. 放入工程的 app/src/main/jniLibs/arm64-v8a/ 目录下。
  3. 在代码中通过拉起本地进程的方式(如 Runtime.getRuntime().exec 指向应用私有目录下的 libadb.so)即可正常调用。

相关代码修改与预编译二进制产物已上传至 GitHub,有相同上架合规需求的同学可自行参考或下载。 github.com/che2n3jigw/…