一种基于符号执行虚拟机的数据竞争检测与重放方法与流程

文档序号:12363510阅读:195来源:国知局
一种基于符号执行虚拟机的数据竞争检测与重放方法与流程

本发明属于软件调试技术领域,更具体地,涉及一种基于符号执行虚拟机的数据竞争检测与重放方法。



背景技术:

随着软件规模越来越大,软件测试以及程序的错误检测变得越来越重要。现有的软件测试及错误检测方法可以分为静态方法和动态方法两种方法。静态方法不实际运行程序,通过分析程序源代码中的调用图、数据流图、控制流图、执行分析等信息,将这些信息和建立的错误模型比较,从而检测程序可能包含的错误代码片段。而动态方法则通过运行程序,分析程序的运行轨迹,检测程序中存在的错误。

目前国内外主流的动态检测方法包括尼古拉斯内瑟科特研发的Valgrind,以及英特尔公司研发的Pin,两者都是二进制动态检测框架,通过将可执行程序反编译成为中间代码,在中间代码上进行程序检测,然后内部虚拟机再执行源代码。

然而,现有动态检测方法存在诸多问题:首先,Valgrind与Pin都属于通用检测框架,二进制代码编译成为中间代码,无法主动甄别线程共享数据,在数据竞争检查时监控过多冗余内存,从而降低了检测的效率;此外,Valgrind内部实现“影子价值”(shadow value)技术,在此框架下实现轻量级的检测工具也会造成较大开销;最后,Pin框架下的检测是根据提供的具体接口来实现,接口的功能相对固定,因此难以满足用户的自定义需求。



技术实现要素:

针对现有技术的以上缺陷或改进需求,本发明提供了一种基于符号执 行虚拟机的数据竞争检测与重放方法,其目的在于,解决现有方法中存在的由于监控过多冗余内存造成检测效率低、检测过程的开销大、以及可扩展性差的技术问题。

为实现上述目的,按照本发明的一个方面,提供了一种基于符号执行虚拟机的数据竞争检测与重放方法,包括以下步骤:

(1)接收来自宿主机端的程序执行轨迹运行请求,并判断该请求是重放请求,还是检测请求,如果是重放请求,则进入步骤(2),否则进入步骤(5);

(2)判断该重放请求对应的记录文件是否存在于磁盘中,如果存在则转入步骤(3),否则过程结束;

(3)获取记录文件中的程序执行信息文件,通过获取符号执行虚拟机中的全局变量读写指令和/或多线程函数调用指令调用检测函数,以加载记录文件中线程指令之间的约束条件,并根据约束条件判断全局变量读写指令和/或多线程函数调用指令是否能够被执行,如果能则执行步骤(4),否则过程结束;

(4)执行全局变量读写指令和/或多线程函数指令,过程结束;

(5)在符号执行虚拟机中执行检测请求对应的多线程程序,监测多个线程之间对多线程程序中的全局变量访问,并记录多线程对全局变量访问的先后顺序,并将这些先后顺序信息记录到二进制文件中,以生成程序执行信息文件;

(6)获取符号执行虚拟机中的指令信息,采用数据竞争检测算法检测指令信息中的全局变量读写,并利用多线程函数调用拦截功能调用指令信息中的多线程函数,以判断全局变量读写是否存在数据竞争,如果是则将数据竞争检测错误的信息报告给客户端,若程序执行未结束,则获取获得符号执行虚拟机的容器存放的下一条指令信息,并转到步骤(5)执行。

优选地,本方法还包括在步骤(1)之前,对符号执行虚拟机进行多线 程扩展,以生成具有多线程函数调用拦截和内部仿真多线程功能的符号执行虚拟机。

优选地,对符号执行虚拟机执行多线程扩展的过程具体为:首先,通过函数指针和结构体搭建多线程仿真的实现框架,然后针对包括线程创建、线程等待、线程加锁和解锁处理的同步函数进行重写。

优选地,步骤(3)包括下述子步骤:

(3-1)读取程序执行信息文件,将程序执行信息文件中的记录文件写入内存;

(3-2)根据符号执行虚拟机的执行轨迹循环选取检测请求对应的多线程程序的指令,判断指令是否为读指令、写指令、函数调用指令,若是,则转入步骤(3-3),否则根据符号执行虚拟机的执行轨迹继续执行该指令;

