程序崩溃的善后工作
分类:技术
说来,写 C/C++ 的程序,由于指针的存在,程序崩溃什么的也就没什么大惊小怪的了。人非圣贤,孰能无过嘛,而且个人觉得程序崩溃比出现错误的结果好调试多了:在 Visual Studio 里 Debug 版本 F5 调试运行直接可以断在崩溃的地方,方便调试。但 Release 版本就没这么幸运了 :(
如果说单纯是是调试 Release 版本,我只用过《游戏之旅》中介绍的勾选 Linker 选项中的 Generate Map File,然后通过崩溃提示信息中提供的 EIP
查这个 Map File 找到崩在哪个函数里,兴致高一点的根据反汇编一步步走下去兴许还能知道是崩在哪句上 :)
不过说到最终交付出去的程序,面对可能存在的各种未知问题,还是生成 Dump 文件,把崩溃那一刻的信息写进文件以供日后分析比较靠谱。
捕捉未捕获的异常
好吧,Windows API 提供了 SetUnhandledExceptionFilter
函数来设置在发生未捕获的异常时调用的回调函数(仅在程序不处于调试运行时调用)。例如设置 CrashCallback
函数:
#include <windows.h>
LONG WINAPI CrashCallback(EXCEPTION_POINTERS *exceptionInfo)
{
// 崩溃处理
return EXCEPTION_EXECUTE_HANDLER;
}
int main()
{
SetUnhandledExceptionFilter(CrashCallback);
// 程序段
}
回调时传入的参数 exceptionInfo
保存了关于该异常的详细信息,不过 Dump 的输出可以不需要亲自干预太多。
生成 Dump 文件
利用上面回调中给出的 EXCEPTION_POINTERS
结构指针提供的信息,MiniDumpWriteDump
函数即可按要求输出一个 Dump 文件内容,其原型:
BOOL WINAPI MiniDumpWriteDump(
HANDLE hProcess, // Dump 目标进程句柄
DWORD ProcessId, // Dump 目标进行 ID
HANDLE hFile, // 输出文件句柄
MINIDUMP_TYPE DumpType, // 输出类型,决定输出哪些内容
MINIDUMP_EXCEPTION_INFORMATION *ExceptionParam,
MINIDUMP_USER_STREAM_INFORMATION *UserStreamParam,
MINIDUMP_CALLBACK_INFORMATION *CallbackParam
);
输出 Dump 文件的内容根据 DumpType
参数变化,详见 MSDN 关于 MINIDUMP_TYPE
的条目。这里举例输出一个最小的 Dump 文件:
HANDLE hFile = CreateFile(TEXT("filename.dmp"), GENERIC_READ | GENERIC_WRITE,
0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if ((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE)) {
MINIDUMP_EXCEPTION_INFORMATION mdei;
mdei.ThreadId = GetCurrentThreadId();
mdei.ExceptionPointers = exceptionInfo;
mdei.ClientPointers = FALSE;
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
hFile, MiniDumpNormal, &mdei, NULL, NULL);
CloseHandle(hFile);
}
需要注意的是,MiniDumpWriteDump
等声明在 DbgHelp.h
中,需要链接 DbgHelp.lib
。当然也可以自行从 DbgHelp.dll
中 LoadLibrary
之。
Dump 文件的使用
Dump 文件是可以用 WinDbg 打开的,不过因为手头没有这东西所以没有试过 =3=
Dump 文件也可以用 Visual Studio 打开,而且(似乎)方便一些:把 dmp 文件、exe 文件、pdb 文件放在同一目录中,然后用 Visual C++ 打开 dmp 文件即出现 Minidump File Summary 页面。可以查看异常信息,或者使用右侧的调试按钮开始调试运行并直接断在崩溃处。
以上,就是好久以来的流水帐……