usbmon数据抓取及其分析
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
usbmon数据抓取及其分析
转⾃
使⽤ usbmon 抓取 usb 总线上的数据
usbmon 即 usb monitor,是 linux 内置的 usb 抓包⼯具。
usbmon 本质是⼀个内核模块,在我的 ubuntu14.0 4中,模块的位置:/lib/modules/4.4.0-31-generic/kernel/drivers/usb/mon/usbmon.ko。
1、检测内核是否⽀持 debugfs ⽂件系统
linux 系统⽀持很多类型的⽂件系统,像 ext3、sysfs、ramfs、tmpfs等⽂件系统,⾸先检测内核是否⽀持 debugfs ⽂件系统。
2、挂载 debugfs ⽂件系统
执⾏ sudo mount -t debugfs none_debugs /sys/kernel/debug 命令,如果提⽰已经挂载,则下次抓包就⽆需运⾏该命令了,表⽰系统默认会挂载该⽂件系统。
如上图所⽰,我的 ubuntu 系统默认已经挂载了 debugfs ⽂件系统,⽆需再去⼿动挂载。
3、确认内核⽀持 usbmon 模块
如上图所⽰,⽬前内核不⽀持 usbmon 模块,需要⼿动安装 usbmon 模块。
4、安装 usbmon 模块
执⾏ modprobe usbmon 命令,可以看到系统成功安装了 usbmon 这个模块。
这⾥的原理是,usbmon是⼀个模块,使⽤ modprobe 安装该模块后,该模块内部调⽤ debugfs 相关的API,这样在 /sys/kernel/debug/usb ⽬录下便形成了 usbmon 这个⽬录。
查看 /sys/kernel/debug/usb/usbmon ⽬录,发现该⽬录下有以下内容:0s、0u、1s、1t、1u、2s、2t、2u,其中1代表 bus1,2代表 bus2,0代表所有 usb 总线。
5、监测 usb 总线上的数据
⾸先需要获取想要监测的设备所在的总线以及设备号。
linux 中查看USB设备列表以及USB设备详细信息的两种⽅法:
⽅法1:执⾏ lsusb 命令
lsusb命令参数
-v:显⽰usb设备的详细信息
-t:以树状结构显⽰usb设备的层次
-s<总线:设备号>:仅显⽰指定的总线或设备号的设备
-d<⼚商:产品>:仅显⽰指定⼚商和产品编号的设备
⽅法2:cat /sys/kernel/debug/usb/devices
然后就可以根据上⾯的结果,查看 usb 总线上的数据。
cat /sys/kernel/debug/usb/usbmon/1u | grep "1:005" // 其中的1:005是根据第4步中确认的,分配给模块的设备号
执⾏该命令,就可以查看模块所挂载的usb总线上的数据传输。
关于该命令输出的数据所代表的含义,参考我的下⼀篇博⽂:
详解 usbmon 抓取的 log 各字段的含义
数据抓取到了,但是放眼⼀看,密密⿇⿇的全是数字,它们代表什么含义呢?本⽂将为你揭晓答案。
1. 预备知识
在解读 usbmon 抓取的数据包的含义之前,我们需要了解⼀下与 usb 传输有关的基础知识,这样才能更好的理解数据包的各个字段所代表的含义。
务必要明确⼀点:usb 总线上传输的数据是以包为基本单位的,但是不能随意的使⽤包来传输数据,必须按照⼀定的关系把这些不同的包组织成事务(transaction)进⾏传输。
1.1 usb 包的种类
介绍⼀下 usb 包的种类,总体上分为四类:令牌包、数据包、握⼿包、特殊包。
1)令牌包
令牌包⽤来发起⼀次 usb 传输,因为 usb 是主从结构的拓扑结构,所有的数据传输都是由主机发起的,设备只能被动的响应,这就需要主机发送⼀个令牌包来通知哪个设备进⾏响应,如何响应。
令牌包有 4 种,分别为输出(OUT)、输⼊(IN)、建⽴(SETUP)和帧起始(SOF)。
各个包的功能见下表:
2)数据包:顾名思义,数据包就是⽤来传输数据的,可以从主机到设备,也可以从设备到主机,⽅向由令牌包来指定。
3)握⼿包:握⼿包的发送者⼀般为数据接收者,⽤来表⽰⼀个传输是否被对⽅确认。
在传输正常的情况下,主机/设备会发送⼀个表⽰传输正确的 ACK 握⼿包。
4)特殊包:特殊包⽤在⼀些特殊的场合,这⾥就不介绍了。
1.2 usb 的事务
介绍了 usb 包的分类,下⾯就要着重介绍 usb 的事务(transaction)及传输类型。
前⾯已经说了,我们不能随意的使⽤ usb 包来传输数据,必须按照⼀定的关系把这些不同的包组织成事务才能传输数据。
那么事务是什么呢?事务通常由三个包组成:令牌包、数据包和握⼿包。
※注意:usbmon 只抓取事务(transaction)中的数据包,不会抓取令牌包和握⼿包。
usb 协议规定了 4 种传输类型:批量传输、等时传输、中断传输和控制传输。
其中,批量传输、等时传输、中断传输每传输⼀次数据都是⼀个事务;控制传输包括三个过程,建⽴过程和状态过程分别是⼀个事务,数据过程可能包含多个事务。
1.3 usb 的四种传输类型
下⾯⽤图来简单的表⽰这四种传输的过程
1)批量传输
2)等时传输
3)中断传输
4)控制传输
2. 分析 usbmon 抓取的 log
前⾯介绍了 usb 协议的⼀些基础知识,下⾯就开始分析 usbmon 抓取的 log。
贴⼀张在 ubuntu 下抓取的 4G 模块拨号上⽹的 log。
下⾯从左到右分析这些字段代表的含义:
1)URB Tag - URB 标签
该字段表⽰驱动中定义的struct urb 结构体变量在内核空间的地址,可以使⽤该字段区分不同的 URB 数据包。
下⾯两张图分别是在 64 位和 32 位系统上抓取的数据包。
可以看到 URB Tag 字段的长度和系统的位数相同。
2)Timestamp - 时间戳
该字段表⽰的是时间戳,单位是微秒。
1微秒 = 10^(-6)秒。
该时间是由下⾯的mon_get_timestamp()函数获取的,使⽤do_gettimeofday() 获取的时间位与(&) 上了0xFFF,因此usbmon log显⽰的秒数范围为0 ~ 4096s
// 2773就是当前URB的时间戳
3)Event Type - 事件的类型
S - submission,向 usb controller 提交 URB
C - callback,URB 提交完成后的回调
E - submission error,向 usb controller 提交 URB 发⽣错误
4)Address word - 地址字段
这个字段包含 4 部分,各个部分之间使⽤分号隔开,这 4 部分分别是 URB 类型及传输⽅向、usb 总线号、usb 设备地址、端点号。
URB 类型及传输⽅向:usb 有四种传输⽅式,分别是控制传输(Control)、批量传输(Bulk)、等时传输(Isochronous)和中断传输(Interrupt)。
usb 数据的传输⽅向是以 Host 端为参考对象的,Host 向 usb 设备发送数据那么传输⽅向就是 Output,Host 读取 usb 设备的数据那么传输⽅向就是 Input。
usb 总线号:该字段表⽰ usb 总线号,Host 端⼀般有多个usb controller,每个usb controller 都有⼀条对应的 usb 总线,使⽤ usb 总线号区分它
们,usb 设备可以挂接到某条总线上。
usb 设备地址:该字段表⽰ usb 设备的地址,每⼀个 usb 设备经过枚举后在 Host 端都有⼀个唯⼀的地址。
端点号:表明该次数据传输是 Input/Output 到设备的哪个端点。
上图中该字段是 0,就表⽰这次数据传输使⽤的是设备的端点 0(控制端点)。
5)URB Status word - URB 的状态
这个字段有两种表⽰形式:
① s + ⼀串数字
②⼀串以分号间隔的数字(或单个数字)构成的,这串数字包含下⾯⼏个部分:URB status、interval、start frame 和 error count。
特别注意⼀点,该字段不同于 "Address word" 字段,对于不同的传输⽅式这⼏部分是可选的,并⾮所有部分都是必须的,下图是不同的传输⽅式包含的信息。
下⾯分析不同的传输⽅式所包含的信息:
批量传输:只包含 URB status 这个字段,它对应着 struct urb 结构体中的 status 成员变量,表⽰ URB 的状态。
URB status 仅仅对 Event Type 中的Callback 有意义,对于 Submission 是⽆意义的,之所以这么做是为了统⼀格式,⽅便使⽤脚本分析 usbmon 的 log。
URB status 的具体含义见内核对于该成员变量的注释。
中断传输:URB status 和 Interval,URB status 见前⾯的分析,Interval 表⽰该 URB 对端点轮询的间隔时间。
等时传输:URB status、Interval、start frame 和 error count。
等时传输包含了所有部分,start frame 和 error count 是等时传输所特有的字段,但因为我们的驱动程序中不使⽤等时传输这种⽅式,因此这⾥不做过多的分析。
控制传输:控制传输在提交时(S:submission)这个字段是 s,这⾥的 s 后⾯紧跟的数据是控制传输的建⽴过程主机发送的数据包,可以参考前⾯控制传输的⽰意图。
控制传输在回调时(C:callback),这个字段代表的是 URB status。
6)Setup packet - 控制传输建⽴阶段的数据包
如果是控制传输,这个字段表⽰控制传输建⽴阶段主机发给设备的数据包。
该字段从左到右的格式如下,括号中的数字表⽰该部分占⽤的字节⼤⼩:
bmRequestType(1) + bRequest(1) + wvalue(2) + wIndex(2) + wLength(2)
每个字段的含义可以在 usb2.0 规范中找到,这部分与 usb 的标准请求等相关。
7)Data Length - 数据包的长度
对于 S(Submission),Data Length 字段是主机请求发送/读取的数据长度,但是设备并不⼀定能够接收/发送主机请求的数据长度。
实际接收/发送的数据长度在 C(Callback)中的 Data Length 字段。
8)Data tag - 数据标签
”=” 后⾯紧跟数据流
“>” 表⽰这是⼀次 Output 数据传输
“<” 表⽰这是⼀次 Input 数据传输
9)Data words follow - 数据流
这个字段就是⼀个事务(transaction)中的数据包,我们平时分析 usbmon 的 log 定位问题,也主要是看这个字段。
注意⼀点,这个字段实际显⽰的数据<= Data Length 的值。
3. 使⽤ usbmon 分析解决问题
下⾯举⼀个使⽤ usbmon 定位问题的⼩例⼦:
客户由于没有正确的根据⽂档移植驱动,可能在 qmi_wwan 驱动中没有剥除以太⽹头,就导致 quectel-CM 在通过 dhcp 获取 IP 地址阶段失败,可以通过抓取的 usbmon 的 log 快速的定位问题。
下图是 usbmon 抓取的 log,可以看到在 dhcp 阶段,主机发送的数据包是以太⽹包⽽⾮ IP 包,从⽽导致获取 IP 失败,进⽽定位到是驱动没有正确的移植。
USB URB的status及其代表的意义
平时在处理客户问题时,经常需要分析出现问题时抓取的usbmon log,这个log中有⼀个字段⾮常重要:URB Status word,这个字段就是struct urb结构体中的status成员变量。
通过这个status的值,可以分析出模块端和HOST端usb的⼀些状态,有利于定位到问题点。
Linux内核中对该成员变量的注释如下:
表明该字段仅仅表⽰批量传输、控制传输和中断传输的URB状态,等时传输不在其中,不过我们的模组也没有等时传输的端点。
在讲解URB的status之前先看下linux中URB的处理流程,如下图所⽰:
①使⽤usb_alloc_urb()创建⼀个URB结构体
②对于不同的传输⽅式,分别使⽤相应的函数初始化URB,中断传输使⽤usb_fill_int_urb(),批量传输使⽤usb_fill_bulk_urb(),控制传输使
⽤usb_fill_control_urb(),等时传输⽐较特殊需要⼿动初始化URB。
③使⽤usb_submit_urb()向usb控制器提交上⼀步初始化完成的URB,提交成功函数返回0,提交失败函数返回⼀个负数。
当提交失败时,usbmon log中的Event Type字段就为 E(submission error)。
④ URB被提交到了usb控制器中,这个URB将来会有三种命运,⽽这三种URB的结束⽅式就对应到URB的status。
第⼀种:成功完成传输
第⼆种:传输发⽣了错误
第三种:URB从usb控制器中"unlinked"
⑤当URB结束时,会调⽤之前注册的complete回调函数
URB成功传输的情况
在这种情况下,URB被成功发送给设备,并且设备返回正确的确认。
对于⼀个输出URB,表⽰数据被成功发送;对于⼀个输⼊URB,成功获取请求的数据。
此时,urb->status = 0;
下⾯是⼀条qmi消息成功交互的过程:
ffff8800b7d91740 1791319201 S Ii:3:008:9 -115:256 8 < // 这⾥的-115(EINPROGRESS)仅仅表⽰URB被提交给usb控制器去处理了
ffff8800b7d90600 1791319354 S Co:3:008:0 s 21 00 0000 0004 000c 12 = 010b0000 00000001 27000000
ffff8800b7d90600 1791320572 C Co:3:008:0 0 12 > // 这⾥urb status为0,表⽰qmi request消息成功发送给模块了
ffff8800b7d91740 1791329515 C Ii:3:008:9 0:256 8 = a1010000 04000000 // 这⾥Ti的urb status为0,表明可以读取qmi的response信息了
ffff8800b7d915c0 1791329530 S Ci:3:008:0 s a1 01 0000 0004 1000 4096 <
ffff8800b7d91740 1791329570 S Ii:3:008:9 -115:256 8 <
ffff8800b7d915c0 1791331992 C Ci:3:008:0 0 19 = 01120080 00000101 27000700 02040000 000000 // 这⾥返回0,表⽰HOST端成功的读取了模块的qmi response
再看⼀下AT指令的交互过程:
ffff8800b1480f00 2819829981 S Bo:3:008:3 -115 1 = 61
ffff8800b1480f00 2819830240 C Bo:3:008:3 0 1 > // 成功向模块发送了 A
ffff8800b1480f00 2819926000 S Bo:3:008:3 -115 1 = 74
ffff8800b1480f00 2819926105 C Bo:3:008:3 0 1 > // 成功向模块发送了T
ffff8800b1480f00 2821094084 S Bo:3:008:3 -115 1 = 0d
ffff8800b1480f00 2821094267 C Bo:3:008:3 0 1 > // 成功向模块发送了换⾏
ffff8800b1481e00 2821094738 C Bi:3:008:4 0 6 = 0d0a4f4b 0d0a
ffff8800b1481e00 2821094752 S Bi:3:008:4 -115 4096 <
ffff8800b1481b00 2821097257 C Ii:3:008:5 0:256 10 = a1200000 02000200 0000 // 成功读取模块的AT Response ==> OK
URB传输异常
urb传输异常就有很多种了,下⾯列举⼀下常见的错误对应的status:
注:下⾯的usb status状态码在linux-x.x.x/Documentation/usb/error-codes.txt 这个⽂件中有详细的说明,可以参考⼀下。
-EINPROGRESS (-115)
这个字段前⾯已经说过了,它出现在URB submission的时候,表⽰这个URB的控制权交给了usb控制器,将由usb控制器处理,这个并不是真正的错误。
-EPROTO (-71)
有两种原因会导致这个错误:
A bitstuff error happened during the transfer
No response packet was received in time by the hardware
这个错误经常发⽣在usb设备枚举失败时,下图就是EC25模块在客户树莓派上枚举失败对应的log,URB的status字段为-71
-EILSEQ (-84)
这个错误不太常见,具体可以参考error-codes.txt
-ECOMM (-70)
对于IN URB,在数据传输中,接收USB设备数据的速度 > 将数据写⼊内存中的速度
-ENOSR (-63)
在OUT URB数据传输中,数据不能从系统内存中获取的⾜够快,以便可以跟上请求的usb的数据速率
-EPIPE (-32)
这个表明usb管道"不通",批量端点可能会设置了halt条件,设置了这种条件的端点必然会堵塞管道,可以使⽤usb_clear_halt()清除halt
-EOVERFLOW (-75)
当发送给端点的数据包长度⼤于端点特定的数据包长度时会出现这个错误,从错误码也能看出来,数据“溢出了“
ENODEV (-19)
usb设备已经从系统中移除,但是hub没有及时检测到usb设备的断开,驱动仍然提交URB,就会导致这个错误发⽣。
Note: -EPROTO、-EILSEQ、-EOVERFLOW⼀般是由于usb设备、设备固件或usb数据线出问题导致的。
URB从usb控制器中"unlinked"
"unlinked"发⽣下下⾯的场景下:
(1)驱动中调⽤usb_unlink_urb( ) 或usb_kill_urb( ) 主动取消⼀个已经提交给usb控制器的URB
调⽤usb_unlink_urb( ) 之后,URB的status值为-104(ECONNRESET)
调⽤usb_kill_urb( ) 之后,URB的status值为-2(ENOENT)
(2)当URB已经提交给某个usb设备,但该设备被remove了,硬件断开了,⽐如将模块从主机上拔掉,这种情况下URB的status字段就为-
108(ESHUTDOWN)
下⾯举⼏个例⼦:
使⽤busybox microcom /dev/ttyUSB2 命令打开模块的AT⼝(open /dev/ttyUSB2),什么都不输⼊,然后ctrl+x退出 microcom(close /dev/ttyUSB2),相应的usbmon log如下:
可以看到,当主动退出microcom时,相应的URB的status字段值为-2,对应到串⼝驱动中就是调⽤了usb_wwan_close()中的usb_kill_urb()
对应的E(submission error)是在回调函数重新提交URB产⽣的,此时串⼝已经close,再去提交就产⽣了错误。
后⾯URB的status字段为-1(EPERM),这⾥分析usb_submit_urb() 的代码,可以看到-1是usb_submit_urb() 的返回值
usb_submit_urb
return usb_hcd_submit_urb;
同样使⽤busybox microcom /dev/ttyUSB2 命令打开模块的AT⼝,然后此时拔掉EVB板,断开usb设备,对应的usbmon log如下,可以⽐较和前⾯的区别:
-108(ESHUTDOWN)是usb物理层断开URB对应的status
下⾯再看⼀下模块autosuspend过程中的usbmon log:
echo auto > /sys/bus/usb/devices/3-2.1/power/control使⽤模块的autosuspend
ffff8800ba722240 2220899617 S Ci:3:009:0 s 80 00 0000 0000 0002 2 <
ffff8800ba722240 2220901604 C Ci:3:009:0 0 2 = 0000
ffff8800b7da3a40 2220901706 S Ii:3:009:5 -115:256 10 <
ffff8800b7da2180 2220901759 S Bi:3:009:4 -115 4096 <
ffff8800b7da2240 2220901798 S Bi:3:009:4 -115 4096 <
ffff8800b7da2f00 2220901824 S Bi:3:009:4 -115 4096 <
ffff8800b7da2d80 2220901850 S Bi:3:009:4 -115 4096 <
ffff8800ba722240 2220901891 S Co:3:009:0 s 21 22 0003 0002 0000 0
ffff8800ba722240 2220902834 C Co:3:009:0 0 0
ffff8800b7da3a40 2220905343 C Ii:3:009:5 0:256 10 = a1200000 02000200 0000
ffff8800b7da3a40 2220905360 S Ii:3:009:5 -115:256 10 <
ffff8800b7da2180 2223569116 C Bi:3:009:4 -2 0 // ①这⾥等待了2s(2223, 569116 - 2220, 905360),然后进⼊了autosuspend ② -2是这个URB在suspend函数中被usb_kill_urb()取消了
ffff8800b7da2180 2223569160 S Bi:3:009:4 -115 4096 <
ffff8800b7da2180 2223569172 E Bi:3:009:4 -1 0 // 这⾥和前⾯出现的原因相同,模块已经进⼊autosuspend,回调函数中⼜去提交这个URB
ffff8800b7da2240 2223569519 C Bi:3:009:4 -2 0
ffff8800b7da2240 2223569523 S Bi:3:009:4 -115 4096 <
ffff8800b7da2240 2223569528 E Bi:3:009:4 -1 0
ffff8800b7da2f00 2223569776 C Bi:3:009:4 -2 0
ffff8800b7da2f00 2223569810 S Bi:3:009:4 -115 4096 <
ffff8800b7da2f00 2223569815 E Bi:3:009:4 -1 0
ffff8800b7da2d80 2223570119 C Bi:3:009:4 -2 0
ffff8800b7da2d80 2223570159 S Bi:3:009:4 -115 4096 <
ffff8800b7da2d80 2223570167 E Bi:3:009:4 -1 0
ffff8800b7da3a40 2223571635 C Ii:3:009:5 -2:256 0
ffff8800ba722540 2223571783 S Co:3:009:0 s 00 03 0001 0000 0000 0
ffff8800ba722540 2223572943 C Co:3:009:0 0 0
ffff8800ba722540 2223573179 S Co:3:003:0 s 23 03 0002 0001 0000 0 // 这⾥是hub发出的,Port1:PORT_SUSPEND
ffff8800ba722540 2223574608 C Co:3:003:0 0 0。