(3-3)判断指令访问的内存地址是否为全局变量,如果是则进入子步骤(3-4),否则如果该指令是多线程函数调用指令中的加锁解锁函数则进入子步骤(3-4),否则根据符号执行虚拟机的执行轨迹继续执行该指令。。

(3-4)根据写入内存的文件记录信息,判断当前指令是否存在依赖关系,若不存在依赖关系,则进入子步骤(3-5),否则获取符号执行虚拟机中的下一条指令,并返回子步骤(3-2)执行;

(3-5)执行当前指令,解除已执行指令的指令间依赖关系,从而更新写入内存的文件记录信息,获取符号执行虚拟机中的下一条指令,程序未结束时返回子步骤(3-2)执行,直至程序结束。

优选地,步骤(5)包括下述子步骤:

(5-1)检测请求对应的多线程程序所执行的指令,并判断该指令是否是读指令或写指令,如果是则进入子步骤(5-2),否则如果该指令是加锁指令或解锁指令,则在执行完加锁或解锁指令后,进入子步骤(5-2),否则根据符号执行虚拟机的执行轨迹继续执行该指令;

(5-2)判断指令访问的内存地址是否是全局变量地址,如果是,获取 全局变量的地址,建立全局变量地址与编号的映射关系,获取线程访问全局变量信息,包括线程号以及时间戳向量,若指令类型为写指令,则转入子步骤(5-3),若指令类型为读指令则转入子步骤(5-4);否则根据符号执行虚拟机的执行轨迹继续执行该指令;

(5-3)根据全局变量地址对应的编号读取该全局变量对应的所有读访问记录的集合,遍历所有的读访问记录,对每个读访问记录按照如下形式处理:将读访问记录的线程号对应的时间戳值与当前访问记录的相同的线程号对应的时间戳值进行比较,若满足小于等于关系,则不做处理,否则表明两个操作存在先后序关系,并进行记录。最后将所有读访问记录的时间戳向量与当前访问的时间戳向量进行合并;

(5-4)根据全局变量地址对应的编号读取该全局变量对应的所有写访问记录的集合,对每个写访问记录按照如下形式处理:将写访问记录的线程号对应的时间戳值与当前访问记录的相同的线程号对应的时间戳值进行比较,若满足小于等于关系,则不作处理,否则表明两个操作存在先后序关系,并进行记录。最后将所有写访问记录的时间戳向量与当前访问的时间戳向量进行合并;

(5-5)将所有读写访问记录的集合写入记录文件,从而完成执行轨迹的记录过程。

优选地,步骤(6)包括下述子步骤:

(6-1)执行请求对应的多线程程序,以获取执行的指令;

(6-2)通过符号执行虚拟机中的条件分支结构判断指令的类型,如果指令为读指令load或者写指令store,则转入子步骤(6-3);如果指令为函数调用指令,则转入子步骤(6-7);如果指令为其它指令,则根据符号执行虚拟机的执行轨迹继续执行该指令,然后返回子步骤(6-1);

(6-3)判断该指令访问的内存地址是否为全局变量,是则转入子步骤(6-4),否则根据符号执行虚拟机的执行轨迹继续执行该指令,然后返回子 步骤(6-1);

(6-4)判断该指令是否为写访问,若为写访问,则调用写处理函数callStore,并转入子步骤(6-5),若为读访问,则调用读处理函数callLoad,并转入子步骤(6-6);

(6-5)根据全局变量的内存地址addr,检查全局变量是否为第一次写访问,如果是则将访问该全局变量的线程号、以及线程的时间戳存放到写记录中,然后返回子步骤(6-1);否则取出全局变量的内存地址addr的写记录信息,包括线程号和时间戳向量等。将当前线程的时间戳向量中记录的当前线程的时间戳值与写记录信息中的时间戳值进行比较,若前者小于后者,表示发生数据竞争,则报告全局变量的写写数据竞争错误给客户端,否则不作处理,同时继续判断两次写访问之间是否存在并发读访问标识,若存在并发读访问,则将此次写访问记录的当前线程的时间戳值与存放两次写访问的所有并发读访问记录的时间戳向量中记录当前线程的时间戳值进行比较,只要满足小于关系,表示发生数据竞争,则报告全局变量的读写数据竞争错误给客户端,清空存放并发读访问记录中的所有信息,更新写访问记录,然后返回子步骤(6-1),否则清空存放并发读访问记录中的所有信息,更新写访问记录,然后返回子步骤(6-1)。

