关于程序崩溃时转储内存DMP,可以设置注册表,使程序崩溃时自动转储内存DMP,见程序崩溃时利用注册表自动转储内存DMP。本文要介绍的是使用SetUnhandledExceptionFilter函数在程序崩溃时取得程序内存DMP,并解决一些困扰人的问题。
从名字上就可以看出SetUnhandledExceptionFilter的作用就是设置未捕获异常函数,程序崩溃就是因为有些异常我们没有捕获,而当这些异常我们没捕获时,系统就会调用SetUnhandledExceptionFilter设置的函数,在此函数中可以进行一些操作,比如弹出对话框、打印语句等。关于SetUnhandledExceptionFilter更详细的信息,参见MSDN,这里不作详细介绍。 见代码:
LONG WINAPI ExpFilter(struct _EXCEPTION_POINTERS *pExp) { cout << "Unhandled Exception!!!" << endl; return EXCEPTION_EXECUTE_HANDLER; } void StartUnhandledExceptionFilter() { ::SetUnhandledExceptionFilter(ExpFilter); } int main() { cout << "begin !" << endl; StartUnhandledExceptionFilter(); int i = 0; i = i / i; cout << "end !" << endl; getch(); return 0; }
main函数的第6行“i = i / i;”语句,产生一个除数为0的异常,这个异常我们没有捕获(使用try、catch或__try、__except等),因此系统调用::SetUnhandledExceptionFilter设置的函数ExpFilter,此函数输出一个语句,然后返回EXCEPTION_EXECUTE_HANDLER,表明异常处理完毕,程序可以退出。 有了上面的经验,于是我们可以在ExpFilter函数中进行一些操作,保存程序的DMP,然后结合PDB,我们就可以分析程序崩溃的原因了。
LONG WINAPI ExpFilter(struct _EXCEPTION_POINTERS *pExp) { char szExec[256]; sprintf(szExec, "ntsd -c \".dump /f c:\\123.dmp;q\" -p %d", ::GetCurrentProcessId()); WinExec(szExec, SW_SHOWNORMAL); Sleep(1000); return EXCEPTION_EXECUTE_HANDLER; }
以上是用ntsd得到程序的DMP,还可以利用Dbghelp.dll提供的MiniDumpWriteDump函数取得程序的DMP,代码如下:
LONG WINAPI ExpFilter(struct _EXCEPTION_POINTERS *pExp) { HANDLE hFile = ::CreateFile( "c:\\123.dmp", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if(INVALID_HANDLE_VALUE != hFile) { MINIDUMP_EXCEPTION_INFORMATION einfo; einfo.ThreadId = ::GetCurrentThreadId(); einfo.ExceptionPointers = pExp; einfo.ClientPointers = FALSE; ::MiniDumpWriteDump( ::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, MiniDumpWithFullMemory, &einfo, NULL, NULL); ::CloseHandle(hFile); } return EXCEPTION_EXECUTE_HANDLER; }
现在来试下用windbg打开DMP,看看程序的堆栈,看是否能找到导致程序崩溃的地方,以下例子使用执行ntsd语句版本的ExpFilter: OK,成功打开DMP!仔细看看程序的堆栈,好像不对。main函数第6行产生异常,但堆栈中没有,却直接跳到了ExpFilter函数中执行ntsd的的地方。这是因为main函数并非程序最开始执行的函数,链接器在链接可执行文件时,选择了正确的C/C++运行库运行函数,在此运行库函数中才调用的main函数,查看堆栈,可以知道,此运行库函数为mainCRTStartup,此函数的相关代码(VC++6.0下为crtexe.c)如下:
__try { ... } __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) ) { _exit( GetExceptionCode() ); }
查找__try、__except的资料可知,__except后面跟一个表达式,表达式的值与SetUnhandledExceptionFilter设置的未捕获异常函数返回值的意义一样,如果设置为EXCEPTION_CONTINUE_SEARCH,表示异常没有被识别到,异常继续往上层抛,至到SetUnhandledExceptionFilter设置的未捕获异常函数。有了这些资料,我们就可以解决不能正确显示堆栈的问题了:
int MyXcptFilter() { return EXCEPTION_CONTINUE_SEARCH; } void StartUnhandledExceptionFilter() { ::SetUnhandledExceptionFilter(ExpFilter); void *_XcptFilter = (void*)GetProcAddress( LoadLibrary("msvcrt.dll"), "_XcptFilter"); DWORD dwOldProtect; VirtualProtect(_XcptFilter, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect); *(char*)_XcptFilter = 0xe9; *(unsigned int*)((char*)_XcptFilter + 1) = (unsigned int)MyXcptFilter - ((unsigned int)_XcptFilter + 5); VirtualProtect(_XcptFilter, 5, dwOldProtect, &dwOldProtect); }
这下就OK了,我们就可以知道哪里产生异常了,查看Locals,可以看到i的值为0。 注意:如果此时显示的堆栈还不正确,可能是因为没有加载kernel32.dll等文件的pdb,需要从微软官网下载,将”srv*downstreamstore*http://msdl.microsoft.com/download/symbols“加入到Symbol File Path中,windbg即可自动从微软官网下载相应版本的pdb文件。 我们还可以这样处理:
void StartUnhandledExceptionFilter() { ::SetUnhandledExceptionFilter(ExpFilter); void *_XcptFilter = (void*)GetProcAddress( LoadLibrary("msvcrt.dll"), "_XcptFilter"); DWORD dwOldProtect; VirtualProtect(_XcptFilter, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect); VirtualProtect(_XcptFilter, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect); *(char*)_XcptFilter = 0x33; *((char*)_XcptFilter + 1) = 0xc0; *((char*)_XcptFilter + 2) = 0xc2; *((char*)_XcptFilter + 3) = 0x00; *((char*)_XcptFilter + 4) = 0x00; VirtualProtect(_XcptFilter, 5, dwOldProtect, &dwOldProtect); }
如果不处理_XcptFilter,非主线程产生异常后的堆栈也不一样,比如用_beginthread创建线程的堆栈就不对,而用CreateThread创建线程的堆栈却是对的,至于原因,有兴趣的可以试试,在非主线程中产生异常,然后分析DMP,一看就明白了。 以上测试例子在VC++6.0环境中编译,其他编译器略有不同,具体环境具体分析。 |
|
来自: tianht > 《软件系统win32异常处理》