面向二进制代码的软错误故障注入器设计与实现
2021-10-15罗予东董守玲
罗予东 董守玲 陆 璐
1(嘉应学院计算机学院 广东 梅州 514015) 2(华南理工大学计算机科学与工程学院 广东 广州 510641)
0 引 言
随着计算机硬件设备集成度的日益提高,现代系统中的硬件故障率也与日俱增。随着芯片小型化和精密化的不断发展,硬件设备更容易受到外部环境的影响。在使用过程中,主要的故障问题是软错误:一些随机位在存储器和寄存器中翻转,导致软件在计算时产生不可预知的错误。导致硬件设备电压变化、存储器位翻转等软错误的原因很多,对于高可靠性要求的软件应用,更加重视软错误的影响。目前,硬件和系统级检测与校正机制几乎不能监视某些软错误。因此,研究软错误对程序计算、程序控制的系统来说具有重要意义。
为了评估软错误对系统的影响,通常的解决方案是手动生成故障并研究程序在故障下的行为及动作。通常的方法是采用软件方式注入故障,除此以外还可以模拟外部环境(如辐射等)产生故障。由于在实际应用中很少发生软错误,因此需要提高软错误的发生概率。实际上,模拟外部环境和直接修改硬件设备状态,代价是昂贵且不切实际的。研究表明在机器代码级通过软件故障注入技术更容易模拟硬件故障,并且具有较高的精度[1]。
在硬件模拟方法中,通过硬件模拟设备实现位翻转。该方法可以提供机器代码级的硬件故障注入,通过检测和校正机制可以覆盖整个注入过程。为了实现汇编代码的故障注入结果与源代码的映射,研究并设计了一种软件故障注入方法,可以针对源代码进行故障注入。目前,该技术面临的主要挑战是如何保证硬件错误能够精确注入。原因是硬件错误可以分布在程序的任何地方和任何部分,而程序的某些组件无法提供源代码。二进制故障注入技术可以有效解决上述两个问题,能够提供可操纵的、准确以及实用的故障注入方法,该方法通过直接修改二进制代码来模拟硬件故障。常用的动态二进制故障检测和注入工具包括PIN[2]和Valgrind[3],基于上述工具进行故障模拟的方法已被证明是准确和有效的。该故障注入技术主要是处理机器代码级的硬件指令,对于源代码级的故障注入更是准确和有效的。
为了研究软错误对程序计算的实际影响,本文设计并开发了一种基于PIN的故障注入器。其主要特点是可以在特定位置检测和修改应用程序,包括各种寄存器和存储器单元。实验证明,该方法针对ECC保护存储器和逻辑单元可以有效地模拟位翻转。本文的主要贡献是:
1) 本文设计的故障注入器基于PIN工具。介绍了故障注入器的设计和实现,它能够注入软错误并分析运行结果,同时提供了一种将故障注入任何特定存储器和寄存器的方法。
2) 研究并分析了基于PIN的故障注入器的准确性,评估了软错误对应用程序的影响。
1 国内外主要研究工作
截至目前,软错误对应用性能和系统可靠性的影响越来越受到关注,故障注入技术作为研究软错误对系统影响的一种常用方法,现已被广泛采用。在故障注入技术领域,已经提出了许多种方法,主要包括以下内容。
1.1 软错误分析的故障注入
硬件模拟方法通过在机器代码级别使用硬件模拟器产生单粒子翻转,可以在系统架构级别轻松注入故障,例如内存和寄存器。研究人员采用了照射硬件的方法使存储器单元在运行过程中引发位翻转。Karlsson等[4]提出了一种通过使用来自acallfbtnhun-252源的重离子辐射将瞬态故障注入到集成芯片电路的方法。该方法模拟软错误的基本原理实现了硬件单粒子翻转。这种方法的缺点显而易见,它们通常是无法控制且高成本的。另外,该方法可能会破坏目标系统。
基于软件的故障注入技术是修改目标系统运行的硬件状态,分析在硬件故障发生时系统的运行情况。Guan等[5]设计并实现了软错误故障注入框架SEFI,这是一种分析软件对软错误敏感性的工具。在不改变其他程序或共享VM的操作系统的情况下,可以通过使用开源虚拟机和处理器仿真器(QEMU)成功地实现在目标程序的逻辑操作中注入软错误。但是该方法不支持对特定的寄存器和存储器进行故障注入。
1.2 基于动态二进制插装的故障注入
目前,比较流行的故障注入技术是动态地模拟应用程序二进制文件中的软错误。王承松[6]提出了一个名为BIFIT的故障注入工具,它基于二进制检测来模拟位翻转。BIFIT能够在任何指定的位置注入错误,如应用程序的全局或堆栈数据对象等。但是不支持对寄存器的注入操作,需要进一步的改进。Li等[7]开发了一种新的故障注入工具FIT-grind,它使用了Valgrind提供的动态二进制插装。该工具从底层硬件架构进行抽象,并将故障注入到Valgrind提供的人工架构中。但是其不支持软件在动态运行过程中的注入操作,所以需要增强故障注入的灵活性。
2 故障模型和动态二进制检测框架
2.1 故障模型
本文主要考虑针对处理器和存储器单元发生的单粒子翻转错误。该错误一般是由外部环境辐射引起的。通常每个应用程序运行过程中仅产生一个bit位的翻转错误,根据研究表明,在短时间内产生多位翻转的概率非常低。在选择注入故障的指令时,采用随机均匀分布。例如,如果应用程序选择与插装条件匹配的N条指令,则指令注入故障的概率为1/N。由于寄存器值和存储器单元内容具有多个位,因此采用随机均匀分布的方法选择翻转故障的位[8]。
依据软错误模拟的相关研究,将实验结果分为以下几类,具体定义内容如下。
定义1良性故障——故障注入的程序输出与原始执行结果相同。其主要原因是可以通过后续的程序计算来覆盖故障数据。因此,故障不会扩散到应用程序中,也不会影响其运行结果。
定义2硬件异常——由于故障注入引起的硬件陷阱或异常,软错误可能会导致应用程序崩溃。
定义3内存捕获——注入的错误被内置检测机制捕获。
定义4静默数据损坏(SDC)——故障注入程序的输出与原始程序成功执行后的结果不一致,通常被认为是危险故障,因为它们在程序执行期间无法被检测到,但确实会影响其结果。
定义5应用程序挂起(“超时”)——如果应用程序没有在规定的时间内终止,则定义为超时。通常在不执行故障注入的情况下运行程序以获得基准时间[9]。
2.2 PIN
PIN是由英特尔设计的程序分析框架,它在执行Linux应用程序过程中进行二进制插装,具有可移植性、透明性、高效率和健壮性,采用C/C++编程实现,提供了一个支持多种体系结构的插装平台,实现了一种程序分析器和故障注入器[10]。PIN配备了丰富的API,抽象出架构的具体细节,可以编写便携式插装工具。PIN采用即时(JIT)编译的方法插入和优化代码,提供了一种高效的检测机制,该检测机制可以在程序运行过程中探测代码,从而使得PIN比静态检测或代码修补的系统工具更强大。
3 软错误故障注入器
软错误故障注入器是一种基于二进制检测的故障注入工具,可在机器代码级注入硬件故障。该注入器使用PIN(动态二进制插装和分析工具)模拟存储器和各种寄存器中的单粒子翻转。故障注入的目标是可执行的二进制程序,可以在没有源代码的情况下模拟软错误,其故障注入方法不受编程语言的影响[11]。
3.1 软错误故障注入器概述
图1给出了故障注入器的架构设计,主要由三个模块组成:控制器、监视器和故障注入模块。使用动态检测工具可以在应用程序执行期间将故障注入到存储器和寄存器的随机位置。为了提高插装性能,设计并开发了一个收集指令信息的监视器。为了协调预处理和插装功能,采用了基于Python脚本的控制器。控制器不仅仅是提供给用户直接使用的组件,而且还是整个故障注入过程的控制器。在控制器的协同下,注入器使用预处理的指令信息执行故障注入操作;故障注入模块负责软错误的模拟与实现。图2为故障注入过程,其整个过程如下:
1) 用户控制器,该控制器通过界面的方式提供给用户,配置实验的相关参数信息。
2) 控制器激活目标系统和监视器并执行。监视器获取有关故障注入点的相关数据。故障注入点为程序监视模式下的具体实例,故障信息被传递到故障注入器中,注入器激活故障并注入到与控制器相连的进程中。
3) 故障注入器启动后,将故障注入到二进制程序中。故障注入产生的数据传递给控制器,然后存储在文件中以供离线分析。在注入操作完成并且从目标系统收集了所有数据之后,控制器退出实验并关闭监视器。
图1 故障注入器架构设计
图2 故障注入过程
3.2 监视器
监视器是故障注入器的一个模块,用于判断在测试过程中注入软件故障的位置。该模块能够收集所有可能的故障信息以及相关的故障模式,并为故障注入器提供该信息。在注入操作之前监视器获取了指令的详细信息进而加速故障注入过程。应用程序的分析过程不会影响程序对故障注入的响应。事实上,监视器为所有可能的故障位置提供了特定插装操作的相关故障模式。控制器启动可执行程序和监视器。后者识别某些指令模式并将分析信息存储到文件中作为故障注入点。当应用程序到达指定的执行点并匹配输入的分析信息时,故障注入模块立即注入故障以模拟bit位翻转。故障注入操作将使不相关的代码快速运行。通过在指定的故障注入执行点之前注入故障和插装,可以精确地提高插装性能。
3.3 控制器
控制器为用户与故障注入器之间的接口,通过用户配置实现故障注入操作。采用Python脚本的方式实现控制器信息配置,故障支持多次注入,用户可以灵活地设计各种故障实例集。控制器将用户设置的参数传递给监视器,并协调监视器和故障注入模块之间的工作。
控制器负责收集故障注入操作产生的相关数据,包括应用状态、执行输出和故障注入信息。此外,如果应用程序运行很长时间,控制脚本将中止应用程序执行。如果执行时间超过设定值,故障注入器将可执行程序状态定义为超时。根据之前的故障注入研究成果,超时时间系数设置为10,表示故障注入下目标应用程序的执行时间比无错误执行时间延长了10倍。
3.4 故障注入模块
故障注入模块是整个系统的关键模块。通过监视器和控制器的协调,故障注入器可以在任何执行位置产生位翻转错误。故障注入模块应能够识别故障注入点,通过PIN的API识别与用户配置匹配的特定故障模式。PIN提供了一种通过允许分析例程覆盖应用程序寄存器和应用程序内存来修改程序行为的方法,这种机制可以模拟寄存器和存储器的位翻转。对于寄存器,可以通过确定操作数是否带PIN助手的寄存器来选择限定指令,同时,可以指定寄存器的类型,包括数据寄存器、标志寄存器等。对于存储器借助PIN的功能接口判断是否进行加载或存储操作[12]。
4 实验与结果分析
4.1 典型应用程序的选取
实验所选应用程序代表了典型的应用程序。本文中使用的目标应用程序主要包括:
1) 多线程程序应用。多线程协同工作,共享存储资源,线程之间通过锁控制不同顺序的执行过程。要求访问全局存储线程安全的工具或手段能够与其他线程协调。
2) 预测算法。基于线性关系数据的算法,通过读写存储器完成数据的访问,通过寄存器保存中间过程,完成复杂的预测式计算。
3) 浮点计算。通过浮点寄存器传递参数,完成浮点计算,输出计算结果到其他应用。
4.2 实验方法
本节主要介绍了故障注入实验的结果。实验环境的操作系统是Linux(x64),在该环境中下载PIN 3.7并进行二次开发以实现功能扩展。在选择注入故障的模式时,采用随机均匀分布的方法。
通过实验评估软错误对应用程序执行时间和应用程序输出结果的影响。为了评估故障注入对执行时间的影响,定义了三个执行时间。T0为最初的程序执行时间,作为基准时间;T1为故障注入后的执行时间;T2为在没有故障注入但是使用插装的情况下的执行时间。将故障注入的性能影响定义为P。其计算公式如下:
P=(T1-T2)/T0
对于故障注入操作,根据监视器的指令信息随机选择800条指令,选择一个随机均匀分布的指令来随机翻转。对于每个应用程序,模拟寄存器和存储器的单粒子翻转,分别执行三次故障注入,观察整个故障注入期间应用程序的执行时间,并评估软错误对其性能的影响。
4.3 实验结果
在同一应用程序的不同位置注入故障会对执行时间产生不同的影响。图3显示了软错误对多线程应用程序执行时间的影响,同时可以看到寄存器故障注入下的执行时间明显长于内存故障注入时的执行时间,这说明应用程序的寄存器单元对软错误故障特别敏感。
图3 多线程应用程序的扩展执行时间
图4说明了在预测算法应用程序中,寄存器故障的执行时间明显比存储器的执行时间延长,这表明应用程序的寄存器故障对软错误故障响应明显。而在寄存器故障方面性能损耗较小。图5说明了内存故障的浮点计算应用程序的执行时间比寄存器故障的应用程序执行时间更为敏感。
图4 预测算法应用程序的扩展执行时间
图5 浮点计算应用程序的扩展执行时间
实验结果表明,应用程序执行时间对软错误非常敏感。此外,还可以得出结论,寄存器中的软错误对性能的影响更大,因为寄存器错误注入会显著延长执行时间。在预测算法应用程序中,可以看到执行时间增加了几倍,这表明寄存器中的数据一旦被破坏,就会导致算法收敛达到预期精度的时间延长。同时在某些情况下,可以观察到执行时间的增加取决于某些特殊的故障注入点。
图6和图7分别比较了三个目标应用程序之间的故障注入结果对性能的影响,通过结果可以看出预测算法应用程序最容易受到软错误对程序性能的影响。这主要可能是预测算法需要通过计算收敛到预期的精度,从而导致预测的时间增加。
图6 寄存器故障注入执行时间
图7 存储器故障注入执行时间
图8和图9分别给出了目标应用程序的寄存器和存储器的故障注入结果,错误行为也因不同的应用程序而不同。从图9中可以看到寄存器故障的浮点计算应用成功率高于其他应用,统计上证明了该应用对寄存器故障不敏感。内存捕获仅在多线程应用程序上发生,因为它在此应用程序上有自己的检测机制。无论是在寄存器还是在内存中,类型SDC错误在预测算法应用程序中比在其他应用程序中更为常见,这表明此类故障对应用程序的输出具有更大影响。良性故障的成功率在浮点计算中比在其他应用程序中更为常见;硬件异常在多线程应用中比在其他应用程序中更为常见;超时在预测算法中比在其他应用程序中更为常见。通过评估软错误对应用程序的影响,本文发现如果应用程序对组件敏感,应用程序执行时间和应用程序输出几乎会同时受到影响。
图8 存储器故障注入结果统计
图9 寄存器故障注入结果统计
4.4 软错误结果分析
根据4.3节中的实验结果,分析故障注入器的实验结果。如图3所示,可以看出寄存器故障注入下的应用程序其执行时间明显长于内存故障浮点计算应用程序。为了探究软错误对各种寄存器的影响,将故障分别注入通用寄存器,临时寄存器和标志寄存器,以分析故障注入对特定寄存器的影响。
每个故障注入都针对以下三种寄存器之一:
1) 1x86-64 ISA中的16个64位通用寄存器(RAX,RBX,RCX,RDX,RDI,RSI,RSP,RBP,R8,R9,R10,R11,R12,R13,R14,R15)。
2) 一个64位标志寄存器。
3) 32个64位临时寄存器,代表寄存器的高位或低位。
图10显示了多线程应用中的故障注入崩溃率。临时寄存器只延长执行时间,不会产生崩溃。R8寄存器和R15寄存器的影响很小,因为它们很少使用。由此可以得出结论,在堆栈指针寄存器中注入的故障大大增加了该实验中程序的崩溃率。
图10 多线程应用程序寄存器故障崩溃率
根据4.3节中的结果,进一步研究注入故障后的程序产生不同行为的原因。注入的故障通常从硬件传播到软件级别,然后破坏各种程序状态和行为。
故障注入类型分为三种:
1) 控制方式注入:如果将故障注入用于条件分支的寄存器,则定义为控制故障。在这种情况下,故障注入器将故障直接注入到条件分支中的条件标志。
2) 地址方式注入:如果故障被注入到影响加载或存储操作中的存储器地址的寄存器中,则将其作为地址故障。
3) 数据方式注入:这种类型的故障通常涉及整数算术指令、浮点计算、寄存器之间的数据移动、逻辑指令等。从后续程序的运行结果分析,在软错误传播之后,数据故障将转变为地址故障或控制故障。
从图11中可以看出,在多线程应用中,地址方式注入的故障率高于其他类型,这说明在内存操作阶段注入故障时可能会导致程序中止。此外,可以得出结论,当在预测算法的数值计算阶段中注入故障时,可能会导致应用程序的执行时间增加。由于算法结构和数据规模的原因,故障注入下不同程序的执行时间和输出变化很大。
图11 不同故障类型注入结果
5 结 语
本文设计一种软错误的故障注入器,用于模拟机器级代码的软错误,主要基于英特尔的动态插装和分析框架PIN。该注入器能够模拟寄存器和存储器中的单bit错误。为了提高插装性能,设计并实现了监视和控制脚本。此外,将该注入器应用于典型的应用程序,并运行数百次故障注入实验以评估其对程序执行时间和程序输出的影响。
实验结果表明,典型应用程序的软错误对性能和精度敏感。故障注入的位置不同,会对应用程序产生不同的影响,基于应用程序固有的算法特性和计算规模的不同,其对软错误响应也不同。
下一步的工作是开发一个完全集成的故障注入环境,该环境可以在各种操作系统下使用。此外,还要进一步改进支持的故障类型,以便将来在实际系统上进行应用。