案例研究:使用 ETW 和 Netmon 对未知 USB 设备进行故障排除

本主题提供了如何使用 USB ETW 和 Netmon 对 Windows 无法识别的 USB 设备进行故障排除的示例。

在本示例中,我们插入了设备,它显示为设备管理器中的未知设备以及用户界面(UI)的其他部分。 硬件 ID 为 USB\UNKNOWN。 为了进一步诊断,我们拔下设备,启动 ETW 跟踪,并再次插入设备。 设备显示为未知设备后,我们停止了跟踪。

关于未知设备问题

若要调试未知的 USB 设备问题,了解 USB 驱动程序堆栈在用户将设备插入系统时对设备进行枚举的操作会大有帮助。 有关 USB 枚举的信息,请参阅标题为“USB 堆栈如何枚举设备”的博客文章?

通常,当 USB 驱动程序堆栈无法枚举设备时,集线器驱动程序仍会向 Windows 报告设备的到达,并且 USB 设备在设备管理器中被标记为未知设备。 该设备的设备 ID 为 USB\VID_0000&PID_0000,硬件 ID 和兼容 ID 均为 USB\UNKNOWN。 以下事件导致 USB 中心驱动程序将 USB 设备枚举为未知设备:

  • 枚举过程中端口重置请求超时。
  • USB 设备的“设置地址”请求失败。
  • USB 设备的设备描述符请求失败。
  • USB 设备描述符格式不正确,验证失败。
  • 配置描述符的请求失败。
  • USB 配置描述符格式不正确,验证失败。

在 Windows 7 中,枚举失败的未知设备在设备管理器中会标有故障 代码 43

如果设备在设备管理器中显示故障 代码 28 ,说明该设备已经成功枚举,但依然被识别为未知设备。 此失败代码指示设备在枚举期间未提供产品 ID 字符串,Windows 找不到设备用于安装驱动程序的匹配 INF。

启动事件跟踪分析

由于这是设备故障,因此建议将 Netmon 与 USB 分析器一起使用来分析日志文件。

查看事件跟踪日志

  1. 运行 Netmon,单击 “文件 -> 打开 -> 捕获”,然后选择该文件。

  2. 选择 “帧摘要 ”窗格中的第一个事件,其中包含 SystemTrace 说明。 此图像显示选择第一个事件时屏幕的外观。

    “Microsoft 网络监视器”窗口中选择第一个事件后的屏幕截图。

  3. 若要自定义 Netmon 显示的列,请右键单击列名称,然后选择“ 选择列”。

  4. 第一个标识为 SystemTrace 类型的事件包含有关日志的一般信息。 可以在 “帧详细信息 ”窗格中展开信息树,以查看丢失的事件数和跟踪开始时间等信息。

USB 设备摘要事件

事件 2 是日志中的第一个 USB 事件。 此事件和多个后续事件描述了我们启动跟踪时连接到系统的 USB 主机控制器、集线器和设备。 我们可以将这组事件称为设备摘要事件,或者仅称为摘要事件。 与第一个事件一样,摘要事件不描述驱动程序活动。 事件摘要记录在日志会话开始时设备的状态。 其他事件表示总线上发生的情况、与客户端驱动程序或系统的交互,或内部状态的更改。

USB 集线器和 USB 端口驱动程序都记录总结事件。 记录事件的驱动程序在“协议名称”列中标识。 例如,USB 端口驱动程序记录的事件具有USBPort_MicrosoftWindowsUSBPORT协议名称。 USB 事件跟踪通常包含端口汇总事件序列,后跟一系列集线器汇总事件。 许多 USB 端口和 USB 中心摘要事件在其说明中包含“信息”或“属性”一词。

如何确定摘要事件的结束? 如果在日志开头,USB集线器事件的时间戳模式中存在重大中断,那么该中断可能是设备摘要的结束标志。 否则,任何 USB 集线器事件之后的第一个 USB 端口事件可能是第一个非摘要事件。 下图 3 显示此示例跟踪中的第一个非摘要事件。

在此示例中,当我们启动跟踪时,感兴趣的设备未连接到系统,因此你可以暂时跳过设备摘要事件。

在“帧摘要”中选择的感兴趣的设备在启动跟踪时并未连接,以下屏幕截图显示了这一情况。

事件描述和数据负载

在示例日志中,设备摘要事件后的第一个事件是 USB 集线器等待唤醒 IRP 已完成事件。 我们插入了一个设备,主机控制器或集线器正在响应中唤醒。 若要确定哪个组件正在唤醒,请查看事件的数据。 数据位于“框架详细信息”窗格中,该窗格以大致以下形式显示在树结构中:

Frame information
ETW event header information
    ETW event descriptor (Constant information about the event ID such
    as error level)
Event payload (Data logged at the time of the event)
    Name of a USB-specific structure
        Structure members and their values (Types: numbers, strings,
        or arrays)
    ...

展开 USB 中心等待唤醒 IRP 已完成事件的有效负载数据,你将看到名为 fid_USBHUB_Hub 的 ETW 结构。 结构的名称具有以下组件:

术语 DESCRIPTION
fid_ USB ETW 结构的典型前缀。
USBHUB_ 表示 USB 集线器驱动程序记录了事件。
字符串的其余部分 结构数据描述的对象的名称。 对于此事件,它是一个 Hub 对象。

USB 中心驱动程序使用 fid_USBHUB_Hub 结构来描述 USB 中心。 在其数据有效负载中具有此中心结构的事件是指一个中心,我们可以通过使用结构的内容来标识特定中心。 图 4 显示了“帧详细信息”窗格,其中 展开了fid_USBHUB_Hub 结构以显示其字段。

microsoft 网络监视器 - 帧详细信息。

中心结构非常类似于 USB ETW 事件:fid_USBHUB_Devicefid_USBPORT_Device中通常出现的另外两个结构。 以下重要字段适用于所有三个结构:

领域 DESCRIPTION
fid_idVendor 设备的 USB 厂商ID (VID)
fid_idProduct 设备的 USB 产品 ID (PID)
fid_PortPath 连接 USB 设备的基于一个中心端口号的列表。 列表中的端口号包含在 PortPathDepth 字段中。 对于根中心设备,此列表全部为零。 对于直接连接到根中心端口的 USB 设备,PortPath[0] 中的值是设备附加到的端口的根中心端口号。

对于通过一个或多个附加 USB 集线器连接的 USB 设备,集线器端口号列表从根集线器端口开始,并按照距离根集线器的远近顺序排列其他集线器。 忽略所有零。 例如:

示例值 DESCRIPTION
[0, 0, 0, 0, 0, 0] 该事件是指根集线器(电脑上的一个端口,由 USB 主机控制器直接控制)。
[3, 0, 0, 0, 0, 0] 该事件指的是集线器或设备被插入根集线器的第3号端口。
[3, 1, 0, 0, 0, 0] 集线器插入到根集线器的端口 3。 该事件是指连接到此外部集线器的端口 1 的集线器或设备。

应监视任何感兴趣的设备的端口路径。 枚举设备时,VID 和 PID 未知,并记录为 0。 在一些低级别设备请求(如重置和挂起)期间,VID 和 PID 不会显示。 这些请求将发送到设备插入的集线器。

在我们的示例日志中,等待唤醒完成事件的端口路径包含六个零。 该事件指示根集线器的等待唤醒操作。 这符合逻辑,因为我们的动作:我们已将设备插入根集线器端口,因此根集线器正在唤醒。

USB Netmon 筛选器

如果有时间,可以按时间顺序检查日志中的每个事件。 即使有经验,也很难通过扫描事件说明列表来快速识别重要事件。 若要更快地查找未知设备的原因,可以使用 Netmon 筛选器功能。

USB 错误筛选器

若要在 Netmon 中激活 USB 错误筛选器,请单击“筛选器 - 显示筛选器 - 加载筛选器 - 标准筛选器 - USB - USB 集线器错误”,然后在“显示筛选器”窗格中单击“应用”。

USB 错误筛选器将事件列表缩小为仅满足下表所示条件的事件列表。

筛选文本 DESCRIPTION
(USBPort_MicrosoftWindowsUSBUSBPORT 与 NetEvent.Header.Descriptor.Opcode == 34) 操作码为 34 的 USB 端口事件是端口错误。
(USBHub_MicrosoftWindowsUSBUSBHUB AND NetEvent.Header.Descriptor.Opcode == 11) 具有操作码 11 的 USB 集线器事件是集线器错误。
(NetEvent.Header.Descriptor.Level == 0x2) 具有级别0x2的事件通常是错误。
(USBHub_MicrosoftWindowsUSBUSBHUB AND NetEvent.Header.Descriptor.Id == 210) ID 为 210 的 USB 中心事件是“USB 中心异常记录”事件。 有关详细信息,请参阅 了解错误事件和状态代码

此图像显示了在将 USB 错误筛选器应用到示例跟踪日志后显示在 “帧摘要 ”窗格中的较小事件集。

显示应用 USB 错误筛选器后“帧摘要”窗格中的一组事件的屏幕截图。

若要查看错误序列的概述,可以简要查看每个错误事件。 要观察的重要字段包括 fid_NtStatusfid_UsbdStatusfid_DebugText。 有关详细信息,请参阅 了解错误事件和状态代码。 若要关闭筛选器,请单击“显示筛选器”窗格中的“删除”按钮。

自定义 Netmon 筛选器

可以在 Netmon 中创建自定义筛选器。 最简单的方法是通过以下方式之一从屏幕上的数据创建筛选器:

  • 右键单击 “框架详细信息 ”窗格中的字段,然后选择“ 添加所选值以显示筛选器”。
  • 右键单击 “框架摘要 ”窗格中的字段,然后选择“ 添加[字段名称]”以显示筛选器

可以更改运算符(如 OR、AND 和 ==)和筛选器值,以生成适当的筛选器表达式。

了解错误事件和状态代码