(6-6)根据全局变量的内存地址,检查全局变量是否为第一次读访问,如果是第一次读访问,则将线程号以及线程的时间戳值存放到读记录中,然后返回子步骤(6-1);如果不是第一次读访问,则取出全局变量的内存地址的写记录信息,包括线程号和时间戳向量等,将当前线程的时间戳向量中记录的当前线程的时间戳值与记录的时间戳值进行比较,若两者满足小于关系,表示发生数据竞争,则报告数据竞争给客户端,否则不作处理,同时继续判断两次写访问之间是否存在并发读访问标识,若存在并发读访问,则将此次读访问直接添加到地址为addr的并发读访问记录中;否则取出读访问记录信息,包括线程号和时间戳向量等,将当前线程的时间戳向 量中记录的当前线程的时间戳值与记录的时间戳值进行比较,若两者满足小于关系,则更新并发读访问标识位,将此次读访问添加到地址为addr的并发读访问记录中,然后更新地址为addr的读访问记录,并返回子步骤(6-1);

(6-7)通过字符串匹配的方式检查函数调用指令调用的当前调用函数是否为多线程函数,是则进入子步骤(6-8),否则根据符号执行虚拟机的执行轨迹继续执行该指令;

(6-8)在符号执行虚拟机中条件分支结构的函数调用指令处,通过字符串匹配的方式识别当前调用函数的名称,并获取函数的参数,根据函数的名称和参数判断当前调用函数是线程创建函数,还是线程等待函数,还是加锁函数,还是解锁函数,如果是线程创建函数则转入子步骤(6-9);若为线程等待函数,则转入子步骤(6-10);若为加锁函数,则转入子步骤(6-11);若为解锁函数,则转入子步骤(6-12);

(6-9)调用线程创建函数创建子线程,子线程初始化自身时间戳向量,并与父线程的时间戳向量进行合并,同时父线程的时间戳值自增1;

(6-10)调用线程等待函数,以将等待线程的时间戳向量与被等待线程的时间戳向量进行合并;

(6-11)调用加锁函数,并在成功加锁后,将线程的时间戳向量和锁对象的时间戳向量进行合并;

(6-12)调用解锁函数,并在成功解锁后,将线程的时间戳向量赋予锁对象的时间戳向量。

总体而言,通过本发明所构思的以上技术方案与现有技术相比,能够取得下列有益效果:

(1)本发明的检测效率高:由于采用了在步骤(6)的检测数据竞争的方法中监测的是全局变量的读写访问和同步函数的调用访问信息,并且仅保存了最后一次写访问记录,在进行全局变量的读写数据竞争检测和写写数据竞争检测时,只需要将当前读写记录的时间戳值与最后一次写访问 记录的时间戳值进行比较即可判断是否存在数据竞争错误,将检测的时间复杂度度由线性时间复杂度降低到了常量时间复杂度,因此检测效率有所提高。

(2)本发明的检测开销小:由于采用了步骤(5)中的子步骤(5-2)仅在判断出指令访问的内存地址为全局变量地址时才建立全局变量地址与编号的映射关系,在检测过程中仅存储全局变量的访问记录,检测全局变量的访问是否存在数据竞争错误,降低了检测开销。

(3)本发明的扩展性好:由于本发明步骤(3)中采用的符号执行虚拟机,在对其进行多线程扩展过程中,并且重写了线程创建、线程等待、线程加锁和解锁处理同步函数,可扩展实现更多的同步函数进行检测,可扩展性强。

附图说明

图1是本发明基于符号执行虚拟机的数据竞争检测与重放方法的整体框架图。

图2是本发明基于符号执行虚拟机的数据竞争检测与重放方法的流程图。

图3是本发明方法中数据竞争检测流程图。

图4是本发明方法中数据竞争记录运行轨迹的流程图。

图5是本发明方法中数据竞争重放方法的流程图。

具体实施方式

