原因:一开始想查找由于ipc初始化顺序的问题导致tray卡死的原因,但恰好遇到主进程弹出退出确认框后也卡死了,于是开始查找原因.
首先是跟踪代码,发现消息循环是活着的,但整个消息循环只能取到timer和paint消息,使用消息工具抓窗口,可以看到也可以取到GetItemText等消息.
(一般来讲这时已经可以定位是由于attachthreadinput的原因了,但这时候我还不知道);
仔细想,最可能的情况就是当前线程所有窗口都已经被disable掉了,于是仔细检查进程的所有窗口,没有发现问题.
回想一下,窗口输入消息队列是这样的,用户点击某窗口后系统将该窗口设置为forground窗口,接着拥有该窗口的线程成为前台线程,此时系统的原始输入线程内保存的分发消息队列地址切换到改线程的虚拟输入队列地址上,接着系统接下来将所有输入消息投放到这个队列中去.
窗口收不到鼠标和键盘消息,首先确定该窗口是不是前台窗口,是用工具检测发现,确实是前台窗口:
但此时的active窗口为null,这是肯定是无法收到键盘消息,于是将其强制设置为active窗口,主进程依然活不起来.
从另一个角度,即使这是的active窗口为0,但理论上应该还是能接收到鼠标消息,鼠标消息特殊在,系统找到当前鼠标下的窗口,并将消息分发到改窗口的线程中.此时与forgound和active窗口都没有关系.
直观猜测,应该是某个api使用不当造成的,所以接下来就是找到所有和窗口消息队列有关的api函数,影响最大的就是attachthreadinput函数,该函数会将两个线程的输入队列合并,并共享所有关于前台窗口等状态,因此这个函数嫌疑最大.
测试发现,若此函数没有配对使用则其中一个线程的等待必然导致另一个线程卡死
使用代码搜索了所有这个函数的调用,发现都是配对使用,故排除了attachthreadinput使用不当这个原因.
野史记载,KTHREAD有一个user32thread数据接口记录了一个THREADINFO来保存消息队列,于是尝试内核调试来观察之,但此变量为void类型,公共符号没有找到THREADINFO的定义,最终已失败告终.
另外一个猜想是既然和线程有关系,那就杀线程吧,于是开始将主进程的所有非主线程杀掉,但依然没有激活窗口.
郁闷之后开始杀进程,发现杀死tray进程后主进程被激活,后测试发现tray send完ipc消息后马上返回也不会导致主进程卡死,焦点再次回到了attachthreadinput使用上.怀疑是某个我们没有源代码的模块使用没有配对造成的bug.于是还是选择调试来查找
由于操作步需要tray启动主进程在右键退出,且操作流畅才可复现bug,所以采用如下办法来检查attachthreadinput函数到底在哪个模块没有配对,故采用如下方法:
将windbg的所有断点事件禁用,同时:
启用进程初始断点并关联命令:
bp USER32!NtUserAttachThreadInput"|.;.printf \"src:%N,to:%N,isatt:%N\",poi(@esp+4),poi(@esp+8),poi(@esp+c);.echo;kc;gc";gc;
这样新进程会在调用NtUserAttachThreadInput时就会终端并打印出参数:
bug复现后发现:
两次线程的attach和detach并不配对,第二次会直接失败.代码如下:
经过测试发现:
两个线程attach以后,若一个线程卡死,则另一个线程也会卡住,此时所有鼠标和键盘消息在两个线程都无法收到,另外那个线程会阻塞在GetMessage或者类似的等待函数中,但该线程仍然可以接受诸如timer之类的消息,原因如下:
当两个线程attach以后输入队列合并为一个,此时要想取出硬件输入消息,必须两个线程同事到场,也就是说两个线程的getmessage同步去取才会取得出来,否则无法取出,相当于一把需要两把钥匙才能开启的锁,持钥匙的 两人同时在场才能开门,而timer等消息在另外一个消息队列中,则可以顺利取出.
原因确定就好复现bug了,从tray启动主进程后不断的右键点击tray,使焦点保持在menu上,此时主进程启动会将自己与tray的主线程attach,之后从tray中sendipc消息到主进程,然后tray会等待主进程的回复因此tray终止了消息循环,等待在sendipcmessage上,因此导致了baiduaan也无法接受鼠标消息从而卡住.
这同时也说明了为什么主进程调试的时候经常卡住vs,windbg或者任务栏等的原因.
总结:
1,若发生接受不到鼠标消息,且消息循环或者则考虑1,是否所有窗口都被diable,2,是否与另外一个线程有attach关系
2,不要轻易排除一个问题,需要有确切的理由和仔细的检查才可以排除.
3,慎用attachthreadinput.
4,其实如果刚开始死盯住attachthreadinput会很快找到原因,马虎的测试和粗略的代码检查导致了后来的纠结
参考
http://blogs.msdn.com/b/oldnewthing/archive/2013/06/19/10426841.aspx
http://www.slideshare.net/wvdang/five-things-every-win32-developer-should-know
http://msdn.microsoft.com/en-us/library/windows/desktop/ms644927(v=vs.85).aspx