添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

关于程序崩溃时转储内存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;.detach;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,代码如下:

LONG WINAPI ExpFilter(struct _EXCEPTION_POINTERS *pExp)
	HANDLE hFile = ::CreateFile(
		"c:\\123.dmp", 
		GENERIC_WRITE, 
		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)如下:

__try {
__except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
	_exit( GetExceptionCode() );

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

  查找__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); 

  修改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文件。

  我们还可以这样处理:

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环境中编译,其他编译器略有不同,具体环境具体分析。

  更新一下,WinDbg上看到堆栈不对,可以像如上介绍的那样解决,但是也可以用DebugDiag来分析,分析后在“Recovered stack”里也是可以看到完整的堆栈,见WinDbg常用调试命令写在前面的话。

关于程序崩溃时转储内存DMP,可以设置注册表,使程序崩溃时自动转储内存DMP,见程序崩溃时利用注册表自动转储内存DMP。本文要介绍的是使用SetUnhandledExceptionFilter函数在程序崩溃时取得程序内存DMP,并解决一些困扰人的问题。  从名字上就可以看出SetUnhandledExceptionFilter的作用就是设置未捕获异常函数,程序崩溃就是因为有些异常我们没有捕
MiniDumpWriteDump是MS DbgHelp.dll 中一个API, 用于导出当前运行的程序Dump. 这个dll程序系统中就有, 但是很多软件, 都在自己的安装目录下保存了这个.dll的最新的版本. 为了测试这个API, 参考网上一些资料, 写了一个简单的C++ 程序. 目的是当有异常发生的候, 自动生成Dump文件供之后的分析. 有了Dump文件, 我们就可以使用WinDBG
// 异常处理函数 LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo) { // 生成dump文件的路径和名称 const char* filePath = "C:\\dump\\myapp.dmp"; // 创建dump文件 HANDLE hFile = CreateFileA(filePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile != INVALID_HANDLE_VALUE) { // 写入dump文件头部 MINIDUMP_EXCEPTION_INFORMATION exceptionInfo; exceptionInfo.ThreadId = GetCurrentThreadId(); exceptionInfo.ExceptionPointers = ExceptionInfo; exceptionInfo.ClientPointers = FALSE; // 写入dump文件内容 MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, &exceptionInfo, NULL, NULL); // 关闭dump文件 CloseHandle(hFile); // 继续执行程序 return EXCEPTION_CONTINUE_EXECUTION; 以上代码会在程序发生异常生成一个名为“myapp.dmp”的dump文件,路径为“C:\dump\”。你可以根据需要修改dump文件的名称和路径。注意,如果程序没有发生异常,dump文件不会生成。 另外,如果你使用的是Visual Studio,可以在项目属性的“Debugging”选项卡中设置生成dump文件的路径和名称。这样,在程序发生异常,Visual Studio会自动生成dump文件。