APP下载

一种利用深度学习的数据流错误检测方法

2021-12-08胡志诚晏祖佳

小型微型计算机系统 2021年12期
关键词:源代码数据流脆弱性

胡志诚,庄 毅,晏祖佳

(南京航空航天大学 计算机科学与技术学院,南京 211106) E-mail:zy16@nuaa.edu.cn

1 引 言

在太空辐射环境中,因为空间辐射或高能粒子撞击,使计算机系统中的硬件部分如半导体数字电路等受到干扰,这种现象称为单粒子效应.它的具体表现有单粒子翻转(SEU,Single Event Upset)等.SEU属于一种瞬时故障,是由于高能电离粒子对微电子设备(微处理器、半导体存储器、功率晶体管等)的敏感节点进行轰击,使得这些信息逻辑位上发生单位翻转的物理现象[1].其发生的位置和时间,具有随机性、可恢复性、瞬时性等特点,因此也被称为软错误[2,3].

由软错误所触发的瞬时故障不会对计算机硬件造成永久性损坏,但会对运行在计算机硬件上的软件带来不可忽视的影响,比如造成程序陷入死循环、程序结果错误、程序奔溃等后果.在航空航天等领域,软错误的发生会带来巨大的经济损失,甚至灾难性的后果.例如,2011国家首颗火星探测卫星“萤火一号”,由于空间粒子辐射导致的软错误,未能完成火星探测任务.2016年美国的SUN公司因为存储器出现软错误而影响了全美大量服务器的正常工作,导致数百万美元的经济损失.研究表明,在已发生的瞬态故障中,有相当大一部分故障由数据流错误引发.

本文致力于解决软错误中的数据流错误检测问题.数据流错误是软错误的一个重要类型.当单粒子效应发生在寄存器、Cache、内存或者其它存储设备存储的数据中或者影响到运算单元和数据总线等部件时,就有可能导致程序正常执行但输出结果错误,这类错误被称为数据流错误.基于软件实现的数据流错误检测方法主要有两种:程序冗余[4]和程序断言[5].在源代码级数据流错误检测算法的最基本方法是冗余复算,属于程序冗余.冗余复算一般步骤为:副本变量和原始变量的一致性规则,根据规则生成副本变量;将原始程序转换为由副本变量组成的副本程序;将原始程序和副本程序合并为复算程序;插入检查和恢复语句,完成加固程序转换.在基于冗余复算的数据流错误检测方法中,源代码级已有的方法存在检测率有待提升且时空开销大的问题.如何解决检测率和时空开销的平衡问题是一个挑战.

本文主要贡献如下:

1)提出了一种基于深度学习的数据流错误检测方法DEDDL(Data flow Error Detection based on Deep Learning);

2)设计并实现了针对源代码级的智能分析机制,包括提取程序级代码中关键信息和脆弱变量集合等;

3)提出基于智能分析的数据流错误检测算法DAIA(Detection Algorithm based on Intelligent Analysis),自动添加冗余并插入相关检测代码后得到具有检测功能的容错程序.

文章还设计实现了基于GDB的故障注入工具GDFI(GDB Fault Injection).通过分析反汇编后,源代码所使用到的数据寄存器,得到程序使用的数据寄存器集合.从集合中选取任意数量的数据寄存器进行单位或多位翻转,以验证本文提出方法的有效性.本文共进行了2000次故障注入实验,相比于同类的研究工作,本文的方法在时空开销方面具有更大的优势.并具有独立于具体编译器,便于实施,快速部署等优点.

2 相关工作

现有的数据流错误检测方法主要有硬件方法和软件方法两种[6].基于硬件实现的数据流错误检测方法通常需要添加额外的硬件来进行故障检测.这类检测方法具有较高的错误检测率和较低的时间开销,但是却增加了成本和空间开销.在宇航级计算机系统和一些商用嵌入式计算机系统中,这样的开销是难以承受的.基于软件实现的数据流错误检测因其具有成本低、灵活度高、开发效率高等优势,被越来越多地应用到检测单粒子效应导致的瞬时故障中,同时也成为研究的热点.

基于软件的数据流错误检测算法,针对程序的不同层级,从下到上又可分为指令级,源代码级,进程级和线程级.相比于指令级数据流检测技术,在高级语言层实现冗余计算机的优点是独立于具体应用平台和编译器,实现策略比较容易,而且具体的实现策略还可以由用户根据需要自由配置和扩展[7].而且,省略了重新分配寄存器和复制内存等底层操作,能够较为有效地检测数据总线、寄存器和内存中的软错误.Benso等人设计了一个面向C和C++程序的编译工具RECOO(Reliable Code Complier)[8].这一工具主要包括代码重排和变量复制两种技术,来检测发生在寄存器、内存等存储部件中的数据错误.其主要通过缩短程序变量的生命周期以有效减少错误发生概率,最终提高程序运行的可靠性,但却带来了巨大的时空开销.在此基础上,RECCO还给出了一些优化方法,根据变量生命周期以及数据依赖关系,定义变量的权重,从而统计出关键变量和代码段,以此实现有限复制关键变量来达到减少开销的目的.但是,该方法在一定程度上牺牲了检测率,且造成的时空开销仍然很大,没有本质性的改变.

