没有理想的人不伤心

静态程序分析技术

2024/07/24
1
0

静态程序分析

  1. 使用三地址码的 basic block 来减少图的节点数量
  2. 在 available expression analysis 中,对于同一个表达式的变量替换成相同的变量名,可以参考这一点,来减少 token 字典的数量
  3. 变量别名指的是不同变量指向相同的地址,那么就可以使用同一个名字来替代,减少变量数。
  4. 或者使用三地址码作为 token 序列训练,使用 CCG、CPG 进行图神经网络的训练如何呢?

引言

不存在完美的完全精确静态分析方法

在程序分析中,宁可误报(假阳性)也不漏报(假阴性),即追求假阳性,避免假阴性很高,也就是说,在追求全面覆盖的情况下,对分析精度和分析速度进行一个平衡把握。

1722514352689-b70279c7-2fe3-4048-afca-8bcc47f16852.png

静态程序分析概括成两个词:抽象(Abstraction)+ 过近似(Over-approximation)

Over-approximation 表示可以有误报,但不能有漏报

under-approximation 表示可以有漏报,不能有误报

IR(intermediate representation)中间表示–主要讲三地址码,3-address code

1722595636185-6ab2d827-a097-4689-b447-1540d004a8aa.png

编译器工作流程:源代码–词法分析(scanner)–>Tokens–语法分析(Parser)–>AST–语义分析(类型检查)–>decorated AST–>translator–>IR–code generator–>机器码

对 IR 的分析就是静态程序分析。

1722596332395-8d813616-c0b2-44ab-81e4-9495aa12ea78.png

三地址码

三地址码:

  • 更接近机器码,较为底层
  • 不依赖于语言
  • 紧凑统一
  • 包含控制信息
  • 通常被认为是静态分析的基础

AST:

  • 接近语法结构
  • 依赖语言
  • 适合做类型检查(type checking)
  • 缺乏控制流信息

1722597949476-733ac667-3e9e-4ac1-baea-65a51ab8b29d.png

三地址码:每条指令通常由一个操作符和三个操作数组成,这三个操作数可以是变量、常量、或者临时变量

三地址码的特点:每条指令只进行一个操作,至多有一个操作符(可以没有)

如:t2 = a+b+3

其三地址码:t1=a+b t2=t1+3

常见的三地址码形式:

  1. 二目运算

x=y bop``bop 是二目运算符

  1. 单目运算

x=uopy uop 是单目运算符,如:负号、取反、类型转换

  1. 赋值操作

x=y

  1. 条件跳转

if xrelopy goto L relop 是关系操作符,如><==,L 是跳转的标签

  1. 无条件跳转

goto L

SSA

SSA:基于三地址码的改进,其给每个变量定义一个新名字,即每个变量有唯一的定义

1722686589424-de21b4de-4514-4b8c-9805-ff334261d4e2.png

当不确定某个变脸的来源时,引入一个 fai 函数

1722686697458-dfcc5317-9a5d-46f8-9f32-ea686d373256.png

SSA 的好处:流信息能够通过唯一的变量定义间接的体现;定义和使用成对出现,容易寻找。

坏处:变量过多、性能较低

控制流图 CFG

CFG 是程序静态分析的基础

CFG 的节点是三地址码的指令,而更通常的是如下图使用 Basic Block(BB)

1722687368134-eb378d30-035c-47f5-877e-f130b9ae5ad7.png

Basic Block(BB):由三地址码中最长的序列构成

满足:(唯一入口出口)

  1. BB 的第一条指令是唯一路口
  2. BB 的最后一条指令是唯一出口
  3. 中间不存在别的指令作为入口或出口

1722688239507-949de9bf-db8f-4637-a1c6-821568fa328d.png

  1. 如何构建 basic block:(算法、规则)

输入:一段三地址码 P

输出:一系列的 BB

方法:

BB 的开始语句:

  • 该段三地址码的首条语句
  • 三地址码中,包含 goto 的语句跳转的目标
  • 三地址码中,紧跟着包含 goto 的语句的第一条语句

BB 的结束语句:

  • 三地址码中的 goto 语句

符合上述规则的三地址码中的最大序列就是 basic block

实现代码如下

