键盘鼠标在 Windows 里集体消失,而 Linux 一切正常
一次由 HIDClass 悬空 LowerFilters 引发的输入失效
结论先行:
- 现象:Windows 里键盘鼠标完全没反应,同一台机器重启进 Linux,键鼠一切正常。
- 根因:迁移 Steam 时手动删文件,把 Steam 的 Xbox 手柄驱动
steamxbox删掉了,但它在三个设备类的LowerFilters里还留着声明。下次重启枚举 HID 设备时,这条指向不存在驱动的引用让所有 HID 设备一起启动失败。 - 修复:清掉那三个残留值。因为 Windows 里没有任何可用输入,我在 Linux 侧读取、验证并离线修补了注册表,全程没有依赖 Windows 能操作(更稳妥的 WinRE 做法见第六节)。
一、现象
我这台机器是 Windows + Linux 双系统。今天早上进 Windows,发现键盘和鼠标完全没反应:没有光标,按键没有任何响应。
重启进 Linux,一切正常。期间我还把键鼠换到另一组 USB 口试过,Windows 里没有任何变化。
这个组合很讨厌。”Windows 有问题”和”硬件没问题”同时成立,意味着故障既不在硬件、也不在常识性的驱动缺失上——否则 Linux 也不会好。问题是它坏在哪一层。
二、先排掉最像的那个嫌疑:快速启动
这个组合几乎能直接对上号:双系统、Windows 键鼠全灭、Linux 一切正常。最像的解释是 Windows 的快速启动(Fast Startup)——它把”关机”变成把内核会话休眠(S4),下次开机是”恢复”而不是冷启动。如果恢复的会话里 USB 控制器状态是坏的,键鼠就会一直不工作;而 Linux 每次都是真冷启动、重新枚举,所以永远正常。
我从 Linux 侧只读挂载 Windows 分区查了一下:C:\hiberfil.sys 根本不存在。快速启动建立在休眠机制之上,没有休眠文件就意味着休眠和快速启动都不可用(这正是执行过 powercfg /h off 之后的状态)。假设作废。
最像的那条也断了,只能往更深的地方挖。
三、换思路:把 Linux 当成取证主机
Windows 不能用,不代表 Windows 的数据不能读。
Windows 的注册表 hive(SYSTEM、SOFTWARE)、事件日志(.evtx)、驱动安装日志(setupapi.dev.log)都是普通文件。只要能只读挂载那个 NTFS 分区,就能在 Linux 侧把它们全部读出来分析。不需要 Windows 启动,更不需要能用的键鼠。
我用 ntfs-3g 只读挂载,把几个关键文件复制出来,然后用 python-registry 解析注册表、python-evtx 解析事件日志。这套东西本来是做数字取证用的,拿来排障意外地好用。
第一步是对齐时间线。 三个不同来源的时间戳互相印证:
| 时间 | 来源 | 内容 |
|---|---|---|
| 11-02 05:10:52 | setupapi.dev.log |
最后一次驱动操作(关键,见后文) |
| 11-02 06:10:58 | SYSTEM / SOFTWARE hive 的最后写入时间 |
最后一次正常关机 |
| 11-03 04:10:02 | System.evtx 的 Kernel-General 事件 12 |
系统启动 |
| 11-03 04:10:11 | System.evtx 中 Microsoft-Windows-Winlogon 事件 7001 |
用户登录成功 |
| 之后 | —— | System.evtx 中再无后续记录 |
这里有两个关键信息:
第一,Windows 今天确实正常启动并完成了登录。所以现象不是”启动失败”,而是”进了桌面但输入设备全灭”,跟”卡在更新界面”那一类问题分开了。
第二,用户配置文件 NTUSER.DAT 的最后写入时间停留在 11-02 06:10。这条曾经误导过我:我一度以为”今天根本没登录成功”。后来想明白,这是因为当场强制断电,用户配置单元没来得及落盘。更可靠的是 System.evtx 里的 7001。做时间线时,不同文件的”最后写入”含义并不一样,这条得记住。
四、元凶:一条 23 小时前就写好的日志
setupapi.dev.log 是 Windows 的设备安装日志,纯文本,直接能读。它只有 32KB。这类日志在正常机器上通常会积累到几 MB,所以这个尺寸只说明可追溯的历史很浅。我特意翻过 Inf 目录找 setupapi.dev.*.log 的轮转存档,一个都没留下,历史补不回来。最关键的那一节能留在末尾其实是运气,不是方法。教训是:这类日志会被轮转或清理,取证要趁早。
日志的最后一节,时间是 11-02 05:10:52——距离第二天那次重启还有 23 小时。内容是一次 steamxbox 驱动的服务注册失败,以及随后的回滚清理:
!!! inf: Failed to create service 'steamxbox'. Error = 0x00000430
!!! dvi: Error 1072: The specified service has been marked for deletion.
!!! dvi: Error while installing services.
!!! dvi: Cleaning up failed installation
!!! dvs: Failed to install device instance 'ROOT\SYSTEM\0001'
flq: DeleteFile: 'C:\WINDOWS\System32\drivers\steamxbox.sys'
flq: DeleteFile: success
! sto: Reboot required to complete unconfiguration of driver package.
逐行读下来是这么个过程:创建服务失败(错误 1072 ——「该服务已被标记为删除」),setup 判定安装失败、开始清理,删掉了驱动包和 steamxbox.sys,最后留下一条「需要重启才能完成卸载配置」的挂起状态。
这里值得注意的错位是:文件被删了,但「卸载」没有真正走完。 一次正常的卸载会在收尾时把设备类里的过滤器声明一起摘掉;而这次走的是一条「服务注册失败 → 回滚清理」的路径(错误 1072 说明该服务早已被标记为删除),它只删驱动包和 .sys 文件,Control\Class 下那几条声明就留在了原地。这个推断是从日志行为反推的,没有卸载器的源码可以佐证,但和后面查到的事实吻合。
至于 steamxbox 是什么、又为什么会消失:它是 Steam 客户端的 Xbox 手柄支持驱动(INF 包里就叫 steamxbox.inf,注册的也是 XboxComposite / XnaComposite 这两个手柄相关的设备类)。而它这次被删,是我自己弄的——就在前一天,我把 Steam 从 Windows 迁到了 Linux,顺手清理了 Windows 上残留的 Steam 文件,这个驱动大概就是那时候一起没的。
于是我回头去查:这个驱动在注册表里还留下过什么痕迹。做法是把注册表里每一个设备类的 UpperFilters 和 LowerFilters 都列出来,逐个核对它所引用的服务/驱动是否真实存在。这个检查很便宜,但能抓一大类故障。
结果只有三处,而且全是它:
| 设备类 | 过滤器 | 引用的服务存在吗 |
|---|---|---|
| XboxComposite | LowerFilters = steamxbox |
❌ 不存在 |
| HIDClass | LowerFilters = steamxbox |
❌ 不存在 |
| XnaComposite | LowerFilters = steamxbox |
❌ 不存在 |
| Keyboard | UpperFilters = kbdclass |
✔ |
| Mouse | UpperFilters = mouclass |
✔ |
| USB | UpperFilters = hrdevmon |
✔ |
| DiskDrive / Volume / MEDIA / … | hrdevmon / partmgr / volsnap / … |
✔ 全部正常 |
整个注册表里只有三处悬空引用,全是 steamxbox。而这三条记录的最后写入时间分别是 07-12、10-05、10-16,说明 11-02 那次清理没有碰它们。驱动文件删了,服务注销了,但”我声明这里要挂一个过滤器”的注册表声明被留在了原地。
五、机制:为什么几条悬空引用能杀掉整个键盘鼠标
Windows 的设备栈里,驱动分两类:功能驱动(function driver)负责真正干活,过滤器驱动(filter driver)插在它上面或下面做些旁路处理(加密、监控、增强功能之类)。
而”要不要插过滤器、插哪些、插在什么位置”是按设备类(device class)声明在注册表里的:
键盘的 HID 设备栈(简化)
kbdclass.sys ← Keyboard 类,UpperFilters
↑
kbdhid.sys ← HID 键盘子驱动
↑
┌─────────────────────────────────────┐
│ HIDClass 类 —— LowerFilters = steamxbox │ ← 断在这里
└─────────────────────────────────────┘
↑
hidusb.sys ← HID 类驱动
↑
usbccgp.sys ← USB 复合设备父驱动
↑
xHCI 控制器 / 物理端口
设备启动时,PnP 管理器会按这两张过滤器表逐个去加载驱动。只要表里任何一个名字对应的服务已经不存在,整条设备栈的启动就会失败——不是缺个功能,是设备根本不会出现。
要把”设备类”说准:一个物理键盘在设备树里同时表现为好几种”设备”——
- 它的 USB 复合接口(
MI_00~MI_03)属于 HIDClass 这一层; - 在 HIDClass 设备之上,Windows 再派生出真正的 Keyboard 类设备(由
kbdhid/kbdclass驱动)。
本次断掉的是最底层 HIDClass 的 LowerFilters。父层起不来,上层的 Keyboard / Mouse 类设备不会被创建,所以哪怕 Keyboard 和 Mouse 自己的 UpperFilters(kbdclass / mouclass)都没有问题,也一样没有键盘鼠标可用。
具体到这台机器:Enum\HID 下登记了 16 个 HID 设备(键盘的 4 个接口、鼠标的多个 collection、还有一个手柄),全部起不来。
这样一来,几个原本说不通的现象都有了解释:
- 两个不同品牌的设备同时失灵,因为它们同属一个设备类,共享同一张过滤器表。
- 换 USB 口没用,因为过滤器声明挂在”设备类”上,跟端口无关。
- USB 层的日志是干净的,因为失效发生在 HID 类这一层,比 xHCI 控制器报错的位置高得多。控制器和 USB 枚举都成功了:
Enum\USB里两个设备实例的Service = usbccgp、ConfigFlags = 0都正常。 - Linux 不受影响,因为”按设备类动态声明过滤器”是 Windows 设备栈独有的机制。
为什么昨天还好、今天突然坏,时间线上写得很清楚:驱动文件 11-02 05:10 被删,但当时驱动还在内存里跑着,系统一直正常,直到 06:10 正常关机。这段时间里没有装过 Windows 更新,也没有动过别的软件——在”能正常用”和”完全不能用”之间,发生的事只有那次文件清理和一次重启。11-03 04:10 那次启动,是这台机器第一次”在没有 steamxbox.sys 的情况下启动”。
还有两个很能骗人的假线索:
- 事件日志里有条
Service Control Manager 7026:引导期驱动加载失败。听着像元凶,但它的参数是dam——Desktop Activity Moderator,这台机器不支持 Modern Standby,每次启动都会报它。历史日志里每次启动都有,属于良性事件。 Kernel-PnP 219(ROOT\DISPLAY\0000的\Driver\WUDFRd加载失败)也是每次启动都有,良性。
判据是”跟历史基线对比”,而不是”看到报错就当真”。
六、修复:在 WinRE 里清掉那三个值
修复目标:把三个设备类 LowerFilters 里的 steamxbox 清掉。
推荐用 Windows 自己的工具做——进 WinRE,用 reg 命令改离线 hive。这条路的风险最低,因为落盘是 Windows 自己完成的,校验和、脏标记、事务日志回放全都不用你操心。
补充一句:部分机器在 WinRE 下 USB 键鼠也未必可用。如果真遇到,备选是——用装系统时的安装介质进 PE(同样能开命令提示符)、接一套 PS/2 键鼠(走完全不同的驱动路径)、或者用带完整 USB 驱动的第三方 PE。
第 0 步:确认盘符。 Windows 分区在 WinRE 里经常被挂成 D:,用 dir C:\Windows\System32\config\SYSTEM 和 dir D:\Windows\System32\config\SYSTEM 试出来。
第 1 步:加载离线 hive,确认当前控制集。
reg load HKLM\OFF D:\Windows\System32\config\SYSTEM
reg query HKLM\OFF\Select /v Current
Current 返回 0x1 就用 ControlSet001,返回 0x2 就用 ControlSet002。这一步不能省:改到那个没在用的控制集,重启后毫无效果,而且你会误以为是修复失败。
还要看有几套控制集。 hive 里通常同时存在 ControlSet001 和 ControlSet002(互为备份,用于”最后一次正确配置”)。如果两套里都有这条悬空引用,就得两套都改,否则将来一旦回落到另一套,故障会复发。我这台机器只有一个控制集。我全量扫过整份 hive:ControlSet002 不存在,Select 里 Current / Default / LastKnownGood 全是 1,所以不涉及这个问题。但你的机器未必。
第 2 步:改之前先留档。
reg export "HKLM\OFF\ControlSet001\Control\Class\{745a17a0-74d3-11d0-b6fe-00a0c90f57da}" D:\backup-hidclass.reg /y
三个类各导一次(导到 U 盘或非系统盘上更稳)。.reg 双击就能回灌,出问题时比”凭记忆改回去”可靠得多。更彻底的做法是把 SYSTEM、SYSTEM.LOG1、SYSTEM.LOG2 三个文件整体复制到别处。
第 3 步:查清每个值里到底有什么。 三个设备类的身份对照:
| 设备类 | GUID | 说明 |
|---|---|---|
| HIDClass | {745a17a0-74d3-11d0-b6fe-00a0c90f57da} |
HID 设备类,本次键鼠失效的直接原因 |
| XboxComposite | {05f5cfe2-4733-4950-a6bb-07aad01a3a84} |
steamxbox 注册的 Xbox 手柄复合设备类 |
| XnaComposite | {d61ca365-5af4-4486-998b-9db4734c6ca3} |
XNA 输入复合设备类 |
(把下面的 ControlSet001 换成第 1 步查到的编号。)
reg query "HKLM\OFF\ControlSet001\Control\Class\{745a17a0-74d3-11d0-b6fe-00a0c90f57da}" /v LowerFilters
reg query "HKLM\OFF\ControlSet001\Control\Class\{05f5cfe2-4733-4950-a6bb-07aad01a3a84}" /v LowerFilters
reg query "HKLM\OFF\ControlSet001\Control\Class\{d61ca365-5af4-4486-998b-9db4734c6ca3}" /v LowerFilters
第 4 步:只删该删的。 只有当某个值里除了那个已失效的驱动名之外什么都没有时,才可以删掉整个值——本例三条都只含 steamxbox,所以这样做是安全的:
reg delete "HKLM\OFF\ControlSet001\Control\Class\{745a17a0-74d3-11d0-b6fe-00a0c90f57da}" /v LowerFilters /f
reg delete "HKLM\OFF\ControlSet001\Control\Class\{05f5cfe2-4733-4950-a6bb-07aad01a3a84}" /v LowerFilters /f
reg delete "HKLM\OFF\ControlSet001\Control\Class\{d61ca365-5af4-4486-998b-9db4734c6ca3}" /v LowerFilters /f
/f 是无条件删除整个值。如果某条 reg query 显示里面还有别的、仍然有效的过滤器(比如你正在用的某个软件注册的),绝对不要用上面这三条。有效项会被一并删掉,那等于修一个故障、造一个新故障。这种情况要重写整个值、只保留需要的项(reg.exe 在 REG_MULTI_SZ 里用 \0 分隔每个字符串):
reg add "HKLM\OFF\ControlSet001\Control\Class\{GUID}" /v LowerFilters /t REG_MULTI_SZ /d "保留项1\0保留项2" /f
第 5 步:复核,然后 unload。 reg unload 才会把改动真正写回文件:
reg query "HKLM\OFF\ControlSet001\Control\Class\{745a17a0-74d3-11d0-b6fe-00a0c90f57da}" /v LowerFilters
reg unload HKLM\OFF
第一条应该报”找不到指定的值”,或者显示已不含目标项。reg unload 若提示句柄被占用(比如还开着注册表编辑器或其他 reg.exe),关掉占用方再重试;在 unload 成功之前不要重启,否则改动可能没有落盘。
然后重启,键鼠就回来了。
我这次实际走的是另一条路:故障期间唯一能用的操作环境是 Linux,所以我在那边直接改了 SYSTEM 这个注册表文件。那条路确实能走通,但它绕过了 Windows 自己的注册表写入器——校验和、脏标记、事务日志回放全得自己负责,改错了系统可能起不来。同样的修改交给上面的 WinRE 做法,这些细节一个都不用管,所以本文只写这一条。
七、可复用的排查清单
下次遇到”Windows 里某个设备集体消失”,先按这张图走:
设备在 Windows 里用不了
│
├─ 只有一台设备不工作?
│ └─ 查那台设备自己:线、端口、供电、设备本体
│
└─ 一整类设备同时不工作(例如"所有键盘鼠标")
│
├─ 换到另一个控制器/端口后好了?
│ └─ 是 → 那个控制器的问题(驱动 / 资源 / 固件)
│
└─ 换了还不行 → 故障在"类"这一层,与端口无关
│
├─ Caps Lock 灯会切换(Windows 收到了报文)
│ └─ 不是 USB 问题:会话卡死、explorer 或显示驱动异常
│
└─ Caps Lock 灯毫无反应
│
├─ 扫每个设备类的 UpperFilters / LowerFilters,
│ 对照 Services 下是否真有这个服务
│ └─ 引用了不存在的驱动 → 就是它
│
└─ 过滤器都正常 → 查服务 Start 是否被改成 4(禁用)、
查安全软件的 HID 保护驱动、查"首启即坏"的更新
图上的分支判断之外,有一条正文没讲到、但用起来最方便:
Caps Lock 灯能当探针。那个灯是操作系统通过 HID 报文点亮的,不是键盘自己管的:灯会切换说明 Windows 收到键了,问题在别处;灯纹丝不动说明报文根本没到系统。
其余几条(拿另一个操作系统当取证主机、用”第一次在没有 X 的情况下启动”去查时间线、报错先跟历史基线比)正文第三、五节已经讲过,这里不重复。最后补一条最便宜的收尾检查:迁移或卸载过带过滤器驱动的软件之后,回头确认那几条声明真的被清掉了——setupapi.dev.log 里出现”服务已被标记为删除” + “需要重启完成卸载”时,更要盯紧。
八、后记
这次故障没有外部凶手。起因是我在把 Steam 从 Windows 迁到 Linux 时手动删了 Windows 上的文件,顺手把一个仍挂在三个设备类下面的驱动删掉了。Windows 和硬件都没参与,但留下的状态足以让所有 HID 设备一起失效。
有个细节值得记下来:删完文件的当晚一切正常,因为驱动模块还驻留在内存里;注册表里那条悬空声明,要等到下一次设备枚举才会被真正执行。所以它在删除时不爆,在下一次重启时爆——如果那晚没有重启,它还会埋更久。“改完当时没出问题”并不能证明改对了,有些配置错误只在重启后才生效。
排查过程没什么巧劲,就是把每个假设都变成一份能证伪的数据:快速启动被”hiberfil.sys 根本不存在”否掉;USB 层被”setupapi 里没有任何控制器记录 + Enum\USB 状态正常”否掉;最后在一条清理记录里看到那三条悬空声明。而这一切都是在 Windows 完全不可用的前提下完成的——因为数据都在磁盘上,换个操作系统照样能读。
所以,如果哪天你的键鼠(或者硬盘、网卡)在 Windows 里同时集体消失,硬件又查不出问题:先别急着换硬件,把那几张过滤器表捞出来对一遍。还有一条同样便宜的检查——迁移或卸载过带过滤器驱动的软件之后,回头确认那几条声明真的被清掉了。