Rebaudengo等人设计并实现了一个转换工具ThOR(Translator for Reliable Software)[9],可以将导入的源代码进行变量冗余,以生成具有容错功能的源代码程序,即实现了旧源代码到新源代码的转换,具有较高的错误检测率.但是,由于一行高级语言的代码经过编译汇编后会对应多条运算指令,这类方法相对于指令级方法来说粒度更粗,且性能开销也很大.其实验结果显示:该方法的平均空间开销为289%,且运行的平均性能开销高达262%,远高于相关指令冗余方法的开销.

此外,源代码级的数据流错误检测也有使用数据差异变换的思想.数据差异变换技术是利用差异性因子K来完成对源程序的复算,最终产生一个对应副本.如谭兰芳等人提出了一种基于差异转换和冗余复算的错误检测机制,即基于一组差异转换规则,将原程序转换为功能完全一致的冗余程序,通过在特定位置插入比较检测语句来判断程序运行过程中是否发生软错误[10],该方法在错误覆盖率与时空开销方面均有较大的进步,但仍有改进的空间.

故障注入是验证检测效果的重要手段.以往的方法将需要注入故障的指令信息加入故障注入列表,依照列表进行故障注入.这种方法因为需要消耗大量时间而往往代价过高.现有的研究多是通过选择性故障注入以降低开销.如Hari等人提出了一种名为Relyzer的故障点分析方法[11],其目的是压缩故障空间并检测故障结果或其等效故障,以便选取小部分故障执行选择性故障注入,达到减少故障注入系统开销的目的.Li等人提出了一种智能故障注入框架SmartInjector[12],该框架能够在未进行故障模拟的情况下裁剪故障,或者寻找等效故障,从而更有效地压缩故障空间,提高故障注入效率.Xu等人提出了CriticalFault故障注入框架[13],通过指令级脆弱性分析从故障中删除良性故障.

基于人工智能的SDC脆弱性分析方法通过提取程序的一系列特征来预测其脆弱性,也是目前研究热点之一.Bronovetsky等人比较了支持向量机和神经网络这两种机器学习方法在预测程序脆弱性时的优缺点,提出了一个模块化程序故障分析的基本框架[14],该框架能够分析各代码段的脆弱性并生成漏洞模型,但他们提出的方法仅局限于线型代数应用程序.Lu等人提出了一种经验模型SDCTune[15]来预测SDC脆弱性,该模型利用决策回归树对程序中的存储指令和比较指令进行脆弱性预测.Laguna等人针对瞬时故障下科学计算机程序中的SOC(Silent Output Corruption)错误,提出了一种指令复制技术[16],该技术采用支持向量模型对程序中的SOC脆弱指令进行判定,并对其进行冗余保护,该方法可降低错误检测的性能开销.但该方法针对的是科学计算程序,普适性不够好.

综上所述,虽然在源代码级数据流错误检测中,已有的方法已能够取得有效地检测效果或可观的时空开销,但在错误覆盖率与时空开销降低的平衡问题上仍存在不足.对程序减少代码冗余处理,提高错误检测覆盖率的同时保持较低的性能开销,是当前研究的重点.与指令级容错技术相比,高级语言级实现冗余复算的优点在于实现简便,不需要重新进行寄存器分配和内存复制等底层操作,就能有效地检测内存、寄存器、数据总线和算术运算单元等部件中的软错误.而且它独立于具体应用平台和编译器,用户可以根据需要对冗余复算进行配置和优化,以达到可靠性和性能开销之间的平衡,具有跨平台可移植性好等优点.相关研究表明,对源程序脆弱部分进行分析并结合应用机器学习技术进行预测是提升错误检测率,同时降低性能开销的一个有效途径.本文结合深度学习模型,研究源代码变量脆弱性以及源代码级软错误检测方法,以达到提高错误覆盖率和降低开销的目的.

3 源代码级数据流脆弱性智能分析机制

3.1 脆弱性智能分析机制框架

