关于程序崩溃时转储内存DMP,可以设置注册表,使程序崩溃时自动转储内存DMP,见程序崩溃时利用注册表自动转储内存DMP。本文要介绍的是使用SetUnhandledExceptionFilter函数在程序崩溃时取得程序内存DMP,并解决一些困扰人的问题。

  从名字上就可以看出SetUnhandledExceptionFilter的作用就是设置未捕获异常函数,程序崩溃就是因为有些异常我们没有捕获,而当这些异常我们没捕获时,系统就会调用SetUnhandledExceptionFilter设置的函数,在此函数中可以进行一些操作,比如弹出对话框、打印语句等。关于SetUnhandledExceptionFilter更详细的信息,参见MSDN,这里不作详细介绍。

  见代码:

  1. LONG WINAPI ExpFilter(struct _EXCEPTION_POINTERS *pExp)
  2. {
  3. cout << "Unhandled Exception!!!" << endl;
  4. return EXCEPTION_EXECUTE_HANDLER;
  5. }
  6. void StartUnhandledExceptionFilter()
  7. {
  8. ::SetUnhandledExceptionFilter(ExpFilter);
  9. }
  10. int main()
  11. {
  12. cout << "begin !" << endl;
  13. StartUnhandledExceptionFilter();
  14. int i = 0;
  15. i = i / i;
  16. cout << "end !" << endl;
  17. getch();
  18. return 0;
  19. }
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,我们就可以分析程序崩溃的原因了。

  1. LONG WINAPI ExpFilter(struct _EXCEPTION_POINTERS *pExp)
  2. {
  3. char szExec[256];
  4. sprintf(szExec, "ntsd -c \".dump /f c:\\123.dmp;q\" -p %d",
  5. ::GetCurrentProcessId());
  6. WinExec(szExec, SW_SHOWNORMAL);
  7. Sleep(1000);
  8. return EXCEPTION_EXECUTE_HANDLER;
  9. }
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,保存在C盘根目录123.dmp,注意WinExec执行了ntsd语句后,要Sleep一段时间,因为WinExec是异步的,执行ntsd时可能主程序已经退出了,导致ntsd找不到指定的程序,无法生成DMP。

  以上是用ntsd得到程序的DMP,还可以利用Dbghelp.dll提供的MiniDumpWriteDump函数取得程序的DMP,代码如下:

  1. LONG WINAPI ExpFilter(struct _EXCEPTION_POINTERS *pExp)
  2. {
  3. HANDLE hFile = ::CreateFile(
  4. "c:\\123.dmp",
  5. GENERIC_WRITE,
  6. 0,
  7. NULL,
  8. CREATE_ALWAYS,
  9. FILE_ATTRIBUTE_NORMAL,
  10. NULL);
  11. if(INVALID_HANDLE_VALUE != hFile)
  12. {
  13. MINIDUMP_EXCEPTION_INFORMATION einfo;
  14. einfo.ThreadId          = ::GetCurrentThreadId();
  15. einfo.ExceptionPointers = pExp;
  16. einfo.ClientPointers    = FALSE;
  17. ::MiniDumpWriteDump(
  18. ::GetCurrentProcess(),
  19. ::GetCurrentProcessId(),
  20. hFile,
  21. MiniDumpWithFullMemory,
  22. &einfo,
  23. NULL,
  24. NULL);
  25. ::CloseHandle(hFile);
  26. }
  27. return EXCEPTION_EXECUTE_HANDLER;
  28. }
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;
}

  使用MiniDumpWriteDump需要加头文件Dbghelp.h和链接文件Dbghelp.lib。MiniDumpWriteDump中第四个参数可以设置取得DMP的类型,例子中是取得所有内存DMP。

  现在来试下用windbg打开DMP,看看程序的堆栈,看是否能找到导致程序崩溃的地方,以下例子使用执行ntsd语句版本的ExpFilter:

  OK,成功打开DMP!仔细看看程序的堆栈,好像不对。main函数第6行产生异常,但堆栈中没有,却直接跳到了ExpFilter函数中执行ntsd的的地方。这是因为main函数并非程序最开始执行的函数,链接器在链接可执行文件时,选择了正确的C/C++运行库运行函数,在此运行库函数中才调用的main函数,查看堆栈,可以知道,此运行库函数为mainCRTStartup,此函数的相关代码(VC++6.0下为crtexe.c)如下:

  1. __try {
  2. ...
  3. }
  4. __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
  5. {
  6. _exit( GetExceptionCode() );
  7. }
