要说调试USB设备最头疼的事,就是数据看不见摸不着。USB协议栈出错、驱动不识别、设备枚举失败……这些问题靠示波器太麻烦,靠打印日志又太片面。好在WireShark不仅能抓网络包,装上USBPcap插件之后,连USB总线上的原始数据都能给你看得明明白白。这篇文章就是我实际调试过程中总结的WireShark抓USB包教程,从插件安装、环境准备,到具体抓包步骤、过滤器写法、数据包分析,全部覆盖。适合搞嵌入式、写驱动、调USB转串口/虚拟串口的朋友,也适合想学USB协议但被文档劝退的新手。我会把每一步为什么这么做、踩过什么坑都讲清楚,照着做你也能抓到自己想要的USB数据。
1. 抓USB包前必须搞明白的事
1.1 USB数据在总线上到底是什么样的
先说点基础概念,不然你抓到的全是乱码。USB设备和主机之间通信用的是四根线(除了供电和地),一对差分数据线负责传输。总线是半双工的,主机和设备轮流说话,靠包结构来区分每个事务。
一个完整的USB传输流程看起来像这样:主机先发出一个令牌包(Token Packet),告诉设备"我要做什么"(是读还是写),然后要么发数据包,要么等着设备回数据包,最后来一个握手包(Handshake Packet)确认收到。一个事务由这三包组成,而一个传输可能由多个事务组成。
抓包工具做的事情,就是在这对差分线上采集电平变化,然后拼装成USB包。注意,这里是物理层的数据,包含了同步字段(Sync)、包标识符(PID)、地址字段、负载数据和CRC校验。所以你在Wireshark里看到的每一个"URB",其实是一整个USB事务的集合,不是单纯的"一个字节"。
1.2 WireShark能抓USB包的原理
WireShark本身只认识以太网、无线局域网这些网络报文,你要它直接解析USB原始数据是不行的。好在它有很好的扩展机制,通过底层抓包插件把USB总线的原始数据转换成WireShark能识别的格式。
Windows上最常用的是USBPcap,它其实是一个内核驱动加一个用户态服务。驱动在总线层面抓取数据,用户态服务负责把数据按照某种格式写出来,Wireshark再以"接口"的方式呈现给你。Linux上则更直接,内核自带usbmon接口,不需要装任何额外驱动,只要挂载usbmon内核模块,然后把权限放开,Wireshark就能直接打开这些接口。
这两种方式各有优劣。USBPcap对Windows用户友好,安装简单,但只能抓物理控制器层面的数据,虚拟机里的USB设备抓不到(除非穿透直通)。usbmon同样有虚拟机限制,但胜在命令行友好,可以配合脚本做自动化抓包。
注意,无论哪种方式,抓到的都是总线上的逻辑数据,不是电磁波射频数据。如果你想看的是USB信号质量,那得用示波器,不是抓包工具。
2. 工具选型与环境准备:Windows和Linux两条路
2.1 Windows下安装USBPcap
在Windows上,步骤其实很固定,但我见过不少人卡在第一步:装完USBPcap后重启WireShark,发现接口列表里还是只有以太网和无线网络。这里最容易忽略的就是权限问题。
安装流程如下:下载USBPcap的安装包(一般是2.x版本),一路下一步。但注意,安装过程中要勾选"Install USBPcap as a service",就是安装成系统服务。如果不勾选,很多设备会在休眠或拔插之后抓不到数据。装完之后建议重启系统,让驱动真正加载。
接下来打开WireShark,点击抓包选项(Capture Options),你会看到 "USBPcap1"、"USBPcap2" 这样的接口,分别对应不同的USB控制器。先别急着抓,务必用管理员身份运行WireShark(右键 → 以管理员身份运行)。USBPcap驱动是内核态的,没有管理员权限,Wireshark连接口列表都读不完全。
我曾经遇到过这种情况:接口列表里能看到USBPcap1,但双击之后一直提示"Permission denied"(权限被拒绝)。后来查了一下,其实是Windows服务没有正常工作,重新安装USBPcap并确认服务已经启动才解决。在服务管理器里搜USBPcap,确认状态为"正在运行"。
2.2 Linux下开启usbmon
Linux下就清爽很多,不需要装第三方驱动。先加载usbmon模块,命令如下:
sudo modprobe usbmon sudo wireshark然后用root权限运行Wireshark,在抓包接口里就能看到 usbmon1、usbmon2 等接口。usbmonX对应第X个USB控制器,如果你有多块USB卡,会有多个usbmon接口。
但是用root跑Wireshark有风险,我一般建议给当前用户加权限。你可以创建一个udev规则,或者是简单粗暴地给usbmon设备文件加读权限:
sudo chmod +r /dev/usbmon*这样普通用户也能抓包了。不过要注意,usbmon接口的编号是动态的,拔插设备后可能变化,你要是写自动化脚本,建议用ls -l /dev/usbmon*看一眼当前编号再做映射。
2.3 为什么我推荐先搞懂接口选择
接口的选择直接决定了你能抓到什么设备的数据。USBPcap1和usbmon1都是一套完整的USB控制器,而一个控制器下面可能挂着一整颗USB Hub,Hub下再接各种设备。如果你选错了接口,目标设备刚好挂在别的控制器上,那抓来的包全是别的东西。
怎么确认设备挂在哪个控制器?Windows上可以在设备管理器里找到你的USB设备,查看"位置路径",里面会显示"Port_#0001.Hub_#xxxx",这个信息可以在USBPcap接口说明里对应。Linux下可以用lsusb -t查看设备的树形结构,确认它挂在哪个usbmonX下。
如果实在分不清,直接挨个接口都抓一遍,用过滤器把多余设备过滤掉也行,但效率低,我后面讲过滤器时再细化。
3. 实操:从设备识别到数据包过滤
3.1 抓包前先确认你的设备挂在哪个总线上
这一步非常关键,错了你折腾半天也是白抓。假设我手头有一个FT231X USB转串口设备,我想抓它和电脑之间的通信。
在Windows设备管理器里,找到这个设备的"总线类型",显示是"USB",并且"位置路径"可能显示基于某个端口。此时打开Wireshark,进入抓包选项,选中USBPcap1接口,点"选项"按钮,会弹出一个USBPcap特有的对话框。
这个对话框里,你能看到"设备过滤器"(Device Filter)栏,它会列出当前这个控制器下所有接入的USB设备,并给每个设备分配一个临时编号。你可以按住Ctrl键直接多选,或者填写"任何设备"(Any Device)来抓全部。
Linux上就用usbmonX接口直接开抓,但抓到的包是全总线的,所以最好用显示过滤器缩小范围。我的习惯是先确认目标设备的总线地址,可以用cat /sys/kernel/debug/usb/devices或者lsusb -v看一下“Bus=01 Lev=01 Prnt=01 Port=0”之类的字段。
3.2 设置过滤器:抓全包还是抓目标包
第一次抓USB包的人最容易犯的错就是:直接点击开始抓包,然后被满屏的URB淹没。USB总线上每时每刻都有各种控制传输、中断传输,特别是接入USB鼠标键盘这类设备时,中断包多到怀疑人生。
所以一定要用过滤器。这里区分两个概念:抓包过滤器(Capture Filter)和显示过滤器(Display Filter)。USBPcap本身只支持有限的抓包过滤器,更多是用Wireshark的显示过滤器来做。
我推荐几个实用过滤表达式,直接抄作业:
| 过滤目标 | 显示过滤器表达式 |
|---|---|
| 只显示USB协议 | usb |
| 只看特定USB设备地址 | usb.device_address == 5 |
| 只看特定厂商设备 | usb.idVendor == 0x0403 |
| 只看主机发送给设备的控制请求 | usb.bmRequestType == 0x40 |
| 只看设备返回给主机的数据 | usb.endpoint_address == 0x81 |
| 只看某个方向的批量传输 | usb.transfer_type == 0x02 && usb.direction == 0 |
需要解释一下bmRequestType。这是USB控制请求的第一个字节,最低位为0表示主机发送(OUT),最低位为1表示设备返回(IN)。所以0x40就是标准设备请求中的"主机发送到设备",这个过滤器在分析设备枚举时会很有用。
还有一点,设备地址在每次枚举时可能是变化的,建议先用usb.idVendor或usb.product这类跟设备强相关的字段定位,再结合usb.device_address二次确认。
3.3 实操演示:抓USB转串口的数据
我现在就以FT231X USB UART为例演示一遍完整流程。
第一步,插上FT231X设备,确认驱动正常工作。在Windows设备管理器里能看到"USB Serial Port (COM3)"这样的名字,说明驱动装上了。
第二步,打开Wireshark,用管理员身份运行,进入抓包选项,选择USBPcap1接口,然后在设备过滤器里选中FT231X对应的设备(注意,有时候会显示成"Bus 002 Device 004: ID 0403:6015 Future Technology Devices International, Ltd FT231X")。直接双击选中它。
第三步,开始抓包,然后用串口工具打开COM3,发送一串数据,随便写个"HelloUSB"也行。发送完回到Wireshark点停止抓包。此时你看到的基本都是批量传输的URB包,因为FT231X的通信走的是批量传输端点。
第四步,在显示过滤器里输入usb.data(只看有数据载荷的包)或者usb.transfer_type == 0x02,就能看到数据内容。当你双击某个URB包时,你能在详情面板里看到URB data字段,展开后里面有具体的字节。
这里有个很容易搞混的地方:USB的批量传输不像串口那样有严格的帧格式,它是按块(Block)传输的,每个URB可能包含多个字节。FT231X驱动会把UART收到的数据攒成一个缓冲,然后一次性扔给USB总线。所以你抓到的包,看到的是"一堆字节到了一个设备端点",而不是"一个字节接一个字节"的串口数据。
如果你想知道串口发的具体内容,直接展开URB data字段的十六进制就行了。比如你发送"HelloUSB",你会在某个URB的data里面看到48 65 6c 6c 6f 55 53 42,这就是ASCII码。
4. 分析实战:从原始数据到协议解读
4.1 如何看懂USB控制传输的包
USB控制传输是所有USB设备通信的基础,包括设备枚举、配置、状态查询等。在Wireshark里,控制传输的URB通常有单独的类型,你可以用usb.urb_type == URB_CONTROL来过滤。
控制传输分为三个阶段:Setup阶段、Data阶段(可选)、Status阶段。在Wireshark里你会看到一个完整的Control-transfer URB,展开后有以下关键字段:
| 字段 | 含义 |
|---|---|
| bmRequestType | 请求方向、类型和接收者,前面讲过 |
| bRequest | 具体请求码,比如GET_DESCRIPTOR(0x06) |
| wValue | 请求相关的参数,通常是描述符类型和索引 |
| wIndex | 接口端点索引 |
| wLength | 期望传输的数据长度 |
举个例子,假设你看到一个包:bmRequestType=0x80,bRequest=0x06,wValue=0x0100,wIndex=0x0000,wLength=0x12,这是什么意思呢?0x80表示设备向主机返回数据,0x06就是GET_DESCRIPTOR,wValue的高字节是描述符类型(0x01表示设备描述符),低字节是索引(0),wLength是18字节。也就是说主机在请求前18字节的设备描述符。
这类包在新设备插上时一定会出现,所以当你怀疑设备枚举失败时,先用它定位。我看过很多人枚举失败,抓包后才发现wValue写错或者是描述符长度和实际不符。
4.2 批量传输与中断传输怎么看
批量传输(Bulk Transfer)多用于打印、U盘、串口这类大流量数据,而中断传输(Interrupt Transfer)用于鼠标、键盘这类需要响应速度的设备。在Wireshark里,切换到USB视图后,你会看到每个URB都带有传输类型和端点地址。
批量传输的特点是:没有固定间隔,只要总线空闲就发送,但优先级低。中断传输则必须保证间隔时间,例如鼠标每8ms汇报一次。在分析里,批量包的payload通常很多,你可以在抓包后直接用usb.endpoint_address == 0x02 && usb.data这样的组合过滤。
还有一点,USB传输在主机侧往往会有缓冲机制。比如STM32的USB虚拟串口,它内部有端点缓冲区,当缓冲区满时,才提交给USB控制器。所以抓包看到的数据可能是成段的,中间有间隔,这不是bug,是缓冲策略。
4.3 结合STM32虚拟串口场景
很多人调STM32的USB虚拟串口,常在Windows下面临设备识别失败。这时候抓包比看代码日志有效得多。
我举个例子:某个开发板用STM32F407,配置了VCP(Virtual COM Port),但插上电脑后系统提示"无法识别的USB设备"。用Wireshark抓包后发现,在枚举过程中,主机发起了GET_DESCRIPTOR请求,设备返回了数据,但数据最后一个字节的长度不对。原来设备描述符的长度字段被写成了0x11(17),而实际描述符应有18字节。Windows一看长度不对,直接放弃了。修正描述符后,设备立刻识别成功。
如果你在调试时遇到类似问题,记住一个原则:抓到的USBPacket对照USB规范,看每一字段是否合理。特别是描述符的bLength、bDescriptorType,以及端点描述符里面的bEndpointAddress和wMaxPacketSize,这几乎涵盖了90%的USB枚举问题。
5. 常见问题与排查技巧实录
5.1 抓不到包或者接口列表为空
如果你的WireShark在打开抓包选项后,看不到任何USBPcap接口,或者看到接口但一个包都抓不到,先从三个方向排查。
第一,确认USBPcap驱动已经安装。打开设备管理器,在"系统设备"里找"USBPcap Service"字样。如果它被禁用了或者没启动,重新启用。
第二,确认你用的是管理员权限。这点反复强调,很多人就在这里翻车。
第三,确认你的USB设备是否真的活动。抓包工具抓的是总线上的事件,如果设备是静态的、没有任何通信,你当然什么都抓不到。最简单的方法是插上一个U盘然后格式化或拷贝文件,制造数据流量,看看能不能抓到。
5.2 过滤器不生效
过滤器不生效,多半是把显示过滤器和抓包过滤器混用了。在Wireshark主界面顶部的那个输入框是显示过滤器,而抓包过滤器在抓包选项对话框里。如果你在抓包选项里写了usb.device_address == 5,那是无效的,因为抓包过滤器有独立的语法和底层驱动支持。
如果你用的是显示过滤器,但发现包还是满屏都是,可能是语法写错了,Wireshark会用红色底色提示你。建议在写过滤器时先用小写字母,并且看看字段名是否拼写正确。像usb.device_address是标准字段,usb.destination这种根本不存在,所以查不到。
5.3 数据全是乱码或看不懂
数据乱码或者全是一堆无法理解的二进制,通常是两种情况。一种是设备本身用的是私有协议,比如某个设备的数据是加密或压缩后的,此时你只能看到流,没法直接看出含义。另一种是你抓到了多个设备的混合数据,需要缩小范围。
再就是URB数据里除了有效载荷,还有协议包头(QH、TD等)。Wireshark已经帮你解析好了,你只需要展开"USB URB"这个层,找到"Data"字段或"URB data"字段,那才是真正的业务数据。如果你用文本模式看,可能会看到乱码,因为很多数据是二进制或者半结构化数据,用十六进制视图看更靠谱。
我遇到最典型的乱码场景是:抓包显示的每个URB只有十几个字节,里面还夹杂着端点状态。后来发现是抓包长度设置不对,默认只抓了64字节,导致大量实际数据被截断。这时候在抓包选项里把"Limit each packet to"的数值调大,比如改成65535,就能看到完整数据。
5.4 一个临时踩坑记录
最后说一个我自己的小插曲。某次抓包,明明USBPcap1能看到很多数据包,可怎么都找不到目标设备的数据流。耗了半天,后来在设备管理器里看到我的设备其实挂在芯片组的另一个USB控制器上,也就是USBPcap2。这种多控制器的情况在笔记本上最常见,右边接口走的是集线器下的控制器,左边接口走的又是另一个控制器。调试前用lsusb -t(Linux) 或者设备管理器看位置路径,比瞎猜快多了。
结束语可以省,这里补一个实用小技巧
我调试了这么多USB设备,最大的心得就是:抓包要抓"时间点"。USB的枚举过程很短,通常只有几百毫秒,如果你提前开着抓包再插设备,很容易把无关流量混进来。所以我推荐一种做法:先点开始抓包,再插上USB设备(或者重连一次设备),这样枚举包就被干净利落地捕捉到了。
如果你用的是Windows的USBPcap,在设备过滤器里选中"任何设备"后,还能看到枚举之前的总线清理动作,这些细微信息在排查插拔不稳时有奇效。另外,如果你想保存一段干净的日志给同事分析,建议在抓包结束后先用显示过滤器,然后使用"文件 → 导出特定分组"来保存过滤后的包,避免一大包垃圾数据干扰别人。
这个工具用熟了,你会觉得Windows和Linux下调试USB其实没那么多玄学,数据都在那儿,就看你会不会看了。