Win32K里的死循环
引用注明>> 【作者:张佩】【原文:www.YiiYee.cn/blog】
这是我到新公司后上手的第一个issue。一线project师找到我,说有一个urgent issue有没有兴趣看一看。当时他已经组织了一个team的人帮助他,我便成了当中一员。
问题确实非常紧急,已经影响了产线的生产。当时正临清明假期,导致非常多人都岌岌可危地面临清明加班的可能。事实上这个问题已经报出来好几个月,但一线project师一直以绥靖政策对待,以各种方式把问题发生率减少到客户接受的水平。绥靖政策长时间以来是有效的,但近期却突然失效,发生率飙升到了20%。一线project师才真的着急了。
问题定性
组一个team的人debug同一个问题,在我以前是未以前历过的。初始的想法当然是觉得人多力量大。但真正实施的时候,也可能遭遇人多嘴杂的窘况。
在怎样定性issue的问题上大家意见不一致。从一线project师描写叙述的情况看,问题发生时没有蓝屏。先前曾debug过此问题的同事说,问题发生时仅仅有一个进程和线程活着,其他的全部进程都处于block状态(除了ideal进程);最关键的是,他仍然能够通过Windbg单步。
有人把issue定性为系统崩溃、BSOD或系统异常,这是全然不对的。假设被这样定性,CPU必须是hang住的。但如今CPU却是活的,还能够单步。况且并没有蓝屏发生,定性为BSOD就更不对了。
应该属于什么类型的问题呢?从描写叙述上来看,系统是活的,仅仅是失去了响应而已。所以我把issue定性为系统的Software Hang。可能的情况非常多,比方一个系统进程突然跑偏了,导致系统界面失去响应。或者像本例中的情况:系统内核自己陷在一个死循环里面,无暇处理其他任务。
分析问题
我第一天仅仅拿到了dump文件。由于仅仅有一台能live debug的系统,使用上非常难统筹。我拿到dump文件后,第一动作是赶紧看那个唯一live的线程。这类issue的问题点非常集中,所以我非常有把握能够非常快找到一些实用线索。
ChildEBP RetAddr Args to Child
85846dd0 8fd95b75 ffffffff 85846ef0 nt!KeClockInterruptNotify+0x28a
(Inline) -------- -------- -------- -------- hal!HalpTimerClockInterruptEpilogCommon+0xa
85846de0 00000000 000000d1 00000000 hal!HalpTimerClockInterruptCommon+0x3e
85846de0 00000000 000000d1 00000000 hal!HalpTimerClockInterrupt+0x1cb
85846ef0 80c66050 8584762c 00000002 win32k!ENUMAREAS::ENUMAREAS+0xb9
8584711c 80da5d98 80da5d98 00000000 win32k!bSpBltScreenToScreen+0x2f8
858474dc 80da5d98 80da5d98 00000000 win32k!SpBitBlt+0x2bc
85847510 80da5d98 80da5d98 00000000 win32k!SpCopyBits+0x27
85847654 000006ce 000000a0 00000027 win32k!NtGdiBitBltInternal+0xa39
85847700 80d440a8 80c7d5e0 80c7d598 win32k!zzzBltValidBits+0xc6557
85847768 85847b18 85847ad0 80dac008 win32k!xxxEndDeferWindowPosEx+0x20b
858477a8 00000000 8312c5a0 80dac008 win32k!xxxProcessDesktopRecalc+0x10b
858477e0 d4b8b27d 85847d00 80dac008 win32k!xxxProcessEventMessage+0x7a
……
85847d3c 0118fb90 00000000 00000000 nt!KiSystemServicePostCall
调用栈上面有时钟中断的处理函数。当硬件中断发生的时候,CPU会亲自把当前活动线程的运行权抢占下来给ISR用。所以这是非常正常的事情。并非发生了所谓的“系统异常”。去掉中断相关处理函数后,调用栈的最后一个函数就是win32k!ENUMAREAS::ENUMAREAS。
我把这个函数的汇编代码稍微研究了一下,发现这里面居然存在一个死循环!当然,不是全部的循环都天生会成为“死循环”。死循环仅仅可能发生在特殊的状态下。从汇编代码看,Win32k正运行在一个循环中。这个循环仅仅会进入一次。同事给我提供了一个实用信息:每次问题发生的时候,看到的都是在运行同一个函数。结合这些信息,当然就能够断定这个循环是“死循环”了。
但当我把这个发现提出来准备team内讨论时,却受到了冷遇。没有人相信或愿意和我讨论这个死循环。debug team的一位同事没看分析过程,但把同一个问题问了我两遍:你真有把握这是死循环吗?令我非常郁闷。
第二天当Live Debug环境再次被建立起来,我能亲自上手的时候,我就首先在这个函数里面单步了好一会儿,确定它确实是在我发现的那4行汇编代码里面循环不止的。事实证明,无论我按多少次F10,指令寄存器都再没有离开过那4行汇编代码(除了发生时钟中断抢占的情况)。我当时心里立马舒缓了非常多,由于我还是非常害怕的,万一不是死循环,就颜面扫地了。
死循环的逻辑非常easy,仅仅有4行汇编。两个跳转(jump)语句在互相跳来跳去,是一个典型的while循环。
901b7b0f 394104 cmp dword ptr [ecx+4],eax
901b7b12 7e5e jle win32k!ENUMAREAS::ENUMAREAS+0xb6 (901b7b72) 901b7b72 034908 add ecx,dword ptr [ecx+8]
901b7b75 eb98 jmp win32k!ENUMAREAS::ENUMAREAS+0x53 (901b7b0f)
花几分钟时间把这4行代码反汇编成C语言,由于这时候我们已经有了win32k的private symbol,所以反出来的内容可读性非常强。
while (p->yBottom <= this->yBoundsTop)
{
(char*)P += p->sizeScan;
}
这是一个普通的循环。但假设循环体里面的p->sizeScan值为0,就能导致死循环。这正是原因所在。
猜猜猜
每一个资深Debugproject师都有一样本领,那就是“猜”。闭源调试的时候,“猜”是不二利器。当然了不能乱猜,否则会导致怨狱的——不能变成猜疑。
我发现当死循环发生时,yBottom的值是1200,yBoundsTop的值是1780。我对1200非常敏感,由于目标平台的分辨率是1800×1200。再加上通过private symbol展现出来的变量名称,更添加了猜的底气。所以就先猜1200是屏幕的高。
1780是什么呢?结合Win32k类的名称ENUMAREAS以及变量名称yBoundsTop,猜它是某个窗体的左顶点y轴位置。
继续猜。再细致看调用栈中各个函数的名称,能够大致知道它在干什么:通过GDI接口实现BitBlt亦即绘图操作。
如今基本给出一个合理猜想:问题发生的时候,有一个窗体被移出了屏幕以外区域,当这个窗体试图刷新UI界面时,有概率会导致Win32K的死循环。
下一步是依据上述合理猜想,给出一个解决方式:測试过程中,避免一切移动窗体位置到屏幕外的动作。
一线project师在了解了这个情况后还不放心,在解决方式中又加了一条:避免一切窗体最小化动作。
当这个方法报告给客户后,客户立马进行了測试,在上千次測试过程中,问题再也没有发生。而且,到如今为止的一个多月时间里,issue再也没有发生。
技术细节
当事实确定下来后,大家都非常吃惊。由于win32k是一个非常稳定的OS模块,假设真是win32k的bug,其影响一定非常地深远。这个问题如今已经报告给微软,微软project师还在分析,并已承认是系统Bug的事实。但微软project师更倾向于觉得这不是Win32k的bug,而是传给Win32K的參数被破坏了。最后的结果怎样,还需等待。
假设我是这位微软project师,我就会把这个调用栈上相关函数的实现逻辑,先好好地分析一遍。对比出问题和正常情况下的參数区别,区别出此issue是由代码逻辑导致的,还是异常參数导致的。假设是异常參数导致的,需确定是參数传入时就有问题,还是后面出的问题。以此逐步厘清。
while (p->yBottom <= this->yBoundsTop)
{
(char*)P += p->sizeScan;
}
当中this的类型为ENUMAREAS,其部分定义例如以下:
win32k!ENUMAREAS
+0x000 iDir
+0x004 xBoundsLeft
+0x008 yBoundsTop
+0x00c xBoundsRight
+0x010 yBoundsBottom
p指向的结构体为SPRITESTATE,其前面部分定义例如以下:
_SPRITESTATE
+0x000 yTop
+0x004 yBottom
+0x008 sizeScan
我分析觉得:p指向一个类型为SPRITESTATE的变长结构体数组,当中变量sizeScan表示到达下一个结构体的长度。当问题发生时,p已经枚举到了数组中的最后一个结构体,其成员变量sizeScan等于0。由于这个循环缺少了异常处理,从而使得p一直在处理最后一个结构体,在它身上做死循环,并奇怪地把整个系统都hang住了。当我通过Windbg强制跳出这个死循环后,系统奇迹般地活了过来。
Win32K里的死循环的更多相关文章
- 关于Android中为什么主线程不会因为Looper.loop()里的死循环卡死?引发的思考,事实可能不是一个 epoll 那么 简单。
( 转载请务必标明出处:http://www.cnblogs.com/linguanh/, 本文出自:[林冠宏(指尖下的幽灵)的博客]) 前序 本文将会把一下三个问题阐述清楚以及一个网上的普遍观点的补 ...
- (转)关于Android中为什么主线程不会因为Looper.loop()里的死循环卡死?引发的思考,事实可能不是一个 epoll 那么 简单。
( 转载请务必标明出处:http://www.cnblogs.com/linguanh/, 本文出自:[林冠宏(指尖下的幽灵)的博客]) 前序 本文将会把一下三个问题阐述清楚以及一个网上的普遍观点的补 ...
- 20140213-想念是while里的死循环
// stdafx.h : 标准系统包含文件的包含文件,// 或是经常使用但不常更改的// 特定于项目的包含文件// #pragma once#include "targetver.h&qu ...
- python之线程进程协成
线程与进程 什么是线程 线程是进程一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源,但是它可与同属一个线程的 ...
- python之进程与线程
什么是操作系统 可能很多人都会说,我们平时装的windows7 windows10都是操作系统,没错,他们都是操作系统.还有没有其他的? 想想我们使用的手机,Google公司的Androi ...
- 记一次程序排错与std::getline
今天忙活了半个下午,查找正式环境上面一个程序的问题.这个程序的作用是监控文件夹,处理每一个文件,分析每个文件的每行记录,然后将这个文件拆分成两个结果文件投放到另外两个不同的目录下面去,当处理完这个文件 ...
- MFC双缓冲绘图(2015.09.24)
问题引入: 最近在尝试编写贪吃蛇游戏时遇到这么一个问题:当系统以较快频率向窗口发送WM_PAINT消息时,调用OnPaint()函数在窗口中绘制图形就会发生闪烁现象. 问题分析: 当我们把绘图过程放在 ...
- python 进程间共享数据 (三)
Python的multiprocessing模块包装了底层的机制,提供了Queue.Pipes等多种方式来交换数据. 我们以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Q ...
- 深入浅出 - Android系统移植与平台开发(四)- Android启动流程
作者:唐老师,华清远见嵌入式学院讲师. 一.Android init进程启动 还是从Linux的启动开始吧.Linux被bootloader加载到了内存之后,开始运行,在初始化完 Linux运行环境之 ...
随机推荐
- UVA 11111-Generalized Matrioshkas(栈)
题意:有很多层盒子,盒子里面再套盒子,一个盒子可能套多个独立的子盒子,但子盒子的总体积必须小于该盒子,否则不合法,输入给一行数,负数代表左边,正数代表右边,大小表示其体积,如-2,-1,1,2则表示体 ...
- 【Python脚本】Python创建删除文件-----------我的第一个Python脚本
Python相对C++和Java来说,是解释性语言,非常适合来编写脚本. 很久之前就开始学习Python的语法了,今天写了第一个Python的脚本,来简化我的一些日常工作. 我平时学习的时候喜欢新建一 ...
- UIScollView Touch事件
customScrollView.h #import <UIKit/UIKit.h> @interface customScrollView : UIScrollView @end cus ...
- .Net语言中关于AOP 的实现详解
来源: IT人家 发布时间: 2011-03-22 20:28 阅读: 3546 次 推荐: 2 原文链接 [收藏] 摘要:该文章主要和大家讲解开发应用系统时在.Net语言中关于AOP ...
- ubuntu中文实训手册
http://people.ubuntu.com/~happyaron/udc-cn/lucid-html/ http://www.apachefriends.org/zh_cn/xampp-linu ...
- 走进C++程序世界------继承和派生
继承和派生 继承是面向对象编程语言的最重要方面之一,正确的使用继承可编写出设计良好,容易于维护和扩展的应用程序.下面是在其他博客中的总结: ****************************** ...
- Kolor Neutralhazer v1.0.2 (照片雾气模糊去除过滤器)+破解RI
由于空气污染.阴霾几天越来越,根据照片始终是一个灰色,怎么做?有了这个插件.能够解除您的烦恼. Neutralhazer这是消除你的风景照片和雾气模糊的全景图的有效途径photoshop小工具. wa ...
- Flex布局实践
介绍常见布局的Flex写法. 你会看到,不管是什么布局,Flex往往都可以几行命令搞定. 我只列出代码,详细的语法解释请查阅<Flex布局教程:语法篇>.我的主要参考资料是Landon S ...
- php中的short_open_tag的作用
在php的配置文件(php.ini)中有一个short_open_tag的值,开启以后可以使用PHP的段标签:(<? ?>). 同时,只有开启这个才可以使用 <?= 以代替 < ...
- WebService调用1(.Net)
1.创建一个最简单的Web Service (1) 新建-项目-ASP.NET空WEB应用程序 (2)添加新项-WEB服务 默认会添加一个HelloWorld方法: using System; us ...