一种动态监测堆内存使用错误的内存监测装置及方法与流程

文档序号:11386377阅读:208来源:国知局

本发明涉及计算机技术领域,具体地说是一种动态监测堆内存使用错误的内存监测装置及方法。



背景技术:

软件的可靠性对于一个系统来说至关重要,而内存问题则是影响软件可靠性的重中之中,例如非法的内存访问可能会导致系统死机,也可能会导致不可预料的运行结果,而内存泄漏则会使系统可用内存越来越少,导致系统运行越来越慢甚至会死机。因此,追踪内存错误是一个软件开发人员必不可少的工作。

常见的动态内存分析工具诸如valgrind等,由于功能过于庞大、依赖太多,导致这些工具本身就需要占用很多系统资源,再加上移植性的考虑,并不适合在一些资源紧张的嵌入式系统中使用。

另外一些现有的动态内存工具,由于设计上的原因,总是存在这样那样的缺陷,例如无法覆盖所有的内存错误,或者效率低下从而影响原软件的运行速度。

为了解决这一难题,本发明提出了种动态监测堆内存使用错误的内存监测装置及方法。



技术实现要素:

本发明的技术任务是针对以上不足之处,提供一种动态监测堆内存使用错误的内存监测装置及方法。

一种动态监测堆内存使用错误的内存监测装置,该内存监测装置导入运行在内存中,其结构包括,

内存申请接口及内存释放接口,用于对系统默认的内存申请、释放接口进行拦截;

构造函数接口,提供整个装置被载入内存后默认执行的构造函数,并监测构造函数参数的配置初始化;

内存管理模块,用于对内存进行二次封装、管理记录;

析构函数接口,提供进程退出时默认执行的析构函数,此接口内将所有已泄露内存的详细信息显示输出给用户;

异常处理接口,提供异常处理函数,捕获到内存非法访问的异常,并将异常指令处的详细信息输出给用户。

所述内存监测装置以动态库的形式存在,配合被监测软件一并导入运行内存中,内存监测装置将被监测软件的内存申请及释放接口截获,并从堆中申请一段内存,然后对这段内存进行封装处理,再返回给调用者,即被监测软件;当被监测软件对这段内存进行非法处理时,内存监测装置检测到并及时告知用户;当被监测软件退出时,内存监测装置打印输出所有已泄露内存的信息,帮助用户进行问题定位。

所述构造函数接口提供被调用的构造函数后,进行初始化工作,其过程为:解析动态符号表,获取系统原有的内存申请及释放接口指针,并当作本装置内的私有内存接口;解析系统环境变量,获取函数栈帧深度及内存管理区的保护级别,该系统环境变量在运行本内存监测装置及被监测软件之前设置,设置的内容包括需要记录的内存对应的函数栈帧深度及保护级别,保护级别分为不可读写、只读、可读写三种级别;初始化全局双向链表,用于将来记录内存地址。

一种动态监测堆内存使用错误的方法,其实现过程为:

一、首先将内存监测装置导入运行内存中,截获被监测软件的内存申请函数及内存释放函数的调用;

二、内存监测装置从堆中申请内存,进行二次封装并记录管理,然后将封装后的内存地址返回给被监测软件;

三、当被监测软件对该内存地址进行越界读写时,内存监测模块立刻让被监测软件中止运行并将错误信息输出给软件调试人员;当被监测软件正常退出时,内存监测装置自动将所有尚未释放的内存的详细信息显示输出给软件调试人员。

运行被监测软件时,将内存监测装置动态插入被监测软件的运行地址空间;内存监测装置通过动态符号表的重定位功能,拦截所有申请内存及释放内存相关的系统接口,将本监测装置内的内存分配及释放接口替换掉被监测软件的原内存申请及释放接口地址。

步骤二中,当被监测软件运行至内存进行分配调用时,内存监测装置截获该调用,得到申请内存的大小,然后通过本内存监测装置内部私有的内存申请接口,从堆内存中申请一段可容纳前置管理区、用户申请内存大小及后置管理区的内存,即这块内存分成三段:前置管理区、即将返回给被监测软件的用户内存区、后置管理区,其中前置管理区、后置管理区均包括不可读写、只读、可读写三种保护级别;前置管理区及后置管理区用于对内存区用户内存区进行二次封装,其中前置管理区位于用户内存区的起始地址的左侧,后置管理区位于用户内存区的结束地址的右侧;所述内存监测装置将申请该内存的函数的堆栈信息、用户内存区大小、魔数字存入前置管理区,然后将用户内存区的首地址插入一个全局的双向链表,在前置管理区中记录用户内存区在双向链表中的节点指针;最后将魔数字存储在后置管理区;内存监测装置将前置管理区及后置管理区设置为不可读写的权限;最后内存监测装置将内存地址用户内存区返回给被监测软件。

