【软件分析/静态程序分析学习笔记】8.指针分析基础知识(Pointer Analysis Foundations)

本文详细介绍了指针分析的基础知识,包括指针分析的规则、如何实现指针分析、指针分析算法以及在方法调用中的处理。通过对New、Assign、Store和Load语句的分析,阐述了指针域的影响和传播。此外,还探讨了过程间分析中的调用规则和处理函数调用语句的算法,强调了与CHA算法的区别。整个内容深入浅出,适合对程序分析感兴趣的读者学习。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写在前面的话

本渣有幸成为南京大学软件学院研究生,在前往仙林校区蹭课的时候偶然发现了这门宝藏课程,听了以后感觉深有收获,但又因为课程难度较大,国庆假期归来发现遗忘较多,因此开了一坑来记录自己对每节课知识点的理解。也由于这是本人第一次开坑写博客,结构内容自有诸多不合理之处,希望有问题的地方大家可以指出。

本文虽名为基础知识,但是是底层的那种基础,建议先看一下这篇文章了解一下指针分析是啥,再来看这个。


系列文章目录

1.静态程序分析(Static Program Analysis)介绍
2.中间表示(Intermediate Representation)
3.数据流分析(Data Flow Analysis) (上):可达性分析(Reaching Definitions)
4.数据流分析Data Flow Analysis) (下):存活变量分析(Live Variables Analysis)及可用表达式分析(Available Expressions Analysis)
5.数据流分析基础(Data Flow Analysis-Foundations)
6.过程间分析(Interprocedural Analysis)
7.指针分析(Pointer Analysis)入门
8.指针分析基础知识(Pointer Analysis Foundations)


一、指针分析的规则

1.1 语句、域与符号

上一篇文章中提到了如图五种会对指针指向的域产生影响的语句:在这里插入图片描述
我们首先看前四条语句,后面再看调用语句。
首先需要定义指针分析中需要用到的规则,主要是各种类型的符号及其对应的域如何表示:
在这里插入图片描述
如图所示,用V表示变量域,F表示域,O表示对象实例域,那么O x F表示了一个对象的方法对应的域,而指针的域就是变量与对象方法的域的并集。
P(O)表示用集合表示的域O,pt表示将指针映射到这个集合表示的域O上,换句话说,pt表示的是一种指向关系,pt(p)表示将p映射到对应的指针域上。p可以是x、f、oi、oi.f,后续看到pt(x)就直接当作x的指针集或者说指针域即可。
(其中的f主要是指对象的属性,表现为类的成员变量等)

1.2 规则

在这里插入图片描述
前四条语句的规则如图所示,横线上方表示条件,下方表示结论,接下来挨个分析。

1.2.1 New语句

在这里插入图片描述
New语句负责新建对象实例,由于等号右边没有对应指针域,所以就没有前提条件,所以直接得到等号左侧的指针对象x对应的指针集为oi,表示为𝑜𝑖 ∈ 𝑝𝑡(𝑥)。
此处的𝑜𝑖 ∈ 𝑝𝑡(𝑥)代表𝑜𝑖 是 𝑝𝑡(𝑥)的子集,即𝑜𝑖 是x的指针集的子集,也可以简单理解为x指向oi

1.2.2 Assign语句

在这里插入图片描述
assign语句即将等号右侧的指针域传递给等号左侧,理解无问题,不赘述。

1.2.3 Store语句

在这里插入图片描述
store语句是将指针存储到对象的域中的操作,将等号右侧指针域传递给左侧对象的域的指针集中。

1.2.4 Load语句

在这里插入图片描述
load语句类似于store语句,将等号右侧的对象的域的指针集的数据加载到等号左侧的y中。

1.2.5 总结

在这里插入图片描述
总结一下,四种规则如上,第一个New语句负责创建对象,后三个语句负责传播对象。
可以简单理解为:将等号右侧的指针整体指向的指针集表示出来,然后将其传递给等号左侧的指针,表示为右侧指针集是左侧指针集的子集。(右边 ∈ 左边)
其中之所以是子集而不是等号的原因在于:这样可能会导致精度丢失,产生假指针集,下面举个例子说明。

x = a;
x = b;

