分类 调试 下的文章

一个失败的终止

撞车事故时有发生。任何比“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会在数百万用户的计算机上记录崩溃转储并将其存储起来,而且大多数开发人员也知道可以访问您的软件的崩溃转储。这是一个很好的方法,可以找出你的软件在实际客户的实际机器上实际崩溃的地方。虽然有一些困难需要克服,但还是值得一试的。不过,我不知道如何安排这样的访问,所以我就不多说了。

WER的一个鲜为人知的特性是,你可以让它记录你自己机器上的崩溃。你所要做的就是设置一些注册表项。我要在这里走开,说Windows上的每个C++开发者都应该配置这个。它非常简单,而且WER有时会捕捉到其他系统没有的崩溃。WER非常擅长捕捉进程的启动和关闭崩溃,忘记添加小转储处理的进程崩溃,甚至记录纯虚拟函数调用和无效CRT参数的小转储。

今天我将讨论一个简单但功能强大的GUI工具,用于本地或远程配置Windows系统进行内存转储。工具的名称是DumpConfigurator它可以从CodePlex访问。

该工具可用于当前支持的所有Windows操作系统版本。下载后,请使用管理员权限启动它以获得以下UI:

GUI是不言而喻的,所有设置都可以通过单击“保存设置”进行编辑和保存。必须重新启动系统才能使设置生效。

 

在帮助客户调试问题的过程中,有些事情会反复出现,从而产生“痛点”。想到的主要问题是:线程、锁、内存、异常和性能。至少这些似乎是我参与的主要问题。
除了例外,人们常常惊讶地发现,他们的应用程序中发生了多少他们不知道的异常。然而,对于微软的异常处理指南来说,一个很好的“切入点”是处理和聚焦异常。重要的是不要使用异常来控制正常的程序流。例外情况只有在特殊情况下才会发生。如果可以在不引发异常的情况下检查特定条件,那么就这样做。抛出异常对性能不好,而且还会使调试正在生产中的应用程序变得更困难,因为它们会妨碍您的工作,并使您更难解决真正(和真正意想不到的)问题。

我发现一个很棒的工具,可以很好地显示进程中的内存使用情况。

这个示例工具将为您提供虚拟内存空间的直观概述(从内存转储),显示您的分配存在于何处以及您拥有的分配类型。例如,在下面的屏幕截图中,您可以看到在内存空间的开始,我们有大量的虚拟分配(深绿色-已提交,浅绿色-保留),然后我们有大量的可用空间(白色),在内存空间的末尾,我们可以看到我们的DLL分散开来(深红色)。


在底部屏幕中,我们可以看到GC(.NET)堆。换句话说,我们在顶部屏幕上看到的大多数虚拟分配实际上是GC堆。有一点需要注意的是,对于GC堆,它不显示为GC堆保留的内容,只显示提交的内容,即我们实际使用的内容。


我把它们分开,这样你就可以在非.net应用程序中使用这个工具了。


研究这样的东西的目的是为了弄清楚我们有多少碎片,我们有多少保留内存和提交内存等等。如果我们确实有很多碎片,我们应该从哪里开始寻找以减少碎片。

原来的工具有点复杂,因为它可以读取内存转储等,并允许您放大不同的区域以获得更多的细节,但在大多数情况下,上面看到的已经足够了。

要使用示例工具,请执行以下步骤:

1、在windbg中打开内存转储并正确设置符号

2、运行!address ,然后拷贝输出到一个文本文件

3、Load sos

4、运行!eeheap –gc ,然后拷贝输出到另一个文本文件

5、打开工具,点击加载

 

您看到的是一个非托管异常,它在CLR中引发。它是非常通用的,用于在深入本机代码时发出错误条件的信号。名称的“消息”部分是为Microsoft测试人员设计的。只需等待这个异常变成一个托管异常。如果是良性的话就被吞下去。
或者取消选中Project+Properties,Debugging,Enable unmanaged code Debugging复选框,这样你就看不到它了。或者使用Debug+Exceptions并取消选中Win32异常的抛出复选框,这样调试器就不会在这类异常上停止。