基于可达性分析的残差运行时验证
1. 行为切割
在寻找方法中的违规路径时,我们的思路是逐个遍历构建好的控制流图(CFG)自动机 $A_c^m$ 的状态,检查从当前访问状态出发,是否存在一条路径能使扩展的不良前缀自动机到达最终状态。这里我们主要讨论匹配不良前缀的情况,不过同样的分析方法也适用于匹配良好前缀,匹配良好前缀的路径即为安全路径。
1.1 相关定义
- 给定自动机 $A$,$A(q)$ 表示将 $q$ 设为初始状态的自动机 $A$。
- 对于有限状态机 $A(q)$ 及其扩展转移函数 $\hat{\delta}$,若存在一个字符串 $s \in \Sigma^ $ 使得 $\hat{\delta}(q, s) \in F$,则状态 $q$ 是可共达的;若存在一个字符串 $s \in \Sigma^ $ 使得 $\hat{\delta}(q_0, s) = q$ 且 $q_0$ 是初始状态,则状态 $q$ 是可达的。
1.2 标记违规和安全路径的算法
Algorithm 1: Marking violating and safe paths
1 Given A_c^m = (Σ ∪{#}, Q, δ, q_0, F), A↑badϕ
2 Vm = ∅ // represents all states in a violating path
3 Sm = Q // represents all states in a safe path
4 work := q_0 // represents a worklist stack
5 visited = ∅
6 while work not empty do
7 q = work.pop()
8 if q ∉ visited then
9 visited = visited ∪ q
10 if q ∉ Vm then
11 Â = A_c^m(q) × A↑badϕ
12 if L(Â) ≠ ∅ then
13 Vm = Vm ∪ {{q′ | (q′, −) ∈ coreachable(Â)} ∩ reachable(A_c^m(q))}
14 foreach q′′ in {q′′ | ⟨q, s, q′′⟩ ∈ δ} do
15 work.push(q′′)
16 end
17 end
18 Sm = Sm \ Vm
该算法从 $A_c^m$ 的初始节点开始进行深度优先搜索。通过维护一个工作栈和已访问集合,对每个访问的状态 $q$,将其设为初始节点并与 $A↑badϕ$ 求交集。若交集不为空,则找出交集中所有可共达状态,将其在 $A_c^m$ 中对应的状态添加到 $V_m$ 中。最后,$S_m$ 为所有状态减去 $V_m$ 中的状态。
1.3 示例
图中展示了从程序构建的 CFG 自动机,红色标记的状态存在于违反属性的路径中,这些状态需要进行插桩,而绿色状态则无需插桩。原本需要在 8 个不同位置插桩,现在只需在 4 个位置插桩。
对于每个分析的方法 $m$,我们将 $S_m$ 中状态对应的指令添加到集合 $SP$ 中,而 $V_m$ 中状态对应的指令则进行运行时监控插桩。
2. 分析的范围和可靠性
2.1 分析范围
我们的分析仅影响在一个方法中完全生成的跟踪。若某个跟踪 $t$ 包含由方法 $m$ 之外的指令生成的事件,则 $m$ 中生成 $t$ 中事件的指令都不会被标记为安全。
2.2 命题 1(分析范围)
给定参数化跟踪 $\tau$ 在 $[P]$ 中:
$\forall t \in Proj(\tau), \forall m \in Methods :$
$( \exists i \in Instructions\Instructions_m : instrument(i) \in t )$
$\Rightarrow { ev(q) | q \in S_m} \cap events(t) = \emptyset$
证明:假设存在跟踪 $t$ 包含方法 $m$ 之外的指令生成的事件,可分为两种情况:
- 运行时在执行 $m$ 之前包含事件的跟踪:由于我们排除了接收会生成事件类型参数的方法,此类跟踪不受分析影响,若要受影响,方法 $m$ 需接收生成事件的对象,此时 $m$ 将被排除分析,导致 $S_m = \emptyset$。
- 从 $m$ 开始但运行时有部分事件由 $m$ 之外的指令生成的跟踪:对象的逃逸(可能在 $m$ 之外生成事件)会被
#
转换捕获,根据不良前缀自动机的构造和算法 1,此类转换会使 CFG 自动机的任何状态到达最终状态,导致 $S_m = \emptyset$。
2.3 定理 1(分析的可靠性)
给定语言 $L \subseteq \Sigma^*$ 和对方法 $m$ 分析得到的 $S_m$,分析可靠的充要条件是:
$\forall a_1 \cdots a_i \cdots a_n \in \Sigma^+, \forall i \in N :$
$match_L(a_1 \cdots a_i \cdots a_n) \neq match_L(a_1 \cdots a_{i - 1}a_{i + 1} \cdots a_n)$
$\Rightarrow a_i \notin { ev(q) | q \in S_m}$
证明:假设分析移除 $a_i$ 后,$match_L(a_1 \cdots a_i \cdots a_n) \neq match_L(a_1 \cdots a_{i - 1}a_{i + 1} \cdots a_n)$,这意味着 $a_i$ 处于 $a_1 \cdots a_{i - 1}$ 的一个扩展中,该扩展会使 $L$ 的不良前缀监控器到达最终状态。根据算法 1,$a_i$ 会被添加到 $V_m$ 中,因此 $a_i \notin S_m$。
3. 实现
我们将工作实现为 BISM Java 字节码插桩工具的插件。BISM 中的插桩指令通过转换器给出,类似于面向方面编程的方面。BISM 提供了组合多个转换器的机制,这些转换器能够控制指令的可见性。
对于每个属性,我们对程序应用两个转换器:
- 静态分析器:对方法代码进行单次遍历,执行残差分析并隐藏安全指令。
- 运行时监控器:对残差部分进行插桩以进行运行时监控。
我们还扩展了 BISM,添加了一个模块,用于执行残差分析并提供自动机操作,包括生成方法的 CFG 自动机、扩展不良前缀自动机以及检测违反属性的执行路径。
4. 评估
4.1 实验设置
我们比较了使用残差分析(RRV)和不使用分析(RV)时的插桩开销。使用 BISM 对 DaCapo 套件中的程序进行插桩,监控经典的 SafeListIterator(P1)、SafeMapIterator(P2)和 SafeHasNext(P3)属性。
4.2 评估指标
- 受影响的指令、方法和类的数量。
- 改进因子。
- 运行时开销,包括执行时间和使用的内存(强制垃圾回收后测量堆和非堆内存)。
4.3 实验结果
| Bench | Property | # Classes (Rel) | # Classes (Nop) | # Methods (Rel) | # Methods (Nop) | # Instructions (Rel) | # Instructions (Nop) | Imp (Instructions) | # Events (RV) | # Events (RRV) | Imp (Events) |
|---|---|---|---|---|---|---|---|---|---|---|---|
| avrora | P1 | 41 | 14 | 99 | 56 | 165 | 86 | 2.09 | 1.36M | 1.36M | 1.00 |
| fop | P1 | 123 | 33 | 275 | 103 | 700 | 210 | 1.43 | 729K | 490K | 1.49 |
| sunflow | P1 | 11 | 2 | 35 | 15 | 50 | 15 | 1.43 | 2.55M | 1.27M | 2.00 |
| pmd | P1 | 86 | 27 | 200 | 95 | 420 | 146 | 1.53 | 4.77M | 778K | 6.13 |
| avrora | P2 | 41 | 19 | 111 | 78 | 160 | 117 | 3.72 | 353K | 246K | 1.43 |
| fop | P2 | 100 | 28 | 206 | 85 | 2.9K | 2.6K | 9.19 | 545K | 351K | 1.55 |
| sunflow | P2 | 11 | 6 | 32 | 24 | 40 | 26 | 2.86 | 2.55M | 1.27M | 2.00 |
| pmd | P2 | 81 | 27 | 168 | 70 | 392 | 211 | 2.17 | 3.01M | 2.6M | 1.16 |
| avrora | P3 | 32 | 11 | 76 | 33 | 160 | 79 | 1.98 | 1.5M | 1.29M | 1.16 |
| fop | P3 | 70 | 7 | 145 | 31 | 376 | 67 | 1.22 | 1.07M | 882K | 1.21 |
| sunflow | P3 | 8 | 2 | 12 | 3 | 29 | 3 | 1.12 | 3.93M | 2.65M | 1.48 |
| pmd | P3 | 65 | 21 | 126 | 48 | 343 | 115 | 1.50 | 5.64M | 5.23M | 1.08 |
结果表明,残差分析平均将插桩点数量减少了 2.5 倍(最高达到 9.19 倍),运行时生成的事件数量平均减少了 1.8 倍(最高达到 6.13 倍)。不过,插桩点的减少并不总是导致运行时事件的减少,部分优化的缺失是由于逃逸事件(
#
)。通过逃逸分析改进 SafeList 可以进一步减少逃逸事件,这将作为未来的工作方向。
图中展示了所有三个属性组合时的执行时间和内存使用情况,RRV 在所有基准测试中的性能都优于传统插桩(RV)。
5. 相关工作
5.1 CLARA
CLARA 能够处理可以用有限状态自动机表达的属性,通过在编译时部分评估运行时监控器来减少插桩点。它进行三个阶段的分析,精度逐渐提高,更精确的阶段使用需求驱动的指针分析并处理过程内分析。不过,CLARA 及其底层的 abc 编译器已不再维护。
5.2 CLARVA
CLARVA 扩展了 CLARA,用于处理由 DATEs 表达的属性,将 Java 代码转换为基于自动机的模型,并允许结合控制流分析。它能够减少插桩点并对属性进行推理和修剪,但分析依赖于构建整个程序的调用图和使用 Soot 进行指针分析。
5.3 STARVOORS
STARVOORS 结合了演绎定理证明和控制流可达性分析,能够处理控制和数据导向的属性。它使用 ppDATE 进行属性规范,通过解决 Hoare 三元组来修剪属性转换,但不减少插桩点。
5.4 预测语义运行时监控
该方法在过程内级别进行分析,使用控制流图(CFG)和程序依赖图(PDG)找到预测词。运行时监控器可以接收单个事件或预测词,虽然不减少插桩点,但可能产生更快的判定结果。
6. 结论与展望
我们提出了一种支持对可以用有限状态自动机表达的参数化属性进行残差运行时验证的分析方法。该方法通过过度近似程序行为,仅依赖方法的控制流图来静态识别安全区域。我们证明了该方法在监控属性不良前缀方面的有效性,同时也可用于监控良好前缀。
该方法无需依赖特定的静态分析技术即可实现开销优化,将静态分析任务与残差分析分离,并能与许多运行时验证框架无缝集成。它已完全实现并集成到 BISM 插桩工具中,在运行时表现出显著的性能优势。
未来,我们计划通过插件扩展该方法,让用户能够轻松结合静态分析结果,减少过度近似并提高精度。具体包括静态调用图构建、逃逸分析和数据流指针分析等。我们还将提供一种语言,通过细化安全指令列表将这些分析结果集成到残差分析中。此外,我们计划将该方法扩展到并发程序分析,处理线程逃逸引用。
7. 方法总结与优势
7.1 方法总结
- 行为切割 :通过遍历 CFG 自动机 $A_c^m$ 的状态,结合扩展的不良前缀自动机,找出违规路径和安全路径。使用深度优先搜索算法标记状态,区分安全状态和违规状态。
- 分析范围和可靠性 :分析仅影响在一个方法中完全生成的跟踪,确保了分析的范围和可靠性。通过命题和定理证明了分析的正确性。
- 实现 :作为 BISM Java 字节码插桩工具的插件实现,利用转换器控制指令可见性,扩展 BISM 模块执行残差分析和自动机操作。
- 评估 :实验表明,残差分析能有效减少插桩点和运行时生成的事件数量,提高性能。
7.2 优势
- 独立性 :无需依赖特定的静态分析技术,将静态分析与残差分析分离,可与多种运行时验证框架集成。
- 性能提升 :显著减少插桩开销,提高程序运行时性能,在多个基准测试中表现优于传统插桩方法。
- 灵活性 :可用于监控属性的不良前缀和良好前缀,适用于不同类型的参数化属性。
8. 技术细节分析
8.1 自动机操作
在整个分析过程中,自动机操作起到了关键作用。例如,在标记违规和安全路径时,需要计算 $A_c^m(q)$ 与 $A↑badϕ$ 的交集 $\hat{A}$,并判断 $L(\hat{A})$ 是否为空。这些操作依赖于自动机的基本理论和算法。
8.2 深度优先搜索
深度优先搜索算法是标记状态的核心。通过维护工作栈和已访问集合,确保每个状态只被访问一次,提高了算法的效率。其流程如下:
graph TD;
A[初始化工作栈和已访问集合] --> B[工作栈非空?];
B -- 是 --> C[弹出栈顶元素 q];
C --> D[q 是否已访问?];
D -- 否 --> E[标记 q 为已访问];
E --> F[q 是否在 Vm 中?];
F -- 否 --> G[计算交集 \hat{A}];
G --> H[L(\hat{A}) 是否为空?];
H -- 否 --> I[更新 Vm];
I --> J[将 q 的后继状态压入工作栈];
J --> B;
F -- 是 --> B;
D -- 是 --> B;
B -- 否 --> K[结束];
8.3 逃逸分析
逃逸分析在确定安全指令和减少逃逸事件方面起着重要作用。通过识别对象的逃逸情况,我们可以更准确地判断哪些指令是安全的,从而减少不必要的插桩。例如,在分析中,将所有赋值给类字段、通过引用传递对象的方法调用以及返回对象的返回语句都视为逃逸事件。
9. 应用场景与建议
9.1 应用场景
- 性能敏感的程序 :对于对性能要求较高的程序,使用残差分析可以显著减少插桩开销,提高程序的运行效率。
- 复杂属性的监控 :当需要监控复杂的参数化属性时,该方法可以通过静态分析识别安全区域,减少运行时监控的负担。
9.2 建议
- 结合静态分析 :虽然该方法不依赖特定的静态分析技术,但结合静态调用图构建、逃逸分析和数据流指针分析等技术,可以进一步提高分析的精度。
- 优化 SafeList :通过逃逸分析改进 SafeList,将更多安全的指令纳入其中,减少逃逸事件的影响。
10. 总结
本文介绍的基于可达性分析的残差运行时验证方法,为参数化属性的验证提供了一种有效的解决方案。该方法通过过度近似程序行为,仅依赖方法的控制流图,实现了静态识别安全区域的目标。在运行时,它能够显著减少插桩开销,提高程序性能。
与其他相关工作相比,该方法具有独立性、灵活性和性能优势。未来,通过扩展插件和结合更多的静态分析技术,该方法有望进一步提高精度和适用性,为更广泛的应用场景提供支持。同时,将其扩展到并发程序分析,处理线程逃逸引用,也将是一个有意义的研究方向。
总之,这种残差运行时验证方法为软件验证领域带来了新的思路和方法,有望在实际应用中发挥重要作用。
超级会员免费看
58

被折叠的 条评论
为什么被折叠?