如上代码中假设a指向o1,b指向o2,那么正常来说x应该指向{o1, o2},但是如果改成等号的话,在第二步x会指向o2,然后x反向传播使得a变成指向o2,就产生了假指针了。
这原本是 Steensgard style的一种指针分析方式,提出时间很早,由于当年的计算机硬件水平很低,此方法的提出旨在降低精度的同时大大提高效率,可如今已经不需要这种方法了。


二、如何实现指针分析

本质上,指针分析是将指向信息传播到由变量和字段构成的指针之间的过程。根据Andersen1994年提出的理论,指针分析是一种解决指针包含约束的系统,这中包含关系可以从1.2.5的图中看出。
为了实现指针分析,我们可以通过一张来链接相关联的指针,当x的指针域pt(x)改变的时候,将改变的部分传递给x的后继。

2.1 指针流图(Pointer Flow Graph, PFG)简介

程序的指针流图是一种有向图,表示对象如何在程序中的指针之间流动。

  • 节点:一个节点n表示一个变量一个抽象对象的域,表示为 Pointer = V ⋃ (O × F)
  • :一个边 x → y 意味着指针x指向的对象图可能流向指针y指向的对象,可以理解为 y = x ⇒ x → y ⇒ pt(x) ∈ pt(y)
    再来看之前的四种语句用节点和边来表示的话,得到下图:
    在这里插入图片描述

2.2 指针流图绘制示例

接下来用一个具体的例子来看看指针流图具体是怎么绘制的:

在这里插入图片描述
此程序有个前提假设,就是c和d都指向oi(c指向oi说明oi肯定在c的指针集中,但是c的指针集中可能还有别的),也就是说二者是利用同一个类创建的对象。第一句和第二句可以画出两条边,代表b流向a,a流向 c.f 即 oi.f ;同理第三句第四句可以划出下面的两条边,代表c流向d,d流向 c.f 即 oi.f ;第五句代表 d.f 流向e,又因为前提条件中c和d都指向oi,所以得到第五条边。

由上述过程,可以总结出指针分析主要有两个过程,一是画指针流图,二是在指针流图中更新指向信息,这两个过程交互着动态进行的。例如在画第五条边的时候就需要前面的指向信息才能画出。


三、指针分析算法

在这里插入图片描述
首先看一下整体的算法长什么样,接下来将对其进行拆分讲解。
先说明一下三个符号分别是什么意思。

  • S:输入程序中的语句
  • WL:work list
  • PFG:指针流图

其中WL装了一系列的指向信息,表示为 𝑊𝐿 ⊆ <Pointer, 𝒫(O)>,也就是一系列的<指针,指针集>,例如[<𝑥,{𝑜𝑖}> , <𝑦,{𝑜𝑗, 𝑜𝑘}> , <𝑜𝑗. 𝑓,{𝑜𝑙} >…]的形式。

3.1 初始化算法

在这里插入图片描述
首先来看算法的第一节,前四句负责对整个算法进行初始化,并检测所有语句中的 New 语句,将指针与对应的域绑定传入WL中;后两句对每个 Assign 语句进行一次AddEdge(y, x)中。
在这里插入图片描述
AddEdge语句如图所示,首先判断从s流向t的边是否已经在PFG中存在,如果存在就什么是都不干,如果不存在就进行下一步。如果不存在首先将其加入,然后判断源点s的指针域是否为空,只要不为空,就将其与目标点构成对加入WL中,以确保s指向的指针域一定会传递给t。

3.2 差分传播

在进行完3.1的初始化操作以后,算法就会开始循环处理WL,首先是差分传播在这里插入图片描述
先从WL中按照某种规则取出一个对,取pts和pt(n)的差集,即去除pts中所有存在于n指针域中的指向信息,得到Δ,再对n和Δ做一个 Propagate传播操作:在这里插入图片描述
传播函数做两件事,将传入的指针域pts与加入n的指针域,然后将其与n的所有后继构成对加入WL中。结合之前的Δ,可以理解为,将n缺少的指向信息加入n的指针域中,然后将这变化部分传播给n的后继。
其中之所以做差分,也是为了进行去重,减少系统开支。算法中pt(n) ⋃= pts 也是整个算法里唯一一处改变指针集的地方。

3.3 处理Store和Load语句

在这里插入图片描述
由于在3.1中已经将所有的New语句和Assign语句处理过了,此时只有Store和Load语句需要分析。在取出一个对并进行差分传播过程后,如果n是一个变量,查看所有在Δ中的指向信息,然后在程序语句中找到与这个变量有关的n.f然后进行对应的AddEdge操作。

