目录
4、如何转储 dump [自己监控异常; 另起进程 监控异常]
一、初步诊断:
通过 ‘任务管理器’ 查看软件的状态 与 CPU 使用情况
1、死循环:
软件状态:未响应
CPU:一直保持非0,处于活跃状态
原理:
如果主线程出现死循环,那么windows将不能从消息队列中取出消息,并进行处理,所以出现卡死现象。为了验证是这个原因导致界面卡死,打开任务管理器,如果该进程的cpu使用率一直保持非零,比如一直保持在 12%,那么界面卡死的原因是主线程死循环了。
2、死锁:
软件状态:正在运行
CPU:进程的 cpu 使用率一般是0
原理:
如果主线程由于跟其他的线程由于争夺资源或者锁,出现了死锁,那么主线程会一直等待资源或者锁,导致主线程不能继续往下执行,分发和处理消息,所以出现卡死
二、进一步定位分析:
1、初步诊断为 死循环 时,可转存为 Dump 进行分析
2、死锁 情况就比较复杂:
--- 使用 “任务管理器” 来检测一个程序是否陷入死锁
--- 打开 【任务管理器 - 详细信息 - 分析等待链】
--- 分析结果如下,可以看到当前测试程序处于死锁状态
--- 最后,对其进行【创建转储文件】转换为dump,再通过WinDbg对dump进行分析
3、死锁的定位和分析
如果死锁的程序刚好在我们自己的开发机器上,那么使用 WinDbg 的 Attach To A Proccess 功能将死锁程序直接附加到 WinDbg 中进行分析;
如果不在我们的机器上,可以通过 Windows 的 资源管理器 对进行进行 创建转储文件 转换为 dump(注意32位和64位的区别),再通过 WinDbg 对 dump进行分析。
为了方便,我们直接以 Attach 方式讲解。
Attach 程序后,输入 !locks 命令查看锁的状态:
总共有两个锁cs1(012e4394)和cs2(012e437c),这两个锁的LockCount(表示还有多少个线程在等待这个临界区)都为1,说明这两个锁都处于等待状态。
再输入~*kb查看一下当前各个线程的堆栈:
线程2534和线程1920都处于等待状态,线程2534等待锁012e437c,线程1920等待锁012e4394。
再结合上面的锁信息可以知道,锁012e4394被线程2534拥有,锁012e437c被线程1920拥有,所以这两个线程陷入了互相等待的死锁中。
等待锁的源码也直接定位到了,问题解决。
4、如何转储 dump [自己监控异常; 另起进程 监控异常]
A>、自己监控异常
MS 为我们提供了一个dbghelp.dll 的动态库
通过其提供的 MiniDumpWriteDump方法,我们可以进行dump的转储。
什么时候转储 dump 呢?
这里来介绍一下SetUnhandledExceptionFilter这个函数,当异常没有处理的时候,系统就会调用 SetUnhandledExceptionFilter 所设置异常处理函数,通过传入的回调函数来进行一些处理。
那么这个时候,我们就可以在回调函数中对当前程序的进程信息转储dump了。
缺陷:
这种方式有一些无能为力的时候,如果程序自身出现了无响应,整个进程卡死了,或者是程序的主线程中出现了死循环,以及程序在运行时闪退了
这些时候,程序自身是很难处理所出现的异常的,或者是根本没来得及处理
解决方法:另起进程来监控异常
B>、另起进程 监控异常
如果使用一个另外的程序来监测一个指定的程序,在其出现异常的时候,做一些后续的处理,那么首先面临的两个问题是:
问题一:用于监测的程序如何知道被监测的程序出现异常了;
问题二:当被监测的程序出现异常之后该如何处理。
问题一的解决方法:
Win32 有两个 API 函数可以帮助我们解决问题一,它们分别是 IsHungThread 和 IsHungAppWindow
它们可以根据所传入的进程句柄,来判断当前的进程是否处于正常响应的状态。
当被监测的程序出现了非正常响应,且满足一定的条件之后,我们可以认为它是出现了异常了,或者说不是一个理想的状态下了。
问题二的解决方法:
处理的目标同样是提示用户程序出现了异常,同时生成dump文件
MS为我们提供的另外一个工具:procdump,这个工具可以通过命令行来调用,比如对指定的进程进行dump转储,可以使用以下命令:
-ma 是转储参数,表示是以什么模式来进行转储。
ma 是全量转储,即将所有进程相关的信息都进行转储。
mp 是只转储有用的信息。具体介绍可以参考msdn介绍文档。
8978 是对应进程的进程id。
Test.dmp 是转储后dump文件的名称。
所以当我们知道被监测的程序出现异常之后,可以通过在程序中调用这个工具来对其进行dump的转储。
详情请查看: https://rdc.hundsun.com/portal/article/dump-607.html
扩展阅读:https://www.cnblogs.com/xybaby/p/8025435.html
三、、代码与图示:
1、死循环:
while (1){}
2、死锁:
#include <windows.h>
#include <iostream>
#include <thread>
#include <string>
#include <mutex>
#include <fstream>
using namespace std;
CRITICAL_SECTION cs1;
CRITICAL_SECTION cs2;
DWORD WINAPI Thread1(LPVOID lpParameter);
DWORD WINAPI Thread2(LPVOID lpParameter);
int main(int argc, TCHAR* argv[], TCHAR* envp[]){
//初始化关键代码段
InitializeCriticalSection(&cs1);
InitializeCriticalSection(&cs2);
//创建线程
HANDLE hThread1 = CreateThread(NULL, 0, Thread1, NULL, 0, NULL);
HANDLE hThread2 = CreateThread(NULL, 0, Thread2, NULL, 0, NULL);
//等待线程结束
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
Sleep(2000);
//关闭线程句柄
CloseHandle(hThread1);
CloseHandle(hThread2);
//释放关键代码段
DeleteCriticalSection(&cs1);
DeleteCriticalSection(&cs2);
return 0;
}
DWORD WINAPI Thread1(LPVOID lpParameter){
for (int i = 0; i < 10; i++){
EnterCriticalSection(&cs1);
Sleep(500);
EnterCriticalSection(&cs2);
std::cout << "Thread1" << std::endl;
LeaveCriticalSection(&cs2);
LeaveCriticalSection(&cs1);
}
return 1;
}
DWORD WINAPI Thread2(LPVOID lpParameter){
for (int i = 0; i < 10; i++){
EnterCriticalSection(&cs2);
Sleep(500);
EnterCriticalSection(&cs1);
std::cout << "Thread2" << std::endl;
LeaveCriticalSection(&cs1);
LeaveCriticalSection(&cs2);
}
return 1;
}