Windows下诊断阻止睡眠的 Bug
Windows下诊断阻止睡眠的 Bug
TL;DR: 在 Windows 上诊断导致“失眠”的程序非常简单。
我的雇主对 Windows 机器设置了一项策略,即每个计算机在一段时间不活动后自动锁定。我的一位同事注意到,如果他们在后台运行我们产品的最新版本,则此自动锁定不会生效。我被要求调查此事。
这个 Bug 的优先级相当低,因为乍一看,这种现象似乎不会造成太大的麻烦。我对所有这些在 Windows 上是如何运作的一无所知,但我有一种预感,如果锁屏被阻止,那么计算机进入睡眠状态也会受到同样的待遇。这是一个更大的问题,因为它可能会对笔记本电脑的电池续航时间产生巨大影响。
断点
重现过程非常简单(启动程序,然后等待 X 分钟,并注意锁屏未出现),而且我还有被调查软件的源代码,所以我的第一直觉是在允许你与 Windows 的此功能交互的函数上设置断点。第一个搜索结果是一个名为 SetThreadExecutionState 的函数:
允许应用程序通知系统它正在使用中,从而防止系统在应用程序运行时进入睡眠状态或关闭显示器。
我在这个函数上设置了一个断点,但根本没有命中。
好的,下一个搜索结果将我指向 PowerCreateRequest 和 PowerSetRequest。这个 API 的基本思想是,你创建一个电源请求对象,并根据你的特定用例,增加/减少给定类型的请求的引用计数。
在这些函数上设置断点显示了以下事件序列:
- 当应用程序启动并显示 UI 时,调用了
PowerCreateRequest
- 不久之后,使用
PowerRequestDisplayRequired
调用了PowerSetRequest
- 在程序的常规操作期间,没有进一步调用这些函数
- 退出时,调用了
PowerClearRequest
因此,总而言之,有人请求 不要 关闭显示器,并一直坚持这个请求直到程序退出。电源请求创建的调用堆栈如下(高度简化):
KERNEL32!PowerCreateRequest
libcef![…]
[…]
Product_exe!Application::MainMessageLoop
Product_exe!WinMain
[…]
ntdll!RtlUserThreadStart
libcef.dll
模块的存在大大缩小了范围,因为它意味着某种 Web 内容是罪魁祸首(该产品使用了 CEF)。在这一点上,这个 Bug 看起来很容易解决:最近,一个新的 onboarding 功能被合并到代码库中,它显示了一个小的浮动“软件中的新功能”类型的对话框,包括一个用于演示的小视频片段。
啊,也许同事在输入 Bug 时忽略了这个细节。我应该关闭这个对话框,问题肯定会消失 – 我想。令我惊讶的是,即使我关闭了这个对话框,问题也_没有_消失。
有时,你没看到的才是你(仍然)得到的
在这一点上,我的 PTSD 开始发作:也许这是那些超级罕见的 CEF Bug 之一,实际上除了你之外没有人遇到过,需要很长时间才能调试,然后在你升级到未来版本时随机消失(经历过,做过)。幸运的是,我有一个幸运的预感:如果代码——由于某种原因——实际上并没有关闭这个对话框,而是简单地隐藏了它呢?
由于我不会在这里详细说明的原因,在这种特定情况下,使用 Spy++ 测试这个理论更容易(即使我完全可以访问源代码):
- 我使用 Find Window 工具找到了有问题的窗口
- 我“关闭”了目标窗口
- 在 Spy++ 中,我点击了 Refresh 按钮
Spy++ 没有显示 Invalid window,而是仍然存在,证实了我的理论。我通知了引入此 onboarding 功能的团队,他们很快修复了这个 Bug。
其他工具
即使对于这个相对简单的案例,断点也绰绰有余,但在某些情况下,电源请求的数量可能会使这种方法变得不可行。幸运的是,还有一些替代方案。
Windows 自带的 powercfg 可以让你大致了解哪些程序有活动的电源请求(但这只是一个快照):
powercfg /requests
---
DISPLAY:
[PROCESS] \Device\HarddiskVolume2\Product.exe
如果你安装了 WDK,则有一个名为 pwrtest 的有用实用程序。使用“requests scenario”,你可以实时监控电源请求的生命周期(但仍然没有调用堆栈):
pwrtest.exe /requests
---
15:52:56 Create: \Device\HarddiskVolume2\Product.exe
Type:Application ProcessID:34712 SessionID:14
Allow: System Display AwayMode PerfBoost ExecutionRequired FullScreenVideo
Count: System:0 Display:0 AwayMode:0 PerfBoost:0
ExecutionRequired:0 FullScreenVideo:0
-------------------------------------------------------------------------------
15:52:56 Change: \Device\HarddiskVolume2\Product.exe
Count: System:0 *Display:1 AwayMode:0 PerfBoost:0
ExecutionRequired:0 FullScreenVideo:0
-------------------------------------------------------------------------------
15:53:24 Change: \Device\HarddiskVolume2\Product.exe
Count: System:0 Display:0 AwayMode:0 PerfBoost:0
ExecutionRequired:0 FullScreenVideo:0
-------------------------------------------------------------------------------
15:53:24 Close: \Device\HarddiskVolume2\Product.exe
-------------------------------------------------------------------------------
最后但同样重要的是,你可以使用 ETW 和 Microsoft-Windows-Kernel-Power
provider 来获取你需要的所有数据,包括调用堆栈:
xperf -on PROC_THREAD+LOADER -start user -on Microsoft-Windows-Kernel-Power:::'stack'
---
@rem Repro the problem here
xperf -d k.etl
xperf -stop user
xperf -merge k.etl C:\user.etl merged.etl
Call stacks!