一个失败的终止
撞车事故时有发生。任何比“Hello world”更复杂的程序都可能有一些bug。专业软件开发的一个衡量标准是如何处理这些崩溃。程序应该保存一个崩溃转储,然后自杀(TerminateProcess()或_exit(),而不是ExitProcess()或exit())。
你不想让这个注定要失败的进程弹出一个对话框,说“嘿,我是一个注定要失败的进程”。但不幸的是,这是VisualC++C运行时间(VC++)在某些情况下所做的,正如我们看到的那样。
如果您不小心调用了一个纯虚拟函数,则此处理程序将打开一个对话框。如果你是一个开发人员,那么你可以附加一个调试器并获得一个调用堆栈,但世界上大多数人都不是开发人员。他们不知道什么是纯虚函数调用,也不在乎。显示此对话框只会减慢崩溃恢复过程,同时让用户感到困惑。
但更糟糕的是。如果你有一群异常处理程序准备捕捉Win32异常(访问冲突等),那么你会失望的,因为他们不会捕捉到纯粹的调用错误,即使有人按了OK。所以,你的内部崩溃转储记录系统对这个错误无能为力,这意味着它需要更长的时间来修复。
更更糟糕的是,如果这个错误发生在服务器上(我见过),那么您的无头服务器现在有一个挂起的进程正在等待某人单击“确定”。单元测试最终会超时,如果您有看门狗,服务器可能会超时,但是整个过程会被这个对话框延迟。
除非我能提供解决方案,否则我不会写这篇文章。上面的对话框是默认行为,但是一旦您知道应该更改默认值,就很容易了。您所要做的就是使用一个故意崩溃的函数调用_set_purecall_handler()。我最喜欢的实现是在TerminateProcess()后面执行一个_debugbreak()。如果我在调试器下运行,这会很好地将我放入其中,如果我不是,那么我未处理的异常过滤器将捕捉异常并写出一个小转储。TerminateProcess()用于阻止在调试器中捕获异常的用户尝试继续。
无效参数在技术上不是崩溃
VC++CRT检测到CRT函数的一些无效参数,并将它们视为致命错误。如果您使用更安全的CRT函数(并且您没有请求截断),这包括缓冲区溢出检测,但是触发这些检查的最简单方法是使用“printf(NULL);”。
不会弹出任何对话框(至少在发布版本中没有),并且进程被终止,但不会通过调用精心编制的异常处理程序来终止。Windows错误报告(WER)会收到问题的通知,这很好,但我希望这些无效参数像崩溃一样处理,以便调用异常处理程序。幸运的是,这个问题也有一个简单的解决方案。如果调用_set_invalid_parameter_handler(),则可以为它提供与纯调用处理程序相同的代码(只是带有不同的签名),以便异常处理程序注意到发生了错误。现在你的程序会比以前更糟糕。这是件好事。示例代码中也演示了这种技术。
WER是你的朋友
Windows错误报告(WER)是Windows内置的一个方便的功能。大多数开发人员都知道WER会在数百万用户的计算机上记录崩溃转储并将其存储起来,而且大多数开发人员也知道可以访问您的软件的崩溃转储。这是一个很好的方法,可以找出你的软件在实际客户的实际机器上实际崩溃的地方。虽然有一些困难需要克服,但还是值得一试的。不过,我不知道如何安排这样的访问,所以我就不多说了。
完整的文档可以在这里找到。如果您花两分钟来配置这个(我在c: empcrashdumps中将最近30次崩溃保存为full dumps),那么无论哪个进程崩溃,您都可以更好地调查计算机上的崩溃。
更新–更多失败类型
有些库将通过调用abort()来处理错误,这可能是进程在没有调用崩溃处理程序的情况下失败的另一种方式。调signal(SIGABRT,&AbortHandler);以安装一个处理程序,如果abort()被调用,该处理程序将被调用。Signal还可以用于安装其他类型故障的处理程序。
作业
请确保调用_set_purecall_处理程序、_set_invalid_parameter_handler和signal。如果你使用CRT的DLL版本,那么每个进程一次就可以了。如果您使用静态链接版本的CRT,那么您需要为每个CRT副本调用一次,对于静态链接CRT的每个DLL调用一次。这里提供的示例代码应该会有所帮助。
按照这里的简单说明,配置注册表以保存所有计算机上的崩溃转储。
如果您还没有,那么一定要按照上周文章中的说明进行操作,包括配置VS以在第一次出现异常时停止,调用enablecrashingonclass(),并使用SetUnhandledExceptionFilter()捕捉崩溃。
使用MiniDumpWriteDump或breakpad或Steamworks api设置一个记录和上传小型转储的系统。
就这样。祝你好运,目标是通过猛烈的崩溃来实现更稳定的软件。