最近在调试一个小程序发现,windows UI会随机性的hang住,表现为所有窗口都很能动了,做任何操作都要等半天。
在一般的用户看来,这就是windows死机了,肯定会去按reset按钮。
但要是这样就reset的话,程序就没法调试了。咱不是一般用户,自然去看一下为什么UI会hang住。首先立刻就能想到,应该是所有当前桌面的进程里,有一个dll,它在进行同步操作,而被同步的那个进程呢,恰好就是被调试进程,所以同步就失败了。这时候就会死锁。但如果WaitForobject函数超时不是无限的话,到时候还能活一下。现在这个表现,正好符合这一点,因此wait函数肯定是有限超时的。
好,拿出万能的调试器ollydbg来查吧。首先,打开一个记事本,然后用deskswitcher创建一个desktop。然后开始调试,一直调到卡住时。还好deskswitcher的快捷键还是有反应的,切换到这个新的桌面上。然后就开始调试前面这个桌面的记事本。
为什么要调记事本呢,因为它最简单,最容易找到问题。
attach上调试器后,直接break下来,看callstack,最后翻到调用wait的地方:
在输入法的msctf.dll中有如下调用:
push 1388h (dec=5000)
push xxx
call WaitForSingleObject
可以看到,msctf里waitforsingleobject(xxx,5000), 5000就是5000毫秒就是5秒,所以做任何一个动作之后,等5秒桌面又能活一小下。再次进入这个call又要5秒。把5000改回1,切回原桌面,发现这个记录本响应很快了。其它程序还是基本不能操作,更进一步证明:问题就在这里。msctf等待一个share的对象5秒时间,但这个share对象被某进程A占用了,而A恰好又处在调试中,被调试器中断了,因此不能释放这个对象。A不能释放,其它所有进程都在等。结果当前桌面就半死锁了。之所以说是半死锁是因为还有5秒超时,要是没有超时时间的话,就是真死锁了。
知道了原因,解决方案就有了,两个方案任取其一:
- 新建一个桌面,在此桌面上进行其于本机的远程调试。
- patch msctf.dll,把5000改成1. 即把push 1388h改成push 1。不过,这个方案需要禁用windows file protection。而且下回msctf.dll被哪个升级包升级一下又要重新改。
方案1每次都麻烦,方案2呢每升级完一次麻烦一次。不过出现这样的死锁的情况不多,暂时就用方案1好点。关于desk switcher这个软件也是有东西可以写的,且听下回分解。
“表现为所有窗口都很能动了”
应该是 “表现为所有窗口都不能动了” 吧