Practical Memory Leak Detection using Guarded Value-Flow Analysis 论文阅读

本文于 2007 年投稿于 ACM-SIGPLAN 会议1

概述

指针在代码编写过程中可能出现以下两种问题:

  1. 存在一条执行路径,指针未成功释放(内存泄漏),如下面代码中注释部分所表明的:

    int foo()
    {int *p = malloc(4 * sizeof(int));if (p == NULL)return -1;int *q = malloc(4 * sizeof(int));if (q == NULL)return -1; // 注意这里,q为NULL时p一定不为NULL,但是函数直接返回,导致p所指向的区域未释放// some code to executefree(p);free(q);return 0;
    }
    
  2. 存在一条执行路径,指针被重复释放(未定义行为),如 free 一个空指针。

    	int *p = (int *)malloc(4 * sizeof(int));int *q = p;free(q);q = p;free(q);
    

最笨拙的方法是枚举每一条可能的路径,依次判断。但是这显然是不切实际的。因而本文的主要工作是提出一个能够发现未释放指针或重复释放指针的高效算法,并进行了代码实现,提示编写者具体可能的错误原因。即给定一个程序,找到其中可能存在的这种问题。

首先进行控制流图(Condition-Flow Graph,CFG)的约定:

  1. 赋值(运算节点): e = e ′ e=e' e=e
  2. 函数调用: e = f ( p 1 , p 2 , ⋯ , p m ) e=f(p_1,p_2,\cdots,p_m) e=f(p1,p2,,pm)
  3. 返回: return e \texttt{return}\ e return e
  4. 分支节点: switch e ( c 1 , n 1 ; c 2 , n 2 ; ⋯ ; c k , n k ; n t ) \texttt{switch}_e(c_1,n_1;c_2,n_2;\cdots;c_k,n_k;n_t) switche(c1,n1;c2,n2;;ck,nk;nt)。即一个节点根据表达式 e e e 的值不同可以有 k + 1 k+1 k+1 个分支跳转地址,分别记作 I n = { n 1 , n 2 , ⋯ , n k , n t } I_n=\{n_1,n_2,\cdots,n_k,n_t\} In={n1,n2,,nk,nt},最后一个为默认跳转地址。

此外,本文将问题进行了规约:定义 s o u r c e − s i n k [ n , m ] {\rm source-sink}[n,m] sourcesink[n,m] 问题为从 s r c \rm src src 会流入到 [ n , m ] [n,m] [n,m] s i n k \rm sink sink 的条件可满足性问题。对于未释放,则是 s o u r c e − s i n k [ 0 , 0 ] {\rm source-sink}[0,0] sourcesink[0,0] 问题,而多次释放则是 s o u r c e − s i n k [ 2 , ∞ ] {\rm source-sink}[2,\infty] sourcesink[2,] 问题,而合法性判断是 s o u r c e − s i n k [ 1 , 1 ] {\rm source-sink}[1,1] sourcesink[1,1] 问题。

算法流程

整体算法流程图如下所示:

在这里插入图片描述

  1. 利用编译器前端搭建 CFG。
  2. 到达定值点分析
  3. 值流图构建
  4. 无条件可达性分析,即不考虑具体控制流图上条件进行的分析
  5. 条件可达性分析,即考虑控制流图上条件进行的分析。

在实现该算法的同时还需要调用:

  1. 指针区域分析,即分析流图中每个指针所指向的内存区域。
  2. 条件分析。
  3. SAT(可满足性问题)解决器,即给定一组条件约束,返回一组可满足所有条件的初始值或报告无解。下文会将本论文中提出的问题规约到可满足性问题。

到达-定值分析(Reaching-Definition Analysis)