步骤二中,前置管理区、后置管理区存储信息的具体过程为:

将从堆中申请的这段内存的起始地址填入前置管理区的相应位置;

将固定的魔数字填入前置管理区和后置管理区的相应位置;

根据函数栈帧深度,申请一段内存,获取当前函数栈的相关信息,然后将这段内存的起始地址填入前置管理区的相应位置;

把即将返回给被监测软件的内存地址,加入全局的双向链表中,并将对应的链表节点指针存入前置管理区的相应位置;

设置前置管理区及后置管理区的内存访问权限;

将用户内存区的起始地址返回给被监测软件。

步骤三中,当内存监测装置拦截到释放内存的系统接口调用时,根据传入的内存地址,即用户内存区存储内容,得到相应的前置内存管理区地址,将前后管理区的内存访问权限改成可读写,然后从前置内存管理区地址中查找到记录用户内存区的双向链表的节点指针,再将用户内存区从双向链表中删除,最后释放该内存;

当被监测软件对用户内存区的访问发生越界时,触发系统信号,使得被监测软件中止运行,内存监测装置捕获到中止运行的信号,然后将当前的函数调用栈信息显示输出给软件调试人员;

当进程正常退出时,内存监测装置在析构函数中,将全局双向链表中的所有未释放内存的详细信息显示出来,软件调试人员便可以得知哪些地方发生了内存泄漏。

当被监测软件调用内存释放接口时,内存监测装置将截获其接口调用,得到被释放内存的起始地址,并对该内存地址做如下判断及操作:

step1,将该内存地址对应的前置管理区、后置管理区的内存访问权限修改为可读写模式;

step2,判断前置及后置管理区内的魔数字是否与默认的值一致,如果不一致,说明被监测软件传入的这段内存不是一个合法申请的内存地址,输出错误信息给软件调试人员;如果一致,则执行step3;

step3,判断前置管理区的链表节点指针是否为空,如果为空,说明这段内存已经被释放过了,被监测软件执行了错误的二次释放,输出错误信息给软件调试人员;如果不为空,则根据链表节点指针快速定位到双向链表中的节点,并将该节点从双向链表中删除,然后将前置管理区的链表节点指针置为空;

step4,得到前置管理区的起始地址,然后调用内存监测装置内的私有接口来释放这段内存。

当被监测软件对已申请的内存地址发生访问越界时,触发系统异常,进入内存监测装置内的异常处理函数,异常处理函数根据包括异常地址、栈顶地址的寄存器值,解析并输出显示当前的函数栈详细信息给软件调试人员,该详细信息包括文件名称、函数名称、调用行数;

当被监测软件正常退出时,内存监测装置的析构函数被调用,析构函数内部,遍历全局双向链表的所有节点,显示每个内存的详细信息,包括内存地址、内存大小;

根据前置管理区内的起始地址中的每一个返回地址,在动态符号表中解析出对应的文件名、函数名、所在行数。

本发明的一种动态监测堆内存使用错误的内存监测装置及方法和现有技术相比,具有以下有益效果:

本发明的一种动态监测堆内存使用错误的内存监测装置及方法,首先不用修改被监测软件的原二进制文件,也即不用重新编译被监测软件;其次在于可仅在需要的时候再将内存监测装置导入进程内;被监测的内存通过前后两个管理区信息,实现了内存错误的精准定位;前置管理区内的链表节点指针设计,巧妙的实现了用户内存地址在全局双向链表中的快速定位,避免了遍历链表带来的系统开销,从而不会对被监测软件的性能产生较大影响;实现了函数调用栈的详细记录;实现了对内存的读写越界的监测;体积精简,可移植性好;执行效率高,对被监测软件的运行效率的影响很小;定位精准,对内存问题的覆盖面广,实用性强,适用范围广泛,具有很好的推广应用价值。

附图说明

附图1为本发明实施例中方法的实现示意图。

具体实施方式

下面结合附图及具体实施例对本发明作进一步说明。

一种动态监测堆内存使用错误的内存监测装置,该内存监测装置导入运行在内存中,其结构包括,

内存申请接口及内存释放接口,用于对系统默认的内存申请、释放接口进行拦截;

