关于程序崩溃时转储内存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. Nmap扫描教程之基础扫描详解

    Nmap扫描教程之基础扫描详解 Nmap扫描基础扫描 当用户对Nmap工具了解后,即可使用该工具实施扫描.通过上一章的介绍,用户可知Nmap工具可以分别对主机.端口.版本.操作系统等实施扫描.但是,在 ...

  2. JDBC之 大数据内容的传输

    JDBC之 大数据内容的传输 什么是大数据内容? 在数据库中,有一条一条的记录,记录中很多字段都是几个字符就够的,假如现在要把一部小说存入数据库,这本小说当然不是几个字符组成,而是由几万字组成,这本小 ...

  3. BZOJ.3522.[POI2014]Hotel(DP)

    题目链接 BZOJ 洛谷 以为裸点分治,但数据范围怎么这么小?快打完了发现不对.. n^2做的话其实是个水题.. 枚举每一个点为根,为了不重复计算,我们要求所求的三个点必须分别位于三棵子树上. 考虑当 ...

  4. HDU.5819.Knights(概率DP)

    题目链接 参考一下这的. \(Description\) 数轴上有n个骑士,分别位于1,2,3,...,n,它们的移动速度相同,初始移动方向已知.当两个骑士相遇时,各有50%的概率获胜,失败的骑士就死 ...

  5. Lua打印table树形结构

    --这是quick中的工具,作用就是打印Lua中强大的table的结构, 当table的嵌套层级比较多的时候,这个工具非常方便,开发中必备的工具.--具体使用方法:local debug = requ ...

  6. javascript 正则限制文本输入框只允许输入数字,简单实现。

    <input type="text" id="memberId" lay-verify="title" autocomplete=&q ...

  7. allowDefinition='MachineToApplication错误

    配置错误说明: 在处理向该请求提供服务所需的配置文件时出错.请检查下面的特定错误详细信息并适当地修改配置文件. 分析器错误信息: 在应用程序级别之外使用注册为 allowDefinition='Mac ...

  8. HDU 4764 Stone (2013长春网络赛,水博弈)

    Stone Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submi ...

  9. STM32 Timer : Auto-reload register register

    Auto-reload register (TIMx_ARR) The auto-reload register is preloaded. Writing to or reading from th ...

  10. Serial Wire Debugging the STM32 via the Bus Pirate

    Serial Wire Debugging the STM32 via the Bus Pirate 2 October 2010 Step 1 - The Bus Pirate Step 2 - D ...