简介:面向嵌入式 Linux 与 Android 开发者的 adb 工具源码包,支持在 ARM、ARM64、x86 等架构上交叉编译生成可用的 adb 工具。包内为 adb-master 源码工程,作者已在实际 ARM 机器上按 README 编译通过,产物可正常使用;使用者无需摸索依赖关系,按文档操作即可获得适合目标平台的调试工具。该源码包能让读者直接看到 Android Debug Bridge 的主体实现,在开发板联调、系统 ROM 定制、车载与物联网设备维护等场景中发挥作用,也能帮助理解 adb 的编译依赖、配置选项与交叉编译流程。资源以 tgz 格式打包,整体约 41.9MB,上游暂未提供文件总数与类型明细,这里不做无中生有;解压后可通过目录结构快速定位源码、构建说明与配置脚本,建议在 Linux 或类 Unix 环境下配合交叉编译器使用。已有 180 人学习/下载,对有 Android 调试、嵌入式移植需求的开发者具有实际参考价值。
1. 看清 arm_adb-master_default.tgz:这一类包在解决什么问题
拿到一个名叫arm_adb-master_default.tgz的压缩包,先别急着解压,命名里其实藏着关键信息:arm 指目标架构,master 指源码分支,default 指构建配置。这类包多出自内部构建流水线,是面向 ARM Linux 设备交叉编译出来的 adb 调试工具;官方 SDK 的 platform-tools 从不发布 linux-arm 预编译版,做开发板、盒子、工业设备调试的人只能自己编译,或从内部 CI 取这种产物再手工部署。这篇笔记把拿到包之后的所有动作讲透:怎么安全解包、判断 armv7 还是 arm64 并部署,怎么从源码自产一个,怎么排查连不上、没权限、反复 offline 的坑,最后让 adb 服务稳定常驻。适合刚接触自编译 adb 的嵌入式开发者,也适合正在排 ARM 设备调试链路故障的人。
2. 解包与部署:动手前先确认它是不是你能跑的 ARM 版本
2.1 解包先看内清单:tar tf 与 file 组合判断架构
拿到arm_adb-master_default.tgz后,我从不先解压,而是先执行一次tar tzf看包内到底有几个文件。很多自编译包只塞了一个裸的 adb 二进制,也有的会把lib/目录、README 甚至 udev 规则样本一起打进去。先看清单能避免后续满头找文件。
tar tzf arm_adb-master_default.tgz这一步能看到完整路径。如果二进制在子目录里,后续部署时别直接把整个目录拷到/usr/local/bin,否则会得到一堆散落文件。看懂清单后解压到工作目录,再用file确认它到底是哪个指令集的产物:
mkdir -p ~/arm-adb-pkg tar xzf arm_adb-master_default.tgz -C ~/arm-adb-pkg file ~/arm-adb-pkg/adbfile输出里最常出现两类结果:ELF 32-bit LSB executable, ARM, EABI5对应 armv7,ELF 64-bit LSB executable, ARM aarch64对应 arm64。看到 x86_64 就说明这个包根本不是给 ARM 用的,趁早放下。注意有些包会做压缩壳,file首行会显示statically linked或dynamically linked,后者意味着后续必须检查动态库依赖。
2.2 部署到目标路径:install、PATH 与权限一次到位
确认架构匹配后,部署时不要用cp结束。cp 只拷贝文件内容,不保证可执行位正确,也不解决目标系统缺少目录的问题。我一般用install一步完成拷贝加权限设置,再顺手检查 PATH。
sudo install -m 0755 ~/arm-adb-pkg/adb /usr/local/bin/adb adb versioninstall -m 0755意思是复制文件到/usr/local/bin,并保证根用户可写、所有用户可读可执行。adb version是最快的冒烟测试:能打出版本号和 1.0.x 协议版本,说明解释器和基本库没问题。输出类似Android Debug Bridge version 1.0.41才是正常状态。
如果目标板是非 root 的嵌入式系统,install装不上就退回用户目录部署,再把路径加进~/.profile或~/.bashrc:
install -m 0755 ~/arm-adb-pkg/adb ~/bin/adb echo 'export PATH=$HOME/bin:$PATH' >> ~/.profile source ~/.profile这里有个容易忽视的细节:adb在运行时会找$HOME/.android/adbkey作为设备授权私钥,还要能写$HOME/.android目录。如果部署路径没问题但授权一直失败,先查运行用户的 HOME 是否指向一个可写的目录。
2.3 验证可执行性:ldd 与 readelf 检查运行依赖
ARM Linux 上的“能执行”和“能跑起来”是两件事。文件格式对了,动态链接器找不到,终端只会甩一句No such file or directory,而且这句话连文件名都指不出来。原因是内核找不到 ELF 解释器。所以部署完adb version如果报这种错,立刻用ldd查依赖:
ldd /usr/local/bin/adbldd会列出一串libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6之类的映射。常见缺失项是libstdc++.so.6、libgcc_s.so.1、libusb-1.0.so.0。瘦身过的 rootfs 经常没有 C++ 运行时库,补装对应架构的libstdc++即可;libusb直接影响 USB 枚举功能,缺失时adb devices会直接空列表。真正难查的是 glibc 版本偏老:
readelf -V /usr/local/bin/adb | grep -i "GLIBC_"这段会列出GLIBC_2.27、GLIBC_2.28之类的版本需求。目标板执行ldd --version看自带的 glibc 版本,如果低于二进制需求,报错会出现在运行时而不是加载时。遇到这种问题只有两条路:换一个在更低版本 glibc 上编译的包,或者走第 6 章的静态编译方案,没有第三条捷径。
3. 自己产出一个 arm 版 adb:交叉编译最小流水线
3.1 官方为什么没有 linux-arm 预编译包:从构建产物命名说起
很多人第一次见到arm_adb-master_default.tgz会问:为什么官方不直接提供 ARM Linux 版?原因是 adb 在官方构建体系里分为两个完全不同的产物:跑在设备上的守护进程 adbd,和跑在开发机上的客户端 adb。官方发布渠道只维护 Windows、macOS、Linux x86_64 三类客户端,ARM 设备上的 adbd 则被打进系统镜像,极少单独分发。
于是做嵌入式的人只能自给自足。master_default这个组合是有含义的:master是源码分支,default是构建系统里的默认目标配置。理解了这层命名,就明白这个包不是某个厂商的定制版,而是“某条构建流水线用默认配置编出来的产物”,拿到手必须自己验证质量。
自产一个 ARM 版 adb 有三条路线:在 AOSP 源码树里用 Soong 构建系统单编、用 NDK 工具链小范围手工编、直接在目标板子上用原生工具链编。第三种最稳但速度慢,适合验证。前两种适合批量出包,下面分别拆开。
3.2 从源码树单编 adbd:lunch 到 m adbd 的产物路径
如果你的目标是把 adbd 集成到系统镜像里,或者想获得与官方同源的构建方式,AOSP 源码树单编是最可靠的路径。关键点在于 lunch 目标决定了 ARM 位宽:aosp_arm64-userdebug出 64 位产物,aosp_arm-userdebug出 32 位产物。userdebug 和 user 的区别直接影响第 5 章要讲的adb root权限。
source build/envsetup.sh lunch aosp_arm64-userdebug m adbd构建完成后,产物默认在out/target/product/generic_arm64/system/bin/adbd。如果你是 x86 主机上执行这条命令,得到的 host 端adb客户端仍然是 x86_64 的,因为 lunch 只切换目标系统架构,不切换构建机架构。很多人在这步翻车:lunch 之后以为m adb会产出 ARM 客户端,实际拿到out/host/linux-x86/bin/adb才发现编错了方向。
所以源码树单编最适合的场景是做系统镜像内嵌的 adbd。想拿一个能跑在 ARM Linux 发行版上的独立 adb 客户端,这条路走不通,要继续往下看 NDK 方式。
3.3 用 NDK 工具链小范围出包:命令与必调参数
NDK 方式适合只想要一个二进制、不想拉整个源码树的情况。常见做法是把 adb 相关源文件从源码树里取出来,连带 libbase、libcutils 等依赖头文件,用 NDK 的 clang 交叉编译。命令骨架如下,完整依赖列表以源码树内 Android.bp 编译单元为准:
export NDK=/opt/android-ndk-r21e export TARGET=aarch64-linux-android export API=30 export TC=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin $TC/${TARGET}${API}-clang++ -std=c++17 \ -I./include -I./libbase/include -I./libcutils/include \ adb/*.cpp adb/client/*.cpp \ -L./out -lbase -lcutils -lcrypto -lpthread \ -o adb-arm64这里几个参数必须说明白。API=30决定链接哪个版本的 bionic libc,API 太低会导致新符号缺失,太高会让产物在旧系统上无法运行;-lcrypto是 openssl 的加密库,adbd 在做安全连接和数据传输时依赖它,缺失会报一堆 undefined reference;-lpthread在多线程模型下必不可少。整套命令的坑在于源文件之间的依赖关系经常超出这几个目录,所以正式出包不要手工拼命令,而是在源码树里写好 Android.bp 再编。
NDK 编出来的产物链接的是 bionic,而不是大多数 ARM Linux 发行版的 glibc。放在纯 Linux rootfs 上执行时,个别系统调用行为有差异,适合目标环境本来就是安卓用户态的场景。如果目标是 Debian 系 ARM 板卡,更稳妥的是用aarch64-linux-gnu-g++配 glibc 静态链接,第 6 章详细展开。
3.4 两种出包路线的选择对比
| 路线 | 产物场景 | 依赖管理 | 体积 | 主要坑 |
|---|---|---|---|---|
| AOSP 源码树单编 | 系统镜像内嵌 adbd | 随镜像分发,自动解决 | 较大 | host 工具仍是 x86,方向易搞混 |
| NDK 手工编 | 独立 ARM Linux 二进制 | 需自备 libcrypto 等库 | 较小 | bionic 与 glibc 行为差异 |
| 板端原生编 | 验证与调试为主 | 完全随板子走 | 不定 | 速度慢,工具链需现装 |
选哪条看交付形态:要进系统镜像就 AOSP 单编,要独立发布、能复制到任意同架构板子上跑就 NDK 或交叉工具链手工编。arm_adb-master_default.tgz这类包名里的default暗示的就是 AOSP 默认构建配置,所以它多半是第一种路线出来的,拿到手先按第 2 章查依赖,别假设它自带一切。
4. 跑通 ARM 板上的 adb:从识别到连接授权的完整链路
4.1 adb devices 为空时按顺序查的三个地方
部署完二进制,接上 USB 设备后最常见的结果不是连接成功,而是adb devices打印一行空列表。很多人在这一步开始怀疑二进制编坏了,其实二进制能打印版本号就基本没问题。排查顺序要固定,不要乱跳:先查 USB 层,再查 adb server,最后查授权状态。
adb kill-server adb start-server adb devices -l lsusb dmesg | tail -20adb kill-server和adb start-server是强制重置 adb server 的标准动作,很多 disconnect 或 offline 状态靠这一步就能恢复。lsusb看 USB 总线是否枚举出设备,如果这里都看不到目标设备,问题在硬件连接或驱动层,adb 层不必继续查。dmesg最后 20 行能看到 USB 设备的插拔事件,出现device descriptor read/64, error -110之类的报错,多半是供电不足,换带屏蔽的线或加独立供电。
USB 层正常但 adb 列表仍为空时,重点看设备上有没有弹授权确认框。自编译 adbd 在某些固件上默认不弹框,而是静默拒绝,这时adb devices会显示<unknown>或offline,优先检查 adbd 进程是否真的在跑:
adb shell ps -A | grep adbd在 ARM 板子上,这条命令如果提示error: no devices found,说明 adbd 根本没起来,回到部署目录检查权限和动态库;能列出adbd进程则说明 USB 枚举和 adb 传输层都通了,问题只剩授权。
4.2 Udev 规则与 USB 权限的最小配置
嵌入式 Linux 上adb devices显示一串问号或者直接报no permissions,基本是 udev 规则没写好。adb 需要访问 USB 设备节点,默认权限是 root 专属,普通用户必须靠 udev 规则把设备节点权限放开。最小可用规则如下:
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", MODE="0666", GROUP="plugdev"' | sudo tee /etc/udev/rules.d/51-android.rules sudo udevadm control --reload-rules sudo udevadm trigger18d1是安卓调试模式的常见 vendor ID,但很多国产设备用的是自己的 ID。插上设备后执行lsusb看输出,把ID xxxx:xxxx里的厂商段替换进规则,否则规则永远不会命中。MODE="0666"表示所有用户可读写,开发环境图省事可以这么写;讲究一点就只开放plugdev组,并把自己的账号加进组里:
sudo usermod -aG plugdev $USER newgrp plugdev规则加载后必须重新插拔 USB 线,udevadm trigger只重新扫描设备,不能替代物理重插。我在几块板子上踩过这个坑:规则文件路径写错成/etc/udev/rules.d51-android.rules,少了斜杠导致根本不生效,排查时udevadm info /dev/bus/usb/001/003能看到匹配到了哪条规则,建议每次都看一眼确认。
4.3 adb tcpip 无线调试:端口占用与 RSA 授权的两个隐性条件
USB 口不够用或板子位置不便时,无线调试是刚需。ARM 设备上启用无线调试的标准操作是 adb tcpip 模式:先用 USB 连接,执行adb tcpip 5555让 adbd 监听 5555 端口,再拔掉线通过局域网连接。
adb tcpip 5555 adb connect 192.168.1.100:5555 adb devices第一次adb connect大概率会得到unable to connect,原因通常不是网络不通,而是设备端没有开启对应的 TCP 监听。部分运行自定义 Linux 的板子,adbd 启动参数里没有-a(监听所有网络接口),功能正常却只监听 USB 抽象设备,这时候adb tcpip 5555不会生效,需要在启动脚本里给 adbd 加-a -L tcp:5555参数。端口也被 5555 占用时,换端口要同步知道设备端用的是-L tcp:你的端口,两端不一致必然连不上。
无线连接成功后,授权环节一样不少。上位机的~/.android/adbkey.pub会被推送到设备的/data/misc/adb/adb_keys,如果设备端无法写入(例如只读 rootfs),就会出现反复要求授权但永远unauthorized。解决方式是把上位机公钥直接追加进设备端 adb_keys 文件,再重启 adbd。
5. arm 版 adb 常见坑:五条现象、原因、解决排障记录
5.1 能执行但报错缺 libstdc++.so.6:动态链接的依赖没带全
现象:adb version输出一串error while loading shared libraries: libstdc++.so.6: cannot open shared object file,文件本身存在且权限正确。
原因:交叉编译时选择了动态链接,产物依赖目标板的 C++ 运行时库,而精简 rootfs 根本没装 libstdc++。NDK 工具链编出的二进制依赖尤其隐蔽,报错信息会指向一个并不存在的路径。
解决:优先补装目标板架构的 libstdc++,Debian 系用apt-get install libstdc++6,自研 rootfs 则从工具链目录把libstdc++.so.6拷到/usr/lib。想彻底摆脱这类问题,改用静态编译(见第 6 章)。
5.2 Exec format error:armv7 与 arm64 子架构拿错
现象:执行./adb直接返回cannot execute binary file: Exec format error,file却正常打印 ELF 信息。
原因:包名只写了 arm,没区分 armv7 还是 arm64。把 64 位产物拷到仅支持 32 位指令集的板子上,内核加载器直接拒绝。arm64 内核通常能运行 32 位兼容程序,反向则不行。
解决:在目标板执行uname -m,aarch64 就找 arm64 包,armv7l 找 32 位包。自编译时,lunch 目标aosp_arm64-userdebug和aosp_arm-userdebug必须先确认清楚,别拿 CI 产物省这一步。
5.3 device offline:server 与 adbd 协议版本不一致
现象:adb devices能看到设备序列号,但状态栏一直是offline,拔插和 kill-server 都无效。
原因:上位机 adb server 的协议版本和设备端 adbd 的协议版本差距过大。自编译 adbd 用的源码分支是 master,而上位机 platform-tools 是长期稳定版,两边的加密握手和消息格式不兼容,server 无法识别设备状态。
解决:把上位机 adb 也换成同分支产物,或者反过来让设备端跑稳定分支。判断差距最简单的方式是两边分别执行adb version,版本号 1.0.4x 后面的 x 代表特性版本,差距超过 1 就要警惕。临时恢复通信的办法是adb kill-server后让 server 重新握手,但根源不解决,offline 还会复现。
5.4 no permissions:udev 规则或组权限没有生效
现象:adb devices输出no permissions (user in plugdev group; are your udev rules wrong?),设备节点看得到但打不开。
原因:udev 规则没命中目标设备,或规则加载后没有重新插拔。还有一种隐蔽情况:设备先以充电模式枚举,再切换到 adb 模式,vendor ID 变了但规则只覆盖了第一种。
解决:执行lsusb拿到实际 vendor ID,确认 udev 规则里的 ID 完全匹配;重新插拔后执行udevadm test /sys/class/usb_device/...看规则匹配日志。把规则里加了ATTR{idProduct}的更麻烦,同厂商不同型号会误伤,建议只锁 vendor 段。
5.5 adb root 被拒绝:user 版固件不开放 root
现象:adb root返回adbd cannot run as root in production builds,即使当前用户是 root、adb shell也能正常执行。
原因:adbd 在编译时通过 ro.secure 属性决定是否允许 root 切换。user 版固件强制 secure=1,adbd 收到 root 请求会主动放弃特权,这是安全设计,不是故障。
解决:开发用固件选择 userdebug 版本,编镜像时lunch aosp_arm64-userdebug而不是aosp_arm64-user。已经刷了 user 版固件的板子,不想重刷可以用adb shell setprop ro.secure 0临时摸一下,但重启即失效,且部分固件禁止修改该属性。工程上最稳的还是在编译阶段就把 debug 配置固化进镜像。
6. 让这个包真正焊进开发流:静态编译、服务守护与稳定性验证
6.1 静态编一版:免掉目标板上所有动态依赖
如果arm_adb-master_default.tgz拿到手后,第 2 章那一串依赖检查让你头疼,最釜底抽薪的解法是出一个静态链接版本。用 glibc 交叉工具链时,在编译命令里加-static,产物会把 libc、libstdc++、libgcc 全部打进二进制,即使目标板 rootfs 里根本没有 C 库也能运行。
aarch64-linux-gnu-g++ -static -std=c++17 \ -I./include adb/*.cpp \ -L./out -lbase -lcutils -lcrypto -lpthread \ -o adb-arm64-static代价是体积明显变大,常见结果是十几兆到几十兆。体积敏感的场景改用 musl 工具链做静态编译,能做到体积和兼容性的平衡;NDK 的 bionic 静态产物也常见,但要在非安卓 rootfs 上先做一轮完整测试,个别系统调用行为差异只能靠实测暴露。出包后用file确认输出里带statically linked,再拷到板子上跑一次adb version。
6.2 以服务方式常驻:systemd 写法解决掉线重启
单独执行 adb server 会随终端退出而结束,板子重启后调试通道也没了。做嵌入式交付时,我习惯把 adb server 交给 systemd 托管,统一处理开机自启和崩溃重启:
[Unit] Description=Android Debug Bridge Server After=network.target [Service] Type=simple ExecStart=/usr/local/bin/adb -a -L tcp:5037 nodaemon server Restart=always RestartSec=5 [Install] WantedBy=multi-user.target-a让 adbd 监听所有网络接口,适合板子需要被局域网设备访问的场景;-L tcp:5037把服务端口定死,避免运行时被其他进程抢占;nodaemon server表示以前台方式运行,systemd 能直接管理它的生命周期。不习惯 systemd 的旧板子,可以退回到/etc/inittab里加一行 respawn 条目,效果类似。服务写好后systemctl enable adb-server设为开机自启,并用systemctl status adb-server确认没有进入 restart 死循环。
6.3 出包前的稳定性验证脚本
交付自编译 adb 之前,别只测一次adb version就发出去。调试工具的稳定性要靠压力验证,尤其无线模式和反复插拔场景。下面这段脚本可以放到 CI 的最后一个阶段,循环重连并记录失败次数:
for i in $(seq 1 100); do timeout 20 adb wait-for-device || echo "round $i: device timeout" adb connect 192.168.1.100:5555 &> /dev/null adb -s 192.168.1.100:5555 shell uptime &> /dev/null || echo "round $i: shell failed" sleep 3 donetimeout 20防止 wait-for-device 永久阻塞;shell uptime验证的不只是设备存在,而是 adbd 能真正处理指令,这个才是调试链路的完整通路。100 轮里出现 1 到 2 次失败,先看是不是网络抖动;失败率超过 5%,基本可以判定是二进制或服务配置的问题,进第 5 章逐条对照。这类验证跑过一次,后面调参数就有底气,不用靠运气赌连接。
我自己在这个方向上交过不少学费,最贵的一条教训是拿了 CI 产物不验证依赖就直接刷进镜像,结果现场所有板子无法调试,最后只能逐个补库。后来养成的习惯是任何自编译 adb 包到手,先file后ldd,再跑一轮百次重连,通过了才进交付流程。这套流程看着啰嗦,但能拦下大部分隐蔽问题,希望帮到你。
本文还有配套的精品资源,点击获取