程序切片准备知识
概述
几乎每个程序员都遇到过这样的情况:为了在一行代码中发现错误,需要阅读成百行的代码。(可能不止)此外,程序切片作为一种程序分析基础方法也应用于软件安全分析领域中,在程序和网络协议逆向、软件漏洞成因和机理分析、软件漏洞利用自动生成领域都发挥了重要作用。
程序切片旨在从程序汇总提取满足一定约束条件的代码片段(对指定变量施加影响的代码指令,或者指定变量所影响的代码片段),是一种重要的程序分解技术。
准备阶段
程序的基本结构
以C语言为例,不说也都知道,为了叙事的完整性,还是加上喽哈哈哈
顺序结构
其实就是什么结构都么有…
i = 1;
j = i;
条件结构
判断
if (i == 1)
j = 1;
else
j = 2;
循环结构
while(count --) {
i += 1;
}
函数调用结构
这个用汇编直观一点
call func
控制流分析
对于绝大部分程序而言,形成控制流的结构主要为3种,即顺序、条件和循环,本质上循环就是一种条件结构。因此控制流的基本结构除了顺序执行外就是由条件结构形成的非顺序执行结构。
基本块
下面我们引入“基本块”的概念,“基本块”就是一个顺序执行的语句序列,只能从该块的第一条语句开始执行一直到该代码块的最后一句离开该块。
所以基本块应该满足下面的条件:
- 程序执行时,只能从该基本块的第一句指令(入口指令)进入该基本块
- 程序执行时,离开该基本块前最后一条指令必须是该基本块的最后一条指令。
学过编译原理的小伙伴应该对基本块这个东西并不陌生。
一般而言,入口指令包括程序入口指令,函数入口指令,跳转指令的目的地址处的指令。获得了入口指令之后,只需要把两两入口指令之间的代码抽取出来就是一个基本块。
控制流图(Control Flow Graph, CFG)
程序被划分为基本块后,若将基本块视为一个基本单元节点,基本块之间在程序执行流程上互为前驱和后继关系视为两个基本块之间存在一条边,则整个程序能够转换为一个有向图,该图被称为控制流图。
举个例子
if A = 10 then
if B > C
A = B
else A = C
endif
endif
print A, B, C
流程图

CFG

在上图CFG中,我们用<1, 2>作为图上连接1,2的有向边,这里称2为1的直接后继,1为2的直接前驱。1的所有直接前驱集合为Pred(1)
,所有直接后继为Succ(2)
。
必经节点
对于两个节点a, b,若从开始接节点Entry
到节点b的所有路径都经过节点a,则称节点a支配节点b,并称a是b的前必经节点,表示为a—>b。若a<>b,则a是b的严格前必经节点,表示为a—>pb
如果不存在节点使得a—>pq, q—>pb,则称a是节点b的严格直接前必经节点。
反之亦然。
数据流分析
数据流分析关注的是跨越多条语句的变量定义、复制和运算操作,但是由于变量所存储的数据在多条语句中时动态变化的,难以同时进行分析。
所以,在数据分析的过程中需要确定一条特定的语句为基准进行数据流分析。通常一条语句都会有引用变量和定义变量两种行为。
- 分析语句中引用的变量由哪些语句定义的称为数据的可到达定义分析
- 分析该语句所定义的变量又在后续的哪些语句被引用和重新定义被称为变量的活性分析
可到达性
-
产生集: Gen(s)
- 当前语句或块所定义的变量的集合
-
消灭集: Kill(s)
- 当前语句或者块所定义的变量kill了其他块或者语句对同一变量的定义。
- 这里和《软件安全分析与应用:苏璞瑞、应凌云、杨轶》这本书上的定义有所初入,书上写的是kill的要是该语句之前的定义,但是翻阅一些资料,结合这本书中的例子后,感觉使用我这种定义更为好理解,如有错误欢迎指正。
-
入集: In(s)
- 在进入当前语句或块前仍然有效的定义(没有被kill掉的)
-
出集: Out(s)
- 所有离开当前语句或块时有效的定义
- 这里给一个式子可能会更为清晰
O u t [ s ] = I n [ s ] + g e n − k i l l Out[s] = In[s] + gen - kill Out[s]=In[s]+gen−kill
举个例子

loop0 | loop1 | loop2 | loop3 | |||||||
---|---|---|---|---|---|---|---|---|---|---|
gen(Bi) | kill(Bi) | In | Out | In | Out | In | Out | In | Out | |
Entry | Φ | Φ | Φ | Φ | Φ | Φ | Φ | Φ | Φ | Φ |
B1 | 1,2 | 3,4,5 | Φ | 1,2 | 3 | 1,2 | 2,3 | 1,2 | 2,3 | 1,2 |
B2 | 3 | 1 | Φ | 3 | 1,2 | 2,3 | 1,2 | 2,3 | 1,2 | 2,3 |
B3 | 4 | 2,5 | Φ | 4 | 2,3 | 3,4 | 2,3 | 3,4 | 2,3 | 3,4 |
B4 | 5 | 2,4 | Φ | 5 | 3,4 | 3,5 | 3,4 | 3,5 | 3,4 | 3,5 |
Exit | Φ | Φ | Φ | Φ | 3,5 | 3,5 | 3,5 | 3,5 | 3,5 | 3,5 |
在程序的某处为 x 引入一个未定义值 ⊥,如果 ⊥ 能达到某个 x 的使用,那么说明这个地方的使用可能是未定义值,这就是一个程序错误隐患。java中的nullpointerexception
就可以套用这个分析发现错误。
活性分析
活性分析是指对某个语句定义的变量是否在后续语句中被引用以及被哪些语句引用的情况。
程序依赖图
程序依赖和上文中前后必经关系的最大区别在于,程序依赖需要被依赖节点能够决定依赖节点的被执行与否。能够决定是否被执行的情况往往为条件、循环等。顺序结构没有必然的前后因果关系所以他们没有依赖和被依赖的关系。
累了累了,直接放个图…
切片的基本原理
最初的程序切片技术应用于软件维护阶段的排错调试阶段,即为研发人员提供一种观察和理解程序的方法。在实际的程序调试过程中,通常程序员只关注程序的部分行为。
在一段程序中找到感兴趣的代码段,即为切片准则。这个感兴趣通常是与某个变量相关。
程序切片通常包括三个部分。
- 程序依赖关系提取
- 程序依赖关系提取主要是从程序中提取各类信息,包括控制流和数据流信息,形成程序依赖图。
- 切片规则制定
- 依据具体的程序分析需求设计切片准则
- 切片生成
- 依据前述的切片准则选择相应的程序切片方法,然后对第一步中提取的依赖关系进行分析处理,从而生成程序切片。