为了使本发明的目的、技术方案及优点更加清楚明白,以下结合附图及实施例,对本发明进行进一步详细说明。应当理解,此处所描述的具体实施例仅仅用以解释本发明,并不用于限定本发明。此外,下面所描述的本发明各个实施方式中所涉及到的技术特征只要彼此之间未构成冲突就可以相互组合。

如图1所示,本发明基于符号执行虚拟机的数据竞争检测与重放方法 所基于的整体框架是由宿主机端和目标机端构成。宿主机端运行图形化界面,通过网口发送数据竞争检测的相关命令到目标机端,并处理从目标机发送过来的数据,进行相关的处理筛选,最终显示给用户;目标机端接收到有宿主机段发送过来的命令,执行数据竞争的检测或者重放,并将执行信息与检测结果通过网口发送给宿主机端。从目标机端接收到数据竞争的检测结果以及程序的执行信息之后,通过对信息的解析和分类,将数据竞争的检测结果显示在面板上,将程序的执行轨迹绘制在另外一块面板上,供用户查看。

如图2所示,本发明基于符号执行虚拟机的数据竞争检测与重放方法包括以下步骤:

(1)接收来自宿主机端的程序执行轨迹运行请求,并判断该请求是重放请求,还是检测请求,如果是重放请求,则进入步骤(2),否则进入步骤(5);具体而言,如果请求中的标识位是replay,则该请求是重放请求,否则是检测请求;

(2)判断该重放请求对应的记录文件是否存在于磁盘中,如果存在则转入步骤(3),否则过程结束;

(3)获取记录文件中的程序执行信息文件,通过获取符号执行虚拟机(symbolic virtual machine)中的全局变量读写指令和/或多线程函数调用指令调用检测函数,以加载记录文件中线程指令之间的约束条件,并根据约束条件判断全局变量读写指令和/或多线程函数调用指令是否能够被执行,如果能则执行步骤(4),否则过程结束;在本发明中,使用的符号执行虚拟机是KLEE;

需要注意的是,在本发明的过程执行之前,需要对符号执行虚拟机进行多线程扩展,以生成具有多线程函数调用拦截和内部仿真多线程功能的符号执行虚拟机。

具体而言,对符号执行虚拟机执行多线程扩展的过程具体为:首先, 通过函数指针和结构体搭建多线程仿真的实现框架,然后针对同步函数(线程创建、线程等待、线程加锁和解锁处理)进行重写,其中:

针对线程创建函数,在符号执行虚拟机中每条指令作为一条执行路径,执行路径有独立的执行环境,并且都存放在容器中,可将每条执行路径视为一个线程,线程创建的过程首先获取指令信息、参数个数、函数地址、以及具体的参数信息,进而通过调用符号执行虚拟机内部函数创建新的线程,初始化线程号等信息,进而对每个线程的时间戳向量进行初始化,主要操作为本线程对应的时间戳值设置为1,其他线程的时间戳值设置为0,然后将子线程与父线程的时间戳向量进行合并,得到新的时间戳向量赋值给子线程。最后将创建的线程存放到符号执行虚拟机的容器中。

针对线程等待函数,从存储线程的容器中取出等待线程的信息,判断等待线程是否已经结束,如果未结束,则对于未结束的等待线程,将等待线程加入调用线程的等待集合中,等待线程结束时被唤醒,如果结束,则扫描线程集合,唤醒那些等待它的线程,并且调用线程做相关的退出清理操作。

针对线程加锁函数,首先判断此锁是否在锁集合中,对于不存在锁集合中的锁,表示第一次访问该锁,创建锁对象,并且设置锁为忙碌状态,将锁的持有线程设置为当前线程,对处于空闲状态的锁,设置锁为忙碌状态,将锁的持有线程设置为当前线程;对忙碌状态的锁,将当前线程设置为阻塞状态,将当前的线程加入到锁的等待队列中,待锁释放后再获取。

针对线程解锁函数,对于处于忙碌状态的锁的对象,在当前线程不持有当前锁时返回错误;对不为空的锁的等待队列,则取出队列最开始的线程,将锁的持有线程设置为该线程,并将该线程的等待状态重置,表示线程不再是阻塞状态,其中对等待队列为空的锁,则设置锁为空闲状态,清除锁的持有线程。

(4)执行全局变量读写指令和/或多线程函数指令,过程结束;