__try {
...
}
__except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
{
_exit( GetExceptionCode() );
}

  省略号的地方就是调用main的地方,可以看到,调用main的地方被__try、__except包起来了,产生异常时,首先会调用_XcptFilter,之后的第二步才到我们设置的函数,堆栈自然也就变了。

  查找__try、__except的资料可知,__except后面跟一个表达式,表达式的值与SetUnhandledExceptionFilter设置的未捕获异常函数返回值的意义一样,如果设置为EXCEPTION_CONTINUE_SEARCH,表示异常没有被识别到,异常继续往上层抛,至到SetUnhandledExceptionFilter设置的未捕获异常函数。有了这些资料,我们就可以解决不能正确显示堆栈的问题了:

  1. int MyXcptFilter()
  2. {
  3. return EXCEPTION_CONTINUE_SEARCH;
  4. }
  5. void StartUnhandledExceptionFilter()
  6. {
  7. ::SetUnhandledExceptionFilter(ExpFilter);
  8. void *_XcptFilter = (void*)GetProcAddress(
  9. LoadLibrary("msvcrt.dll"), "_XcptFilter");
  10. DWORD dwOldProtect;
  11. VirtualProtect(_XcptFilter, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
  12. *(char*)_XcptFilter = 0xe9;
  13. *(unsigned int*)((char*)_XcptFilter + 1) =
  14. (unsigned int)MyXcptFilter - ((unsigned int)_XcptFilter + 5);
  15. VirtualProtect(_XcptFilter, 5, dwOldProtect, &dwOldProtect);
  16. }
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);
}

  修改msvcrt.dll中函数_XcpFilter,使调用_XcpFilter时直接跳转到我们自己的函数MyXcptFilter去执行,此函数返回EXCEPTION_CONTINUE_SEARCH,因此__except后面表达式就为EXCEPTION_CONTINUE_SEARCH,表示此异常未能被识别,进而调用ExpFilter,堆栈也被保存下来了,结果如下:

  这下就OK了,我们就可以知道哪里产生异常了,查看Locals,可以看到i的值为0。

  注意:如果此时显示的堆栈还不正确,可能是因为没有加载kernel32.dll等文件的pdb,需要从微软官网下载,将”srv*downstreamstore*http://msdl.microsoft.com/download/symbols“加入到Symbol File Path中,windbg即可自动从微软官网下载相应版本的pdb文件。

  我们还可以这样处理:

  1. void StartUnhandledExceptionFilter()
  2. {
  3. ::SetUnhandledExceptionFilter(ExpFilter);
  4. void *_XcptFilter = (void*)GetProcAddress(
  5. LoadLibrary("msvcrt.dll"), "_XcptFilter");
  6. DWORD dwOldProtect;
  7. VirtualProtect(_XcptFilter, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
  8. VirtualProtect(_XcptFilter, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
  9. *(char*)_XcptFilter       = 0x33;
  10. *((char*)_XcptFilter + 1) = 0xc0;
  11. *((char*)_XcptFilter + 2) = 0xc2;
  12. *((char*)_XcptFilter + 3) = 0x00;
  13. *((char*)_XcptFilter + 4) = 0x00;
  14. VirtualProtect(_XcptFilter, 5, dwOldProtect, &dwOldProtect);
  15. }
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,不过不是让其跳转,而是让其直接返回EXCEPTION_CONTINUE_SEARCH的值0,也可以达到目的。

  如果不处理_XcptFilter,非主线程产生异常后的堆栈也不一样,比如用_beginthread创建线程的堆栈就不对,而用CreateThread创建线程的堆栈却是对的,至于原因,有兴趣的可以试试,在非主线程中产生异常,然后分析DMP,一看就明白了。

  以上测试例子在VC++6.0环境中编译,其他编译器略有不同,具体环境具体分析。

使用SetUnhandledExceptionFilter转储程序崩溃时内存DMP .的更多相关文章

  1. 使用SetUnhandledExceptionFilter转储程序崩溃时内存DMP注意事项

    使用代码手工生成dmp文件 SetUnhandledExceptionFilter 为每个线程设置SetUnhandledExceptionFilter(MyCallBack),(必须在每个线程中启动 ...

  2. 如何在.NET程序崩溃时自动创建Dump?

    今天在浏览张队转载文章的留言时,遇到一个读者问了这样的问题,如下图所示: 首先能明确的一点是"程序崩溃退出了是不能用常规的方式dump的",因为整个进程树都已经退出.现场已经无法使 ...

  3. java程序运行时内存分配详解

    java程序运行时内存分配详解 这篇文章主要介绍了java程序运行时内存分配详解 ,需要的朋友可以参考下   一. 基本概念 每运行一个java程序会产生一个java进程,每个java进程可能包含一个 ...

  4. Java程序运行时内存划分

    1.Java程序跨平台运行的原因 主要原因是:各种平台的JVM和字节码文件 Java源程序--具体平台的机器代码文件---被编译器翻译成平台无关的Class文件,又用特定JVM运行字节码文件,JVM在 ...

  5. 让linux中的程序崩溃时生成core文件

    当我们的linux程序崩溃的时候,常常会有这样的提示:    Segmentation fault (core dumped)    段错误 (核心已转储)    提示说生成了core文件,但是此功能 ...

  6. android在程序崩溃时Catch异常并处理

    Android系统的"程序异常退出",给应用的用户体验造成不良影响.为了捕获应用运行时异常并给出友好提示,便可继承UncaughtExceptionHandler类来处理.通过Th ...

  7. java程序运行时内存分配详解 (转)

    转自:http://www.tuicool.com/articles/uU77v2 一.  基本概念 每运行一个java程序会产生一个java进程,每个java进程可能包含一个或者多个线程,每一个Ja ...

  8. C++ 程序崩溃时生成Dump文件

    #include <DbgHelp.h> //生产DUMP文件 int GenerateMiniDump(HANDLE hFile, PEXCEPTION_POINTERS pExcept ...

  9. <转>关闭 程序崩溃时 windows 正在检查该问题的解决方案

    本文转自:http://www.cnblogs.com/dabaopku/archive/2011/07/04/2097029.html 尤其是使用visual studio开发程序 ,自己特意thr ...

随机推荐

  1. BZOJ2278 : [Poi2011]Garbage

    如果两个环相交,那么相交的部分相当于没走. 因此一定存在一种方案,使得里面的环都不相交. 把不需要改变状态的边都去掉,剩下的图若存在奇点则无解. 否则,每找到一个环就将环上的边都删掉,时间复杂度$O( ...

  2. JVM进程cpu飙高分析

    在项目快速迭代中版本发布频繁  近期上线报错一个JVM导致服务器cpu飙高 但内存充足的原因现象.  对于耗内存的JVM程序来而言,  基本可以断定是线程僵死(死锁.死循环等)问题. 这里是纪录一下排 ...

  3. 如果想使用GIT Extentions的解决冲突窗口,安装时必须勾选KDIFF3

    因为第一次安装时,没有选择同时安装KDIFF3,所以遇到冲突时,点击合并,始终无法弹出合并窗口. 还有一个问题,就是在安装时,要选择OpenSSH,不要选择PuTTY.

  4. 0xWS2812 STM32 driver for WS2812(B) RGB LEDs

    0xWS2812 STM32 driver for WS2812(B) RGB LEDs 0xWS2812 pronounced "hex-WS2812" This code ai ...

  5. STM32 CRC-32 Calculator Unit

    AN4187 - Using the CRC peripheral in the STM32 family At start up, the algorithm sets CRC to the Ini ...

  6. kaleidoscope-llvm

    http://kaleidoscope-llvm-tutorial-zh-cn.readthedocs.io/zh_CN/latest/chapter-1.html

  7. python脚本后台执行

    在Linux中,可以使用nohup将脚本放置后台运行,如下: nohup python myscript.py params1 > nohup.out 2>&1 & 1 但 ...

  8. Android adb logcat使用技巧

    前言 新买的笔记本E431装了最新版的Eclipse,搞定了Android开发环境,可是logcat里查看东西居然仅仅显示level,没有错误的具体信息.我本身也不是一个愿意折腾图形界面,更喜欢纯命令 ...

  9. C#写的COM组件注册问题兼论微软Regasm注册的BUG

    工作中自己用C#写了专门读写EXCEL(不需要OFFICE环境,直接读原始文件,速度快)的COM组件,在使用过程中,发现原先的注册程序是有问题的.网上也有同样的网友碰到这个问题,但都没找到合适的解决办 ...

  10. poi workbook转成流

    try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); workbook.write(bos); byte[] barray = ...