12、USB调试与日志分析:USB抓包工具与常见错误码解析

说到USB调试,我个人的经验是:不会抓包分析,就等于闭着眼睛修车。你想想看,USB通信就像两个人对话,一个说“我要数据”,另一个说“给你”。如果其中一方没回应,或者回错了,你不监听一下对话内容,光靠猜,那得猜到什么时候?

这一章,我就带你把这层窗户纸捅破。咱们从抓包工具讲起,再到Android端的日志,最后把那些让人头疼的错误码(-1、-32、-71)彻底搞明白。

核心观点:USB调试的本质,就是“监听总线 + 解析描述符 + 定位错误码”。这三件事做好了,90%的USB问题都能找到根因。

USB调试与日志分析知识体系 USB调试核心 抓包工具 Wireshark usbmon Bus Hound Android端日志 dmesg logcat 错误码 & 描述符 -1 / -32 / -71 描述符分析 监听总线 → 解析描述符 → 定位错误码

12.1 抓包工具:三把利刃

抓包工具,说白了就是USB世界的“窃听器”。我常用的有三款,各有各的脾气。

12.1.1 Wireshark(跨平台,功能最强)

Wireshark大家应该不陌生,抓网络包的老牌工具。但它也能抓USB包,很多人不知道。在Linux上,你需要先加载usbmon模块,然后Wireshark就能看到USB接口了。

# 加载usbmon模块
sudo modprobe usbmon

# 查看USB设备编号
lsusb

# 启动Wireshark,选择 usbmonX 接口(X是总线编号)
sudo wireshark

我个人习惯用Wireshark的过滤表达式,比如只过滤某个设备的URB(USB Request Block):

usb.device_address == 3

小技巧:Wireshark里可以设置“USB URB”的列,把传输类型、端点号、数据长度都显示出来。我在项目中排查过一次UVC摄像头卡顿问题,就是靠Wireshark发现某个等时传输端点一直在丢包。

12.1.2 usbmon(Linux内核原生,轻量级)

usbmon是Linux内核自带的USB监控机制。它不依赖任何图形界面,特别适合嵌入式环境。你想想看,在Android板子上没有显示器,怎么抓包?usbmon就是答案。

# 查看usbmon是否加载
cat /sys/kernel/debug/usb/usbmon/0s

# 如果没有,手动加载
modprobe usbmon

# 抓取总线0上的所有USB通信
cat /sys/kernel/debug/usb/usbmon/0u > usb_log.txt

嗯,这里要注意:usbmon的输出是文本格式的,可读性不如Wireshark。但它的优势在于——极轻量,不干扰总线时序。我曾经在一个USB音频延迟问题上,用usbmon抓到了微秒级的时序异常,Wireshark反而因为自身开销把问题掩盖了。

12.1.3 Bus Hound(Windows平台,商业软件)

如果你在Windows上做USB开发,Bus Hound几乎是必备的。它界面虽然老派,但功能一点不含糊。可以按设备、按端点过滤,还能直接看到原始数据。

我记得有一次,一个USB转串口设备在Windows上工作不正常,在Linux上却好好的。用Bus Hound一抓,发现Windows主机发送的SETUP包长度字段填错了。这就是典型的“主机端驱动bug”。

避坑指南:Bus Hound在抓包时会占用USB带宽,如果设备对实时性要求高(比如UVC摄像头),抓包本身可能导致问题复现不出来。我曾经被这个坑过——抓包时一切正常,一关抓包就出问题。后来改用usbmon才抓到真凶。

12.2 Android端USB日志:dmesg与logcat

在Android设备上调试USB,你离不开两个命令:dmesglogcat。它们一个管内核,一个管用户空间。

12.2.1 dmesg:内核的USB“日记本”

dmesg输出的是内核日志。USB驱动的枚举、断开、错误,都会在这里留下记录。我一般这样用:

# 实时查看USB相关内核日志
adb shell dmesg -w | grep -i usb

# 或者先清空,再插拔设备,看增量日志
adb shell dmesg -c
# 插拔USB设备
adb shell dmesg

你想想看,当设备插上后没有任何反应,第一件事就是看dmesg。如果dmesg里连“new USB device”都没有,那说明硬件层面就没通——可能是线坏了,或者VBUS没供上电。

