当程序崩溃发生后,我做了如下工作
Posted yilang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了当程序崩溃发生后,我做了如下工作相关的知识,希望对你有一定的参考价值。
这是一个相当简单的错误。使用字节计数而不是字符计数调用了宽字符串函数,从而导致缓冲区溢出。找到问题后,修复方法很简单,只需将sizeof更改为_countof,很容易的。但像这样的BUG浪费时间。由于崩溃,playtest被取消了,而且由于缓冲区溢出破坏了堆栈,因此找到错误代码并非易事。我知道这种类型的错误是可以避免的,我知道还有很多工作要做。
我所做的工作包括:
尽量早点诊断
如果程序在发出错误的宽字符函数调用的函数内部崩溃了,那么找到错误将是微不足道的——代码检查会很快发现它。但是,一旦执行从该函数返回,垃圾堆就模糊了bug的位置,使其调查变得更加棘手。
事实证明,有一个VC++编译器开关可以防止在破坏堆栈后返回。所以,我做的第一件事就是打开开关。这个开关告诉VC++在堆栈上添加一个标志,并在返回之前进行检查。当这个开关打开的时候,溢出的功能被抓住了,找到它花了几秒钟。GS开关是作为一种安全特性,用于防止恶意的缓冲区溢出,但它也可以作为开发人员的生产力工具很好地工作。它确实有一些运行时开销,但这种权衡通常是值得的,尤其是在内部构建上,推荐使用。
修改bug
一旦我打开/GS并重新调试了这个bug,找到这个bug就很简单了,所以下一步就是修复它,如前所述。
保护证据
当缓冲区溢出破坏堆栈时,buggy函数返回到一个垃圾地址,这个地址正好在堆中。抹掉bug所在位置的证据已经够糟糕的了,但更糟糕的是,在返回堆中的地址后,会将堆中的数据作为指令执行。这扰乱了寄存器和堆栈,通常会使事情变得混乱。
还有,一个可执行堆?真正地?这是一个首要的安全漏洞。因此,下一个任务是将链接器设置更改为/NXCOMPAT。这将告诉Windows使堆和堆栈不可执行。这大大提高了安全性,还可以简化调试。而且,这个选项没有运行时成本,推荐使用。实际上,这应该被认为是必要的。
我还打开了我们的发布分支中的/DYNAMICBASE链接器开关,以进一步提高安全性,同时也没有运行时开销。
规避崩溃风险
在这一点上,我已经修复了这个错误,使以后的此类错误更容易调查,并提高了安全性。但还有很多事情要做。原来这种错误很容易犯。当开发人员传递缓冲区和大小时,传递错误大小的方法至少有六种。将来避免这些错误的最好方法是避免传递大小。创建以数组为参数并以100%的精度推断大小的模板函数非常容易。与其写这个:
mywprintf(buffer, _countof(buffer), …); // Verbose and dangerous
你可以写成下面的:
mywprintf_safe(buffer, …); // Compact and safe
它打字少,阅读少,而且保证是正确的。接受数组引用的模板化函数的语法有点粗糙,但您只需要在几个地方正确地使用它。如果有一个原始指针,模板技术就不起作用,但是对于任何其他目标,您应该能够创建一个重载来处理它。手动传递的大小应该很少。因此,我的下一个任务是为所有字符串函数添加模板重写,并鼓励每个人在所有新代码中使用它们,从而使避免再次编写此类错误变得非常简单。
避免过去的风险
回到未来
当然,不再发生的崩溃是不可能的,不可能知道这项工作是否阻止了一次严重的黑天鹅事件。这些好处的无形性使得这种预防性错误修复很难得到认可,所以请记住这一点。
简单的bug不应该总是触发数月的工作,但重要的是要认识到它们应该在什么时候出现。
以上是关于当程序崩溃发生后,我做了如下工作的主要内容,如果未能解决你的问题,请参考以下文章