编译原理中经典的数据流分析方法。下文中用 p d o m ( x , n , m ) pdom(x,n,m) pdom(x,n,m) 来描述变量 x x x 能不能从 CFG 上流图节点 n n n 值不发生改变的到节点 m m m。论文中的 S S S 仅为一个记忆化的集合,不做具体参数使用。 p d o m pdom pdom 的计算使用逆向数据流分析方法:
p d o m ( x , n , m ) = { t r u e , m = n f a l s e , n 没有出边(返回节点) ⋀ i ∈ I n p d o m ( x , i , m ) ∧ ¬ d e f i n e ( x , i ) , 其他情况 pdom(x,n,m)= \begin{cases} true, m=n\\ false,\text{$n$ 没有出边(返回节点)}\\ \bigwedge_{i \in I_n} pdom(x,i,m) \wedge ^\lnot{define}(x,i),\text{其他情况} \end{cases} pdom(x,n,m)= true,m=nfalse,n 没有出边(返回节点)iInpdom(x,i,m)¬define(x,i),其他情况
其中 d e f i n e ( x , i ) define(x,i) define(x,i) 表示 i i i 节点没有进行对变量 x x x 的赋值操作。

构建值流图(Value-Flow Graph)

在构建值流图之前首先需要介绍 free 函数的工作原理或特性。它释放传入参数给定的指针所指向的区域,也就是说它是针对内存区域而非指针的。例如下面的两个例子:

  1. 下面代码中 p1p2 指针所指向的区域都被释放了。
			int *p1 = malloc(4 * sizeof(int)), *p2 = malloc(6 * sizeof(int));int *q = p1;free(q);q = p2;free(q);
  1. 下面代码中 p 指针指向区域并未完全释放——p 指针所指向的区域仍有一个 int 大小的空间未释放。
			int *p = malloc(4 * sizeof(int));int *q = p + 1;free(q);

基于以上两个特性,构建如下的节点:

  1. 赋值(运算)节点。针对 CFG 上每个形如 x = y x=y x=y 形式的赋值语句都对应一个 VFG 的节点。

  2. 内存区域节点。由于 free 是针对区域而非指针型变量,因而需要用一个单独的节点描述它是否有被释放的途径。该部分节点用 n r n_r nr 表示,可以使用这篇论文2中的方法快速描述代码中每个指针可能对应的内存区域集合。

    这里还需要注意的是,由于指针存在加减法操作,因而这里需要额外使用一个偏移量来去衡量该内存地址的具体使用情况。

  3. 释放节点。每个 free 函数调用的节点都对应一个 VFG 上的汇(sink)点。

  4. 函数调用实参节点。由于进行函数调用,可以视为进行一次变量的值使用,记为 x @ n x_{@}n x@n

  5. 函数调用形参节点。在被调用函数(callee)中该函数作为新变量使用,同时它对应于调用函数(caller)的一个变量。为避免函数多次调用导致边关系混乱,因而新建一个形参节点以解决图过于混乱的问题,记为 [ p ] [p] [p]

  6. 函数返回节点。函数的返回值可能在 caller 中作为一个新变量继续存在,并涉及后续赋值和计算。

遍历 CFG,按如下规则建图(假设当前节点为 n n n):

  1. 赋值语句 y = x y=x y=x、释放节点 f r e e ( x ) free(x) free(x)、返回节点 r e t u r n ( x ) return(x) return(x):将所有 x x x 的定值点集合 n x n_x nx 建立一条边连到 n n n 节点。
  2. y = f ( x 1 , x 2 , ⋯ , x m ) y=f(x_1,x_2,\cdots,x_m) y=f(x1,x2,,xm):对于每个函数实参 x i x_i xi,首先将 x i x_i xi 的定值点集合连接一条边到对应的实参节点( n x i → x i @ n n_{x_i} \to {x_i}_@n nxixi@n),然后每个实参节点连接到形参节点 x i @ n → p i {x_i}_{@}n \to p_i xi@npi,最后将 callee 函数的返回节点 n r e t f n_{\rm ret}^f nretf 连接到当前函数调用节点(该语句可以视为一种特殊类型的赋值语句) n r e t f → n n_{\rm ret}^f \to n nretfn
  3. 对于赋值变量中 x x x y y y 不是一个有效节点的(如 malloc 函数返回的堆区域指针),调用内存区域查询函数返回对应的内存区域节点。

无条件可达性分析(Unguarded Reachability Detection)

该步骤中忽略了程序流图中的条件,即认为所有的边都是可走到的,在这种情况下先简单分析是否每个 malloc 函数都有至少一个 free 与之对应。

  1. 枚举一个起始点 s r c src src,首先找到 VFG 顺向流图中可到达节点集合 F s r c F_{src} Fsrc
  2. 找到 F s r c F_{src} Fsrc 中所有的 free 语句对应节点,记为 K K K
  3. 分类讨论:
    1. 如果 K K K 为空,则该 malloc 语句无对应 free 语句,直接报告内存泄漏。
    2. 如果从该 s r c src src 节点可以到达一个内存区域节点,则说明该代码片段中存在全局变量,暂时不继续分析该代码的内存泄漏问题,直接退出。
    3. 找到能从 s r c src src 到并且能到达 K K K 的集合 R R R,并将 R R R 集合传递到下一步继续分析。

条件可达性分析(Guarded Reachability Detection)

预处理

首先在 CFG 上进行条件分析。考虑每个分支节点 n n n 需要满足其分支出口唯一,即对于分支节点 n n n switch e ( c 1 , n 1 ; c 2 , n 2 ; c k , n k ; n t ) \texttt{switch}_e(c_1,n_1;c_2,n_2;c_k,n_k;n_t) switche(c1,n1;c2,n2;ck,nk;nt),需要满足:
C n = [ ⋁ i ( e = c i ) n ] ∧ [ ⋀ i ≠ j ( e = c i ) n ∧ ( e = c j ) n ‾ ] C_n=\left [\bigvee_{i}(e=c_i)_n\right] \wedge \left[\bigwedge_{i \ne j} \overline{(e=c_i)_n \wedge (e=c_j)_n}\right] Cn=[i(e=ci)n] i=j(e=ci)n(e=cj)n
即存在一条出路,且不存在一个条件同时满足两条出路。

定义函数 c g ( x , n , m ) cg(x,n,m) cg(x,n,m)(下简写成 c g u a r d ( n → m ) cguard(n \to m) cguard(nm),其中程序点 n n n 为形如 x = e x=e x=e 的赋值语句)表示变量 x x x 从程序点 n n n m m m 需要满足的输入条件集合,有如下递推:
c g ( x , n , m , E ) = { t r u e , 如果满足支配关系即 p d o m ( x , n , m ) c g ( x , n 1 , m , E ) , n 处无分支, n 1 为 n 语句的唯一后续语句 ⋁ i ∈ I n , x , E c o n d ( n , n i , E ) ∧ c g ( x , n i , m , E ∪ { ⟨ n , n i ⟩ } ) , 其他情况 cg(x,n,m,E)= \begin{cases} true,\texttt{如果满足支配关系即 $pdom(x,n,m)$}\\ cg(x,n_1,m,E),\texttt{$n$ 处无分支,$n_1$ 为 $n$ 语句的唯一后续语句}\\ \bigvee_{i \in I_{n,x,E}} cond(n,n_i,E) \wedge cg(x,n_i,m,E \cup \{\langle n,n_i\rangle\}),\texttt{其他情况} \end{cases} cg(x,n,m,E)= true,如果满足支配关系即 pdom(x,n,m)cg(x,n1,m,E),n 处无分支,n1  n 语句的唯一后续语句iIn,x,Econd(n,ni,E)cg(x,ni,m,E{⟨n,ni⟩}),其他情况
其中 I n , x , E I_{n,x,E} In,x,E 表示满足后续节点不是形如 d e f i n e ( n i , x ) define(n_i,x) define(ni,x) ⟨ n , n i ⟩ \langle n,n_i \rangle n,ni 不在 E E E 中的后续节点集合 { n i } \{n_i\} {ni} c o n d ( n , n i , E ) cond(n,n_i,E) cond(n,ni,E) 函数表示从 n n n 节点走到 n i n_i ni 节点所需要满足的条件,并且要求 n n n 节点后续不能有 E E E 中的边:
c o n d ( n , n i , E ) = { ( e = c i ) n , n 是一个分支节点,且 n 的后续集合不在 E 集合中 t r u e , 其他情况 cond(n,n_i,E)= \begin{cases} (e=c_i)_n,\texttt{$n$ 是一个分支节点,且 $n$ 的后续集合不在 $E$ 集合中}\\ true,\texttt{其他情况}\\ \end{cases} cond(n,ni,E)={(e=ci)n,n 是一个分支节点,且 n 的后续集合不在 E 集合中true,其他情况
这里 ( e = c i ) n (e=c_i)_n (e=ci)n 表示在程序分支判断点 n n n 处进行条件判断 e = c i e=c_i e=ci

这里加入 E E E 集合(已遍历的边集合)作为参数是方便代码实现上的,由于流图中可能有环存在,因而不能重复遍历同一条边,通过在状态中多维护一个 E E E 集合可以有效防止重复遍历到同一条边。在 c o n d cond cond 中,显然要进入循环然后退出该循环需要同时满足在循环的出口判断中 e = c i e=c_i e=ci e ≠ c i e \ne c_i e=ci,这显然是永假的。因而这里加入了边的限制条件以防止上述情况的出现。当然这里会存在一个小漏洞就是仅判断了循环入口点的条件,未判断出口点的。这里会对未释放问题的判定产生一定的影响,但是对多重释放不会。

接下来就是考虑利用这些条件在 VFG 上进行条件判断。同样定义 v g u a r d ( n , m ) = v g ( n , m ) vguard(n,m)=vg(n,m) vguard(n,m)=vg(n,m) 函数表示从 VFG 图上 n n n 节点走到 m m m 的约束条件集合,有:
v g ( n , m , E ) = { t r u e , n = m ⋁ n → n ′ ∈ E c g u a r d ( n → n ′ ) ∪ v g ( n ′ , m , E ∪ { n → n ′ } ) , 其他情况 vg(n,m,E)= \begin{cases} true,n=m\\ \bigvee_{n \to n' \in E} cguard(n \to n') \cup vg(n',m,E \cup \{n \to n'\}),\texttt{其他情况} \end{cases} vg(n,m,E)={true,n=mnnEcguard(nn)vg(n,m,E{nn}),其他情况
和在 CFG 上的情况类似,这里只需要在 VFG 图上再加入 CFG 上的信息即可。

分析过程

考虑对于一个确定的 s r c src src 节点和所有该指针的释放操作汇点 K K K,在 2.3 节中阐述的 R R R 集合上进行分析。定义 G k = v g u a r d ( s r c , k ) G_k=vguard(src,k) Gk=vguard(src,k) C C C R R R 上所有的分支节点需要满足的 C n C_n Cn 的与集合。此时就满足了一个 SAT 问题的框架:

  1. 如果此处存在一组初值指派满足 ∨ k ∈ K G k ‾ ∧ C \overline{\vee_{k \in K} {G_k}} \wedge C kKGkC,则表明存在某种初值指派,使得从该 s r c src src 语句无法走到任何一个汇点,即发生了内存泄漏。
  2. 如果存在一组初值指派,满足 ∃ i ≠ j , G i ∧ G j ∧ C \exists i \ne j,G_i \wedge G_j \wedge C i=j,GiGjC,则表明某组初值指派可以到达两个不同的 free 语句,进行多次释放操作,因而发生未定义行为(Undefined Behavior)。

可以注意到上述的操作时间复杂度是比较高的,特别是对于有大量 malloc 语句存在的时候。因而本文中仅对操作数不超过阈值 30 30 30 的代码进行分析。

本文同时实现了代码,将所有的错误种类分成以下几类:

  1. 从未释放。又分为:a)指针作为局部变量在 main 函数或其他函数未释放;b)指针作为全局变量或存在于数组等结构中未释放。
  2. 释放。又分为:a)一切条件下都能释放;b)某些情况下能释放,有些情况未释放。
  3. 不能判定,认为释放了。这种情况通常因为分析语句过多导致超过阈值停止分析。

  1. Cherem S, Princehouse L, Rugina R. Practical memory leak detection using guarded value-flow analysis[C]//Proceedings of the 28th ACM SIGPLAN Conference on Programming Language Design and Implementation. 2007: 480-491. ↩︎

  2. Bjarne Steensgaard. Points-to analysis in almost linear time. In Proceedings of the ACM Symposium on the Principles of Programming Languages, St. Petersburg Beach, FL, January 1996. ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/100985.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

centos下安装配置redis7

1、找个目录下载安装包 sudo wget https://download.redis.io/release/redis-7.0.0.tar.gz 2、将tar.gz包解压至指定目录下 sudo mkdir /home/redis sudo tar -zxvf redis-7.0.0.tar.gz -C /home/redis 3、安装gcc-c yum install gcc-c 4、切换到redis-7.0.0目录下 5、修改…

学习记忆——数学篇——案例——代数——方程——一元二次方程

重点记忆法 a x 2 b x c 0 ax^2bxc0 ax2bxc0 整体可以由: 根(多少,正负,区间) ⟹ \Longrightarrow ⟹ △ △ △ ⟹ \Longrightarrow ⟹ 求根公式 x 1 , 2 x_{1,2} x1,2​ − b △ 2 a \frac{-b\sqrt{△}}{2a} 2…

LaunchView/启动页 的实现

1. 创建启动画板,LaunchScreen.storyboard 添加组件如图: 2. 项目中设置只支持竖屏,添加启动画板,如图: 3. 创建启动画面动画视图,LaunchView.swift import SwiftUI/// 启动视图 struct LaunchView: View {/// 字符串转换为字符串…

TensorFlow入门(十六、识别模糊手写图片)

TensorFlow在图像识别方面,提供了多个开源的训练数据集,比如CIFAR-10数据集、FASHION MNIST数据集、MNIST数据集。 CIFAR-10数据集有10个种类,由6万个32x32像素的彩色图像组成,每个类有6千个图像。6万个图像包含5万个训练图像和1万个测试图像。 FASHION MNIST数据集由衣服、鞋子…

java - 设计模式 - 状态模式

文章目录 前言java - 设计模式 - 状态模式1. 概述2. 作用3. 示例 前言 如果您觉得有用的话,记得给博主点个赞,评论,收藏一键三连啊,写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差,实在白嫖的话&#xf…

TensorFlow入门(十九、softmax算法处理分类问题)

softmax是什么? Sigmoid、Tanh、ReLU等激活函数,输出值只有两种(0、1,或-1、1或0、x),而实际现实生活中往往需要对某一问题进行多种分类。例如之前识别图片中模糊手写数字的例子,这个时候就需要使用softmax算法。 softmax的算法逻辑 如果判断输入属于某一个类的概率大于属于其…

Android系统定制之监听USB键盘来判断是否弹出软键盘

一.项目背景 在设备上弹出软键盘,会将一大部分UI遮挡起来,造成很多图标无法看到和点击,使用起来不方便,因此通过插入usb键盘输入代替软键盘,但是点击输入框默认会弹出软键盘,因此想要插入USB键盘时,默认关闭软键盘,拔出键盘时再弹出,方便用户使用 二.设计思路 2.1…

JavaScript进阶 第一天笔记

JavaScript 进阶 - 第1天 学习作用域、变量提升、闭包等语言特征,加深对 JavaScript 的理解,掌握变量赋值、函数声明的简洁语法,降低代码的冗余度。 理解作用域对程序执行的影响能够分析程序执行的作用域范围理解闭包本质,利用闭包…

可拓展的低代码全栈框架

尽管现在越来越多的人开始对低代码开发感兴趣,但已有低代码方案的局限性仍然让大家有所保留。其中最常见的担忧莫过于低代码缺乏灵活性以及容易被厂商锁定。 显然这样的担忧是合理的,因为大家都不希望在实现特定功能的时候才发现低代码平台无法支持&…

OpenGL LUT滤镜算法解析

1. 简介 滤镜:一些图像处理软件针对性地提供了一些对传统滤镜效果的模拟功能,使图像达到一种特殊效果。滤镜通常需要同通道、图层、色阶等联合使用,才能使图像取得最佳艺术效果。在软件界面中也直接以“滤镜”(Filter&#xff09…

实现一个自己的脚手架教程

前言 脚手架并不实现,难的是最佳实践的整理和沉淀。本文不会涉及到最佳实践方面的内容,只是教会你如何实现一个最基础的脚手架,以此作为展示最佳实践的载体。 如何搭建一个脚手架的工程 如何开发和调试一个脚手架 脚手架中如何接收和处理命…

12.2 实现键盘模拟按键

本节将向读者介绍如何使用键盘鼠标操控模拟技术,键盘鼠标操控模拟技术是一种非常实用的技术,可以自动化执行一些重复性的任务,提高工作效率,在Windows系统下,通过使用各种键盘鼠标控制函数实现动态捕捉和模拟特定功能的…

数字孪生和数据分析:数字化时代的力量结合

在当今数字化时代,数据是无处不在的。企业、政府和个人不仅生成了大量数据,还寻求从中获取有价值的信息以进行更好的决策。在这个背景下,数字孪生和数据分析成为了迎合这一需求的两个关键概念。本文带大家一起探讨二者之间相辅相成的关系。 一…

Spring Boot:自定义注解--annotation

目录 自定义注解的定义和作用范围如何创建自定义注解创建注解接口 如何使用自定义注解进行数据验证创建注解处理器控制器中使用注解 如何为字段添加注解 自定义注解的定义和作用范围 自定义注解可以作用在类、方法、属性、参数、异常、字段或其他注解上。 如何创建自定义注解…

Google AdSense 账户开通网站广告位后如何配置付款电汇账号的详细教程!

本篇文章主要讲解:Google AdSense 账户开通网站广告位后如何配置付款电汇账号的详细教程。通过本文章可以快速了解开通账户配置权限的整体流程,很多小白朋友注册完毕后发现根本没有配置账号的入口,这篇文章能够告诉你详细的原有。 日期&#…

知识增强语言模型提示 零样本知识图谱问答10.8+10.11

知识增强语言模型提示 零样本知识图谱问答 摘要介绍相关工作方法零样本QA的LM提示知识增强的LM提示与知识问题相关的知识检索 实验设置数据集大型语言模型基线模型和KAPIN评估指标实现细节 实验结果和分析结论 摘要 大型语言模型(LLM)能够执行 零样本cl…

CAN和CANFD通信介绍

CAN(Controller Area Network,控制器局域网)是一种串行通信技术,专门用于在汽车电子控制单元(ECU)之间实现可靠的数据交换。 CAN协议介绍 电子化 汽车近年来的发展呈现出以电子化为主的特点。电子化的主…

Lab 1: Unix utilities汇总

这个实验主要学习了常用的一些系统调用。 Lab 1: Unix utilities Boot xv6 (easy) git克隆,切换分支,qemu。根据要求进行操作即可。 $ git clone git://g.csail.mit.edu/xv6-labs-2020 $ cd xv6-labs-2020 $ git checkout util $ make qemusleep (ea…

数据中心供配电及能效管理系统的设计要点

摘要:现代的数据中心中都包括大量的计算机,对于这种场所的电力供应,都要求供电系统需要在所有的时间都有效,这就不同于一般建筑的供配电系统,它是一个交叉的系统,涉及到市电供电、防雷接地、防静电、UPS不间…

分类预测 | MATLAB实现KOA-CNN-BiGRU开普勒算法优化卷积双向门控循环单元数据分类预测

分类预测 | MATLAB实现KOA-CNN-BiGRU开普勒算法优化卷积双向门控循环单元数据分类预测 目录 分类预测 | MATLAB实现KOA-CNN-BiGRU开普勒算法优化卷积双向门控循环单元数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.MATLAB实现KOA-CNN-BiGRU开普勒算法优化…