基于流敏感上下文敏感指向图的内存泄漏检测方法

文档序号:6584141阅读:302来源:国知局
专利名称:基于流敏感上下文敏感指向图的内存泄漏检测方法
技术领域
本发明主要涉及计算机程序中内存泄漏的静态检测方法,尤其是一种利用流敏
感、上下文敏感的指向图来检测程序中内存泄漏的方法。
背景技术
(:、0++等语言中都支持指针的使用,这可以使代码变得灵活、简洁,但是由此引发 的内存错误也是各类错误中比较难以分析、追踪和消除的。内存泄漏就是一种典型的错误。 在编写程序阶段,编程人员往往很难确定程序在运行的时候需要多少内存,因此动态内存 分配是必不可少的操作。指针和动态内存分配提供了方便、灵活的内存操作机制。 一块内 存的分配者同时也承担着释放它的责任。如果只是不断的分配,而没有相应的释放操作,程 序将占用越来越多的内存,最终会耗尽计算机系统的内存,导致计算机系统崩溃。对于大型 的、需要长时间运行的程序——例如操作系统——来说,内存泄漏错误有可能是致命的。但 内存泄漏的检测是一项比较困难的工作,因为与其他错误不同,内存泄漏没有明显的、易于 观察的错误行为,无法回收的内存并不能立刻对程序以及系统的运行产生影响。因此,内存 泄漏的检测是一项十分有意义而又面临着重大挑战的任务。
内存泄漏有多种不同的定义,这取决于如何定义一块内存单元的生命周期。若一
块内存在生命周期结束时仍未被释放,则认为有内存泄漏发生。而生命周期的定义主要可
以分为以下三种基于引用、基于可达性和基于活跃性的定义等。基于引用的生命周期是 指如果一个计算机内存单元没有被任何指针所指向,则生命周期结束。
目前检测内存泄漏的方法主要有动态方法和静态方法两大类。动态方法是在程序
的运行过程中收集相关信息,判断是否有内存泄漏发生,但该检测方法过于依赖程序运行
所用的测试用例的质量,并且由于程序实际上的执行路径可能有无穷多条,一般情况下无
法产生足够多的测试用例以检查所有可能的执行路径,也就难以检测到所有的错误。静态
方法不需要动态执行程序,可以找到所有可能的内存泄漏错误。采用静态方法进行检测的
工具有很多,包括PREfix, SATURN, LCLint和Metal等,但该方法的主要缺陷是误报率比较
高。因此,如何在保持检测效率的同时降低误报率,是静态方法的研究热点之一。 现有的围绕内存泄漏的静态检测主要有基于形态分析的检测方法、基于指针向图
的检测方法和基于特殊模型的检测方法等。 Hackett和Rugina提出了一种利用形态分析(shape analysis)来检测内存泄漏 的方法。该方法可以对程序的存储形态做局部推理,而不是做全局推理。它使用了形态抽 象的技术,根据变量的指向信息把所有的内存划分为若干个相互独立的区域。针对每个区 域做独立的分析,而与其他区域无关,这提高了分析的效率。另外,还可以通过参数来设置 分析精度,精度越高,相互独立的内存区域就越多,计算复杂性随之升高。该方法的不足在 于其健壮性不够,某些表达式或者特定语句顺序会对内存区域的划分产生重大影响,从而 导致算法效率或者精度的急剧降低。 0rlovich和Rugina提出了一种基于指针分析的数据流分析方法,该方法以需求驱动的方式执行。传统的穷尽式方法以整个程序作为检测对象,提供程序中所有与该内存 泄漏相关的信息;而需求驱动方式则只检测所关心的特定语句,只提供与这些语句相关的 信息。Orlovich等人的方法利用反证法来判断内存泄漏是否存在,它以流不敏感、上下文不 敏感的指向图作为输入,甚至不需要程序的完整代码,只检测代码段的内存泄漏错误。由于 流不敏感、上下文不敏感的精度较低,有可能导致该方法产生较多的误报。
Heine和Lam提出了基于ownership模型的内存泄漏检测方法。该方法为动态分 配的内存指定了持有者,持有者是一个指针或者结构中的指针域。每个动态分配的而尚未 被释放的内存都有唯一的持有者,它的责任是释放该内存或者把持有者的身份转移到另一 个指针或者指针域中。该方法最终转化为一个线性约束方程组的求解问题,通过方程组解 的存在与否来判断是否有内存泄漏发生。这种方法的不足之处在于只有"固定位置(fixed location)"才能够充当持有者。对于一些比较复杂的表达式和操作,比如链表、树等,基于 ownership模型的方法就难以处理或者产生误报。因为在这些操作中,可能要求动态分配的 内存来充当持有者。 综上所述,有多种不同的内存泄漏检测方法,每种方法都有各自的特点与适用范 围,也有不足之处。针对很多C程序中指针及动态内存分配语句较少的事实,需求驱动的检 测是一种比较适合而又有效的技术,且这种方法也表现出了较好效率。但是,在流不敏感的 指向图中,由于其不考虑语句的执行顺序,很多不同的内存位置被视为相同,且合并为一个 内存位置,这也造成了不同的指针被合并。在基于引用的内存泄漏的定义中,这样的合并会 导致把多个引用视为一个引用,从而产生误报,降低了方法准确性。

发明内容
本发明要解决的技术问题在于针对指针和动态内存分配操作的特点,提供一种 基于流敏感、上下文敏感指向图的内存泄漏检测方法,以需求驱动方式保证方法的效率,同 时充分利用流敏感、上下文敏感指向图所提供的信息,提高静态检测内存泄漏方法的精确度。 本发明的技术方案为结合流敏感、上下文敏感指向图和需求驱动的方式,独立地 检查某条语句是否会引发内存泄漏错误。通过逐一检查每条指针赋值语句,报出程序中所 有可能的内存泄漏错误。在检查的过程中,采用后向数据流分析方法,逆着程序控制流的方 向做计算,同时根据语句的类型以及相关的指向图,只检查程序中与当前关注问题相关的 语句,避免不必要的计算。
具体技术方案为 第一步,利用编译器(典型的有GCC、SUIF、LLVM等)对被检测程序做词法分析、语 法分析,生成编译器自身支持的中间文件。 第二步,使用编译器自带的指针分析器或其他独立的指针分析器对中间文件做流 敏感、上下文敏感的指针分析,在程序控制流图中的每个代表程序语句的结点上生成指向 图。指向图是程序运行时指针变量或者结构中的域指针之间指向关系的一种表示形式,它 是一个有向图,图中每个结点代表一个位置集合。位置集合是内存的一种抽象表示,一个位 置集合代表程序运行时的一个或者多个内存。在指向图中,如果从结点u到v之间有一条 有向边,则u中存在一个元素所代表的内存中保存的地址指向v中某一元素所代表的内存。生成指向图的步骤如下 2. 1获取指向图中的结点。每个指针变量都代表一个位置集合,并作为结点在指向 图中出现,其名称就是变量名字;对于动态分配的内存,每一条分配语句产生一个结点,所 有同一语句分配的动态内存使用一个结点来表示,用语句所在的行数作为结点的名字。
2. 2逐一分析程序中的语句,每条指针赋值语句产生一个指向关系,即赋值语句中 指针变量指向表达式的值,由此产生指向图的一条边。 2. 3合并指向图中的指向关系若某一条语句s有多个直接前驱语句(即该语句 执行完后下一步可直接执行s)可直接抵达该语句,则合并这些前驱语句传递过来的指向 关系所代表的边。此步骤操作使得指向图成为流敏感的。 2.4在程序的过程间生成指向关系。通过计算形参实参的值,在程序的入口处合并
过程在不同调用上下文中所产生的指向关系。此步骤使得指向图成为上下文敏感的。 2. 5判定当前语句是否导致新的指向关系出现,若出现,转2. 3 ;若不再出现新的
指向关系,则得到了被检测程序的流敏感和上下文敏感的指向图,转第三步。 第三步,利用第2步生成的流敏感和上下文敏感的指向图检测被检测程序的每个
过程内是否存在内存泄漏,方法是 3. 1生成过程内初始化信息 3. 1. 1基于流敏感和上下文敏感的指向图,针对被测语句s中值被改变的表达式 e建立位置集合的集合,该位置集合的集合由左值位置集合和访问位置集合构成,其中左值 指保存表达式e的值的内存位置,左值位置集合是e在指向图中的所有左值构成的集合,访 问位置集合包含要得到e在指向图中的左值所必须访问的位置集合。 3. 1. 2建立"被测语句s引发了内存泄漏错误"的前提,从该前提得到下述有用信 息被s所改写的表达式的值是一个指针,并且唯一指向某个将被泄漏的内存。因为每次只 针对一条语句做假设与检测,不再考虑无关语句的执行效果,这种需求驱动式的做法可以 避免大量的冗余计算。 3. 1. 3生成被测语句s的数据流事实的初始化信息。数据流事实用一个四元组(d, S, H, M)表示,其中d是被泄漏掉的内存的地址;S代表所有可能指向被泄漏掉的内存的指 针的集合;H是必然指向被泄漏掉的内存的表达式集合;M是一定不指向被泄漏掉的内存的 表达式集合。对于被测语句s,用init(s, G)表示在指向图G中为s生成的数据流事实的 初始化信息。为被测的赋值语句、动态内存分配语句、释放语句、返回语句这四种类型语句 生成数据流事实的初始化信息 3. 1.3. l对于赋值语句e。二^,生成初始化信息的方法为init (e。= G) = (1, hold(l,G) n 11(e。,G), {e。}, {&})。其中,hold(l,G)代表指向图G中指向1的那些内存 位置;11 (e。, G)代表表达式e。在指向图G中的左值。根据3. 1. 2中的前提,该赋值语句引 发了内存泄漏错误,所以执行之前,e。是指向被泄漏内存的唯一指针,也即e。 G H,而^一 定不指向被泄漏的内存,也即e工G M。 3. 1.3. 2对于动态内存分配语句e。 = malloc (),初始化信息的获取方法为 init (e。 = malloc () , G) = init (e。 = NULL, G),该方法把动态内存分配语句看作是源数值 为NULL的赋值语句e。 = NULL,然后按照3. 1. 3. 1中赋值语句的方式生成初始化信息。
3. 13. 3对于释放语句free (e),如果e指向一个指针,则检查这个指针是否是某一内存块的唯一持有者,这种情况下采用3. 1. 3. 1的方法为赋值语句*e = NULL生成初始化 信息。如果e指向一个结构,则逐一检查该结构中所有的指针域,对于每一个指针域n,采用 3. 1. 3. 1的方法为赋值语句e — n = NULL生成初始化信息。 3. 1. 3. 4对于返回语句return e,初始信息的获取可分为两个步骤第一个步骤 是在返回语句之前加入一系列的赋值语句Vl = NULL ;. . . ;vm = NULL,其中Vi (1《i《m) 是该过程中所有的局部变量,然后用3. 1. 3. 1的方法获取这些赋值语句的初始信息;第二 个步骤是对于调用该过程的语句,实际上是把e赋给该调用语句中赋值号左端的表达式, 因此可以把该调用语句看作是一个赋值语句,按照3. 1. 3. 1赋值语句的方式来获取初始信 息。 3. 2利用流敏感和上下文敏感的指向图,从当前程序语句处反向找到在其之前执 行的前驱语句s,针对s的类型(包括赋值语句、动态内存分配语句、释放语句、比较语句), 采用下述方法逐步生成新的数据流事实
3. 2. Is是赋值语句e。 = ei时 3. 2. 1. 1如果一个表达式的左值位置集合和访问位置集合中的元素都不被赋值语 句s改写,则该表达式的值在s执行前后保持不变。 3. 2. 1. 2如果ei在s执行之前不指向被泄漏的内存,且某个表达式e在s执行之 后指向被泄漏的内存,并且e的左值没有被s所改变,则e在执行之前仍然指向被泄漏的内存。 3. 2. 1. 3如果某个表达式e和^的左值不被修改,e在执行之后指向被泄漏的内
存,A在执行之后不指向被泄漏的内存,则e执行之前指向被泄漏的内存。 3. 2. 1. 4如果某个表达式e和^的左值不被修改,e在执行之后不指向被泄漏的
内存,e工在执行之后指向被泄漏的内存,则e执行之前不指向被泄漏的内存。 3. 2. 1. 5若表达式ei的左值不被语句改变,则其右值也不会改变(右值指一个表
达式的实际值)。 3. 2. 1. 6若e。的左值没有被语句改变,则e。在语句s执行之后与^在语句s执行 之前的值相同。 3. 2. 2s是动态内存分配语句或释放语句,则s被看作源数值为NULL的赋值语句, 然后使用3. 2. 1中赋值语句的方法生成新的数据流事实。 3. 2. 3s是比较语句e。 = ^或e。半ei。对于e。= 因为e。和^的指向相同, 将e工中与e。中相同的表达式指向的内存位置设置为e。中相应表达式指向的内存位置。对 于e。 # ei,因为e。和ei的指向不相同,将ei中与e。中相同的表达式指向的内存位置设置 为与e。中相应表达式指向的内存位置不同。 第四步,获取MOD (modification side-effects)信息。MOD信息是调用语句中被 调用过程可能产生的修改副作用,获取MOD信息就是获取数据流事实中出现的表达式在被 调用过程中被修改的结果。 MOD信息分显式和隐式两种一个过程的显式MOD信息是指该过程所有赋值语句 以及调用语句中赋值号左端表达式的左值的集合;一个过程的隐式MOD信息是被该过程调 用的过程所修改的内存位置。能够修改程序状态信息的语句包括赋值语句和调用语句。赋 值语句有赋值号左端表达式和赋值号右端表达式,执行时需要计算出左端表达式的左值,
9然后把右端表达式的右值写入到左端表达式的左值中。调用语句为一类特殊的赋值语句, 只是其右端表达式是一个过程调用,其效果是把过程的返回值赋给左端表达式。释放语句 free(e)是一类特殊的调用语句,其效果等价于e = NULL。 获取MOD信息的方法是先计算出每个过程可能修改的内存位置集合,然后利用 调用点的上下文信息,计算出在每个过程调用点上会被调用过程所修改的内存位置的集 合。具体步骤如下 4. 1对于过程p,通过找到p中所有赋值语句的左端表达式,可以获得显式修改的 内存位置的集合,将这个集合传回到那些调用P的过程中的调用点。 4. 2在回传过程中,根据调用点的上下文信息对传回的内存位置进行削减,去掉那 些不可能在当前调用环境下被修改的内存位置。对于任何一个调用了 P的过程q而言,这 初步求得了过程q隐式修改的内存位置。 4. 3判定过程q被修改的内存位置集合是否发生了变化,若变化则转4. 1,若过程 q被修改的内存位置集合不再发生变化,则内存位置集合到达一个稳定状态,这时即得到了 MOD信息,计算结束。 在上述计算过程中,调用语句所调用的过程p可能修改了许多内存位置,但是有 的内存位置不是在当前的调用上下文中被修改的,因此通过计算不可达的内存位置,在MOD 计算结果中去掉这些不可达的内存位置,可以使MOD信息的计算更为精确。如果内存位置 1对下面三个条件均不满足,则1是不可达的。 [OO46] (l)l属于全局变量; [OO47] (2)l是动态分配的内存; (3)1是调用语句执行之前的指向图中从实参的左值对应的结点出发沿指向边可 达的结点。其中,对于实参r,若r在指向图中的左值存在,则求出从这些左值出发可达的内 存位置;若某一实参r的左值不存在(例如,形如& r的表达式左值就不存在),则在指向 图中沿着指向边得到从訂的左值出发可达的内存位置,訂是r所指向的值。
第五步,对被检测程序检测过程间的内存泄漏,方法是 5. 1获取过程调用语句e = p(a。, ... , an)的初始化信息。由于被调用过程 p(a。, ..., an)中可能有多个return语句,而每个return语句的值都可能赋给e,所以在 生成调用语句的初始化信息时,进入被调用过程P (a。, . . . , an)内,逐一把p (a。, . . . , an)中 的return reti语句替换为e = reti (reti代表第i个返回语句的返回值,O《i《K, K为 P (a。, . . . , an)中返回语句的数目,K为整数),这样就把调用语句的初始化信息计算转化为 赋值语句的初始化信息计算。若表达式e中的变量是全局变量,则称e是一个全局表达式, 其在被调用过程P中可见;否则e是一个局部表达式。 若语句e = p(a。, . . . , an)所在的过程为q,通过以下方式来获取语句e = p (a。, . . . , an)的初始化信息 5. 1. le是一个全局表达式时,通过3. 1.3. 1的方法逐一计算被调用过程 p (a。,. . . , an)中语句e = re^的初始化信息来获取语句e = p (a。,. . . , an)的初始化信息。
5. 1. 2e是过程q中的一个局部表达式,并且在p中被修改时,不获取初始化信息, 直接报出一个内存泄漏错误。 5. 1. 3e是过程q中的一个局部表达式,并且没有被p修改时,如果语句e =
10p(a。, . . . , an)执行之前的指向图是Gs, p中每个返回语句Si之前的指向图是Gri,针对每个 可能被泄漏的内存和P(a。, ..., an)中的每个返回语句return re^,分别计算该返回语句 的初始化信息和需要保存的信息,初始化信息为init (return re^, = (d, S, H,M),其 中d是可能被泄漏掉的内存的地址,S = hold(d,Gri) n (ll(e,Gs) U local (p)) , hold(d, Gr》代表指向图GH中指向d的那些内存位置,11 (e, Gs)代表表达式e在指向图Gs中的左 值,local (p)代表p中所有的局部变量,//=0,鋥=0。 5. 1. 4在从调用过程进入被调用过程之前,采用函数catch (c,D,s)获取要保存的 信息,即数据流事实中那些在当前过程中可见的表达式。其中c是返回语句所处的调用上 下文,D是进入被调用过程之前的数据流事实,s代表调用语句e = p(a。, . . . , an) 。 catch 函数的初始化信息计算过程如下
catch (c, D, s) = (d, S, H, M),其中 S = hold(d, Gs) n ll(e,Gs),hold(d,Gs)代表指向图Gs中指向d的那些内存 位置; 參H = {e} ;M= 0。 e没有被过程p改变,在从过程中返回之后仍然指向被泄漏的 内存,所以要保存,并且尚无M中元素的信息。 从被调用过程返回之后,计算出的(d, S, H, M)被合并到返回的数据流事实中。
5. 2逐步计算生成新的数据流事实,包括以下步骤 5. 2. 1计算需要保存的信息。使用s代表e = p (a。, . . . , an),该语句执行之后的 数据流事实为(d, S, , H, , M,),当前的调用上下文为c,则需要保存的信息catch (c, (d, S,, H,, M, ) , s) = (d, S, H, M),其中: S = hold(d, Gs) n (S' -m0dp (p)) , modp (p)是过程p所修改的位置集合; //=- {em I G》u ,加G》)n ,物* 0} , 11 (e迈,Gs)代表表达式em在指向图Gs
中的左值,al(em,G》表示表达式em在指向图Gs中的访问位置集合;em是a。,. . . , an中存在
的任意一个表达式;
M=— { ew I G》uG》)n附oc/p(p) # 0}。 5. 2. 2把调用语句处的数据流信息映射到被调用过程的出口处。如果调用语句处 的数据流信事实为(d,S',H',M')),则被调用过程出口处的数据流事实为(d,S',H,M),其 中H和M分别只包含H'和M'中的全局表达式。 5. 2. 3把信息从被调用者中映射回调用者。当在被调用过程中后向分析到达过程
的入口处时,就要返回调用者中,此时需处理返回过程中的信息映射,步骤是 5. 2. 3. 1把形参的值赋给调用语句处对应的实参。 5. 2. 3. 2删除当前数据流事实中H和M中p的局部表达式。 5. 2. 3. 3把此处的数据流事实和5. 1. 4步中计算的调用语句e = p(a。,. . . ,an)处
使用catch函数所保存的信息做合并操作,即直接对两个数据流事实中对应元素做集合的
并操作,生成的新数据流事实再和该语句之前程序点上的数据流事实进行合并操作。 第六步,判断新得到的数据流事实是否矛盾以检测是否有内存泄漏,步骤如下 6. 1使用第3. 2步过程内生成新的数据流事实的方法更新数据流事实。 6. 2使用第5. 2步的过程间生成新的数据流事实的方法,通过形参实参的计算,在过程间传播信息,更新数据流事实。 6. 3当新的数据流事实与原数据流事实不同时,转6. 1 ;当新的数据流事实与原数 据流事实相同,即新的数据流事实不再变化时,表明到达稳定状态,执行6. 4。
6.4数据流事实稳定之后,逐个检查每个数据流事实中是否有矛盾对于数据流 事实(d, S, H, M),如果一定不指向被泄漏掉内存的表达式集合M与必然指向被泄漏掉的内 存的表达式集合H或可能指向被泄漏内存的指针集合S的交集不为空,则存在矛盾。当存 在矛盾时,根据3. 1. 2节,由于在一开始建立了"被测语句s引发了内存泄漏错误"的前提, 所以该前提不正确,即所检查的语句s不会引发内存泄漏;当不存在矛盾时,则说明该前提 正确,即存在内存泄漏。检测结束。 与现有技术相比,采用本发明可以达到下列技术效果 (1)本发明在步骤2. 3和2. 4生成高精度的流敏感、上下文敏感指向图,并在步骤 3. 1. 2采用需求驱动方式,兼顾了方法的效率和精确度,在静态检查的精度和效率之间找到 一个较好的平衡点。使用者可以指定某些或某条重点语句,检查它是否引发内存泄漏错误, 且检查时只检测那些影响待测语句的语句和信息,避免了不必要的计算。同时,流敏感、上 下文敏感的指针分析有助于计算出更加精确的数据流事实,从而提高方法的精度,降低误 报率,加快求解速度。 (2)使用流敏感、上下文敏感的指向图,在过程内检测中计算数据流事实的稳定状 态时,可以降低数据流事实到达稳态的速度,有助于提高生成矛盾信息的可能性,降低误报率。 (3)本发明中提出的数据流事实更新方法利用每个程序点上的指向图可以预知该 更新执行之前的一些状态信息,用来证明内存泄漏的不存在,有利于增大检测的精确度,提 早结束计算。例如在3. 2. 1. 2步中,可以利用语句执行之前的指向图G计算出ei在执行之 前状态下的信息,对表达式的指向情况作计算。 (4)具有很好的灵活性。由于程序终止性在理论上是不可判定的,过程的调用上下 文的数目可能是无穷的,要做到完全的上下文敏感也是不可能的。本发明需要使用一个流 敏感、上下文敏感的指针分析,但根据第二步可见该分析过程并不依赖于具体的算法,从而 使得本发明在精度上可以调节。 (5)本发明应用范围广泛,可直接用于多级指针、数组、递归数据结构、递归程序等 多种语法成分与复杂结构,根据步骤3. 1和5. l,都最终归为赋值语句的相关信息计算。5. 2 中的方法在进入被调用过程之前,通过保存被调用过程不可见的信息,一方面提高了检测 的精度,另一方面由于在被调用过程中只分析可见信息,提高了计算的效率。


图1是本发明的总体流程图。
图2是本发明第3步过程内分析的流程图。
图3是本发明第4步MOD信息计算流程图。
图4是本发明第5步过程间分析的流程图。具体实施方案 图1是本发明的总体流程图。输入是程序源代码,输出是程序源代码中的语句是
否会引发内存泄漏错误的判断结果。 本发明包括以下几个步骤 1.首先利用编译器对程序源代码以及待检查的语句做语法分析和词法分析,生成 中间文件。 2.采用指针分析器对编译器生成的中间文件计算流敏感、上下文敏感的指针信
息,在每条语句所关联的程序点上生成一个流敏感和上下文敏感的指向图。 3.利用流敏感和上下文敏感的指向图检测被检测程序的每个过程内是否存在内
存泄漏。 4.获取M0D信息。 5.利用流敏感和上下文敏感的指向图检测程序过程间的内存泄漏。
6.检查新生成的数据流事实以判断是否有内存泄漏产生。
图2是本发明第3步过程内检测的流程图,包括以下步骤
3. 1生成过程内初始化信息。 3. 2利用流敏感和上下文敏感的指向图,从当前程序语句处反向找到在其之前执 行的前驱语句,针对该语句的类型(包括赋值语句、动态内存分配语句、释放语句、比较语 句),生成新的数据流事实。 图3是本发明第4步MOD信息计算流程图,包括以下步骤
4. 1计算出每个过程可能修改的内存位置集合。
4.2获取过程隐式修改的内存位置。 4. 3判定过程被修改的内存位置集合是否发生了变化,若变化则转4. l,若过程被
修改的内存位置集合不再发生变化,计算结束。 图4是本发明第5步过程间检测的流程图。 5. 1获取过程调用语句的初始化信息。 5. 2逐步计算生成新的数据流事实。
权利要求
一种基于流敏感上下文敏感指向图的内存泄漏检测方法,其特征在于包括以下步骤第一步,利用编译器对被检测程序做词法分析、语法分析,生成编译器自身支持的中间文件;第二步,使用编译器自带的指针分析器或其他独立的指针分析器对中间文件做流敏感、上下文敏感的指针分析,在程序控制流图中的每个代表程序语句的结点上生成指向图,指向图是程序运行时指针变量或者结构中的域指针之间指向关系的一种表示形式,它是一个有向图,图中每个结点代表一个位置集合;位置集合是内存的一种抽象表示,一个位置集合代表程序运行时的一个或者多个内存;在指向图中,如果从结点u到v之间有一条有向边,则u中存在一个元素所代表的内存中保存的地址指向v中某一元素所代表的内存;生成指向图的步骤如下2.1获取指向图中的结点每个指针变量都代表一个位置集合,并作为结点在指向图中出现,其名称就是变量名字;对于动态分配的内存,每一条分配语句产生一个结点,所有同一语句分配的动态内存使用一个结点来表示,用语句所在的行数作为结点的名字;2.2逐一分析程序中的语句,每条指针赋值语句产生一个指向关系,即赋值语句中指针变量指向表达式的值,由此产生指向图的一条边;2.3合并指向图中的指向关系若某一条语句s有多个直接前驱语句可直接抵达该语句,则合并这些前驱语句传递过来的指向关系所代表的边,使得指向图成为流敏感的;所述直接前驱语句是指该语句执行完后下一步可直接执行s;2.4在程序的过程间生成指向关系通过计算形参实参的值,在程序的入口处合并过程在不同调用上下文中所产生的指向关系,使得指向图成为上下文敏感的;2.5判定当前语句是否导致新的指向关系出现,若出现,转2.3;若不再出现新的指向关系,则得到了被检测程序的流敏感和上下文敏感的指向图,转第三步;第三步,利用流敏感和上下文敏感的指向图检测被检测程序的每个过程内是否存在内存泄漏,方法是3.1生成过程内初始化信息3.1.1基于流敏感和上下文敏感的指向图,针对被测语句s中值被改变的表达式e建立位置集合的集合,该位置集合的集合由左值位置集合和访问位置集合构成,其中左值指保存表达式e的值的内存位置,左值位置集合是e在指向图中的所有左值构成的集合,访问位置集合包含要得到e在指向图中的左值所必须访问的位置集合;3.1.2建立“被测语句s引发了内存泄漏错误”的前提,从该前提得到下述有用信息被s所改写的表达式的值是一个指针,并且唯一指向某个将被泄漏的内存;3.1.3生成数据流事实的初始化信息,数据流事实用一个四元组(d,S,H,M)表示,其中d是被泄漏掉的内存的地址;S代表所有可能指向被泄漏掉的内存的指针的集合;H是必然指向被泄漏掉的内存的表达式集合;M是一定不指向被泄漏掉的内存的表达式集合;对于一条语句s,用init(s,G)表示在指向图G中为s生成的数据流事实的初始化信息;为被测的赋值语句、动态内存分配语句、释放语句、返回语句这四种类型语句生成数据流事实的初始化信息3.1.3.1对于赋值语句e0=e1,生成初始化信息的方法为init(e0=e1,G)=(l,hold(l,G)∩ll(e0,G),{e0},{e1}),其中,hold(l,G)代表指向图G中指向l的那些内存位置;ll(e0,G)代表表达式e0在指向图G中的左值;3.1.3.2对于动态内存分配语句e0=malloc(),初始化信息的获取方法为init(e0=malloc(),G)=init(e0=NULL,G);3.1.3.3对于释放语句free(e),如果e指向一个指针,则检查这个指针是否是某一内存块的唯一持有者,这种情况下采用3.1.3.1的方法为赋值语句*e=NULL生成初始化信息;如果e指向一个结构,则逐一检查该结构中所有的指针域,对于每一个指针域n,采用3.1.3.1的方法为赋值语句e→n=NULL生成初始化信息;3.1.3.4对于返回语句return e,初始信息的获取分为两个步骤第一个步骤是在返回语句之前加入一系列的赋值语句v1=NULL;...;vm=NULL,其中vi是该过程中所有的局部变量,1≤i≤m,然后用3.1.3.1的方法获取这些赋值语句的初始信息;第二个步骤是对于调用该过程的语句,实际上是把e赋给该调用语句中赋值号左端的表达式,因此把该调用语句看作是一个赋值语句,按照3.1.3.1赋值语句的方式来获取初始信息;3.2利用流敏感和上下文敏感的指向图,从当前程序语句处反向找到在其之前执行的前驱语句s,针对s的类型采用下述方法逐步生成新的数据流事实3.2.1s是赋值语句e0=e1时3.2.1.1如果一个表达式的左值位置集合和访问位置集合中的元素都不被赋值语句s改写,则该表达式的值在s执行前后保持不变;3.2.1.2如果e1在s执行之前不指向被泄漏的内存,且某个表达式e在s执行之后指向被泄漏的内存,并且e的左值没有被s所改变,则e在执行之前仍然指向被泄漏的内存;3.2.1.3如果某个表达式e和e1的左值不被修改,e在执行之后指向被泄漏的内存,e1在执行之后不指向被泄漏的内存,则e执行之前指向被泄漏的内存;3.2.1.4如果某个表达式e和e1的左值不被修改,e在执行之后不指向被泄漏的内存,e1在执行之后指向被泄漏的内存,则e执行之前不指向被泄漏的内存;3.2.1.5若表达式e1的左值不被语句改变,则其右值也不会改变,右值指一个表达式的实际值;3.2.1.6若e0的左值没有被语句改变,则e0在语句s执行之后与e1在语句s执行之前的值相同;3.2.2s是动态内存分配语句或释放语句,则s被看作源数值为NULL的赋值语句,然后使用3.2.1中赋值语句的方法生成新的数据流事实;3.2.3s是比较语句e0=e1或e0≠e1,对于e0=e1,将e1中与e0中相同的表达式指向的内存位置设置为e0中相应表达式指向的内存位置;对于e0≠e1,将e1中与e0中相同的表达式指向的内存位置设置为与e0中相应表达式指向的内存位置不同;第四步,获取MOD信息,MOD信息是调用语句中被调用过程可能产生的修改副作用,获取MOD信息就是获取数据流事实中出现的表达式在被调用过程中被修改的结果;MOD信息分显式和隐式两种一个过程的显式MOD信息是指该过程所有赋值语句以及调用语句中赋值号左端表达式的左值的集合;一个过程的隐式MOD信息是被该过程调用的过程所修改的内存位置;获取MOD信息的方法是先计算出每个过程可能修改的内存位置集合,然后利用调用点的上下文信息,计算出在每个过程调用点上会被调用过程所修改的内存位置的集合;具体步骤如下4.1对于过程p,通过找到p中所有赋值语句的左端表达式,获得显式修改的内存位置的集合,将这个集合传回到那些调用p的过程中的调用点;4.2在回传过程中,根据调用点的上下文信息对传回的内存位置进行削减,去掉那些不可能在当前调用环境下被修改的内存位置,对于任何一个调用了p的过程q而言,这初步求得了过程q隐式修改的内存位置;4.3判定过程q被修改的内存位置集合是否发生了变化,若变化则转4.1,若过程q被修改的内存位置集合不再发生变化,则内存位置集合到达一个稳定状态,这时即得到了MOD信息,计算结束;第五步,对被检测程序检测过程间的内存泄漏,方法是5.1若过程调用语句e=p(a0,...,an)所在的过程为q,通过以下方式来获取e=p(a0,...,an)的初始化信息5.1.1e是一个全局表达式时,逐一把p(a0,...,an)中的return reti语句替换为e=reti,通过3.1.3.1的方法逐一计算被调用过程p(a0,...,an)中语句e=reti的初始化信息来获取语句e=p(a0,...,an)的初始化信息,其中reti代表p(a0,...,an)中第i个返回语句的返回值,0≤i≤K,K为p(a0,...,an)中返回语句的数目,K为整数;5.1.2e是过程q中的一个局部表达式,并且在p中被修改时,不获取初始化信息,直接报出一个内存泄漏错误;5.1.3e是过程q中的一个局部表达式,并且没有被p修改时,针对每个可能被泄漏的内存和p(a0,...,an)中的每个返回语句return reti,分别计算该返回语句的初始化信息和需要保存的信息初始化信息为init(return reti,Gri)=(d,S,H,M),其中d是可能被泄漏掉的内存的地址,S=hold(d,Gri)∩(ll(e,Gs)∪local(p)),hold(d,Gri)代表指向图Gri中指向d的那些内存位置,ll(e,Gs)代表表达式e在指向图Gs中的左值,local(p)代表p中所有的局部变量,Gs是语句e=p(a0,...,an)执行之前的指向图,Gri是p中每个返回语句si之前的指向图,5.1.4在从调用过程进入被调用过程之前,采用函数catch(c,D,s)获取要保存的信息,即数据流事实中那些在当前过程中可见的表达式,其中c是返回语句所处的调用上下文,D是进入被调用过程之前的数据流事实,s代表调用语句e=p(a0,...,an),catch函数的初始化信息计算过程如下catch(c,D,s)=(d,S,H,M),其中●S=hold(d,Gs)∩ll(e,Gs),hold(d,Gs)代表指向图Gs中指向d的那些内存位置;●H={e},从被调用过程返回之后,计算出的(d,S,H,M)被合并到返回的数据流事实中;5.2逐步计算生成新的数据流事实,包括以下步骤5.2.1计算需要保存的信息使用s代表e=p(a0,...,an),该语句执行之后的数据流事实为(d,S’,H’,M’),当前的调用上下文为c,则需要保存的信息catch(c,(d,S’,H’,M’),s)=(d,S,H,M),其中S=hold(d,Gs)∩(S’-modp(p)),modp(p)是过程p所修改的位置集合;ll(em,Gs)代表表达式em在指向图Gs中的左值,al(em,Gs)表示表达式em在指向图Gs中的访问位置集合;em是a0,...,an中存在的任意一个表达式;5.2.2把调用语句处的数据流信息映射到被调用过程的出口处,如果调用语句处的数据流信事实为(d,S’,H’,M’)),则被调用过程出口处的数据流事实为(d,S’,H,M),其中H和M分别只包含H’和M’中的全局表达式;5.2.3当在被调用过程中后向分析到达过程的入口处时,就要返回调用者中,此时需处理返回过程中的信息映射,步骤是5.2.3.1把形参的值赋给调用语句处对应的实参;5.2.3.2删除当前数据流事实中H和M中p的局部表达式;5.2.3.3把此处的数据流事实和5.1.4步中计算的调用语句e=p(a0,...,an)处使用catch函数所保存的信息做合并操作,即直接对两个数据流事实中对应元素做集合的并操作,生成的新数据流事实再和该语句之前程序点上的数据流事实进行合并操作;第六步,判断新得到的数据流事实是否矛盾以检测是否有内存泄漏,步骤如下6.1使用第3.2步的过程内生成新的数据流事实的方法更新数据流事实;6.2使用第5.2步过程间生成新的数据流事实的方法,通过形参实参的计算,在过程间传播信息,更新数据流事实;6.3当新的数据流事实与原数据流事实不同时,转6.1;当新的数据流事实与原数据流事实相同,即新的数据流事实不再变化时,执行6.4;6.4逐个检查每个数据流事实中是否有矛盾对于数据流事实(d,S,H,M),如果一定不指向被泄漏掉内存的表达式集合M与必然指向被泄漏掉的内存的表达式集合H或可能指向被泄漏内存的指针集合S的交集不为空,则存在矛盾;当存在矛盾时,所检查的语句s不会引发内存泄漏;当不存在矛盾时,则存在内存泄漏,检测结束。2. 如权利要求1所述的基于流敏感上下文敏感指向图的内存泄漏检测方法,其特征在于计算不可达的内存位置,在MOD计算结果中去掉这些不可达的内存位置,如果内存位置l对下面三个条件均不满足,则l是不可达的(1)l属于全局变量;(2)l是动态分配的内存;(3)l是调用语句执行之前的指向图中从实参的左值对应的结点出发沿指向边可达的结点,其中,对于实参r,若r在指向图中的左值存在,则求出从这些左值出发可达的内存位置;若某一实参r的左值不存在,则在指向图中沿着指向边得到从*r的左值出发可达的内存位置,*r是r所指向的值。F2009102270748C00041.tif,F2009102270748C00042.tif,F2009102270748C00043.tif,F2009102270748C00051.tif
2.如权利要求1所述的基于流敏感上下文敏感指向图的内存泄漏检测方法,其特征 在于计算不可达的内存位置,在MOD计算结果中去掉这些不可达的内存位置,如果内存位 置1对下面三个条件均不满足,则1是不可达的(1) l属于全局变量;(2) l是动态分配的内存;(3) l是调用语句执行之前的指向图中从实参的左值对应的结点出发沿指向边可达的 结点,其中,对于实参r,若r在指向图中的左值存在,则求出从这些左值出发可达的内存位 置;若某一实参r的左值不存在,则在指向图中沿着指向边得到从訂的左值出发可达的内 存位置,訂是r所指向的值。
全文摘要
本发明公开了一种基于流敏感上下文敏感指向图的内存泄漏检测方法,要解决的技术问题在于一方面提高内存泄漏检测的精确度,一方面保证内存泄漏效率。技术方案是先对源代码做语法分析和词法分析,生成中间文件;对中间文件计算流敏感、上下文敏感的指针信息,生成流敏感和上下文敏感的指向图;利用流敏感和上下文敏感的指向图检测被检测程序的每个过程内是否存在内存泄漏,获取MOD信息,再利用流敏感和上下文敏感的指向图检测程序过程间的内存泄漏;最后判断新得到的数据流事实是否矛盾以检测是否有内存泄漏。采用本发明能提高内存泄漏检测的效率和精确度。
文档编号G06F11/36GK101710303SQ20091022707
公开日2010年5月19日 申请日期2009年12月1日 优先权日2009年12月1日
发明者刘万伟, 徐厚峰, 王戟, 董威, 马晓东 申请人:中国人民解放军国防科学技术大学
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1