一种用于动态检测C/C++内存泄露的方法及系统
技术领域
本发明涉及计算机软件安全分析领域,尤其涉及一种用于动态检测C/C++内存泄露的方法及系统。
背景技术
C++因其灵活性、高效性等特点一直以来都是主流程序设计语言之一。它与Java等高级语言相比,在编程中程序员需要自己管理内存,并对程序中所涉及的内存操作有很清晰的认识。内存问题很难让人察觉,特别是内存泄漏,它不同于其他内存错误,如多次释放、野指针、或者是数组越界等容易暴露出来,内存泄漏错误一般隐藏得比较深,在数万行的代码之中寻找内存泄漏无异于大海捞针。因此借助内存分析工具来检验内存错误是非常具有实用价值的,不仅能够提高软件的质量,还能缩短软件的开发周期。
一般地,内存泄漏的检测方法分为两大类,一类是静态检测方法,另一类是动态检测方法。静态检测方法通过分析程序源代码,模拟所有可能执行路径,判定程序可能执行路径中存在的安全缺陷。这种方法无需实际执行程序,克服了动态分析性能开销较大的问题,但是由于静态分析无法精确判断程序的输入、环境变量等信息,模拟执行路径中可能存在不可行路径。因此,静态分析方法较高的误报率是至今未解的一个重要难题。动态检测方法主要原理是在程序中进行动态内存分配时,在堆中作以标记。当程序退出并释放所有已分配的内存时,检查堆上残留的对象,这些残留的对象就是程序中泄漏的内存。这种分析方法能够直接发现实际发生的程序缺陷。但是,动态特性要求实际执行程序引入较大性能和时间开销,另外由于程序执行的路径覆盖存在死角,检测结果完备性不足,漏报率较高。
动态检测法的实现主要有以下几种方法:第一种方法是通过重新编写编译器来实现。重新编写编译器需要花费大量的时间,而且需要的人力和物力代价也很高。因此,几乎没有采用该种方法来完成C++内存泄露检测的工具。第二种方法是对源代码进行修改,通过语法分析和语义分析,插入监测代码,将原始的源代码转换为新的源代码。该种方法虽然实现的代价比较低,但是由于源代码的转换需要人工参与,自动化程度低,而且得出的结果不太理想,该种方法的应用范围也不太广泛。
发明内容
鉴于上述问题,提出了本发明,以便提供一种克服上述问题或至少部分地解决上述问题的一种用于动态检测C/C++内存泄露的方法及系统。
根据本发明的一个方面,提供一种用于动态检测C/C++内存泄露的方法,所述方法包括如下步骤:
截获C/C++源程序中动态内存的内存管理函数;
在截获的内存管理函数中加入监测代码,分析程序所申请的内存资源,推测出内存泄露的原因并进行监控;
依据Valgrind工具内存泄露的规则,确定系统中应用程序内存泄露的原因并最终给出分析报告。
进一步的,通过Valgrind工具提供的相关API获取所述内存管理函数的控制权。
进一步的,所述监测代码为内存信息块,所述内存信息块记录所述内存管理函数中所涉及的内存相关信息,所述内存相关信息包括内存的大小,返回给用户内存块首地址、申请的时间、当前堆栈中最近几层函数的调用地址。
根据本发明的另一方面,提供一种用于动态检测C/C++内存泄露的系统,所述系统包括内存管理函数接口模块、内存信息块管理模块、内存泄露检测模块和日志输出模块,其中:
所述内存管理函数接口模块,用于对C/C++源程序中内存管理函数进行捕获和记录,从而获得这些函数的控制权;
所述内存信息块管理模块,用于记录C/C++源程序中申请或释放的动态内存块的相关信息的数据结构;
所述内存泄露检测模块,用于对内存泄露的类型进行检测;
所述日志输出模块,用于输出内存泄露错误信息并提供可以定位到内存泄露具体位置的信息。
进一步的,所述内存管理函数接口模块,具体通过截获C/C++程序中对应的符号信息来获取到该函数的控制权限。
进一步的,每一个函数族都有一个完全属于本函数族的内存信息块链表,所有的函数族表头都链成一个链表,由全局指针索引;同时,采用生命周期法需要将程序中分配的所有内存信息块统一进行分组管理,以执行到内存分配函数时当前堆栈中的最近五层函数返回地址作为关键字进行分组。
进一步的,在系统截获申请函数后,将内存信息块加入管理列表,具体流程包括:记录当前分配时间,将当前堆栈中的最近五层函数返回地址的异或值作为组号,判断该组号是否存在,如果存在,则将内存信息块加入该组并更新该组的相关信息;否则,将申请组结点空间并初始化,将该组结点加入内存信息块管理列表中,同时将内存信息块加入该组,并设置该组的相关信息。
进一步的,在系统截获释放函数后,将内存信息块从管理列表中删除,具体流程包括:通过内存块管理信息中记录的组号,找到该内存块所在的组;计算当前内存块的生存时间,判断该内存信息块的生存时间是否大于该组所保存的最大生命周期,如果是,则将该组中最大生命周期更新为当前内存块的生存周期后,将该内存信息块从该组中删除;否则,直接将该内存信息块从该组中删除。
进一步的,当一个内存块的生存周期大于所在组中最大生命周期的两倍则将该块内存标记为疑似泄漏;在C/C++程序结束以后检测各个函数族中是否还有没有释放的内存信息块,如果有则表示存在内存泄漏,报告内存泄漏错误,内存泄漏类型是确定泄漏。
进一步的,所述系统还包括应用程序函数调用跟踪模块,用于在加入监测代码后,对动态内存使用情况进行跟踪,并将动态内存使用信息输出给内存信息块管理模块。
本发明可动态检测C/C++源程序中内存错误的问题,从而解决静态分析技术的缺陷。
附图说明
为了更清楚地说明本发明实施例的技术方案,下面将对实施例描述中所需要使用的附图作简单地介绍,显而易见地,下面描述中的附图仅仅是本发明的一些实施例,对于本领域普通技术人员来讲,在不付出创造性劳动性的前提下,还可以根据这些附图获得其他的附图。
图1为本发明一种实施例的一种用于动态检测C/C++内存泄露方法的流程示意图。
图2为本发明一种实施例的一种用于动态检测C/C++内存泄露系统的框架示意图。
图3为本发明一种实施例的一种用于动态检测C/C++内存泄露系统的功能结构示意图。
图4为本发明一种实施例的一种用于动态检测C/C++内存泄露系统在加入内存信息块时的处理流程示意图。
图5为本发明一种实施例的一种用于动态检测C/C++内存泄露系统在删除内存信息块时的处理流程示意图。
具体实施方式
下面将参照附图更详细的描述本发明的示例性实施例。虽然附图中显示了本发明的示例性实施例,然而应当理解,可以以各种形式实现本发明,而不应被这里阐述的实施例所限制。相反,提供这些实施例是为了能更透彻的理解本发明,并且能够将本发明的范围完整的传达给本领域的技术人员。
本发明的动态检测方法是对中间代码进行修改,即在编译或链接阶段插入监测代码。这种方法的实现代价介于现有技术中提到的第一种方法和第二种方法之间,而且自动化程度很高,得出来的结果比较令人满意。
Valgrind工具的检测原理是通过修改可执行文件来进行内存泄露检测,所以不需要重新编译程序。但它并不是在执行前对可执行文件和所有相关的共享库进行一次性修改,而是和应用程序在同一个进程中运行,动态地修改即将执行的下一段代码。Valgrind是插件式设计的,它的Core部分负责对应用程序的整体控制,并把即将修改的代码,转换成一种中间格式,这种格式类似于RISC指令,然后把中间代码传给插件。插件根据要求对中间代码修改,然后把修改后的结果交给Core。Core接下来把修改后的中间代码转换成原始的x86指令,并执行它。
基于以上原理,本发明在检测程序存在的内存问题时首先截获内存操作信息,即截获相关内存管理函数的控制权,如C中的操作符malloc、free等,C++中的操作符new、delete等。获得内存管理函数的控制权以后,采取监控和替换的方式在被测程序中添加插桩代码,记录内存的操作行为,包括记录内存相关信息(比如内存的大小,返回给用户内存块首地址、申请的时间、当前堆栈中最近几层函数的调用地址等),并对这些信息进行统一管理。记录内存相关信息后,利用Valgrind工具的判定内存泄露的规则和方法来判定内存出现的错误。本发明检测出内存错误后,报告内存相关错误并提供可以定位该错误的相关信息。
具体的,根据本发明的一个方面,提供一种用于动态检测C/C++内存泄露的方法,如图1所示,所述方法包括如下步骤:
步骤S110,截获C/C++源程序中动态内存的内存管理函数,即捕获和记录C/C++源程序中动态内存的申请函数和释放函数。具体的,通过Valgrind工具提供的相关API(Application Programming Interface,应用程序编程接口),获取应用程序中与内存相关函数的控制权。
步骤S120,在截获的内存管理函数中加入监测代码,分析程序所申请的内存资源,推测出内存泄露的原因并进行监控。具体的,获取执行权限以后,在截获的内存管理函数中插入插桩代码来对内存相关操作进行监控。比如对截获的内存申请函数来说,用插入一个数据结构(比如内存信息块)记录该函数中所涉及的内存相关信息,如申请内存的大小,返回给用户内存块首地址、申请的时间、当前堆栈中最近几层函数的调用地址等,监测并推测出内存泄露的原因。
步骤S130,依据Valgrind工具内存泄露的规则,确定系统中应用程序内存泄露的原因并最终给出分析报告。
根据本发明的另一方面,提供一种用于动态检测C/C++内存泄露的系统,该系统、valgrind以及C/C++源程序的关系如图2所示,检测系统截获应用程序(C/C++)中的申请函数和释放函数,插入监测代码统一进行内存管理,对于申请函数,将内存信息块加入管理列表;对于释放函数,将内存信息块从管理列表中删除,推测出内存泄露的原因并进行监控。之后,利用Valgrind工具内存泄露的规则,最终确定出系统中应用程序内存泄露的原因,并最终给出分析报告。
如图3所示,一种用于动态检测C/C++内存泄露的系统,具体包括内存管理函数接口模块31、内存信息块管理模块32、内存泄露检测模块33和日志输出模块34。其中内存管理函数接口模块31是检测系统和被测程序的接口模块,日志输出模块34是检测系统和用户的交互模块,内存信息块管理模块32是本发明的核心。具体阐述如下:
内存管理函数接口模块31,用于对C/C++源程序中内存管理函数进行捕获和记录,从而获得这些函数的控制权。C/C++动态内存接管在定位函数信息时,需要确认函数的符号信息,通过找到指定的符号来确定函数调用的起始处。比如,对于C++中分配动态内存的new和new[]在不同编译器中编译的符号不一样,通过编写一段只包含new[]和delete[]的小程序来查看当前编译器编译出的符号信息,在Linux环境中采用将该程序编译成汇编文件,再查找汇编文件中的指令,便可知在该编译器下对应的符号信息,在C++程序中通过截获对应的符号信息就可以获取到该函数的控制权限。
内存信息块管理模块32,用于记录C/C++源程序中申请或释放的动态内存块的相关信息的数据结构。对于不同函数族申请的内存信息块放在一个全局链表中进行管理比较混乱而且不容易区分,因此采用以函数族为单位的管理方式来解决此问题。每一个函数族都有一个完全属于本函数族的内存信息块链表,所有的函数族表头都链成一个链表,由全局指针索引。本发明采用内存块的生命周期法解决运行过程中的内存泄漏问题。采用生命周期法需要将程序中分配的所有内存信息块统一进行分组管理,以执行到内存分配函数时当前堆栈中的最近五层函数返回地址作为关键字进行分组。
结合图4和图5,可理解内存信息块的加入流程和删除流程。如图4所示,在系统截获申请函数后,将内存信息块加入管理列表,具体流程包括:记录当前分配时间,将当前堆栈中的最近五层函数返回地址的异或值作为组号,判断该组号是否存在,如果存在,则将内存信息块加入该组并更新该组的相关信息;否则,将申请组结点空间并初始化,将该组结点加入内存信息块管理列表中,同时将内存信息块加入该组,并设置该组的相关信息。如图5所示,在系统截获释放函数后,将内存信息块从管理列表中删除,具体流程包括:通过内存块管理信息中记录的组号,找到该内存块所在的组;计算当前内存块的生存时间(生存周期),判断该内存信息块的生存时间是否大于该组所保存的最大生命周期,如果是,则将该组中最大生命周期更新为当前内存块的生存周期后,将该内存信息块从该组中删除;否则,直接将该内存信息块从该组中删除。
在很多C/C++程序中,对动态内存的申请和释放都有一定的规律性,将内存块依据函数的调用关系来分组,可以认为同一组内存的内存块具有相似的行为,也即生存周期应该大致相同。如果有一个组中的内存块生存周期大大超过本组的其他内存块的生存周期,则可以认为这块内存是可能泄漏的内存,称为疑似泄漏。内存泄漏的判定,是约定当一个内存块的生存周期大于所在组中最大生命周期的两倍则将该块内存标记为疑似泄漏,然后再对该内存块进行是否有读写操作的判定,如果该内存块在一段时间内都没有写操作,就认为该内存块可能是泄漏的内存,将该内存块报告为泄漏的内存块,类型为疑似泄漏。
C/C++程序结束以后检测各个函数族中是否还有没有释放的内存信息块,如果有则表示存在内存泄漏,报告内存泄漏错误,内存泄漏类型是确定泄漏。
内存块管理的算法如下表所示:
内存泄露检测模块33,用于对内存泄露的类型进行检测。内存泄露主要包括内存泄露、内存越界访问、内存多重释放以及内存不匹配释放等错误。内存块管理模块给出内存块管理信息,内存泄露检测模块依据valgrind的内存泄露的判断规则和方法,给出内存泄露的错误类型,并将错误类型报告给日志输出模块。
日志输出模块34,用于输出内存泄露错误信息并提供可以定位到内存泄露具体位置的信息。日志输出模块34是检测系统与用户的交互模块,内存泄露检测模块将内存泄露错误类型报告给日志输出模块,日志输出模块依据这些信息输出内存泄露错误信息并提供可以定位到内存泄露具体位置的信息。
作为上述实施例的进一步改进,一种用于动态检测C/C++内存泄露的系统还包括应用程序函数调用跟踪模块35,用于在加入监测代码后,对动态内存使用情况进行跟踪,并将动态内存使用信息输出给内存信息块管理模块。
本发明针对C/C++程序常出现的内存泄漏、内存越界访问、内存的不匹配释放等错误进行了研究,分析了现有的内存错误检测工具和方法,在基于开源的动态插桩框架工具Valgrind的基础上,采用函数族的内存信息块管理方法和生命周期法,实现了在Linux平台下运行的内存检测工具。该工具能有效地检测出内存泄漏、内存越界访问、内存的不匹配释放等问题。
本说明书中的各个实施例均采用递进的方式描述,各个实施例之间相同相似的部分互相参见即可,每个实施例重点说明的都是与其他实施例的不同之处。尤其,对于装置或系统实施例而言,由于其基本相似于方法实施例,所以描述得比较简单,相关之处参见方法实施例的部分说明即可。以上所描述的装置及系统实施例仅仅是示意性的,其中所述作为分离部件说明的单元可以是或者也可以不是物理上分开的,作为单元显示的部件可以是或者也可以不是物理单元,即可以位于一个地方,或者也可以分布到多个网络单元上。可以根据实际的需要选择其中的部分或者全部模块来实现本实施例方案的目的。本领域普通技术人员在不付出创造性劳动的情况下,即可以理解并实施。
以上所述仅为本发明之较佳实施例,并非用以限定本发明的权利要求保护范围。同时以上说明,对于相关技术领域的技术人员应可以理解及实施,因此其他基于本发明所揭示内容所完成的等同改变,均应包含在本权利要求书的涵盖范围内。