进行数据流错误检测,需要掌握数据流错误在源代码这一层级的表现形式.对于大多数源代码级的编程语言,程序语句主要分为两种:表达式语句和控制语句[10].其中表达式语句包括运算,赋值等操作语句.这些操作语句的共同特点是它们只对程序数据进行操作,不会改变代码运行的控制流跳转;控制语句包括循环、选择、函数调用和返回等.根据SEU对两种语句的影响,可触发的源代码级数据流错误共分为两类:一类是软错误发生在表达式语句中,导致执行结果错误.这种错误是典型的数据流错误.另一类是软错误发生在控制语句的条件运算过程中,从而导致形式上是控制流错误,其实本质上是数据流错误.因为这是由数据流错误的传递所引起,这可能导致多种控制流检测算法失效[10].因此需要大量的源代码数据,并以此为支撑对获取的数据进行相关预处理,如图1所示.数据预处理操作包括源代码测试集获取,源代码变量提取,源代码基本功能区域划分,以及源代码分析域划分等4个部分.通过数据层的预处理操作,可以得到数据特征.

图1 源代码数据流智能分析机制结构图Fig.1 Structure diagram of intelligent analysis mechanism of source code data flow

然后,将数据预处理层得出的数据特征,以及源代码数据集得到的.c文本文件,利用卷积神经网络中进行训练.在训练前,仍要进行数据预处理,这里的预处理主要是处理文件形式以方便导入CNN中进行训练学习,根据学习模型学习的结果,结合提出的数据流错误检测算法DAIA进行加固.为了验证加固的效果,进行故障注入实验,试验的结果可以为模型提供参考,再次纳入到模型的学习过程中.

3.2 源代码关键特征信息选取

数据流在源代码层级的表现载体主要为代码中定义的数据变量.基于程序要实现的功能不同,不同变量的个数、出现位置,以及同一变量出现的次数、出现的位置都有所不同.而变量的数量和在源代码中出现的位置往往影响着数据流错误的发生,即不同变量因位置与出现频率不同,受到软错误的影响也不同.由于源代码变量的脆弱性分析有利于指导高效费比的软错误检错机制的设计,因此本文研究重点放在如何获取源代码中的脆弱变量并对其进行有选择性的备份处理.

(1)

(2)

(3)

由上述定义知,Rget⊆Rsten.由于程序中的变量存在于程序语句中,因此二者满足包含关系,即v∈s,B⊆R.

定义3.定义基本功能区域(Basic Functional Area,BFA):BFA是在静态分析时能够确定的一组顺序执行的源代码序列.程序的数据流只能从BFA第一条语句开始执行,并在BFA的最后一条语句结束.在BFA中,除了最后一条语句可以跳出程序或者转入下一个BFA中,没有别的语句会使程序控制流跳出程序或者转入别的BFA;同时,除了BFA的第一条语句,无其它入口语句.集合A={a1,a2,…,ai,ai+1,…,an}表示基本功能区域,则有:BFA_in=a1,BFA_out=an,next(ai)=ai+1,i

这里所指的跳出程序语句或者转入其它BFA语句主要是指负责将程序运行信息从内存传递给显示器、文件、键盘等输出设备,如C语言中的error或printf等语句,或者是转移到下一个BFA的语句.存在于完成不同功能的BFA中的变量具有不同的任务与属性,可为变量脆弱性识别提供一种重要的特征.

定义4.定义基本分析区域(Analysis Area,BAA)是由控制流图CFG中连续若干个基本功能区域BFA以及其有向边组成的有向子图.程序的控制流从BAA的第一个BFA开始到达最后一个BFA结束.除了最后一个BFA的最后一条语句,没有其它语句可以使得程序控制流终止或者跳出当前基本分析区域;同理,除了第一个BAA的第一条语句,没有其它语句可以进入到该基本分析区域中.集合M={A1,A2,…,Ai,Ai+1,…,An}表示基本分析区域.设BAA_in=A1,BAA_out=An,next(Ai)=Ai+1|Ai+2,i

通过基本分析区域,可以判断同一变量所处的不同基本功能区域,即一个变量从创建赋值到最终不再使用所经历的过程,为变量提供一种重要的特征.

定义5.控制流图(CFG,Control Flow Graph):CFG是所有基本功能区域组成的集合G={A1,A2,…,An},定义4可知M⊆G,由定义1知Bkind⊆G;它们之间的跳转调用关系形成的边的集合E={br1,br2,…,brm}组成的一个有向图G.控制流图代表程序可以执行的顺序准则.其中控制流图的边指的是从一个BFA跳转到另一个BFA的过程.这包含了BFA的第一条语句和最后一条语句,通常包括选择语句、函数调用语句、循环语句和返回语句等.