3.4 总结

以上便是整个指针分析的算法了,首先对整个程序语句进行分析,用其中的New语句和Assign语句进行初始化,得到一个部分的PFG,然后利用WL的迭代反复补充指向关系和流向关系,完善PFG中的边和节点的指向信息。
下面是一个例子的其中一步,可以大概地了解这个过程。


四、方法调用中的指针分析算法

4.1 CHA与Pointer analysis的区别

过程间的指针分析需要用到调用图call graph,这就让我们想起在第六章中讲到的CHA算法,下面来看一个例子:

void foo(A a){
    ...
    b = a.bar();
    ...
}
  • CHA:基于a的声明对象类型A来处理调用信息,不精确,会引入虚假的调用边和指向关系。
  • Pointer analysis:基于a的指针域pt(a)来处理调用信息,比较精确,因为用指针分析构造call graph,所以同时考虑到调用图和指向关系。

4.2 指针分析中的调用规则

image-20201127154442089

这是一个调用语句,根据指针分析规则,左侧的r由右侧指针域而不是x的声明对象类型决定。不同于CHA关心如返回地址等控制流信息,指针分析关注数据流。
指针分析处理调用的时候一般负责如下四件事情:

  1. 确定目标方法
  2. 传递receive object
  3. 传递参数
  4. 传递返回值

先来看符号的定义:

  • Dispath(oi, k):基于oi的类型(即实例对象x的类型),将在oi上的虚拟调用解析为目标方法。在规则中就是解析为目标方法后赋予给m,然后才有后续的定义。
  • mthis:目标方法m的this变量
  • mpj:目标方法m的第j个参数
  • mret:目标方法m的返回值对应的变量

然后再来看规则,横线上下分别是条件与结论,对应调用语句的等号右侧与左侧。

上方四条语句中先看第二句,𝑚 = Dispatch(o𝑖, k),就是相当于将这个目标方法赋给m,然后剩下的三条语句就可以和下发一一对应了。

x指向oi经过调用传递给mthis,每个实参aj共同指向ou传递给形参mpj,最后返回值mret指向ov传递给r,因此得到右侧PFG上的边。

在PFG中不将x与this连起来是因为x的指针域中可能会有多个类,比如pt(x)={new A, new B, new C},而this只应该指向其中的某个。如果将x与mthis相连,就会导致this指向多个域,引入了不准确的数据流。

4.3 函数调用的指针分析算法

4.3.1 算法总览

image-20201127202316172

相对于之前的过程内的指针分析算法,黄色部分是改变的部分。之前的算法的输入是程序所有语句S,而现在是分析程序的入口mentry,是一种可达性的分析,即每次操作只分析从当前处可以到达的方法,这样可以有效地减少分析时间,提高分析精度(因为这样减少了无用数据流)。

接下来解释一下用到的参数:

  • S:可以到达的语句的集合
  • Sm:在方法m中的语句集合
  • RM:可以到达的方法的集合
  • CG:调用图的边

4.3.2 初始化算法

整个算法的初始化通过第一个新增函数AddReachable(m)完成。

image-20201127203531871

这个函数如果监测到新方法,就会将这个方法加入RM中,并将其中的语句加入Sm中。接着对Sm进行类似3.1中的处理,不赘述了。

4.3.3 差分传播与Store与Load语句

image-20201127204406656

处理方案如红框所示,与3.2和3.3内容差不多,不赘述。

4.3.4 处理函数调用语句

image-20201127204503670
算法主要通过ProcessCall语句来处理函数调用语句。

就像4.2中所说的,对于每个调用语句,先将调用函数赋给m,然后将{mthis, {oi}}加入WorkList中。(这些存在与WorkList中的对将会在4.3.3中进行处理,类似于之前过程内的指针分析处理方式)

接下来检测l → m这个边是否已经在CG中,如果不在就加入,然后对这个m进行AddReachable操作,并将对应的参数和返回值用边相连。(因为这个语句可能在处理同方法的时候已经连接过了,所以要判断一下是否在CG中)

4.3.5 示例

image-20201127212010276

简单地举个例子,可以看出各个集合中的状态如图所示,跟着走一遍差不多就懂了。


五、总结

个人觉得指针分析这块内容还是挺复杂的,需要配合上一篇文章仔细看看才能大概搞懂,如果有写的不清楚的地方欢迎讨论。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值