购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

第3章
内存损坏

内存损坏通常指代码覆写一块不属于自己的内存,或者即使内存属于改写者,但错误的写操作导致内存数据超出有效范围。例如,在竞争条件下,由于多个线程在没有协调的情况下同时改写一个数据,导致最终的内容可能失去意义。这些损坏的数据可能是内存管理器的内部堆数据结构,也就是元数据,也可能是用户空间的应用程序的数据对象。这些错误的最终表现通常是五花八门的。

内存损坏可能是我们需要调试的最棘手的问题之一。主要原因在于这类问题的发生、传播和爆发具有随机性。内存损坏和内存访问的问题,如内存上溢与下溢、重复释放、访问已释放的内存、使用未初始化的变量等,通常在问题发生的时刻或地方不会有什么症状,被损坏的数据要么深深潜伏在其他数据结构中,要么沿着控制流传播到很远,直到很久以后程序在看似毫不相关的地方崩溃。

内存损坏导致的症状受许多因素影响而变化多端,受影响的程序可能崩溃、行为奇怪,或者生成异常的计算结果。由内存损坏导致的程序崩溃是内核确定程序在访问无效内存时采取的措施,这就是众所周知的段错误或者访问错误异常,表示当前指令访问的内存地址不属于程序分配的地址空间(更多细节见第6章)。大多数标准和文档只能简单地警告说内存错误的结果是未定义的,这对于调试来说没有太多帮助。

有时候崩溃发生在错误代码运行时,这使开发人员很容易发现问题,所以这种情况的崩溃是一件“好事”。然而大多数情况下,代码bug的损坏内存从内核角度来看是正常合法的,因为代码访问的地址是内核分配给进程的空间,虽然可能不是内存管理器分配的内存块的应用空间。所以程序不会立马崩溃,相反,数据会被悄悄地错误修改。这就像一个时间炸弹,意外爆炸是迟早的事情。不幸的是,大部分内存损坏的情况属于后者,所以调试内存错误非常困难。

因为很多时候最后的失败出现在不相关的地方,这会让大多数缺乏经验的工程师感到吃惊,他们经常得到的结论是——内存损坏的受害者是问题所在。当面对这样的问题时,我们需要搞明白程序是怎么样达到最后的状态的,从而确定错误的根源。换句话说,我们需要明白内存损坏的“未定义”行为是什么,也就是要解释bug是怎么样从开始隐藏到最后并以出其不意的方式显露出来的。这需要了解更多关于内存管理器的数据结构、编译器特性、架构协议和程序逻辑的密切知识。任何经历过的人都会说这是非常具有挑战性的。在深入讨论调试内存损坏的技巧前,让我们看一些常见的内存错误以及它们是怎么损坏堆的元数据的。 vBjn+5BnN/jnnonqRG4fBO19FbCuyVTdGjdzSoRm7845pRTGthjDVt6DgGkmNB5q

点击中间区域
呼出菜单
上一章
目录
下一章
×

打开