构造函数接口,提供整个装置被载入内存后默认执行的构造函数,并监测构造函数参数的配置初始化;

内存管理模块,用于对内存进行二次封装、管理记录;

析构函数接口,提供进程退出时默认执行的析构函数,此接口内将所有已泄露内存的详细信息显示输出给用户;

异常处理接口,提供异常处理函数,捕获到内存非法访问的异常,并将异常指令处的详细信息输出给用户。

所述内存监测装置作为一个独立的文件,一般以动态库的形式存在,可用不同的编译工具链编译生成运行在不同平台上的版本,在用户需要用来监测某个软件的内存问题时,系统将本内存监测装置与被监测软件一并导入运行内存中,本内存监测装置会将被监测软件的内存申请及释放接口截获,本装置则利用其它方法从堆中申请足够大的内存,然后对这段内存进行封装处理,再返回给调用者(被监测软件);当被监测软件对这段内存进行非法处理时,本内存监测装置能立刻检测到并及时告知用户;当被监测软件退出时,本装置将打印输出所有已泄露内存的详细信息,帮助用户进行问题定位。

所述构造函数接口提供被调用的构造函数后,进行初始化工作,其过程为:解析动态符号表,获取系统原有的内存申请及释放接口指针,并当作本装置内的私有内存接口;解析系统环境变量,获取函数栈帧深度及内存管理区的保护级别,该系统环境变量在运行本内存监测装置及被监测软件之前设置,设置的内容包括需要记录的内存对应的函数栈帧深度及保护级别,保护级别分为不可读写、只读、可读写三种级别;初始化全局双向链表,用于将来记录内存地址。

一种动态监测堆内存使用错误的方法,其实现过程为:

针对堆内存heap使用错误问题,在不修改被监测软件的二进制文件的前提下向运行内存中插入监测装置;截获被监测软件的内存申请函数及内存释放函数的调用;

内存监测装置从堆中申请内存进行二次封装并记录管理,然后将封装后的内存地址返回给被监测软件;

当被监测软件对该内存地址进行越界读写时,内存监测模块立刻让被监测软件中止运行并将错误信息输出给软件调试人员;

当被监测软件正常退出时,内存监测装置自动将所有尚未释放的内存的详细信息显示输出给软件调试人员。

即:

在不修改被监测软件的原始二进制文件的前提下,运行被监测软件时,将内存监测装置动态插入被监测软件的运行地址空间;

内存监测装置利用动态符号表的重定位功能,拦截所有申请内存及释放内存相关的系统接口;

内存监测装置拦截到申请内存的系统接口调用之后,从堆中申请一段更大的内存,将这块内存分成三段:前置管理区pre_mm_zone、即将返回给调用者的用户内存区mem_for_user、后置管理区post_mm_zone,pre_mm_zone及post_mm_zone的用途是对内存区mem_for_user进行二次封装,其中pre_mm_zone位于mem_for_user的起始地址的左侧(更低地址处),post_mm_zone位于mem_for_user的结束地址的右侧(更高地址处);

内存监测装置将申请该内存的函数调用栈信息、用户内存区大小、魔数字等存入pre_mm_zone,然后将mem_for_user的首地址插入一个全局的双向链表,然后在pre_mm_zone中记录mem_for_user在双向链表中的节点指针;最后将魔数字存储post_mm_zone;

内存监测装置将前置管理区pre_mm_zone及后置管理区post_mm_zone设置为不可读写的权限;

内存监测装置将内存地址mem_for_user返回给调用者;

当内存监测装置拦截到释放内存的系统接口调用时,根据传入的内存地址(mem_for_user)计算得到相应的前置内存管理区地址,首先将前后管理区的内存访问权限改成可读写,然后从前置内存管理区地址中查找到记录mem_for_user的双向链表的节点指针,然后将mem_for_user从双向链表中删除,然后释放该内存;

当被监测软件对mem_for_user的访问发生越界时,比如试图读写pre_mm_zone或者post_mm_zone中的内容,由于这段内存没有读写权限,将触发系统信号,使得被监测软件中止运行,内存监测装置中能够及时捕获到中止运行的信号,然后将当前的函数调用栈信息显示输出给软件调试人员;

当进程正常退出时,内存监测装置会在析构函数中,将全局双向链表中的所有未释放内存的详细信息显示出来,软件调试人员便可以得知哪些地方发生了内存泄漏。

内存监测装置的关键参数可手工配置,包括函数栈的层数、当发生读越界时是否触发系统信号;当被监测软件退出时,可自动打印显示当前所有已泄漏内存的详细信息,包括调用者的函数栈信息,内存大小等;