(5)在符号执行虚拟机中执行检测请求对应的多线程程序,监测多个线程之间对多线程程序中的全局变量访问,并记录多线程对全局变量访问的先后顺序,并将这些先后顺序信息记录到二进制文件当中,以生成程序执行信息文件;

(6)获取符号执行虚拟机中的指令信息,采用数据竞争检测算法检测指令信息中的全局变量读写,并利用多线程函数调用拦截功能调用指令信息中的多线程函数,以判断全局变量读写是否存在数据竞争,如果是则将数据竞争检测错误的信息报告给客户端,若程序执行未结束,则获取获得符号执行虚拟机的容器存放的下一条指令信息,并转到步骤(5)执行;

如图3所示,本发明方法的步骤(6)具体包括下述子步骤:

(6-1)执行请求对应的多线程程序,以获取执行的指令;

(6-2)通过符号执行虚拟机中的条件分支结构判断指令的类型,如果指令为读指令load或者写指令store,则转入子步骤(6-3);如果指令为函数调用指令,则转入子步骤(6-7);如果指令为其它指令,则根据符号执行虚拟机的执行轨迹继续执行该指令,然后返回子步骤(6-1);

(6-3)判断该指令访问的内存地址是否为全局变量,是则转入子步骤(6-4),否则根据符号执行虚拟机的执行轨迹继续执行该指令,然后返回子步骤(6-1);

(6-4)判断该指令是否为写访问,若为写访问,则调用写处理函数callStore,并转入子步骤(6-5),若为读访问,则调用读处理函数callLoad,并转入子步骤(6-6);

(6-5)根据全局变量的内存地址(为了方便描述,假设地址为addr),检查全局变量是否为第一次写访问,如果是则将访问该全局变量的线程号、以及线程的时间戳存放到写记录中,然后返回子步骤(6-1);否则取出全局变量的内存地址addr的写记录信息,包括线程号和时间戳向量等。将当前线程的时间戳向量中记录的当前线程的时间戳值与写记录信息中的时间戳 值进行比较,若前者小于后者,表示发生数据竞争,则报告全局变量的写写数据竞争错误给客户端,否则不作处理,同时继续判断两次写访问之间是否存在并发读访问标识,若存在并发读访问,则将此次写访问记录的当前线程的时间戳值与存放两次写访问的所有并发读访问记录的时间戳向量中记录当前线程的时间戳值进行比较,只要满足小于关系,表示发生数据竞争,则报告全局变量的读写数据竞争错误给客户端,清空存放并发读访问记录中的所有信息,更新写访问记录,然后返回子步骤(6-1),否则清空存放并发读访问记录中的所有信息,更新写访问记录,然后返回子步骤(6-1)。

(6-6)根据全局变量的内存地址,检查全局变量是否为第一次读访问,如果是第一次读访问,则将线程号以及线程的时间戳值存放到读记录中,然后返回子步骤(6-1);如果不是第一次读访问,则取出全局变量的内存地址的写记录信息,包括线程号和时间戳向量等,将当前线程的时间戳向量中记录的当前线程的时间戳值与记录的时间戳值进行比较,若两者满足小于关系,表示发生数据竞争,则报告数据竞争给客户端,否则不作处理,同时继续判断两次写访问之间是否存在并发读访问标识,若存在并发读访问,则将此次读访问直接添加到地址为addr的并发读访问记录中;否则取出读访问记录信息,包括线程号和时间戳向量等,将当前线程的时间戳向量中记录的当前线程的时间戳值与记录的时间戳值进行比较,若两者满足小于关系,则更新并发读访问标识位,将此次读访问添加到地址为addr的并发读访问记录中,然后更新地址为addr的读访问记录,然后返回子步骤(6-1);

(6-7)通过字符串匹配的方式检查函数调用指令调用的当前调用函数是否为多线程函数,是则进入子步骤(6-8),否则根据符号执行虚拟机的执行轨迹继续执行该指令;

(6-8)在符号执行虚拟机中条件分支结构的函数调用指令处,通过字符串匹配的方式识别当前调用函数的名称,并获取函数的参数,根据函数 的名称和参数判断当前调用函数是线程创建函数,还是线程等待函数,还是加锁函数,还是解锁函数,如果是线程创建函数则转入子步骤(6-9);若为线程等待函数,则转入子步骤(6-10);若为加锁函数,则转入子步骤(6-11);若为解锁函数,则转入子步骤(6-12);

