崩溃(Crash)的原因是什么
在Windows中,调用?CreateProcess ?函数启动主线程或?CreateThread ?启动线程时,线程函数会在如下代码中运行(下面的代码引自《Windows核心编程》第25章未处理异常和C++异常)
// 主线程启动函数
// CreateProcess启动线程函数
VOID BaseProcessStart(PPROCESS_START_ROUTINE pfnSatrtAddr){
__try{
ExitThread((pfnSatrtAddr)());
}
__except(UnHandledExceptionFilter(GetExceptionInformation())){
ExitProcess(GetExceptionCode());
}
}
// CreateThread启动线程函数
VOID BaseThreadStart(PTHREAD_START_ROUTINE pfnSatrtAddr, PVOID pvParam){
__try{
ExitThread((pfnSatrtAddr)());
}
__except(UnHandledExceptionFilter(GetExceptionInformation())){
ExitProcess(GetExceptionCode());
}
}
会调用?UnHandledExceptionFilter(GetExceptionInformation()) ?来过滤异常信息,然后调用?ExitProcess(GetExceptionCode()); ?退出进程的运行。
也就是说,当线程中出现未捕获异常时,系统/程序就会Crash,蓝屏信息和异常信息对话框的显示都是在UnHandledExceptionFilter(GetExceptionInformation()) ?这句代码中出现的。
如何消除崩溃时出现的异常信息对话框
那么,如何消除程序崩溃时出现的异常信息对话框呢?我们有三种办法来处理。
1. 在每个线程中调用 SetErrorMode(SEM_NOGPFAULTERRORBOX) 来让 UnHandledExceptionFilter(GetExceptionInformation()) 调用时直接返回 EXCEPTION_EXECUTE_HANDLER,不显示异常信息框,直接退出程序。
2. 在每个线程中使用 __try{...}__except{...} 块,捕获未捕获异常,自行处理未捕获异常。
3. 使用自定义的 UnHandledExceptionFilter 函数替换Windows默认的 UnHandledExceptionFilter 函数,自行处理未捕获异常。
这三种方法中,最优的方法是第三种,下面我们就来讨论如何使用第三种方式捕获崩溃(Crash)时的异常,生成dump文件来帮助寻找异常。
如何捕获崩溃(Crash)时的异常,生成dump文件来帮助寻找异常
一般而言,Windows程序都是运行在客户的电脑上的,当程序出现Crash时,如果没有使程序生成dump文件的话,就只能远程控制客户的电脑生成,或者当程序出现Crash的时候,让客户不操作电脑,使用windbg之类的工具调试程序来寻找崩溃点。但是,这样也太麻烦了,而且影响客户对软件的使用。所以,我们让程序在Crash时自动生成dump文件,然后在客户处将dump文件获取到手,最后分析dump文件来寻找Crash原因就好了。
那么,如何让程序在Crash时自动生成dump文件呢?
这就用到了我们上一节讨论到的使用自定义的 MyUnhandledExceptionFilter 函数替换Windows默认的 UnHandledExceptionFilter 函数,自行处理未捕获异常了。
然后在调用函数运行下面代码即可:
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
上述代码的作用就是使用自定义的?MyUnhandledExceptionFilter ?函数来替换Windows默认的?UnHandledExceptionFilter ?函数,来达到在程序出现Crash时自动生成dump文件的结果。
但是,在出现未处理CRT异常时,CRT代码中会调用
SetUnhandledExceptionFilter(UNLL);
来还原我们的 UnHandledExceptionFilter 函数,所以在出现未捕获的CRT异常时还是不能生成dump文件,那么要怎么处理呢?
我们可以通过改写kernel32.dll中的 UnHandledExceptionFilter 函数的地址来使未捕获的CRT异常还是调用到我们自定义的 UnHandledExceptionFilter 函数这样来处理。
步骤如下
1.遍历程序的输入节,找到kernel32.dll的位置。
2.遍历kernel32.dll的输入节,找到UnHandledExceptionFilter函数地址。
3.改写该地址所指向的函数位置,重指向自定义的UnHandledExceptionFilter函数。
完整代码如下:
#include <windows.h>
#include <DbgHelp.h>
#include <stdlib.h>
#pragma comment(lib, "dbghelp.lib")
inline BOOL IsDataSectionNeeded(const WCHAR* pModuleName)
{
if(pModuleName == 0)
{
return FALSE;
}
WCHAR szFileName[_MAX_FNAME] = L"";
_wsplitpath(pModuleName, NULL, NULL, szFileName, NULL);
if(wcsicmp(szFileName, L"ntdll") == 0)
return TRUE;
return FALSE;
}
inline BOOL CALLBACK MiniDumpCallback(PVOID pParam,
const PMINIDUMP_CALLBACK_INPUT pInput,
PMINIDUMP_CALLBACK_OUTPUT pOutput)
{
if(pInput == 0 || pOutput == 0)
return FALSE;
switch(pInput->CallbackType)
{
case ModuleCallback:
if(pOutput->ModuleWriteFlags & ModuleWriteDataSeg)
if(!IsDataSectionNeeded(pInput->Module.FullPath))
pOutput->ModuleWriteFlags &= (~ModuleWriteDataSeg);
case IncludeModuleCallback:
case IncludeThreadCallback:
case ThreadCallback:
case ThreadExCallback:
return TRUE;
default:;
}
return FALSE;
}
inline void CreateMiniDump(PEXCEPTION_POINTERS pep, LPCTSTR strFileName)
{
HANDLE hFile = CreateFile(strFileName, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE))
{
MINIDUMP_EXCEPTION_INFORMATION mdei;
mdei.ThreadId = GetCurrentThreadId();
mdei.ExceptionPointers = pep;
mdei.ClientPointers = NULL;
MINIDUMP_CALLBACK_INFORMATION mci;
mci.CallbackRoutine = (MINIDUMP_CALLBACK_ROUTINE)MiniDumpCallback;
mci.CallbackParam = 0;
::MiniDumpWriteDump(::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, MiniDumpNormal, (pep != 0) ? &mdei : 0, NULL, &mci);
CloseHandle(hFile);
}
}
LONG __stdcall MyUnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo)
{
CreateMiniDump(pExceptionInfo, L"core.dmp");
return EXCEPTION_EXECUTE_HANDLER;
}
// 此函数一旦成功调用,之后对 SetUnhandledExceptionFilter 的调用将无效
void DisableSetUnhandledExceptionFilter()
{
void* addr = (void*)GetProcAddress(LoadLibrary(L"kernel32.dll"),
"SetUnhandledExceptionFilter");
if (addr)
{
unsigned char code[16];
int size = 0;
code[size++] = 0x33;
code[size++] = 0xC0;
code[size++] = 0xC2;
code[size++] = 0x04;
code[size++] = 0x00;
DWORD dwOldFlag, dwTempFlag;
VirtualProtect(addr, size, PAGE_READWRITE, &dwOldFlag);
WriteProcessMemory(GetCurrentProcess(), addr, code, size, NULL);
VirtualProtect(addr, size, dwOldFlag, &dwTempFlag);
}
}
void InitMinDump()
{
//注册异常处理函数
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
//使SetUnhandledExceptionFilter
DisableSetUnhandledExceptionFilter();
}
int _tmain(int argc, _TCHAR* argv[])
{
InitMinDump();
int x=0;
int y = 5;
int z = y/x;
return 0;
}
|