截取嵌入式基准通用测试集合MiBench[17]以及新一代的行业标准化的CPU测试基准套件SPEC CPU 2006 benchmark[18]中的一部分代码段进行基本分析区域的划分.图2所示为基本分析域(BAA)的划分示例.从图2中可知一个基本功能区域(BFA)可以划分为一个最小粒度的基本分析区域,即最小的虚线框所标注的B3基本功能区域.较大虚线框中是划分的一个大的基本分析区域,包括B2、B3、B4、B5这4个基本功能区域.

图2 基本分析区域BAA示意图Fig.2 Schematic diagram of Basic Analysis Area

可根据基本分析区域进行源代码结构分析以评估源代码语句的脆弱性,并以此展开源代码数据集的标注工作.为了更加精确的描述源代码语句的脆弱性,以及降低基本分析区域的划分带来的时空开销.设计了以下脆弱性判定规则并分析规则合理性.

规则 1.若同一变量于同一基本功能区域或基本分析区域两次及以上发生赋值改变时,将该变量加入脆弱性变量集合.

分析:变量的每一次改变,都对应着多条汇编指令与相关数据寄存器的调用,其受到SEU影响的概率则越大,如循环变量,条件赋值变量等.

规则 2.当变量定义并赋值后,若该变量出现在3个及以上基本功能区域或两个基本分析区域,则将该变量加入脆弱性变量集合.

分析:当变量出现在3个或者3个以上基本功能区域或两个基本分析区域时,则说明变量在程序的不同位置被使用,属于多次使用,在汇编指令层级涉及的汇编代码较多,涉及的寄存器、内存单元等容易受到SEU影响的部件增多,则变量更脆弱.

规则 3.若源代码语句中含有脆弱变量,则该源代码语句为脆弱性语句.即若v∈Bget且v是源代码语句s中的一部分,则s∈Rget.

分析:当源代码语句中出现脆弱性变量,则相对于其它不含有脆弱性变量的语句,当程序执行到该语句时出现SUE的概率较大,则应列为脆弱性语句.在程序中标明脆弱性语句,用于构造数据集供机器学习模型使用.

规则 4.所划分的基本分析域满足大于一个基本功能区域的规则.

分析:根据定义,源代码程序根据已经划分好的基本功能区域,可划分的基本分析区域的方式有很多.按照不同的区域划分,程序可分成的区域结构也不同.为了权衡后续变量冗余开销与充分发挥BAA的作用,应使其作用区别于基本功能区域.

3.3 源代码的脆弱性特征提取

源代码的程序特征包括变量类型,出现位置,变量活跃程度,以及其所属的基本功能区和基本分析域等特征.本文利用静态分析技术[19,20]和词法分析工具Flex[21],结合Linux环境下对基于LLVM的编译器[22]进行的二次开发,对程序进行分析.在编译器层面,对高级语言翻译成的中间代码语言进行整合分析,协助分析源代码的程序特征.

源代码的特征提取是DEDDL方法的一项主要内容,设计的特征提取过程如图3所示.具体包括以下步骤:

图3 特征提取流程图Fig.3 Feature extraction flow chart

Step 1.进行静态分析与词法分析.根据Flex规则与LLVM编译原理知识,对源代码程序进行分析,得到关键变量,标记并区别于源代码中其余单词,例如关键字和符号,还包括每个变量的类型,出现的位置等,最终得到变量信息表;

Step 2.源代码与变量信息分析.根据变量信息表统计出的内容,结合定义的规则,判定并统计源代码程序中的脆弱变量与脆弱语句;

Step 3.源代码特征标注.利用得到的源代码程序中脆弱变量与脆弱语句集合,选取MiBench以及SPEC CPU benchmark中部分源代码进行脆弱性特征标注,得到源代码训练数据集.

经历以上3个步骤后,便可以得到含有含有变量特征的源代码特征集.其中,变量的位置是变量所处源代码的行号;变量使用频次指的是变量被赋值或者用于算术和逻辑运算时改变的情况;变量的生命周期是指同一变量所出现的基本功能区的个数.

表1是根据图2划分基本分析区域BAA所用程序统计出的3个变量信息表,为了抹去变量名带来的差异性,用变量n(n=1,2,3)代替变量名.表中列出了每个变量所包含的4个特征:类型、出现位置、所在BFA、所在BAA等.以变量1为例,其类型属于int型,出现在了源代码的第3、第14和第16行,这是其在代码中所处的位置.因为其分属于两个BFA和BAA,且在第2个BFA中出现两次,根据上述脆弱性变量与语句判断规则,判定为脆弱性变量,脆弱性值置为1,并纳入脆弱性变量集合.与变量1不同,变量2属于char型,出现在源代码程序第4行,与变量3的第5行在同BFA与BAA中定义与使用,因其在每个区域只出现一次,根据错弱性变量与语句判断规则,判定为普通变量,脆弱性值为0.