def identify_basic_blocks(three_address_code):
    basic_blocks = []
    leaders = set()
    n = len(three_address_code)

    # Create a dictionary to map line numbers to indices
    line_number_to_index = {}
    for i,instruction in enumerate(three_address_code):
        # Line numbers start from 1,so map i+1 to the index i
        line_number_to_index[i + 1] = i

    # Identify leaders
    for i in range(n):
        instruction = three_address_code[i].strip()

        # First instruction is a leader
        if i == 0:
            leaders.add(i)

        # Instruction containing goto makes the target a leader
        if"goto"in instruction:
            parts = instruction.split()
            if parts and parts[-1].isdigit():
                line_number = int(parts[-1])
                # Map line number to index if it exists
                if line_number in line_number_to_index:
                    leaders.add(line_number_to_index[line_number])
            if i + 1 < n:
                leaders.add(i + 1)

    # Create basic blocks
    leaders = sorted(leaders)
    current_block = []
    i = 0
    while i < n:
        if i in leaders and current_block:
            basic_blocks.append(current_block)
            current_block = []
        current_block.append(three_address_code[i])
        if"goto"in three_address_code[i]:
            basic_blocks.append(current_block)
            current_block = []
        i += 1

    if current_block:
        basic_blocks.append(current_block)

    return basic_blocks

# Sample three address code
three_address_code = [
    "×=input",
    "y=×-1",
    "z=×*y",
    "if z<× goto 7",
    "p=×/y",
    "q=p+y",
    "a=q",
    "b=x+a",
    "c=2a-b",
    "if p==q goto 12",
    "goto 3",
    "return",
]

basic_blocks = identify_basic_blocks(three_address_code)

for idx,block in enumerate(basic_blocks):
    print(f"Basic Block {idx + 1}:")
    for instruction in block:
        print(instruction)
    print()
# Basic Block 1:
# ×=input
# y=×-1

# Basic Block 2:
# z=×*y
# if z<× goto 7

# Basic Block 3:
# p=×/y
# q=p+y

# Basic Block 4:
# a=q
# b=x+a
# c=2a-b
# if p==q goto 12

# Basic Block 5:
# goto 3

# Basic Block 6:
# return
  1. 在 BB 基础上,构建 CFG

规则:

  • 节点是 BB
  • 节点之间边的构建
    • 当节点 A 的结尾是 goto 语句(无论是有条件或无条件跳转)时,添加一条从 A 到 goto 目标语句所在节点的有向边
    • 当节点 A 的结尾不是无条件 goto 语句时,其与相邻的下一个节点之间添加一条 A 到 B 的有向边
  • 最后将 goto 语句的跳转目标从代码行号替换为 BB 的节点代码,以增加容错。
  1. 在控制流图的开头加上 Entry节点作为程序入口,在结尾加上 Exit作为程序出口

如下面的三地址码转换为控制流图

    "×=input",
    "y=×-1",
    "z=×*y",
    "if z<× goto 7",
    "p=×/y",
    "q=p+y",
    "a=q",
    "b=x+a",
    "c=2a-b",
    "if p==q goto 12",
    "goto 3",
    "return",

1722857563201-f2d4b0d2-39da-4dcb-ba73-9f5d0546079e.png

数据流分析应用

1722858482030-7fa5c5af-8bbd-4347-ae73-429b7381ea2f.png

1722859190939-983e67ee-c517-4dc8-81dc-88daac6d2b3b.png

  • may analysis:输出信息可能是正确的(over-approximation)
  • must analysis:输出信息一定是对的(under-approximation)

在控制流图中,

  • 每一条语句都有输入状态和输出状态,
  • 一条语句的输入状态一定与前一条语句的输出状态有关

1722860035361-1a268f6e-c676-4aac-b7a3-a4d01bd7cc67.png

对于一条语句的输入输出状态来说,执行这条语句就相当于执行了一个转换函数(transfer function)

  • 在前向(forward)分析(顺着控制流方向分析)中

$ OUT(s) = f_s(IN(s)) $

1722860616494-b8600553-6ad9-4635-ab86-79dc87d868b0.png

  • 在反向(backword)分析(逆着控制流方向分析)中

$ IN(s) = f_s(OUT(s)) $

1722860702024-e0697cb5-170a-4db4-8235-d62547ec00c9.png

类似的,在 Basic Block 中的转换函数:

1722861018788-df4b27df-2e4e-48b7-9f3c-e7d6be110350.png

Reaching Definitions Analysis 到达定义分析

Reaching Definitions 是 may analysis