在我们的未知设备示例中,大多数 USB 集线器异常具有 fid_DebugText 数据为 CreateDeviceFailure。 目前还不清楚异常有多严重,但调试文本会提示原因:与新设备相关的作失败。 目前,假设相邻的“创建设备失败”事件是冗余的。 最后两个例外是CreateDeviceFailure_Popup和GenErr_UserIoctlFailed。 弹出异常听起来像是向用户公开的错误,但所有这些错误都可能与未知设备问题相关。

USB 错误事件和其他事件在其数据中具有状态值,这些值提供有关问题的重要信息。 可以使用下表中的资源查找有关状态值的信息。

状态类型 资源
fid_NtStatus 请参阅 NTSTATUS 的值
USB 请求块(URB)或fid_UsbdStatus的状态字段 在 Windows 驱动程序工具包(WDK)的 inc\api\usb.h 文件中,查找该值作为 USBD_STATUS。 也可以使用 USBD_STATUS。 本主题列出了USBD_STATUS值的符号名称和含义。

从问题事件开始回溯分析

错误事件之前记录的事件可能会提供有关错误原因的重要线索。 应查看错误之前记录的事件,以尝试确定未知设备的根本原因。 在此示例中,开始从CreateDeviceFailure_Popup事件(第二到最后一个异常)向后查看。 启用 USB 错误筛选器时选择此事件,然后单击“显示筛选器”窗格中的“删除”。 USB 错误筛选器仍显示在 “显示筛选器 ”窗格中,稍后可以重新应用它。 但现在筛选器已禁用, “帧摘要 ”窗格将显示所有事件,如下图所示。

microsoft 网络监视器。

在 CreateDeviceFailure_Popup 事件之前记录的两个事件是 USB 控制传输的调度和完成。 对于这两个事件,fid_USBPORT_Device端口路径字段为零,这表示传输的目标为根集线器。 在完成事件的fid_USBPORT_URB_CONTROL_TRANSFER结构中,状态为零(USBD_STATUS_SUCCESS),指示传输成功。 继续检查以前的事件。

接下来的两个事件是第四个(最终)创建设备失败事件和第四个(最终)CreateDeviceFailure 异常,我们之前检查了该异常。

下一个上一个事件是 Endpoint Close。 此事件表示终结点不再可用。 事件数据描述了设备及其上的终结点。 设备端口路径为 [1, 0, 0, 0, 0, 0]。 运行跟踪的系统只有主机控制器(根集线器)以及我们连接的设备,因此此端口路径不描述集线器。 关闭的端点必须位于我们插入的单个设备上,现在我们知道设备的路径是 1。 驱动程序可能会由于前面遇到的问题而使设备的终结点无法访问。 继续检查以前的事件。

最近的前一个事件是已完成的 USB 控制传输。 事件数据显示传输的目标为设备(端口路径为 1)。 fid_USBPORT_Endpoint_Descriptor结构指示终结点的地址为 0,因此这是 USB 定义的默认控制终结点。 URB 状态 0xC0000004。 由于状态不为零,因此传输可能未成功。 有关此USBD_STATUS值的更多详细信息,请参阅 usb.h 和 了解错误事件和状态代码

#define USBD_STATUS_STALL_PID ((USBD_STATUS)0xC0000004L)

含义:设备返回了停止数据包标识符。 终结点阻滞了哪些请求? 为事件记录的其他数据表示请求是标准设备控制请求。 下面是已分析的请求:

  Frame: Number = 184, Captured Frame Length = 252, MediaType = NetEvent
+ NetEvent:
- MicrosoftWindowsUSBUSBPORT: Complete Internal URB_FUNCTION_CONTROL_TRANSFER
  - USBPORT_ETW_EVENT_COMPLETE_INTERNAL_URB_FUNCTION_CONTROL_TRANSFER: Complete Internal URB_FUNCTION_CONTROL_TRANSFER
   + fid_USBPORT_HC:
   + fid_USBPORT_Device:
   + fid_USBPORT_Endpoint:
   + fid_USBPORT_Endpoint_Descriptor:
   + fid_URB_Ptr: 0x84539008
   - ControlTransfer:
    + Urb: Status = 0xc0000004, Flags 0x3, Length = 0
    - SetupPacket: GET_DESCRIPTOR
     + bmRequestType: (Standard request) 0x80
       bRequest: (6) GET_DESCRIPTOR
       Value_DescriptorIndex: 0 (0x0)
       Value_DescriptorType: (1) DEVICE
       _wIndex: 0 (0x0)
       wLength: 64 (0x40)

将 bRequest (GET_DESCRIPTOR) 与 Value_DescriptorType (DEVICE)组合在一起,你可以确定请求是 get-device 描述符。

若要继续 USB 枚举,设备应已使用其设备描述符响应此请求。 相反,设备停止了请求,这导致枚举失败。 因此,所有四个创建设备失败都是由于设备描述符滞后请求引起的。 你已确定设备为未知设备,因为枚举失败,而枚举失败是因为设备未完成其设备描述符的请求。