实战经验:有一次客户反馈说他们的Android平板无法识别U盘。我远程adb连上去,dmesg一看,发现枚举成功了,但读取描述符时返回了-71(后面会讲)。最终定位是U盘的USB接口氧化,接触不良。换了个U盘就好了。

12.2.2 logcat:用户空间的USB“通话记录”

logcat记录的是Android框架层的日志。比如USB设备插上后,系统服务(如UsbManager)做了什么操作,有没有弹授权对话框,都会在这里体现。

# 过滤USB相关日志
adb logcat -s UsbManager UsbDeviceManager UsbHostManager

# 或者用grep
adb logcat | grep -i usb

我个人习惯是同时开两个终端:一个跑dmesg -w,一个跑logcat。这样内核层和应用层的问题一目了然。比如,dmesg显示枚举成功,但logcat里没有UsbManager的日志,那问题大概率出在HAL层或者权限管理上。

12.3 常见错误码解析:-1、-32、-71

这三个错误码,做USB开发的人几乎天天见。我把它们称为“USB三兄弟”。

错误码 内核定义 含义 常见场景
-1 EPERM 操作不被允许 权限不足、设备被占用
-32 EPIPE 管道断裂(端点停止) 设备发送了STALL握手
-71 EPROTO 协议错误 数据包CRC错误、描述符解析失败

-1 (EPERM):权限不足

这个错误最常见。你在Android应用里调用requestPermission()后,如果用户点了拒绝,后续的claimInterface()就会返回-1。或者,设备已经被另一个进程占用了。

我曾经遇到一个坑:两个App同时想访问同一个USB设备,后启动的那个就会收到-1。解决方案是让App实现UsbManager的广播监听,等前一个App释放了再申请。

-32 (EPIPE):端点停止了

EPIPE意味着设备端主动发送了STALL握手包。说白了就是设备说:“我不干了,你给的数据我处理不了。” 这通常是因为主机发送了设备不支持的请求,或者设备内部状态机出错了。

我记得调试一个自定义HID设备时,主机发送了一个0x22的类请求,设备直接回了STALL,dmesg里就报-32。后来查HID规范才发现,0x22是“SET_IDLE”,但我的设备固件里根本没实现这个请求。加上处理逻辑后,问题解决。

-71 (EPROTO):协议错误

这个错误最让人头疼。它表示USB协议层面出了岔子——CRC校验失败、数据包长度不对、描述符格式错误等等。硬件问题(线缆质量、电气噪声)和软件问题(描述符填错)都可能导致-71。

排查思路:遇到-71,先换线、换USB口、换电脑,排除硬件。如果问题依旧,用Wireshark抓包,看具体是哪个阶段报的错。如果是枚举阶段,十有八九是描述符写错了。

12.4 USB描述符分析:设备的“身份证”

USB描述符,就是设备的身份证。主机通过读取描述符来了解设备是什么、能干什么。描述符是一层套一层的结构:

  • 设备描述符:最外层,包含VID、PID、设备版本等
  • 配置描述符:描述设备的配置信息,比如供电方式、最大电流
  • 接口描述符:描述设备的功能接口,比如HID、CDC、MSC
  • 端点描述符:描述具体的通信端点,包括端点号、传输类型、最大包大小

用Wireshark抓包时,你可以清晰地看到主机请求描述符的整个过程。我一般这样分析:

# 在Linux上用lsusb查看描述符(不需要抓包)
lsusb -v -d 1234:5678

嗯,这里要注意:描述符里有一个字段叫bMaxPacketSize0,它表示端点0的最大包大小。很多初学者会把这个填错。比如全速设备应该是8、16、32或64,有人填了128,主机就会报-71。我踩过这个坑,后来养成了习惯——写完描述符先用lsusb -v验证一遍。

个人建议:如果你在写USB设备固件,描述符一定要逐字节核对。我见过最离谱的bug是接口描述符的bInterfaceClass填成了0xFF(厂商自定义),但实际设备是HID类,导致Windows无法自动加载驱动。这种问题,抓包一看就明白了。

好了,USB调试与日志分析的核心内容就是这些。抓包工具选对、日志看对、错误码认对、描述符读对——这四件事做好了,USB开发中的绝大多数问题都能迎刃而解。

一句话总结:USB调试没有玄学,只有你没看到的日志和没抓到的包。


公众号:蓝海资料掘金营,微信deep3321