C程序员对超出范围的内存错误都太熟悉了。一个典型的场景涉及到一个指针计算,该计算产生一个地址在开发人员希望程序写入的目标内存块之外。由此产生的越界写破坏了程序中其他不相关部分的数据结构,导致程序失败或神秘地生成意外输出。Java和其他类型安全语言中的类似错误会导致越界异常,这通常会终止程序的执行。
本文提出了一种自动分离和修正这些错误的有趣技术。基本的想法很简单。在比执行程序所需的大得多的内存中随机分配块。将剩余未使用的内存设置为包含特定的“金丝雀”值。运行多个版本的程序,直到失败,然后观察被覆盖的金丝雀值,计算1)超出边界的写要写入的目标块,以及2)将写恢复到边界内需要多少额外的内存。解决方法是返回分配目标块的程序中的分配站点,然后应用一个补丁,适当地增加在该站点上分配的块的大小。本文还提出了一种概念上类似的技术,用于处理对过早释放的内存块的访问。注意,随机分配和多堆的组合是隔离目标块的关键所在。随机块的放置提供了所需的块位置多样性,以确保在高概率下,只有目标块与所有堆中覆盖的金丝雀值有相同的偏移量。
这种方法的一个有趣的方面是,它几乎立即生成补丁,不需要人工交互。因此,它适合于那些重视对新暴露的错误获得快速响应的用途(例如消除安全漏洞)。它还可以消除与软件开发组织(潜在的分心、冷漠、顽固或失效)交互的需要,以从错误中获得缓解,即使软件仅以二进制形式分发。
这种方法的一个更有趣的方面是,它很可能会产生不能完全解决问题的补丁——在一个观察到的执行中,增加一定数量的内存会消除超出边界的访问,但这并不能保证它会在其他执行中消除这种访问。这种方法的优点包括实现和部署的简单性和可行性。
在过去的几年里,我有过很多关于自动纠正或容忍错误的(更激进甚至不可靠的)技术的对话。许多开发人员和研究人员对一个程序在遇到错误时仍继续执行感到非常不安。事实上,最常见的反应是,程序在遇到错误时应该停止,而不是继续,直到开发人员修复错误。
我相信我得到这样的回应有两个原因。首先,大多数开发人员都觉得要对他们所开发的程序的行为负责。自动干预常常会使开发人员在开发程序时最初使用的推理失效、破坏或简单地绕过。目前还不清楚什么将取代这种推理,也不清楚谁将对修改后的程序的行为负责。其次,开发人员的需求通常最好通过失败停止方法来满足。在尽可能接近错误的地方停止执行,可以使开发人员在开发程序时更容易地隔离和修复错误。许多开发人员本能地拒绝任何涉及在错误发生后继续执行的方法,大概是因为继续执行会模糊错误的位置,从而使调试复杂化。
但用户的需求与开发人员的需求是非常不同的。在出现错误的第一个迹象时停止程序会拒绝对重要功能的访问,这是不可接受的。这种拒绝服务的不可取性可以在通常的实践中看到(在萌芽状态),即在发布系统用于生产使用之前删除断言。
即使您认为,与没有检测到错误而执行的程序相比,通过错误继续执行的程序更有可能产生不可接受的结果,显然在许多情况下,继续执行是更好的。例如,考虑一个程序,它产生的结果(如绘图或数字编辑的图片)在检查时可接受性是明显的。在出现错误时继续执行很可能使程序产生满足用户需要的可接受的结果。
当应用于控制不稳定物理现象的程序时,故障停止方法也可能是危险的。在这种情况下,通过错误继续执行可以提供获得可接受结果的唯一真正希望。举一个现实世界的例子,考虑臭名昭著的Ariane 5灾难。这一灾难是由在安全关键的嵌入式软件系统中不恰当地使用故障停止方法直接造成的。
那么,自动错误恢复和修复的未来会是怎样的呢?在这一点上,我们有各种可行的策略来处理任何程序都不应该提交的错误(例如越界内存访问或空指针解引用)。未来的进展将集中在纠正特定于应用程序的错误。关键是获得为识别和消除不可接受行为提供基础的规范。一个特别富有成效的领域肯定是不可靠的技术(为了实现和部署的简单性和可行性),忽略了程序转换不应该干扰无错误执行的传统需求。这种技术的成功可以为更成熟的软件工程视角铺平道路,该视角将正确性简单地视为在系统生命周期内适当管理的一组工程权衡之一。
©2008 acm 0001-0782/08/1200 $5.00
允许为个人或课堂使用本作品的全部或部分制作数字或硬拷贝,但不得为盈利或商业利益而复制或分发,且副本在首页上附有本通知和完整的引用。以其他方式复制、重新发布、在服务器上发布或重新分发到列表,需要事先获得特定的许可和/或付费。
数字图书馆是由计算机协会出版的。版权所有©2008 ACM, Inc.