表1 变量信息表Table 1 Variable information table

3.4 基于CNN的变量脆弱性判断

本文运用机器学习中的深度学习方法—卷积神经网络(Convolutional Neural Networks,CNN)[23]来判断源代码中的脆弱性语句.设计的处理流程如图4所示.

图4 基于CNN的数据处理流图Fig.4 Data processing flow chart based on CNN

由于卷积神经网络需要一定量的数据作为分类模型的训练样本以及需要设置合理的参数来调节训练的时间和结果的准确度.所以首先需要对.c的源代码数据集进行预处理.经过处理的源代码文本形成预处理数据集可导入卷积神经网络进行训练.在训练之前,需要配置卷积神经网络的参数,以达到良好的训练效果,最终得到语句脆弱性预测模型,该模型能够判断语句变量是否为脆弱性语句,为设计数据流检测算法提供冗余参考.

在配置的CNN对标注好的源代码训练集进行训练后,会得到一个能够对源代码文件进行语句脆弱性判断的分类模型.

如图5所示,向分类器中导入一个新的.c源代码程序文件,该分类器可以遍历文件中的源代码语句,识别出源代码中的脆弱性语句,将识别出的结果导出到文本中,以备后续应用.

图5 语句判断过程Fig.5 Sentence judgment process

3.4.1 源代码文本数据预处理

由于卷积神经网络需要较大的训练样本作为训练集,并且CNN会从已有样本的训练过程中提取自己的特征.因此,使用嵌入式基准测试集合MiBench测试集以及SPEC CPU benchmark中的C代码作为基准程序集,根据定义的规则进行文本中语句与变量脆弱性的相关标注与数据清洗,以此作为配置好参数的卷积神经网络的训练集.由于源代码语言不同于普通的自然语言,因此针对源代码测试集进行自动停用词提取[24],形成针对源代码程序的停用词集合文本.其中,停用词是指在信息检索中,为节省存储空间和提高搜索效率,并且其存在与否对于自然语言的情感表达不会构成较大影响,在处理自然语言数据(或文本)之前或之后会自动过滤掉某些字词或者符号.

如图6给出的一个源代码预处理示例,从如下几个方面进行预处理工作:1)对测试集中所有源代码进行格式整理,调整成统一的编码风格,包括变量的命名与函数的定义与应用等;2)去掉所有MiBench与SPEC CPU benchmark代码中的注释,并对变量名称的命名规范进行统一的标准化处理,如文件指针变量统一命名为fp,计数器变量统一命名为counter等.这样做的原因不同程序中变量的名称不同,即使所做的变量操作相同,如都进行相同的算术运算与逻辑运算,CNN在进行代码词卷积的时候,由于变量的名称的不同可能会提取不必要的特征;3)在去除停用词时,用空白符进行替代(即图中的“□”),这么做的原因是为了提醒此处有内容,并且减少其在CNN训练过程中对源代码相关特征提取的影响.值的一提的是,没有去掉代码的缩进空格,因为源代码结构特征的表示形式便是所拥有的缩进数量,用空白符来代替缩进的数量,这为CNN提供了一个很好的数据特征.

图6 源代码处理示例Fig.6 Sentence judgment process

3.4.2 基于CNN的数据流脆弱性判断模型

CNN的基本结构由嵌入层(embedding layer)、卷积层(convolutional layer)、最大池化层(max-pooling layer)、全连接层以及输出层构成.卷积层和池化层一般会取若干个,采用卷积层和池化层交替设置,即一个卷积层连接一个池化层,池化层后再连接一个卷积层,以此类推.由于卷积层中输出特征面的每个神经元与其输入进行局部连接,并通过对应的连接权值与局部输入进行加权求和再加上偏置值,得到该神经元数值,该过程等同于卷积过程,CNN也由此而得名[25].

如图7所示为本文构造的用于处理源代码的卷积神经网络图.从已经实现预处理的源代码数据文本导入数据.以一段代码词文本为例,首先对该段代码词进行embeding操作,嵌入为t×k的词向量矩阵,其中t为代码词的数量,k为每次进行卷积操作的代码词数量.因为,结合杨等人的工作[26],并参考源代码文本程序的特点,进行反复的调参实验,最终确定将嵌入大小设为embeding_size=4.通过embeding操作,可以起到对低维度的代码词数据进行升维度的作用,放大不易发现的特征,并把笼统的特征分离开,这对CNN进行特征提取起到很重要的作用.设wt∈m,其中m为m维代码词向量空间,wt为预处理后的源代码语句第t个代码词,则长度为n的源代码语句可表示为:

图7 卷积神经网络构造图Fig.7 Construction graph of convolutional neural network

w1:n=w1⊕w2⊕w3⊕…⊕wt⊕…⊕wn

(4)

其中⊕为并置运算符,可连接不同的代码字符串组成不同的源代码子语句.接下来,进行特征提取操作.考虑到训练的时间与准确性,结合MUHAMMAD等的相关工作[27],本文定义了3个不同大小的卷积核k∈{2,3,4},形成多核卷积层,并在每一个卷积核上进行特征映射.其中一个卷积核x∈mk,x应用于长度为k的滑动窗口以产生新的特征At.其中At是由截取的一段代码词得到的特征,计算公式为:

At=f(x·wt:t+k-1+Δ)

(5)

At是由第t个代码词到第t+k-1个代码词上,通过非线性函数f得到的特征.其中Δ∈,作为函数中的偏置项来使用.本文共使用了3种不同的卷积核,因此所产生的语句集合为{w1:k,w2:k+1,…,wn-k+1:n},则由此可产生的特征集合为:

A={A1,A2,…,An-k+1}

(6)

得到上述获得的特征后,进行CNN的最大池化操作(Max pooling).最大池化操作对某个卷积核抽取得到的若干特征进行抽取,抽取最大值作为保留值,并将其它的特征值全部丢弃.因为值最大的特征代表该特征是最强的,抛弃其它弱的此类特征.最终,卷积神经网络将得到的特征进行dropout和softmax操作,将得到最终的二分类——脆弱与正常的结果.本文dropout操作是在CNN训练过程中对于神经网络单元按照一定的概率将其暂时从网络中丢弃.由于是随机丢弃,故而每一个mini-batch都在训练不同的网络,这样可以减少神经网络中的过拟合问题.最终通过softmax计算输出预测结果分别落在脆弱性语句与正常语句两类中的概率.

4 基于智能分析的数据流错误检测算法

本文设计的基于智能分析的数据流错误检测算法DAIA主要包括两个子算法.算法包括对卷积神经网络筛选出的脆弱性变量进行冗余,并在合适的位置添加检测代码.首先,检测算法对目标源程序代码逐行进行词法分析,结合语法分析生成语法分析树T,从而能够过滤出每行代码中的变量.同时,对得到的脆弱变量集合进行遍历识别训练模型得到的脆弱性变量,为添加冗余备份提供相应的参考,并为后续的语句类型判断添加标记,详情见算法1.

算法1.WordTree(Root)

输入:通过静态分析的源代码语法树的根结点

输出:添加冗余备份后的源代码程序

1.start

2.Search(Root0)//遍历静态分析得到语法分析树,Root0为Root的根结点

3. FOR(Root中的每一个结点Nod)

4. if(Node存在于脆弱变量集合)则用对变量进行备份.

5. Vd=V;Ed=E;get_tag();//初始化,并冗余,加标记

6.end

在添加完冗余备份之后,需要在程序合适的位置插入错误检测代码以检测数据流错误.这里涉及对源代码语句类型的判断.对源代码遍历过程进行细化,给出将原程序的一条语句L转换为对应的冗余复算语句L′和添加检测代码的过程.

Step 1.判断L语句的类型;

Step 2.如果L是表达式语句,则对其进行复制,使得L′包含原程序的语句,并对其进行表达式转换,然后在转换后的表达式后面加入分号,生成冗余的复算语句,加至L′的尾部;

Step 3.如果是选择语句,则对选择语句的表达式进行转换后插入比较检测语句至选择语句前面,进行相应检测处理;

Step 4.如果是函数调用和返回语句,则在函数调用和返回前插入比较检测语句比较参数,并进行相应的例程处理;

Step 5.如果是输出语句,则在输出语句前插入比较检测语句,比较输出数据,并进行相应的例程处理.

冗余复算语句转换算法的详细描述见算法2.

算法2.PROCEDURE Handle_L(L)

输入:有标志的冗余源代码程序

输出:具有检测功能的源代码程序

1.Start

2. 获取标志L的类型标识符数组

3.while循环遍历数组:

4.if:L为表达式语句:

5.then:对L进行复制,使得L′包含原程序的语句

6. L_record′=Copy_L_record(L的表达式);//复制表达式语句

7. 在L_record′后加入分号,生成冗余的复算语句,加至L′的尾部

8.if:L为选择语句:

9.then:L_record′=Copy_L_record(L的表达式);//复制表达式语句

10. insert(test_L)//在选择语句的头部

11. if:L_record与L_record′的计算结果不相等

12. then:

13. 添加检测提示语句printf(“error is detected”);

14. printf(location);;//打印出错位置

15. if:L为if语句

