简单使用SetUnhandledExceptionFilter()函数让程序优雅崩溃
虽然是大公司的产品,QQ它还是会在我们的折腾下崩溃的,但是它总是崩溃的很优雅,还要弹出自己的对话框来结束。并且发送报告,去掉了系统默认的发送报告的对话框。
所以一拍脑袋,想让自己的程序崩溃的体面一点。
自己想了大概的思路,觉得可以用一个进程来监控目标程序。的确也可以拿到了目标程序崩溃的信息,知道它什么时候崩溃的,也可以做额外的操作,但是这样是没办法把默认的发送错误的对话框去掉的。
然后又有人说是不是采用了类似钩子的方法把这个东西在哪里勾掉了。
最后网上查了一番,发现SetUnhandledExceptionFilter这个函数解决了一切。
总结了下搜到的资料,这个函数的返回值有三种情况:
EXCEPTION_EXECUTE_HANDLERequ1表示我已经处理了异常,可以优雅地结束了
EXCEPTION_CONTINUE_SEARCHequ0表示我不处理,其他人来吧,于是windows调用默认的处理程序显示一个错误框,并结束
EXCEPTION_CONTINUE_EXECUTIONequ-1表示错误已经被修复,请从异常发生处继续执行
具体使用方法如下:
#include <windows.h>
long __stdcall callback(_EXCEPTION_POINTERS* excp)
{
MessageBox(0,"Error","error",MB_OK);
printf("Error address %x/n",excp->ExceptionRecord->ExceptionAddress);
printf("cpu register:/n");
printf("eax %x ebx %x ecx %x edx %x/n",excp->ContextRecord->Eax,
excp->ContextRecord->Ebx,excp->ContextRecord->Ecx,
excp->ContextRecord->Edx);
return EXCEPTION_EXECUTE_HANDLER;
}
int main(int argc,char* argv[])
{
SetUnhandledExceptionFilter(callback);
_asm int 3//只是为了让程序崩溃
return 0;
}
vs调试dump
>> 发布二进制文件时生成的pdb文件一定要保留,只有当发布的二进制文件和pdb文件是同时生成的才好正确调试。
>> 如果dump文件和pdb文件放在同一个目录,则可直接运行调试;当然也可以不是同一个目录,那么在启动dmp文件后,需要设置一下vs的符号文件路径:Tools->Options->Debugging->Symbols. 如果需要调试windows自带的一些dll或者exe,则可以在这里添加windows的pdb文件服务器:http://msdl.microsoft.com/download/symbols
3. 二进制文件放在哪里的问题
>> 现场恢复需要二进制文件,但不必所有的二进制文件都需要,所以即使你的机器和用户的机器操作系统不一样也没关系;出问题的如果是你发布的二进制文件,则只需要你发布的二进制文件就可以了。vs在加载二进制的文件失败的时候会打印出其详细路径,但这是用户机器上的路径,没有必要一定要跟这个路径一样,把你发布的二进制文件放到dump文件目录就可以了。
>> 首先需要设置源代码目录,右键solution:Properties->Common Properties->Debug Source Files,里边加入你的本地源代码目录就是了;但是如果代码已经改过了,恢复不到当时的状态,vs显示不了源码怎么办?只要设置:Tools->Options->Debugging->General->Require source files to exactly match the original version 这个复选框钩掉就可以了
Minidump方式保留程序崩溃现场
在Windows平台下用C++开发应用程序,最不想见到的情况恐怕就是程序崩溃,而要想解决引起问题的bug,最困难的应该就是调试release版本了。因为release版本来就少了很多调试信息,更何况一般都是发布出去由用户使用,crash的现场很难保留和重现。目前有一些方法可以解决:崩溃地址+ MAP文件;MAP文件;SetUnhandledExceptionFilter + Minidump。本文重点解决Minidump方式。
1、Minidump概念
minidump(小存储器转储)可以理解为一个dump文件,里面记录了能够帮助调试crash的最小有用信息。实际上,如果你在系统属性->高级->启动和故障恢复->设置->写入调试信息中选择“小内存转储(64 KB)”的话,当系统意外停止时都会在C:\Windows\Minidump\路径下生成一个.dmp后缀的文件,这个文件就是minidump文件,只不过这个是内核态的minidump。
我们要生成的是用户态的minidump,文件中包含了程序运行的模块信息、线程信息、堆栈调用信息等。而且为了符合其mini的特性,dump文件是压缩过的。
通过drwtsn32、NTSD、CDB等调试工具生成Dump文件,drwtsn32存在的缺点虽然NTSD、CDB可以完全解决,但并不是所有的操作系统中都安装了NTSD、CDB等调试工具。根据MiniDumpWriteDump接口,完全可以程序自动生成Dump文件。
当程序遇到未处理异常(主要指非指针造成)导致程序崩溃死,如果在异常发生之前调用了SetUnhandledExceptionFilter()函数,异常交给函数处理。MSDN中描述为:
Issuing SetUnhandledExceptionFilter replaces the existing top-level exception filter for all existing and all future threads in the calling process.
因而,在程序开始处增加SetUnhandledExceptionFilter()函数,并在函数中利用适当的方法生成Dump文件,即可实现需要的功能。
生成dump文件类(minidump.h)#pragma once
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
@H_715_301@
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
@H_600_403@
146
147
148
149
150
151
152
153
154
|
<em id=
"__mceDel"
>
#include <windows.h>
#include <imagehlp.h>
#include <stdlib.h>
#pragma comment(lib,"dbghelp.lib")
inline
BOOL
IsDataSectionNeeded(
const
WCHAR
* pModuleName)
{
if
(pModuleName == 0)
{
return
FALSE;
}
szFileName[_MAX_FNAME] = L
""
;
_wsplitpath(pModuleName,NULL,szFileName,NULL);
(wcsicmp(szFileName,L
"ntdll"
) == 0)
TRUE;
FALSE;
}
CALLBACK MiniDumpCallback(
PVOID
pParam,
const
PMINIDUMP_CALLBACK_INPUT pInput,
PMINIDUMP_CALLBACK_OUTPUT pOutput)
{
(pInput == 0 || pOutput == 0)
FALSE;
switch
(pInput->CallbackType)
{
case
ModuleCallback: <br> {
(pOutput->ModuleWriteFlags & ModuleWriteDataSeg)
(!IsDataSectionNeeded(pInput->Module.FullPath))
pOutput->ModuleWriteFlags &= (~ModuleWriteDataSeg); <br>
}<br>
break
;
IncludeModuleCallback:
IncludeThreadCallback:
ThreadCallback:
ThreadExCallback:
TRUE;
default
:<br>
;
}
FALSE;
}
//创建Dump文件
inline
void
CreateMiniDump(EXCEPTION_POINTERS* pep,
LPCTSTR
strFileName)
{
HANDLE
hFile = CreateFile(strFileName,GENERIC_WRITE,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE))
{
MINIDUMP_EXCEPTION_INFORMATION mdei;
mdei.ThreadId = GetCurrentThreadId();
mdei.ExceptionPointers = pep;
mdei.ClientPointers = FALSE;
MINIDUMP_CALLBACK_INFORMATION mci;
mci.CallbackRoutine = (MINIDUMP_CALLBACK_ROUTINE)MiniDumpCallback;
mci.CallbackParam = 0;
MINIDUMP_TYPE mdt = (MINIDUMP_TYPE)0x0000ffff;
MiniDumpWriteDump(GetCurrentProcess(),GetCurrentProcessId(),hFile,MiniDumpNormal,&mdei,&mci);
CloseHandle(hFile);
}
}
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
{
NULL;
}
PreventSetUnhandledExceptionFilter()
{
HMODULE
hKernel32 = LoadLibrary(_T(
"kernel32.dll"
));
(hKernel32 == NULL)
FALSE;
*pOrgEntry = GetProcAddress(hKernel32,
"SetUnhandledExceptionFilter"
);
(pOrgEntry == NULL)
FALSE;
unsigned
char
newJump[ 100 ];
DWORD
dwOrgEntryAddr = (
) pOrgEntry;
dwOrgEntryAddr += 5;
// add 5 for 5 op-codes for jmp far
*pNewFunc = &MyDummySetUnhandledExceptionFilter;
dwNewEntryAddr = (
) pNewFunc;
dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr;
newJump[ 0 ] = 0xE9;
// JMP absolute
memcpy
(&newJump[ 1 ],&dwRelativeAddr,
sizeof
(pNewFunc));
SIZE_T
bytesWritten;
bRet = WriteProcessMemory(GetCurrentProcess(),pOrgEntry,newJump,0);">(pNewFunc) + 1,&bytesWritten);
bRet;
}
LONG
WINAPI UnhandledExceptionFilterEx(
struct
_EXCEPTION_POINTERS
*pException)
{
TCHAR
szMbsFile[MAX_PATH] = { 0 };
::GetModuleFileName(NULL,szMbsFile,MAX_PATH);
* pFind = _tcsrchr(szMbsFile,monospace;min-height:auto;white-space:nowrap;color:#0000FF;">'\\'
);
(pFind)
{
*(pFind+1) = 0;
_tcscat(szMbsFile,_T(
"CreateMiniDump.dmp"
));
CreateMiniDump(pException,szMbsFile);
}
// TODO: MiniDumpWriteDump
FatalAppExit(-1,monospace;min-height:auto;white-space:nowrap;color:#0000FF;">"Fatal Error"
));
EXCEPTION_CONTINUE_SEARCH;
}
//运行异常处理
RunCrashHandler()
{
SetUnhandledExceptionFilter(UnhandledExceptionFilterEx);
PreventSetUnhandledExceptionFilter();
}
</em>
|
//测试实现文件
CrashTest
public
:
{
}
:
Crash()
strcpy
(NULL,monospace;min-height:auto;white-space:nowrap;color:#0000FF;">"adfadfg"
);
};
int
_tmain(
argc,_TCHAR* argv[])
{
RunCrashHandler();
CrashTest test;
test.Test();
getchar
();
0;