一个主进程卡死的跟踪

原因:一开始想查找由于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

时间: 2024-11-03 22:43:58

一个主进程卡死的跟踪的相关文章

总结一个主进程启动子进程,删除主进程文件和目录的问题

首先,我们启动子进程的时候,要修改子进程的工作目录. ProcessStartInfo pi = new ProcessStartInfo(fileName, arguments) { WorkingDirectory = Path.GetTempPath() }; pp.StartInfo = pi; pp.Start(); 这里我把工作目录随便指定了一个地方,目的是防止子进程默认继承了主进程的工作目录.如果你省了这一个地方,只要子进程开着,那它的工作目录就和主进程的工作目录是一样的,结局就是

AF_UNIX域通信(基于socket和pipe的通信,只适于UNIX系统S&C同在一个主机上,用于进程通信)

服务器端: #include<stdio.h>#include<unistd.h>#include<stdlib.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include <sys/socket.h>#include <sys/un.h>#include <stddef.h>char buf[100];void main

lab6:分析Linux内核创建一个新进程的过程

李俊锋 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.实验原理 1.进程的定义 进程是操作系统的概念,每当我们执行一个程序时,对于操作系统来讲就创建了一个进程,在这个过程中,伴随着资源的分配和释放.可以认为进程是一个程序的一次执行过程. 2.进程与程序的区别 程序时静态的,它是一些保存 在磁盘上得指令的有序集合,没有任何执行的概念. 进程是一个动态的概念,它是程序执行的过程

一个由进程内存布局异常引起的问题

一个由进程内存布局异常引起的问题 前段时间业务反映某类服务器上更新了 bash 之后,ssh 连上去偶发登陆失败,客户端吐出错误信息如下所示:图 - 0 该版本 bash 为部门这边所定制,但实现上并没有改动原有逻辑,只是加入了些监控功能,那么这些错误从哪里来呢? 是 bash 的锅吗 从上面的错误信息可以猜测,异常是 bash 在启动过程中分配内存失败所导致,看起来像是某些情况下该进程错误地进行了大量内存分配,最后导致内存不足,要确认这个事情比较简单,动态内存分配到系统调用这一层上主要就两种方

主进程被杀死时,如何保证子进程同时退出,而不变为孤儿进程(一)

在Python中,由于全局解释器锁GIL的存在,使得Python中的多线程并不能大大提高程序的运行效率(这里单指CPU密集型),那么在处理CPU密集型计算时,多用多进程模型来处理,而Python标准库中提供了multiprocessing库来支持多进程模型的编程.multiprocessing中提供了的Process类用于开发人员编写创建子进程,接口类似于标准库提供的threading.Thread类,还提供了进程池Pool类,减少进程创建和销毁带来开销,用以提高复用(见前文). 在多线程模型中

如何用supervisor守护php-fpm主进程以实现php-fpm的自动重启

最近有同事有个针对php-fpm进程的监护需求,也即:如果php-fpm的master进程意外退出(可能是crash,也可能是被误kill),那么希望master进程能被自动拉起,以免中断服务. 我们知道,supervisor是一个非常强大的进程监控(monitor & control)工具,它理论上可以实现php-fpm master进程的守护需求.因此,我帮同事试验了如何用supervisor完成他的需求,结果表明,supervisor确实是神器,只需一个合理的配置文件,它就能解决问题. 下

实验六———分析Linux内核创建一个新进程的过程

分析Linux内核创建一个新进程的过程 攥写人:李鹏举  学号:20132201 ( *原创作品转载请注明出处*) ( 学习课程:<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ) 本周要求: 阅读理解task_struct数据结构http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h#1235: 分析fork函数对应的内核处理过

实验六:分析Linux内核创建一个新进程的过程

20135108 李泽源 阅读理解task_struct数据结构http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h#1235: 分析fork函数对应的内核处理过程sys_clone,理解创建一个新进程如何创建和修改task_struct数据结构: 使用gdb跟踪分析一个fork系统调用内核处理函数sys_clone ,验证您对Linux系统创建一个新进程的理解,推荐在实验楼Linux虚拟机环境下完成实验. 特别

《Linux内核分析》 第六节 分析Linux内核创建一个新进程的过程

范闻泽 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.实验过程 1.删除原来的menu,并clone新的menu,用test_fork.c覆盖test.c 2. make rootfs之后新的内核启动,测试fork功能 3.使用-s -S冷冻内核,准备调试 4.设置断点 5.根据断点,进行跟踪,得到结果 1.以下是执行指令 cd LinuxKernel rm menu -r