背景技术
随着多核处理器的发展,并发程序,由于其高效性,广受青睐。但是,并发程序由于其通过不同线程间的通信,尤其是共享内存通信来保证程序中不同线程间的一致性,很难在编码过程中不产生Bug,原子性错误就是其中最主要的一类,约占所有并发错误的2/3。
原子性错误有如下几个模式,如表1,其中ei为事件,ti,mi,ai分别为其所对应的线程、所存取的内存位置及其存取类型,这些模式的完整性已得到了理论性的证明。
表1:原子性错误的各种模式
应用于并发程序的另一个技术为确定性回放技术(Deterministic ReplayTechnique)。该技术通过在程序运行时直接或者间接地记录程序中共享内存的执行顺序,在程序运行结束时产生执行日志(Log)。回放时,可以按照日志进行确定性的回放。实际上,执行日志(Log)是程序执行轨迹(Trace)的压缩版本,执行轨迹是执行时的事件序列,每个事件包含各种指令信息,如存取内存位置等。
一个执行轨迹(Trace)至少满足两个约束,方为合法。一是Happens-Before关系,包含以下三方面:
●若事件ei,ej属于同一个线程,且ei在ej前发生,那么在任何情况下ei在ej前发生;
●若事件ei开启了ej所在的线程,那么ei必须在ej前发生;
●若事件ej等待来在ei发送的信息,那么ei在ej前发生;
另一个约束为锁约束(Lock Constraint),即任何时刻,任何一个锁只能被一个线程所拥有,从而达到互斥的作用。
发明内容
本发明所要解决的技术问题是:帮助程序修复用户自动化验证其是否通过同步化技术正确修复了并发程序中原子性错误的问题。
本发明的技术方案为:一种验证并发程序中违反原子性错误是否被正确修复的方法,利用原始程序执行错误时的执行日志,结合原子性错误的各种模式构造新的、适合于修复后程序的执行日志,并通过观察修复后的程序是否能够按照新的日志正确执行来判断原子性错误是否被修复,包括以下步骤:
1)利用原始程序的错误执行日志以及修复后的程序,构造与错误执行日志对应同时又包含修复信息的执行轨迹,所述修复指同步化,所述修复信息指如何使用同步化来修复的信息,一个执行轨迹是一个事件序列,表示为δ=<ei>,其中每个事件ei包含下列属性:
ti:事件ei所属的线程;
mi:事件ei所存取的内存位置;
li:当事件ei执行时,其所拥有的锁;
ai:事件ei的存取类型,包括读,写,获取锁,释放锁,等待,通知,创建线程和等待线程结束8种类型;
2)利用所得执行轨迹,以及原子性错误的不同模式,构造适合于修复后程序的新执行轨迹:
21)使用步骤1)所构造的执行轨迹,首先根据一个并发程序必须满足的约束,即Happens-Before关系和锁约束构造一个有向无环图,该图中包含执行轨迹中的所有事件,事件用图的结点表示,且仅包含Happens-Before关系和锁约束所要求的事件间顺序关系,顺序关系用图的有向边表示;
22)根据原子性错误的各种模式,在步骤1)所构造的执行轨迹中搜索相应的事件序列;
23)不断的向有向无环图中添加符合原子性错误模式的边,直到其不能满足Happens-Before关系和锁约束,输出一个新构造的有向无环图,判断新构造的有向无环图是否覆盖所有满足原子性错误的模式的边,如不能覆盖,则构造新的有向无环图来覆盖他们,直到构造得到的所有有向无环图覆盖所有满足原子性错误模式的边;
24)对于步骤23)中的每一个有向无环图,继续向其中加入有向无环图不包含但原始执行轨迹包含的边,直到不能满足Happens-Before关系和锁约束,最终输出该图的一个拓扑排序,即一个新执行轨迹;
3)按照构造的新执行轨迹,执行修复后的程序,观察程序是否正常执行来判断原子性错误是否被修复。
步骤1)中,修复信息来自于用户,根据修复信息对程序进行插状,构造执行轨迹时包括以下操作:
11)对每一个存取线程间共享内存的指令,使用一个全局锁进行同步,并在其中记录该指令所对应的执行时事件的各种信息,即执行轨迹;
12)根据修复信息,使用条件判断语句让程序选择性的跳过用于修复错误所引入的指令,使修复后的程序仍然能够按照的错误执行日志执行,从而记录与的错误执行日志相对应的执行轨迹;
13)虽然跳过用于修复错误所引入的锁指令,在执行程序构造执行轨迹时,仍然记录新引入的锁,使得执行轨迹与原始的错误执行日志相对应,且包含修复信息。
步骤3)中,根据程序的控制流图和所构造的新执行轨迹按照以下规则进行启发式执行,设en是控制流图中所指示的实际将要执行的事件,er是新执行轨迹所指示的将要执行的事件:
a)若er和en不属于相同线程,则阻塞正在运行的线程,切换到er所属线程;
b)若en与er是同一事件,那么正常执行,er指向新执行轨迹中的下一事件,执行en,同时让en指向er指向的下一将要执行的事件;
c)若en与er不是同一事件,但属于同一线程:
c1)控制流图中存在en到er的路径,执行en,不改变er;
c2)控制流图中不存在en到er的路径,但en存在于新执行轨迹中er之后,那么不做任何执行,er指向新执行轨迹中下一事件;
c3)控制流图中不存在en到er的路径,en也不存在于新执行轨迹中er之后,执行en,er指向新执行轨迹中的下一事件。
进一步的,步骤23)中为了提高算法效率提供了3种无损优化方法以及2种有损优化方法中的一种或多种,所述无损优化方法指不影响算法正确性的优化方法,有损优化方法指会影响正确性的优化方法。
本发明的有益效果为:用户在并发程序出错后,使用同步化方法修复原子性错误,本发明方法能够帮助用户在重复执行修复程序若干次后(一般少于10次)自动判断bug是否被正确修复。本发明方法不要求用户知道Bug的准确位置,符合用户检验错误是否被修复的习惯性方法。
具体实施方式
本发明利用原始程序执行错误时的执行日志,结合原子性错误的各种模式构造新的,适合于修复后程序的执行日志,并通过观察修复后的程序是否能够按照新的日志正确执行来判断原子性错误是否被修复。其主要步骤如下:
1.利用原始程序的错误执行日志Log以及修复后(也就是同步化后)的程序,构造与
错误执行日志一致,同时又包含修复信息(即如何使用同步化来修复的信息)的执
行轨迹Trace,一个Trace是一个事件(event)序列,表示为δ=<ei>,每个事件ei
包含下列属性:
●ti:事件ei所属的线程
●mi:事件ei所存取的内存位置
●li:当事件ei执行时,其所拥有的锁
●ai:事件ei的存取类型,包括读(read),写(write),获取锁(acquire),释放锁(release),等待(wait),通知(notify),创建线程(fork),等待线程结束(join)8种类型。
程序修复用户,一般为软件工程师,程序员,提供他们的修复信息,即如何同步化程序,亦即在程序的哪一行加锁,在哪一行释放锁。根据修复信息,对程序进行插状(Instrumentation)。
(1)对每一个存取线程间共享内存的指令,使用一个全局锁进行同步化,并在其中记录该指令所对应的执行时事件(event)的各种信息,如步骤a)中对Trace的定义。
(2)同时,根据修复信息,使用条件判断语句让程序选择性的跳过用于修复错误所引入的锁,让修复后的程序仍然能够按照原始的执行Log执行,从而记录与之相对应的Trace。
(3)虽然跳过用于修复错误所引入的锁,但是,在执行程序构造Trace时,仍然记录新引入的锁。这样最终构造的Trace与原始的执行Log相对应,又包含修复信息。
2.利用所得Trace,以及原子性错误的不同模式,构造适合于修复后程序的新Trace,
根据实例可能构造多个。
21)使用步骤1所构造的Trace,首先根据一个并发程序必须满足的约束,即Happens-Before关系和锁约束构造一个有向无环图(Happens-Before Graph),该图中包含Trace中的所有事件,用图的结点表示,但仅包含Happens-Before关系和锁约束所要求的事件间顺序关系,用图的有向边表示。
22)根据原子性错误的各种模式,见表1,在步骤1所构造的Trace中搜索相应的事件序列。
23)不断的向Happens-Before Graph中添加符合原子性错误的模式的边,直到其不能满足Happens-Before关系和锁约束,输出一个新构造的Happens-Before Graph,如此往复,直到构造一组Happens-Before Graph,可以覆盖所有满足原子性错误的模式的边。
24)对于步骤23)中的每一个Happens-Before Graph继续向其中加入其不包含但原始Trace包含的其他边,直到不能满足Happens-Before关系和锁约束,最终输出该图的一个拓扑排序,即一个新的Trace。
这里提供5种优化方法,包括3种无损优化方法(即不影响算法正确性的优化方法)以及2种有损优化方法(即可能影响正确性的优化方法),这些优化方法可以一齐使用,也可以按需使用:
无损优化1:减少所需覆盖的满足原子性错误的模式的边。由于事件顺序关系的传递性,一条边实际蕴含了一组顺序关系,这些顺序关系不再需要通过显式的向有向无环图中加入边来加入。
无损优化2:一次向有向无环图加入多组满足原子性错误的模式的边,然后测试其是否满足一个Happens-Before Graph应该满足的约束,若满足,则可加入,若不满足,只需进行回滚操作,即将这些边去除。
无损优化3:算法并行化:在步骤23)开始时,同时开启多个线程,同时通过覆盖不同的边构造不同的Trace来快速覆盖所有的满足原子性错误的模式的边。
有损优化1:从所需覆盖的满足原子性错误模式的边中,去除那些对应相同源码语句,但不同事件的,满足原子性错误模式的边。
有损优化2:按照文献[1][2]所提出的方法预测原子集(Atomic Set)来减少所需覆盖的满足多变量原子性错误的模式的边。
[1]Peng Liu,Julian Dolby and Charles Zhang.Finding Incorrect Compositions of Atomicity.In ESEC/FSE 2013.
[2]Sangmin Park and Mary Jean Harrold and Richard Vuduc.Griffin: grouping suspicious memory-access patterns to improve understanding of concurrency bugs.In ISSTA 2013.
3.按照构造的新Trace,执行修复后的程序,观察其是否正常执行来判断原子性错误是否被修复。
具体为:根据程序的控制流图和所构造的新Trace按照以下规则进行启发式执行,设en是控制流图中所指示的实际将要执行的事件,er是Trace所指示的将要执行的事件:
a)若er和en不属于相同线程,则阻塞正在运行的线程,切换到er所属线程;
b)若en与er是同一事件,那么正常执行,er指向新执行轨迹中的下一事件,执行en,同时让en指向er指向的下一将要执行的事件;
c)若en与er不是同一事件,但属于同一线程:
c1)控制流图中存在en到er的路径,执行en,不改变er;
c2)控制流图中不存在en到er的路径,但en存在于新执行轨迹中er之后,那么不做任何执行,er指向新执行轨迹中下一事件;
c3)控制流图中不存在en到er的路径,en也不存在于新执行轨迹中er之后,执行en,er指向新执行轨迹中的下一事件。
下面通过具体实例来说明本发明的效果。
本实施例选用工业中的、开源的、规模较大的真实程序中的真实的原子性错误进行实验,直接将本发明的系统应用于其上,观察其效果。
实验程序包括Apache Derby,一个常用的数据库管理系统;Apache Tomcat,一个常用的服务器系统;Apache Fop,一个常用的格式化输出处理系统;Apache Sling一个网页框架。
1.用户在编码和执行过程中出现原子性错误,并尝试使用同步化的方法来修复。
2.用户向本发明的系统输入记录的执行日志,和同步化所修改的源码中的行号。
3.本发明对修复后的程序进行查桩及回放,产生包含修复信息的执行轨迹。
4.本发明根据修复信息和执行轨迹,重新构造新的执行轨迹。
5.根据构造的执行轨迹,执行修复后的程序。
6.若出错,则说明没有修复。
7.否则,则说明已修复成功。
表2展示了这些用于实验的真实程序中的真实的原子性错误信息,以及实验结果,包括是否成功验证原子性错误被修复,需要回放多少次才能成功验证修复等信息。实验结果表明,即使对于很复杂的程序,程序员只需要使用本发明方法重复执行该程序1到4次即可成功的验证一个原子性错误是否被修复。
表2:实验结果