16. Handle_L(if语句的语句体)//递归调用

17. if:L为if-else语句

18. Handle_L(if部分的语句体)

19. Handle_L(else部分的语句体)

20. if:L为switch语句

21. Handle_L(case 1);

22. …

23. Handle_L(default);

24. if:L为循环语句:

25. insert(比较检测语句)//在循环体内插入

26. L_record′=Copy_L_record(L的表达式);//复制表达式语句

27. if:L_record与L_record′的计算结果不相等

28. 添加提示语句printf(“error is detected”)

29. Handle_L(循环语句的语句体);

30. if:L为函数调用和返回语句:

31. insert(比较检测语句)//在函数调用和返回前,比较参数

32. if:将函数调用及返回的参数变量和影子变量进行比较不相等

33. 添加提示语句printf(“error is detected”);

34. Handle_L(函数体);

35. if:输出语句:

36. insert(比较检测语句)//输出语句前插入,用于比较输出数据

37. if:将输出语句的参数变量和影子变量进行比较不相等

38. 添加提示语句printf(“error is detected”);

39. Error_Out();//错误处理与程序中断

40. Handle_L(函数体)

41.end

5 实验与结果分析

5.1 故障注入工具与实验流程

已有的故障注入实验方法如LLFI,已经被验证了可用于模拟寄存器硬件故障的准确性[28].然而,LLFI基于LLVM编译器框架,工作在LLVM IR指令层,不适用本文源代码级数据流错误检测的故障注入.鉴于源代码数据流错误发生的载体主要针对的是源代码中的数据变量,以及为了提高故障注入的精准度和注入效率,针对GDB(GUN Project debugger.https://www.ogur.org)调试工具进行二次开发,构建了故障注入工具GDFI(GDB Fault Injection),使其能够具有下列功能:

1)自动分析获取程序运行时的虚拟地址空间范围;

2)统计使用到的数据寄存器如32位的EXP,64位的AXP等的名称与出现的频率;

3)可根据分析的结果以及使用者对注入故障的要求,进行针对性的故障注入等.

总体故障注入实验流程如下:首先,加固后的源代码通过编译器生成汇编代码;其次,汇编代码再通过汇编器,生成机器语言目标文件;然后,通过连接器将生成的机器语言目标文件链接起来生成可执行程序;最后,对可执行程序进行故障注入,统计分析加固代码、故障注入和运行结果.

5.2 变量脆弱性实验与分析

本文使用Tensorflow[28]这一机器学习框架来构建卷积神经网络CNN.该框架已被证明可为多种语言提供接口,来开展人工智能的研究工作,尤其在分布式机器学习与海量数据挖掘方面表现尤为突出[29].依据3.1与3.2节的源代码关键特征信息选取的相关定义与规则,以及使用基于GDFI的脚本程序进行故障注入,综合以上内容,将Mibench与SPEC CPU 2006 benchmark中的.c测试集程序预处理为符合规则要求的数据集合,最终共整合收集了3000条脆弱性标记的测试集样本.部署以上相关环境使用的操作系统为ubuntu18.04,编译器为gcc-7.0.0.

将整理的源代码数据集以80%和20%的比例分成两组.其中80%组作为训练集,20%组作为验证集.依据3.3节提出的基于卷积神经网络的处理模型,构造卷积神经网络.模型的参数如第3.3.2节所示,包括卷积核尺寸k=2,3,4,学习率learning_rate=1e-3,保留比例dropout=0.5等.

图8为监视Tensorflow框架搭建CNN训练数据的追踪图.图的左右为互补,横轴代表训练的数据量,纵轴为训练的准确率,用以对训练效果的描述.从图8中可以看出,随着训练数据量的不断增大,训练的准确度在不断的提升,训练的丢失率在不断下降,直至趋于平稳,这也验证了配置卷积神经网络参数的合理性.

图8 CNN训练过程追踪图Fig.8 CNN training process tracking chart