这里的定义 definition 可以理解为给变量赋值就相当于定义了一个变量

Reaching Definitions:在某个程序点$ p $定义的一个变量$ v $(变量的赋值),到在$ p->q
$的路径上,如果没有对该变量$ v $新的定义,称$ v $可以从$ p 到达(reach)q $

1722863634149-6e61da28-482e-4ea2-abf3-915180724e95.png

Reaching Definitions 可以用来检测可能未定义(为初始化)的变量

  1. 数据抽象(abstraction)

这里的数据是所有变量的定义(definition),一般使用位向量(bit vector)的形式来表示

如变量的定义:D1,D2,···,D100使用位向量00···0共一百个 0 来表示

每个位表示一个定义

  1. transfer function 转换函数

$ \mathrm{OUT}[B]=gen_{B}\cup(\mathrm{IN}[B]-kill_{B}) $

$ gen_B $表示在 B 中新生成的定义

在语$ B $中定义了$ v
$,那么就$ kill $掉其他块中(不只是前面的块,是所有)所有对$ v $的定义,而其余传入$ B $中的定义不受影响

1722919356891-9910accf-0681-482c-a73d-43c0fa81b3b5.png这里的 x 和 y 的定义不受影响

意思就是在 B 中定义了 v,那么从 B 中输出的定义就是新定义的 v 以及 x 和 y 的定义

1722920746967-7e39c61c-3af4-4250-a772-cbbce160dced.png

  1. control flow 控制流

$ \mathrm{IN}[B]=\bigcup_{P-a-predecessor-of-B}\mathrm{OUT}[P] $

1722920933723-dd774c52-80f7-4527-aff5-f5c2152ada23.png

P1、P2 是 B 的前驱(predecessor),B 的输入是 P1、P2 输出的 Union

reaching definition analysis 的算法设计:

输入:CFG
输出:每个 BB 块的输入 IN[B]和输出 OUT[B]
方法:
OUT[entry] = 空集;
for(each BB 除了 entry)
  # 初始化,在 may analysis 中一般都定义为空集,must analysis 中定义为 top(undefined)
  OUT[B] = 空集; 
while(changes to any OUT occur)  # 迭代到所有程序点的当前状态与上一个迭代的状态相同
  for(each BB 除了 entry){
    IN[B] = Union(OUT[P]);
    OUT[B] = gen[B] U(IN[B]-kill[B]])
  }

为什么该算法迭代会停止?

无论迭代多少次,对于同一个 BB 来说,其$ gen_B $和$ kill_B
$都是固定不变的,那么公式中,当输入 IN 与上一次迭代相同时,输出 OUT 也不会改变,且程序点的状态某个位只会从 0->1、1->1,并不会经过一次迭代后,就从 1->0。而对于一个程序点的状态来说,迭代过程中,能使状态改变的无非是在 Union 中前驱的输出发生改变。

当迭代停止时,我们得到最终的结果,称其 reach a fixed point(不动点),与单调性相关。

1722935685332-98ca590d-d7ba-40ba-b733-c5b8f9b64a3a.png 每个程序点关联着一个数据流的值,表示当前状态

Live Variable Analysis 活跃变量分析

活跃变量分析:如果一个变量$ v $,从程序点$ p $处,沿着 CFG 的某条路径,在该路径上$ v
$被使用,且在此之前$ v $没有被重新定义,那么称 v 在 p 点处 live,否则就是 dead

某条路径指的是任意长短的路径

1722943207910-71c3a901-22e2-4f27-b347-e1dbd6cc90ec.png

该种分析可以用于寄存器分配,即寄存器若满了,那么可以将 dead 的变量用来设置新的变量

may analysis、backward analysis

  1. 数据抽象(abstraction)

如前所述,这里的数据是所有的变量(variable),一般使用位向量(bit vector)的形式来表示

如有不同的变量:x1,x2,···,x100使用位向量00···0共一百个 0 来表示

每个位表示不同的变量

  1. Transfer Function

$ \mathrm{IN[B]}= use_{B}\cup(\mathrm{OUT[B]} - def_{B}) $

由于使用逆向分析, 所以用 OUT[B]求 IN[B],use 表示变量在 define 之前被使用

在 B 中,若一个变量首先被使用,再被重新定义,那么 IN[B]仍然是 Live,因为在被定义之前已经被使用了。

若先重新定义再被使用,则 IN[B]为 dead