内存监测装置在对即将返回给调用者的内存地址进行二次封装时,函数调用栈信息仅存储了返回地址,而没有存储所在文件名称、函数名称及所在行数,这保证了被监测软件的运行效率;当且仅当需要列举打印当前所有未释放内存的信息时,才将函数返回地址通过动态符号表来转换成文件名称+函数名称+所在行数的字符串,然后输出到终端。

结合附图1,本发明的具体执行步骤如下:

(1)运行本内存监测装置及被监测软件之前,通过系统环境变量设置如下参数:

需要记录的内存对应的函数栈帧深度,也即图1中的”framedepth”;

前置管理区pre_mm_zone及后置管理区post_mm_zone的保护级别(或者说访问权限),分为不可读写、只读、可读写三种级别。

(2)用户运行被监测软件,并通过系统命令将本装置插入被监测软件的运行内存中,本监测装置内的内存分配/释放接口将替换掉被监测软件的原内存申请/释放接口地址。

(3)本监测装置的构造函数被调用,进行初始化工作,包括:解析动态符号表,获取系统原有的内存申请/释放接口指针,当作本装置内的私有内存接口;解析系统环境变量,获取函数栈帧深度及内存管理区的保护级别;初始化全局双向链表,用于将来记录内存地址。

(4)当被监测软件运行至内存分配调用时,本内存监测装置将截获该调用,得到申请内存的大小,然后利用本装置内部私有的内存申请接口,从堆内存中申请一段足够容纳前置管理区、用户申请内存大小及后置管理区的内存。然后做如下操作:

step1,将从堆中申请的这段内存的起始地址填入前置管理区的相应位置,也即图1的ptr_real;

step2,将固定的魔数字填入前置管理区和后置管理区的相应位置,也即图1的magicnumber;

step3,根据函数栈帧深度,申请一段内存,获取当前函数栈的相关寄存器值,按照函数栈帧结构来循环解析得到每一层函数栈的返回地址,并依次填入这段内存(请见图1的address0~n),然后将这段内存的起始地址填入前置管理区的相应位置,也即图1的frameinfos;

step4,把即将返回给调用者的内存地址,也即图1的mem_for_user的起始地址,加入全局的双向链表中,并将对应的链表节点指针存入前置管理区的相应位置,也即图1的listnodeptr;

step5,设置前置管理区及后置管理区的内存访问权限;

step6,将mem_for_user的起始地址返回给调用者。

(5)当被监测软件调用内存释放接口时,本内存监测装置将截获其接口调用,得到被释放内存的起始地址,并对该内存地址做如下判断及操作:

step1,将该内存地址对应的前置管理区、后置管理区的内存访问权限修改为可读写模式;

step2,判断前置及后置管理区内的magicnumber是否与默认的值一致,如果不一致,说明被监测软件传入的这段内存不是一个合法申请的内存地址,输出错误信息给软件调试人员;如果一致,则执行step3;

step3,判断前置管理区的链表节点指针(也即图1的listnodeptr)是否为空,如果为空,说明这段内存已经被释放过了,被监测软件执行了错误的二次释放,输出错误信息给软件调试人员;如果不为空,则根据listnodeptr快速定位到双向链表中的节点,并将该节点从双向链表中删除,然后将前置管理区的listnodeptr置为空;

step4,得到前置管理区的起始地址,也即图1的ptr_real,然后调用本装置内的私有接口来释放这段内存。

(6)当被监测软件对已申请的内存地址发生访问越界时,例如访问到了前置管理区或者后置管理区,如果访问权限不允许,则会触发系统异常,进入本装置内的异常处理函数,异常处理函数内会根据相关寄存器如异常地址、栈顶地址等值,解析并输出显示当前的函数栈详细信息给软件调试人员,包括文件名称、函数名称、调用行数等。

(7)当被监测软件正常退出时,本内存监测装置的析构函数被调用,析构函数内部,会遍历全局双向链表的所有节点,显示每个内存的详细信息,包括:

内存地址,内存大小;

根据前置管理区内的frameinfos中的每一个返回地址address,在动态符号表中解析出对应的文件名,函数名,所在行数。

本方案不局限于某个芯片架构或某种操作系统,现在以基于arm架构的嵌入式linux系统为例,提供一种实施方案,具体实施步骤:

1)以c语言作为本发明装置的编程语言,将最终代码用arm的交叉编译工具编译生成动态库libmmonitor.so。

2)在libmmonitor.so内部,将至少实现如下函数:

__attribute__((constructor))voidmm_init(void)本函数是进程运行时的构造函数;

void*malloc(intsize)本函数将用来截获原系统库的malloc函数;

voidfree(void*p)本函数将用来截获原系统库的free函数;

__attribute__((destructor))voidmm_uninit(void)本函数是进程退出时的析构函数;

voidsig_handler(intsig,siginfo_t*info,void*secret)本函数作为异常处理函数,当被监测软件发生内存访问越界时,将进入本函数内。

3)对于前置管理区及后置管理区,可用结构体来定义,如下:

typedefstruct

{

intuser_size;

void*list_node_ptr;

unsignedlong*frame_infos;

intframe_depth;

void*ptr_real;

unsignedintmagic_number;

}premmzone_t;

typedefstruct

{

unsignedintmagic_number;

}postmmzone_t。

运行被监测软件及本装置之前,可设置环境变量如下:

exportmmonitor_frame_depth=8表示对每个内存地址,最多记录8层函数返回地址

exportmmonitor_mem_access=0表示将前置/后置管理区将设置成不可读写的权限,我们可假定0代表不可读写,1代表只读,2代表可读写;

5)利用ld_preload来加载本装置的动态库文件libmmonitor.so及被监测的软件;

6)在被监测软件的main函数入口执行之前,本装置的构造函数mm_init将被执行,在本函数内部,将做如下操作:

利用getenv函数解析环境变量,并将其保存到相关的全局变量中;

利用dlsym函数解析获得系统默认的malloc及free接口的符号指针,以实现本装置内部私有的内存函数_priv_malloc及_priv_free;

7)当被监测软件调用malloc(size)时,实际上调用到本装置内的malloc函数内,函数内有如下操作:

根据size值、结构体premmzone_t的大小、postmmzone_t的大小等,再考虑到地址对其问题,综合计算出实际要分配的内存大小,调用_priv_malloc来分配一段内存记为real_mem_ptr,并计算出即将返回给调用者的内存地址记为user_ptr;

分配一段内存并利用backtrace函数获取当前的栈帧的多层返回地址并存入这段内存,将这段内存的首地址存入结构体premmzone_t中;

将real_mem_ptr插入全局的双向链表,对应的链表节点指针记为curr_node;

将curr_node赋给结构体premmzone_t的成员list_node_ptr;

将前置管理区及后置管理区的结构体其它成员赋值;

利用mprotect函数对前置管理区、后置管理区的相关内存范围进行访问权限设置;

将user_ptr作为malloc的返回值返回;

8)当被监测软件调用free(user_ptr)时,实际上调用到了本装置的free函数内,函数内有如下操作:

step1,利用mprotect函数对user_ptr的前置管理区及后置管理区进行权限修改,改成可读写模式;

step2,判断结构体内的magic_number是否与默认值一致,如果不一致,说明被监测软件传入的这段内存不是一个合法申请的内存地址,输出错误信息给软件调试人员;

step3,判断premmzone_t的成员list_node_ptr是否为空,如果为空,说明该内存地址发生了二次释放,输出错误信息给软件调试人员;如果不为空,则根据list_node_ptr快速定位到双向链表中的节点,并将该节点从双向链表中删除,然后将前置管理区的list_node_ptr置为空;

step4,得到前置管理区的成员ptr_real,调用本装置内的私有接口_priv_free来释放该地址。

9)当被监测软件对已申请的内存地址发生访问越界时,例如访问到了前置管理区或者后置管理区,如果访问权限不允许,则会触发系统异常,进入本装置内的异常处理函数sig_handler,函数内会利用backtrace系列系统函数解析并输出显示当前的函数栈详细信息给软件调试人员,包括文件名称、函数名称、调用行数等。

10)当被监测软件正常退出时,本内存监测装置的析构函数mm_uninit被调用,mm_uninit内部会遍历全局双向链表的所有节点,显示每个内存的详细信息,包括:

内存地址,内存大小;

根据每个内存地址的前置管理区内的frame_infos指向的数组中的每一个返回地址,在动态符号表中解析出对应的文件名,函数名,所在行数,并显示输出给软件调试人员。

通过上面具体实施方式,所述技术领域的技术人员可容易的实现本发明。但是应当理解,本发明并不限于上述的具体实施方式。在公开的实施方式的基础上,所述技术领域的技术人员可任意组合不同的技术特征,从而实现不同的技术方案。

除说明书所述的技术特征外,均为本专业技术人员的已知技术。

当前第1页1 2 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1