根据上文提出的方法,进行变量脆弱性预测实验.根据3.1中定义的规则1-4与故障注入结果来衡量源代码变量脆弱性预测的准确率.利用逆向工程工具IDA(Interactive Disassembler.https://www.Ida2020.org),将编译执行的可执行程序对应到源码,以分析程序发生错误对应的具体源码位置,进而找出出错变量的位置.将本文提出的方法DEDDL得到的脆弱变量的SDC错误率与不采用任何加固方法的故障注入实验(NFI)得到的程序中变量的SDC错误率作对比,其中变量SDC率为发生SDC错误的变量占源代码中所有变量的比率,对比结果如图9所示.

由图9中可见,通过故障注入实验得到的矩阵乘法、傅里叶变换、冒泡排序与迪杰斯特拉等4个测试程序的变量平均SDC率为26.5%,略高于本文DEDDL方法预测得到的24.5%,预测效果良好.分析DEDDL预测值略低NFI的原因是因为不采用任何检测加固方法进行的故障注入实验具有较高的随机性,不考虑程序结构与变量出现频次等原因.而本文提出的DEDDL方法强化了变量脆弱性的约束条件,即强调程序结构与变量相关的诸多因素.这为故障检测,减少时空开销提供了的基础.

图9 变量SDC预测准确率对比Fig.9 Comparison of prediction accuracy of variable SDC

5.3 数据流错误检测实验与分析

对数据流错误检测方法的评估主要通过SDC错误检测率和性能开销两个指标.对应用了本文提出的检测算法的加固程序进行2000随机故障注入实验,来验证方法的有效性与相关性能开销.将注入故障后对程序最终运行结果的影响归纳为5类:

1)输出正确(Correct Output):注入故障没有影响程序的正常运行,且输出的结果与未进行故障注入所输出结果相同.

2)输出不正确(Incorrect Output):程序能够顺序运行至结束,但是输出了不正确的结果.

3)程序异常(Exception):程序不能够正常结束,触发操作系统保护机制或不正常终止.

4)陷入死循环(Dead Loop):故障注入导致程序无法正常终止或不能在预期的时间完成并结束.

5)错误检测(Error Detected):程序输出错误但是被检测算法检测到.

通常,因为程序异常会触发操作系统的保护机制,将出错程序移交内核态进行异常处理,因此程序异常的结果归入方法的整体检测率.定义漏检率为输出不正确比率与陷入死循环比率的总和,检测率为输出正确比率、程序异常比率、错误检测比率的总和.

表2展示了对4个测试程序:傅里叶变换,冒泡排序,快速排序,矩阵乘法等进行故障注入的结果.其中2-6行展示了程序在运行过程中表现的5种不同转态.第7行展示了输出不正确与陷入死循环情况的总和.第8行则为上文分析的检测率的总和.从表中可以看出,应用了本文提出的方法后,测试程序在注入故障后运行,平均检测率能达到98.3%.图10为SIDFT[30]方法与本文提出的方法在3个典型测试程序上检测率的对比图,从图中可以看出,两种方法在故障注入后,运行程序的检测率上具有十分相似的效果.

表2 故障注入实验结果Table 2 Experimental results of fault injection

图10 两种方法的检测率对比Fig.10 Comparison of detection rate of two methods

在开销方面,如表3所示.传统的ThOR方法采用的是简单的变量复制思想来实现数据流错误检测,该方法在变量每次更新后都需要重新更新变量副本值,在进行变量读操作时又需要读取副本变量进行比较操作.因此其平均时间与空间开销都十分巨大,分别达到了262%与289%.而SIDFT方法是基于语句复制,只需要在定义的无输出效应基本块的尾部对参数或者分支进行比较,相较于ThOR插入的比较检测语句要少很多,其平均时间空间开销分别为120.4%和131.1%.本文提出的方法采用机器学习识别变量的脆弱性,只需在关键部位插入检测代码,因此平均时间开销与代码开销在SIDFT的基础上又有明显的下降,分别只有119.1%和128.2%,体现出本文提出方法在不损失检测率的情况下,在空间与时间开销方面的优越性.

表3 数据流错误检测方法开销比较Table 3 Cost comparison of data stream error detection methods

6 结 论

本文提出了一种基于深度学习的数据流错误检测方法.不同于指令级数据流错误检测方法,本文的方法针对程序级代码进行分析,能够预测识别出源代码中变量的脆弱性.并且根据该方法的预测结果,文章针对性的提出了基于智能分析的数据流错误检测算法,可自动添加具有复算冗余和有检测功能的语句,实现自动对程序数据流错误的检测.本文还设计并实现了基于GDB的数据流故障注入工具GDFI可用来模拟数据流错误进行实验,以验证本文提出方法的有效性.实验结果表明,本文提出的方法,在保持较高错误检测率的同时,相对于已有的数据流错误检测算法拥有较低的时空开销;并具有独立于具体编译器,便于实施,快速部署等优点.

猜你喜欢

源代码数据流脆弱性
Kaiser模型在内科诊疗护理风险脆弱性分析中的应用研究
优先级驱动的泛化航电网络实时性能分析
农村家庭相对贫困的脆弱性测量及影响因素分析*
基于TXL的源代码插桩技术研究
汽车维修数据流基础(上)
汽车维修数据流基础(下)
基于PSR模型的上海地区河网脆弱性探讨
数据流安全查询技术综述
保护好自己的“源代码”
解密别克安全“源代码”