(6-9)调用线程创建函数创建子线程,子线程初始化自身时间戳向量,并与父线程的时间戳向量进行合并,同时父线程的时间戳值自增1;

(6-10)调用线程等待函数,以将等待线程的时间戳向量与被等待线程的时间戳向量进行合并;

(6-11)调用加锁函数,并在成功加锁后,将线程的时间戳向量和锁对象的时间戳向量进行合并;

(6-12)调用解锁函数,并在成功解锁后,将线程的时间戳向量赋予锁对象的时间戳向量。

如图4所示,本发明方法的步骤(5)包括下述子步骤:

(5-1)检测请求对应的多线程程序所执行的指令,并判断该指令是否是读指令或写指令,如果是则进入子步骤(5-2),否则如果该指令是加锁指令或解锁指令,则在执行完加锁或解锁指令后,进入子步骤(5-2),否则根据符号执行虚拟机的执行轨迹继续执行该指令;

(5-2)判断指令访问的内存地址是否是全局变量地址,如果是,获取全局变量的地址,建立全局变量地址与编号的映射关系,获取线程访问全局变量信息,包括线程号以及时间戳向量,若指令类型为写指令,则转入子步骤(5-3),若指令类型为读指令则转入子步骤(5-4);否则根据符号执行虚拟机的执行轨迹继续执行该指令;

(5-3)根据全局变量地址对应的编号读取该全局变量对应的所有读访问记录的集合,遍历所有的读访问记录,对每个读访问记录按照如下形式处理:将读访问记录的线程号对应的时间戳值与当前访问记录的相同的线程号对应的时间戳值进行比较,若满足小于等于关系,则不做处理,否则 表明两个操作存在先后序关系,并进行记录。最后将所有读访问记录的时间戳向量与当前访问的时间戳向量进行合并,合并过称为:按照数组下标从小到大的顺序遍历两个数组,假设下标为i时,将两个数组中下标为i的较大值赋值给当前访问的时间戳向量下标为i的时间戳值。

(5-4)根据全局变量地址对应的编号读取该全局变量对应的所有写访问记录的集合,对每个写访问记录按照如下形式处理:将写访问记录的线程号对应的时间戳值与当前访问记录的相同的线程号对应的时间戳值进行比较,若满足小于等于关系,则不作处理,否则表明两个操作存在先后序关系,并进行记录。最后将所有写访问记录的时间戳向量与当前访问的时间戳向量进行合并,合并过程为:按照数组下标从小到大的顺序遍历两个数组,假设下标为i时,将两个数组中下标为i的较大值赋值给当前访问的时间戳向量下标为i的时间戳值;

(5-5)将所有读写访问记录的集合写入记录文件,从而完成执行轨迹的记录过程。

如图5所示,本发明方法的步骤(3)包括下述子步骤:

(3-1)读取程序执行信息文件,将程序执行信息文件中的记录文件写入内存;

(3-2)根据符号执行虚拟机的执行轨迹循环选取检测请求对应的多线程程序的指令,判断指令是否为读指令、写指令、函数调用指令,若是,则转入步骤(3-3),否则根据符号执行虚拟机的执行轨迹继续执行该指令;

(3-3)判断指令访问的内存地址是否为全局变量,如果是则进入子步骤(3-4),否则如果该指令是多线程函数调用指令中的加锁解锁函数则进入子步骤(3-4),否则根据符号执行虚拟机的执行轨迹继续执行该指令。。

(3-4)根据写入内存的文件记录信息,判断当前指令是否存在依赖关系,若不存在依赖关系,则进入子步骤(3-5),否则获取符号执行虚拟机中的下一条指令,并返回子步骤(3-2)执行;

(3-5)执行当前指令,解除已执行指令的指令间依赖关系,从而更新写入内存的文件记录信息,获取符号执行虚拟机中的下一条指令,程序未结束时返回子步骤(3-2)执行,直至程序结束。

本领域的技术人员容易理解,以上所述仅为本发明的较佳实施例而已,并不用以限制本发明,凡在本发明的精神和原则之内所作的任何修改、等同替换和改进等,均应包含在本发明的保护范围之内。

当前第1页1 2 3 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1