现代软件开发实践将应用程序构建为协作组件的集合。与将编译后的组件链接到单个单体应用程序的旧实践不同,现代可执行文件由许多作为单独二进制文件存在的可执行组件组成。
这种设计意味着当一个应用程序组件需要来自另一个组件的资源时,会进行调用以将控制或数据从一个组件传输到另一个组件。因此,我们可以通过观察应用程序组成组件边界上发生的活动来观察外部可见的应用程序行为。
除了自身的组件外,应用程序还可以导入第三方组件,例如 COM(组件对象模型)或 CORBA(公共对象请求代理架构)。应用程序还从根本上依赖于其他核心组件,例如操作系统内核和文件系统,并在执行期间几乎持续地与它们通信。所有这些交互都可以在不参考应用程序的源代码或编译器符号的情况下进行观察。
当源代码或编译器符号可用时,调试是最常见的。在这种情况下,调试器可以附加到应用程序并收集运行时信息。源代码和符号允许跟踪诸如内部变量值之类的信息,以便工程师可以发现内部行为的某些方面。然而,正如商业或遗留软件中常见的那样,源代码或符号可能都不可用于此类目的。
在某些情况下,跨越应用程序组件边界的信息可以用于测试和调试等。我们在此感兴趣的是诊断可能涉及核心应用程序与其接口的组件或服务之间交互的错误。我们还想研究信息如何通过应用程序的边界传递,然后能够操纵这些数据以迫使应用程序沿着特定的执行路径运行。
软件开发人员、测试人员和用户并不总是意识到当功能来自外部组件时创建的复杂关系。从软件质量和安全的角度来看,应用程序继承了它所依赖的外部组件的问题。如果外部组件或描述其编程接口的文档中存在错误,则可能会浪费大量时间来隔离错误。每当应用程序中出现故障时,我们都会自动认为错误在该应用程序的代码中。通常,这会导致对代码进行费力且耗时的逐行检查,以追踪到有问题的命令。实际上,软件通常存在缺陷,这意味着其组件经常布满错误。
另一个问题是应用程序开发人员通常期望从外部函数调用返回一组离散的值。在许多情况下,开发人员假设这些调用会成功,并且未能检查函数调用的返回值。观察传递给这些第三方组件的数据以及函数调用的返回值可以大大减少追踪源自外部组件的应用程序错误所需的时间。
这就是黑盒调试的用武之地。黑盒调试通过仅监视应用程序外部的行为来收集有用的调试信息。通过观察应用程序及其依赖项之间传递的数据,开发人员可以更好地理解关系,并诊断和促成各种各样的故障。
考虑以下示例
Microsoft 记事本是一个文本编辑程序,随附在许多版本的 Windows 中。如果我们在 Windows XP 中启动记事本,它的外观与 Windows XP 默认主题相匹配(如图 1 左上角的窗口所示)。使用相同的用户输入序列,即双击记事本可执行文件,应用程序有时会如图 1 右下角所示出现。请注意两个窗口之间的差异。这些窗口是在同一台机器上使用相同的用户输入序列生成的,但显然应用程序从其环境接收到的一部分输入是不同的。黑盒调试可以用来帮助追踪到有问题的输入(见图 2)。
在以下部分中,我们将识别黑盒行为,并讨论如何拦截它们。然后,通过真实软件的示例,我们将展示如何使用黑盒调试来诊断软件缺陷。
我们都接受这个前提,即软件通过任意数量的接口接收输入,并通过内部计算和数据操作生成输出,然后通过其中一个接口将输出发送到最终目的地。例如,用户可以通过键盘接口发送输入,这会导致应用程序通过网络接口发送数据——例如,获取网页。许多这样的输入和输出构成了应用程序的黑盒行为。
黑盒行为存在于应用程序的接口处,而不考虑内部操作。当收集黑盒信息时,我们只关注通过接口输入的输入以及作为这些输入结果生成的输出。输入如何转换为输出正是传统调试过程的领域。
这种对外部行为的关注使得识别给定应用程序的所有可能接口变得重要,以便可以收集跨越这些接口的行为并将其呈现为调试信息。
我们首先定义软件接口的通用类别。理解这些接口很重要,因为它们将决定我们可以为黑盒调试收集的信息。
想象一下应用程序在执行时的行为。在应用程序的边界处,发生六个主要的行为类别
所有这些行为类别都可以在应用程序的边界处识别——即,在其黑盒之外。这是可用于黑盒调试的信息。
当源代码不可用或不易使用时(例如,如果我们无法控制构建环境),我们将使用此信息来了解程序行为的各个方面。它可以用于解释行为、查找错误和分析其根本原因——以及许多与编译后的二进制文件的行为或实现相关的事情。
这可以帮助的一个领域是诊断由应用程序环境中的故障引起的错误。在一个环境中发生且无法在另一个环境中重现的故障是软件测试人员的祸根。描述故障的典型过程是记录导致故障显现的一系列用户操作,然后将此描述传递给应用程序开发人员,然后应用程序开发人员在另一台机器上重现故障以追踪问题的根源。如果故障无法重现,则错误被修复的机会很小。
为什么在同一台机器上失败的相同软件在另一台机器上使用相同的点击和击键顺序却能正常工作?我们都知道软件是确定性的——给定相同的输入,我们期望得到相同的结果;但问题是我们很少考虑到应用程序通过其接口接收的非用户生成的输入。
图 3 显示了我们通常“感知”应用程序如何响应输入。这相当准确地代表了早期记事本示例中发生的情况。我们看到相同的用户输入集被传递到在两台不同机器上运行的相同应用程序。当此输入序列应用于机器 1 时,软件失败。当应用于机器 2 时,应用程序响应正确。乍一看,给定相同输入序列的不同行为似乎违反了软件的确定性本质。
图 3 显示了我们最初未能考虑的另一组应用程序输入:“实际”交互。在机器 1 上,加载库时出现故障,最终导致应用程序失败。要真正理解软件行为,需要了解其通过所有接口的输入和输出;这就是黑盒调试可以提供帮助的地方。
为了观察这些交互并解释通过应用程序边界移动的数据,我们需要专门的工具。许多观察工具可商购获得,也可作为免费软件获得。Sysinternals1 提供了一套适用于此目的的免费软件工具,它生成 Regmon、Filemon 和 ListDLLs 分别用于监视注册表、文件系统和库交互。此领域的两个商业产品是 Identify Software2 的 Appsight 和 Security Innovation3 的 Holodeck。
监视通过应用程序接口传递的数据可以帮助诊断已发生的故障。然而,这种方法是被动的;我们首先等待故障发生,然后收集调试信息以隔离故障源。从黑盒的角度来看,我们对应用程序的控制比用户输入要多得多。借助适当的工具,我们可以控制通过应用程序所有接口的交互。通过操纵来自先前确定的六个来源的输入,我们可以迫使应用程序沿着特定的执行路径运行。在可以通过其接口馈送到应用程序的可能输入中,也许最有趣的是那些模拟应用程序环境中的故障条件的输入。故障可以采取库加载失败、内存不足、磁盘上的写保护错误等形式。这种模拟环境故障的过程称为运行时故障注入。
错误条件很有趣,因为当异常条件由于压力而发生时,任何错误处理例程都会执行。这些是应用程序中的路径,它们不会增加其功能,但旨在防止功能代码失败。这些错误处理例程的测试次数远少于它们旨在保护的功能代码。由于测试的暴露程度有限,这些代码路径是许多类型缺陷的沃土。因此,在调试期间强制环境故障非常重要。这就是运行时故障注入可以提供帮助的地方。
运行时故障注入可以提供帮助的另一个领域是不可重现的错误。这些故障通常是由通过应用程序的隐藏接口之一的意外输入引起的。此值可能是由于与机器上运行的另一个进程争用资源或可能是内存或磁盘中的间歇性故障造成的。即使使用黑盒调试记录下来,这些类型的异常也很难手动重现。
为了演示,请考虑随附在 Windows XP 中的记事本文本编辑程序的“另存为”对话框(见图 4a)。要打开此对话框,可以应用以下输入序列
此重现序列很简短,并准确地表示了导致记事本失败的点击和击键序列。但是,如果您在 Windows XP 机器上按照这些步骤操作,记事本可能会正确响应(见图 4b)。为什么?原因是重现步骤没有考虑记事本接收到的隐藏环境输入。故障仅在系统内存不足时才会显现,这会导致错误返回到记事本对操作系统内核的系统调用,以在内存中分配空间。像这样的错误可能会给试图拼命追踪他们可能错过的击键或点击以重现行为的测试人员带来数小时的挫败感。黑盒调试技术将大大减少隔离真正产生故障的输入所需的时间。
除了识别这些输入外,以黑盒方式方便地向应用程序提供环境输入也很有用。例如,考虑重现记事本故障的挑战。您该如何做?一种选择是启动许多后台进程并创建内存争用。另一种是编写一个程序,该程序分配内存直到系统上的所有内存都被使用。
这些方法存在一些问题。第一个问题是,由于内存消耗的几乎随机的性质,记事本的内存分配调用中的故障可能发生也可能不发生。第二个问题是,您可能想在记事本上使用的任何分析工具可能在内存受限的情况下都无法正常工作,甚至根本无法工作。第三个问题是,消耗系统上的所有内存也可能导致关键操作系统功能失败,从而导致系统挂起或崩溃。
我们真正需要的是工具来控制对应用程序隐藏接口的输入,这些输入只会影响被测应用程序。这个领域有希望。白盒故障注入方法(其中内部结构和编码是已知的)已在行业中用于通过硬编码系统调用的返回值来模拟环境错误条件。在 Unix 平台上,也曾尝试在运行时以黑盒方式注入环境故障。4 我们之前已经演示了如何在 Windows 平台上完成此操作。5, 6, 7 Security Innovation 的 Holodeck 工具可用于在运行时控制通过应用程序边界的所有数据,而无需修改应用程序代码。此类工具带来了强制应用程序沿着特定执行路径运行并正确识别导致故障的软件输入的能力。
挑战仍然存在。我们如何决定何时注入故障?哪些故障是有意义的?哪些故障可能揭示错误?哪些故障更重要——以及故障的风险是什么,例如安全性?这些都是从业人员在测试和调试过程中应用这些技术时面临的重要问题。软件工程和测试社区现在才刚刚开始回答这些问题,但一般的经验法则是当应用程序最需要资源时引入压力。在密集计算期间限制内存或在远程身份验证期间模拟网络故障都是这种有针对性的插入的示例。随着更多工具开始出现,这些技术可能会在大多数测试武器库中找到一个受欢迎的家。
软件开发人员、测试人员和用户可以访问和控制在应用程序及其环境之间交换的大量数据。无需源代码或符号,即可访问和解释此数据。使用讨论的工具和技术,用户可以通过查看所有行为,而不仅仅是用户界面中可见的行为,从编译后的二进制文件中解锁有价值的调试信息。
Sysinternals:请参阅 http://www.sysinternals.com/。
Identify Software:请参阅 http://www.identify.com。
Security Innovation:请参阅 http://www.sisecure.com。
Kao, W. I., Iyer, R. K., 和 Tang, D. FINE:用于追踪 Unix 系统在故障下的行为的故障注入和监视环境。《IEEE 计算机科学汇刊》19, 11(1993 年 11 月),1,105–1,118。
Thompson, H. 为什么安全测试很难,《IEEE 安全与隐私》(2003 年 7 月/8 月),83–86。
Thompson, H., Whittaker, J. A., 和 Mottay, F. 恶意环境中的软件安全漏洞测试,《 SAC 会议论文集》(2002 年),260–264。
Thompson, H., 和 Whittaker, J. A. 软件安全测试,《Dr. Dobb’s Journal》(2002 年 11 月),24–34。
詹姆斯·A·惠特克是佛罗里达理工学院的计算机科学教授。他还是微软可信计算学术顾问委员会的成员,并且是《IEEE 安全与隐私》杂志“应用程序安全”专栏的编辑。他的研究兴趣是软件测试和可靠性、软件设计方法和计算机安全。他是《如何破解软件》(Pearson Addison Wesley,2002 年)的作者,并且是与赫伯特·H·汤普森合著的《如何破解软件安全》(Pearson Addison Wesley,2003 年)的合著者。惠特克拥有田纳西大学计算机科学博士学位,并且是 和 IEEE 的成员。
赫伯特·H·汤普森是 Security Innovation 的安全技术总监。汤普森与詹姆斯·A·惠特克在软件安全和反网络战研究方面进行了广泛合作。他还曾在微软担任软件测试工程师,并且是软件安全领域的常客演讲者和作家。他曾在 IEEE 软件可靠性会议 (ISSRE2001)、众多软件测试会议、 SAC 和 ACSAC 等会议上发表演讲,并且是 SAC 2003 会议软件工程通用轨道主席。他曾在学术期刊和行业杂志上发表文章,包括《软件测试与质量工程》和《Dr. Dobb’s Journal》。他是一名认证信息系统安全专业人员 (CISSP),并且正在攻读精算师执照。汤普森与惠特克合作撰写了《如何破解软件安全》(Pearson Addison Wesley,2003 年)。他拥有佛罗里达理工学院数学博士学位。
最初发表于 Queue 第 1 卷,第 9 期——
在 数字图书馆 中评论本文
桑杰·沙阿 - 企业应用程序的可靠性
企业可靠性是一门学科,它确保应用程序以一致、可预测且经济高效的方式交付所需的业务功能,而不会损害可用性、性能和可维护性等核心方面。本文介绍了一套企业可以应用的核心原则和工程方法,以帮助他们驾驭复杂的企业可靠性环境并交付高度可靠且经济高效的应用程序。
罗伯特·郭 - MongoDB 的 JavaScript Fuzzer
随着 MongoDB 随着时间的推移变得更加功能丰富和复杂,开发更复杂的方法来查找错误的需求也在增长。三年前,MongDB 将自研的 JavaScript fuzzer 添加到其工具包中,现在它已成为我们最多产的错误查找工具,负责在两个发布周期内检测到近 200 个错误。这些错误跨越了从分片到存储引擎的各种 MongoDB 组件,症状范围从死锁到数据不一致。fuzzer 作为 CI(持续集成)系统的一部分运行,它经常捕获新提交的代码中的错误。
罗伯特·V·宾德,布鲁诺·莱热尔,安妮·克莱默 - 基于模型的测试:它处于什么位置?
您可能听说过 MBT(基于模型的测试),但像许多未使用过 MBT 的软件工程专业人士一样,您可能对其他人使用这种测试设计方法的经验感到好奇。从 2014 年 6 月中旬到 2014 年 8 月初,我们进行了一项调查,以了解 MBT 用户如何看待其效率和有效性。2014 年 MBT 用户调查是 2012 年类似调查的后续调查,向所有评估或使用过任何 MBT 方法的人开放。它的 32 个问题包括一些来自 2013 年高级自动化测试用户会议上分发的调查。一些问题侧重于 MBT 的效率和有效性,提供了管理者最感兴趣的数据。
特里·科塔,迈克尔·多纳特,贾法尔·侯赛因 - EA 的自动化 QA 测试:事件驱动
对于数百万游戏爱好者来说,在 Electronic Arts 担任 QA(质量保证)测试员的职位一定像是一份梦想的工作。但是从公司的角度来看,与 QA 相关的开销可能看起来非常可怕,尤其是在大型多人游戏时代。