1723024415521-f7e79a75-6a65-4482-985b-8d34c15e4046.png

  1. control flow

$ \mathrm{OUT[B]}=\bigcup_{S -a -successor -of -B}\mathrm{IN[B]} $

算法设计

输入:CFG
输出:每个 BB 块的输入 IN[B]和输出 OUT[B]
方法:
IN[exit] = 空集;
for(each BB 除了 exit)
  # 初始化,在 may analysis 中一般都定义为空集,must analysis 中定义为 top(undefined)
  IN[B] = 空集; 
while(changes to any IN occur)  # 迭代到所有程序点的当前状态与上一个迭代的状态相同
  for(each BB 除了 exit){
    OUT[B] = Union(IN[S]);# S 是 B 的所有后继节点
    IN[B] = use[B] U(OUT[B]-def[B]])
  }

1723028211684-fba4b04c-de97-4070-a851-844b27c4f249.png

Available Expressions Analysis

对于表达式 x op y和某个程序点 p,当 entry 节点到程序点 p 的所有路径都经过该表达式,且在计算表达式之后没有对 x 或 y 的重新定义,那么九城,表达式 x op y在程序点 p 处**available,**即 Available Expressions

是 must analysis、under-approximation

  1. 数据抽象

如前所述,表示为位向量的形式,每个位表示一个表达式,1 为 available。

  1. transfer function

$ \mathrm{OUT}[B]=gen_{B}\cup(\mathrm{IN}[B]-kill_{B}) $

  1. control flow

$ \mathrm{IN}[B]=\bigcap_{P -a -predecessor -of -B}\mathrm{OUT}[P] $

算法:

输入:CFG
输出:每个 BB 块的输入 IN[B]和输出 OUT[B]
方法:
OUT[entry] = 空集;
for(each BB 除了 entry)
  # 初始化,在 may analysis 中一般都定义为空集,must analysis 中定义为 ALL
  OUT[B] = ALL;# 即为全 1 
while(changes to any OUT occur)  # 迭代到所有程序点的当前状态与上一个迭代的状态相同
  for(each BB 除了 entry){
    IN[B] = intersection(OUT[P]); # intersection 表示交集,所有的前驱节点的交集
    OUT[B] = gen[B] U(IN[B]-kill[B]])
  }

1723032387814-f1436e26-6212-4cb8-b1c0-006f4b2f0d66.png
1723033077175-8f7f3ba2-434f-4069-8d42-7e046094d83b.png

1723033243106-d792bfc3-942f-4977-a63a-7f9a9153df65.png

数据流分析基础

不动点:对于某个函数$ F $,存在点$ X $,使得$ X=F(X) $,那么称$ X $是函数$ F $的不动点。

提出问题:

  • 数据流分析的迭代算法总能达到不动点吗?
  • 只有一个不动点吗?我们得到的不动点是否是最好的(最精确的)不动点呢?
  • 什么时候才能到达不动点呢?(边界)

偏序:

定义一对偏序集$ (P,\sqsubseteq) $其中$ \sqsubseteq $是定义在$ P $上的二元偏序关系,且有以下性质:

  1. 反身性:$ \forall\mathrm{x}\in\mathrm{P},\mathrm{x}\sqsubseteq\mathrm{x} $
  2. 反对称性:$ \forall\mathrm{x,y\in P,x\sqsubseteq y\land y\sqsubseteq x\Longrightarrow x=y} $
  3. 传递性:$ \forall\mathrm{x,y,z\in P,x\sqsubseteq y\land y\sqsubseteq z\Rightarrow x\sqsubseteq z} $

例 1:$ P
$是实数集,$ \sqsubseteq $是$ \leq $,其满足三个性质,因此这就是一对偏序集

例 2:$ P $是下面这些英文单词,而$ \sqsubseteq $表示子串关系,他们满足三个性质,因此也是偏序集

1723120564294-724a2d94-bc6b-41ed-a3af-33958ac3ab07.png

例 3:$ P $是幂集合$ {a,b,c} $,如下图$ \sqsubseteq $表示子集(subset),他们满足三个性质,因此也是偏序集

1723121188422-d13f8635-8f99-4042-ac12-8e0c9e091264.png

换句话说,偏序就是集合中的两个元素可以不满足$ \sqsubseteq $的关系,即元素之间可以是不可比较的关系,如例 2 中,pin 和 sin 并不是子串的关系。