本发明涉及一种基于Linux系统的内存池的调试方法,可用于调试内存泄漏,内存double free(重复释放)等内存使用不当相关的bug(错误)。
背景技术:
在大型程序中,由于代码量很大和逻辑复杂,如果动态申请的内存是直接使用系统接口,那么出现了内存泄漏或者double free(重复释放)会非常难于排查,通常排查此类问题的方法包括:
1.阅读代码,效率低,方法不系统,依赖于解决bug的人的能力和灵感;
2.在代码中增加log(打印),需要重新复现问题,效率低。
3.使用valgrind等类似的开源工具,需要重新编译和复现问题,不能通过出现问题的现场直接定位问题。
这些方法效率都很低,且不能通过出现问题的场景直接定位问题,需要重新复现问题才能排查,不适合在商业产品中使用。
技术实现要素:
针对现有技术缺陷,本发明提供了一种新的用于linux环境中使用内存池来快速定位内存使用不当的bug的方法。
本发明的技术方案提出一种用于linux环境中的内存池调试方法,用内存池来管理动态申请的内存,在内存块的管理结构中存储debug信息,实现方式如下,
在程序启动时,将进程的虚拟地址映射表输出到一个文件里保存,用于函数调用栈地址和代码位置的转换;
之后创建和初始化内存池,内存池里的内存按业务需求分成不同大小的内存块,每个内存块都有一个管理结构用于内存池对内存块进行管理,管理结构对用户不可见;内存块的管理结构内有链表结构,用于将同类型的同状态内存块连接起来,大小相同的状态相同的内存块在一个链表中;
申请内存时,从内存池中取相应大小的内存块返回给用户,并在内存块的管理结构中存储申请时的函数调用栈地址;释放内存时,将要释放的内存块返回给内存池,并在内存块的管理结构中存储释放时的函数调用栈地址;
出现问题时,将存储的函数调用栈地址转换成代码位置,支持直接定位bug出现的代码位置。
而且,内存块的管理结构内有标记表明此内存块是inuse或free的状态,如果对一个free的内存块进行释放操作,就是double free状态;inuse表示被申请状态,free表示空闲未被申请的状态,double free表示重复释放状态。
而且,申请内存时是从对应大小的空闲内存块链表free list里取一个内存块返回给用户使用,同时放置到对应大小的被分配内存块链表inuse list中。
而且,内存池里的内存块被创建出来后,不能被销毁,内存的申请和释放只是将内存块在内存池的不同链表间转换,支持保留内存块管理结构内存储的debug信息。
而且,记录函数调用栈地址是调用__builtin_return_address(n),此调用返回一个int的整数,作为栈地址存储到内存块的管理结构中;其中n表示栈的层数,调用__builtin_return_address(n)表示返回第n层的栈地址。
而且,将函数调用栈地址还原为代码位置是使用linux的工具addr2line和之前记录的虚拟地址映射表。
本发明所提供方法可以直接检测出double free的内存申请和2次释放的代码位置,精确定位问题;对于内存泄漏,遍历内存块的inuse list,计算出每个内存块的申请的代码位置,结合代码可以直接查出内存泄漏点。该方法能在linux系统中实现快速定位double free和内存泄漏的bug。
本发明对比已有技术有以下创新点:
1、使用内存池来管理内存,在内存块中标记状态,以及将状态相同的同级别的内存块放到同一个链表中,可以快速的定位double free和内存泄漏的bug。
2、进一步地,在申请和释放内存时通过linux系统调用__builtin_return_address获取函数栈地址并存储到内存块中,出现问题时再通过addr2line命令将存储的函数栈地址转换成代码行数,可以直接定位bug出现的代码位置。
本发明技术方案将在大型linux服务程序中发挥重要作用,具有重大的市场价值。
附图说明
图1为本发明实施例的流程图。
具体实施方式
以下结合附图和实施例详细说明本发明技术方案,提供了对该实施例的全面理解的详细细节。
本发明提出一种新的用于linux环境中使用内存池来快速定位内存使用不当的bug的方法,包括利用内存池技术和linux技术:
使用内存池来管理动态申请的内存。内存池是指预先申请大量的内存,分割成一定大小的内存块,然后将这些内存块用数据结构管理起来;应用程序申请内存时,从内存池中取合适大小的内存块返回给应用程序;应用程序释放内存时,将释放的内存块归还到内存池内。内存池中的内存块并不真正的释放,只是标记成free(未使用),这样可以在内存块里存储一些debug(调试)信息,方便出现bug后定位问题。
Linux系统可以将当前运行的函数调用栈的代码位置和一个int(整数)型的值相互转换。通过调用__builtin_return_address(系统调用)可以将函数调用栈的当前代码位置转换成一个整数,__builtin_return_address(n)表示将第n层栈的代码位置转换成整数;通过addr2line程序可以将一个整数转换成代码位置。
本发明基于内存池技术和linux技术的特点,实现支持快速定位内存使用不当的bug的调试方法,主要改进为:用内存池来管理动态申请的内存,在内存块的管理结构中存储debug信息。在程序启动时将进程的虚拟地址映射表输出到一个文件里保存,用于函数调用栈地址和代码位置(行数)的转换,进程的虚拟地址映射表是/proc/pid/maps(pid是进程id)。之后创建和初始化内存池,内存池里的内存按业务需求分成不同大小的内存块,每个内存块都有一个管理结构用于内存池对内存块进行管理,管理结构对用户不可见。之后申请内存时从内存池中取相应大小的内存块返回给用户,并在内存块的管理结构中存储申请时的函数调用栈地址;释放内存时将要释放的内存块返回给内存池,并在内存块的管理结构中存储释放时的函数调用栈地址。
进一步地,内存块的管理结构内有标记表明此内存块是inuse(被申请)或free(空闲,未被申请)的状态,这样如果对一个free的内存块进行释放操作,就是double free(重复释放)。
进一步地,内存块的管理结构内有链表结构,用于将同类型的同状态内存块连接起来。大小相同的状态相同的内存块在一个链表中,申请内存时是从对应大小的free list(空闲内存块链表)里取一个内存块返回给用户使用,同时放置到对应大小的inuse list(被分配内存块链表)中。当某级别的内存块free list为空时,就有可能发生了内存泄漏。
进一步地,内存池里的内存块被创建出来后,不能被销毁。内存的申请和释放只是将内存块在内存池的不同链表间转换,这样内存块管理结构内存储的debug信息才可以保留下来。
记录函数调用栈地址是调用__builtin_return_address(n),此调用返回一个int的整数,作为栈地址存储到内存块的管理结构中。n表示栈的层数,调用__builtin_return_address(n)表示返回第n层的栈地址。
将函数调用栈地址还原为代码位置(行数)是使用linux的工具addr2line和之前记录的虚拟地址映射表。虚拟地址映射表记录程序的虚拟地址空间,其中有so文件和可执行文件的地址范围。首先,根据栈地址在内存地址映射表里确定其所对应的文件,然后算出栈地址在文件里的偏移,之后调用命令"addr2line-e文件名地址偏移"(addr2line是系统命令)即可换算出栈地址对应的代码行数。
参见图1,本发明实施例可以分为3个阶段,程序启动阶段,运行阶段和出现问题后的调试阶段。
在启动阶段,需要保存虚拟地址映射表,初始化内存池。
运行阶段,内存申请,释放都要记录对应的函数调用栈地址,同时检测是否出现bug,在double free时终止程序;在内存池耗尽时给出报警。
调试阶段,根据记录的栈地址计算出对应的代码位置,精确定位问题。
为了便于实施参考起见,提供实施例中各操作具体说明如下:
启动阶段
1.将程序的虚拟地址映射表保存到一个文件里。包括下列子步骤,
步骤1.1,调用系统函数getpid()获取进程的pid(进程id);
步骤1.2,进程pid是一个整型数字,假定进程pid是整数xxxx,进程的虚拟地址映射表就是/proc/xxxx/maps,通过调用命令cat/proc/xxxx/maps>文件名,将虚拟地址映射表保存到指定的文件内。
2.初始化内存池。包括下列子步骤,
步骤2.1,向系统申请一大块连续的内存,实施例申请256M内存用来初始化内存池;
步骤2.2,根据业务实际需求,将申请到的大块内存分割成大小不等的小内存块,例如可以将256M的内存分成55000个32字节内存块、25000个64字节内存块、25000个128字节的内存块、30000个512字节内存块和80000个1024字节内存块;每个内存块都有个头部做为内存块的管理结构,管理结构中至少有下列字段:
标记内存状态的flag(标记);
内存块的size(大小);
链表指针;
存储申请内存时函数调用栈地址的变量;
存储释放内存时函数调用栈地址的变量;
步骤2.3,对每一个内存块:初始化管理结构,标记为free,将内存块加入到对应大小的free list里。
运行阶段
1.程序运行过程中,向内存池申请的内存按下列步骤处理:
步骤3.1,根据要申请的内存size决定返回的内存块的size。内存块的size是内存池里比要申请的内存size大的最小size;
步骤3.2,检查内存池里对应size的内存块的free list,如果为空,说明此级别的内存块耗尽,可能出现了内存泄漏,告警,然后返回NULL给申请者;如果不为空,从中取一个内存块,从free list里移出;
步骤3.3,调用__builtin_return_address(n)获取函数调用栈地址,n为栈的层数,记录的栈层数越多,越利于调试。实际记录的栈层数要平衡空间和调试需要,假定记录2层,则是将__builtin_return_address(0)和__builtin_return_address(1)存储到内存块的申请时函数调用栈地址变量内;
步骤3.4,标记内存块状态为inuse状态,将内存块放置到对应大小级别的inuse list里,返回内存块给申请者。
2.释放内存,按下列步骤处理:
步骤4.1,检查要释放的内存块的flag,如果是空闲状态,则说明发生了double free,需要终止程序,可以通过assert(0)让程序core出来,留下问题现场;
步骤4.2,将内存块从inuse list中移除;
步骤4.3,获取此时函数调用栈地址,和步骤3.3的方法一样,存储到内存块的释放时函数调用栈地址变量内;
步骤4.4,标记内存块状态为free,将内存块放置到对应大小级别的free list中。
调试阶段
1.首先确定出现问题的内存块。对于double free,就是正在double free的内存块;对于内存泄漏,遍历内存耗尽的内存块级别的inuse list,检查内存块的申请时的函数栈地址,若其中有函数栈地址大量重复,就可能是内存泄漏点。
2.将内存块中的函数栈地址转换为代码位置,按下列步骤处理:
步骤5.1,在程序的虚拟地址映射表文件中,查找函数栈地址对应的文件。虚拟地址映射表里有程序启动加载的二进制文件的地址范围,函数栈地址所在的地址范围对应的文件就是此函数栈地址对应的代码所在的文件。
步骤5.2,用函数栈地址减去文件的首地址得到函数栈地址在文件里地址偏移。
步骤5.3,执行addrline-e文件名地址偏移将函数栈地址转换为代码位置。
通过上述操作我们可以快速的发现double free和内存泄漏问题,并直接精确定位出现问题的代码位置。
具体实施时,本发明所提供方法可采用软件技术实现自动运行。
本文中所描述的具体实施例仅仅是对本发明精神作举例说明。本发明所属技术领域的技术人员可以对所描述的具体实施例做各种各样的修改或补充或采用类似的方式替代,但并不会偏离本发明的精神或者超越所附权利要求书所定义的范围。