首页> 中国专利> 一种内存统计方法、内存统计装置和计算机可读存储介质

一种内存统计方法、内存统计装置和计算机可读存储介质

摘要

本申请公开了一种内存统计方法、内存统计装置和计算机可读存储介质,获取目标函数的地址,目标函数包括预设动态库中的函数和/或可执行程序中的函数;基于目标函数的原型,构建自定义函数,其中,自定义函数调用目标函数;获取目标函数在全局偏移表中的位置,得到重定位地址;基于重定位地址,对全局偏移表进行重定位处理;基于自定义函数与重定位后的全局偏移表,得到目标函数的内存用量。通过上述方式,本申请能够同时统计可执行程序和动态链接库的内存分配情况。

著录项

  • 公开/公告号CN114968702A

    专利类型发明专利

  • 公开/公告日2022-08-30

    原文格式PDF

  • 申请/专利权人 浙江大华技术股份有限公司;

    申请/专利号CN202210442421.4

  • 发明设计人 钟军;钱小龙;

    申请日2022-04-25

  • 分类号G06F11/30(2006.01);

  • 代理机构深圳市威世博知识产权代理事务所(普通合伙) 44280;

  • 代理人刘桂兰

  • 地址 310051 浙江省杭州市滨江区滨安路1187号

  • 入库时间 2023-06-19 16:36:32

法律信息

  • 法律状态公告日

    法律状态信息

    法律状态

  • 2022-09-16

    实质审查的生效 IPC(主分类):G06F11/30 专利申请号:2022104424214 申请日:20220425

    实质审查的生效

说明书

技术领域

本申请涉及计算机技术领域,具体涉及一种内存统计方法、内存统计装置和计算机可读存储介质。

背景技术

在linux操作系统中开发应用程序时,经常要关注进程占用的虚拟内存以及物理内存的大小,资源占用太多、内存泄漏以及内存碎片都可能导致系统因内存不足而杀死用户进程,在排查这些问题时,如何准确统计系统内存资源消耗显得尤为重要。

发明内容

本申请提供一种内存统计方法、内存统计装置和计算机可读存储介质,能够同时统计可执行程序和动态链接库的内存分配情况。

为解决上述技术问题,本申请采用的技术方案是:提供一种内存统计方法,该方法包括:获取目标函数的地址,目标函数包括预设动态库中的函数和/或可执行程序中的函数;基于目标函数的原型,构建自定义函数,其中,自定义函数调用目标函数;获取目标函数在全局偏移表中的位置,得到重定位地址;基于重定位地址,对全局偏移表进行重定位处理;基于自定义函数与重定位后的全局偏移表,得到目标函数的内存用量。

为解决上述技术问题,本申请采用的另一技术方案是:提供一种内存统计装置,该内存统计装置包括互相连接的存储器和处理器,其中,存储器用于存储计算机程序,计算机程序在被处理器执行时,用于实现上述技术方案中的内存统计方法。

为解决上述技术问题,本申请采用的另一技术方案是:提供一种计算机可读存储介质,该计算机可读存储介质用于存储计算机程序,计算机程序在被处理器执行时,用于实现上述技术方案中的内存统计方法。

通过上述方案,本申请的有益效果是:本申请所提供的方案先获取预设动态库中的函数和/或可执行程序中的函数,得到目标函数并获取该目标函数的地址;然后利用目标函数的原型构建自定义函数,该自定义函数调用了目标函数;然后获取目标函数在全局偏移表中的位置,得到重定位地址;再利用重定位地址对全局偏移表进行重定位处理;然后利用自定义函数与重定位后的全局偏移表,得到目标函数的内存用量,本方案能够同时对可执行程序中的函数和动态库中的函数进行内存检测处理,实现内存用量的统计,适用性较为广泛。

附图说明

为了更清楚地说明本申请实施例中的技术方案,下面将对实施例描述中所需要使用的附图作简单地介绍,显而易见地,下面描述中的附图仅仅是本申请的一些实施例,对于本领域普通技术人员来讲,在不付出创造性劳动的前提下,还可以根据这些附图获得其他的附图。其中:

图1是本申请提供的内存统计方法一实施例的流程示意图;

图2是本申请提供的程序跳转示意图;

图3是本申请提供的内存统计方法另一实施例的流程示意图;

图4是本申请提供的重定位条目表、动态链接符号表、动态链接字符串表的对应示意图;

图5是本申请提供的统计内存用量的示意图;

图6是本申请提供的内存曲线图的示意图;

图7是本申请提供的内存统计装置一实施例的结构示意图;

图8是本申请提供的计算机可读存储介质一实施例的结构示意图。

具体实施方式

下面结合附图和实施例,对本申请作进一步的详细描述。特别指出的是,以下实施例仅用于说明本申请,但不对本申请的范围进行限定。同样的,以下实施例仅为本申请的部分实施例而非全部实施例,本领域普通技术人员在没有做出创造性劳动前提下所获得的所有其它实施例,都属于本申请保护的范围。

在本申请中提及“实施例”意味着,结合实施例描述的特定特征、结构或特性可以包含在本申请的至少一个实施例中。在说明书中的各个位置出现该短语并不一定均是指相同的实施例,也不是与其它实施例互斥的独立的或备选的实施例。本领域技术人员显式地和隐式地理解的是,本文所描述的实施例可以与其它实施例相结合。

需要说明的是,本申请中的术语“第一”、“第二”、“第三”仅用于描述目的,而不能理解为指示或暗示相对重要性或者隐含指明所指示的技术特征的数量。由此,限定有“第一”、“第二”、“第三”的特征可以明示或者隐含地包括至少一个该特征。本申请的描述中,“多个”的含义是至少两个,例如两个,三个等,除非另有明确具体的限定。此外,术语“包括”和“具有”以及它们任何变形,意图在于覆盖不排他的包含。例如包含了一系列步骤或单元的过程、方法、系统、产品或设备没有限定于已列出的步骤或单元,而是可选地还包括没有列出的步骤或单元,或可选地还包括对于这些过程、方法、产品或设备固有的其它步骤或单元。

在现有条件下,使用频率最高的当属top命令,该命令可以直观地打印众多进程的中央处理器(central processing unit,CPU)和内存资源消耗情况,在粗略查看资源消耗信息时比较有用,命令操作简单。

除top命令以外,如果开发人员还希望得到更多关于进程资源消耗的数据,还可以借助一些专用的工具,比如:sysstat工具集里中的sar命令或pidstat命令,通过采用这些工具可以统计出关于线程级别的内存消耗情况。

对于一些对linux系统有过深入理解的研发人员来说,还可以通过/proc路径下的文件来打印内核运行数据,例如:/proc/meminfo可以查看系统详细的内存消耗,其包括物理内存、虚拟内存或cache等等,信息量很大;/proc/xxx/status可以查看虚拟内存和物理内存的消耗值。

以上三种方法在排查内存泄漏时可实现快速诊断,如果需要进一步查看各个子模块(比如:动态库)的内存消耗数据则无法做到;而且,在软件工程中,各个领域的软件开发通常是由不同的团队完成的,提供各自的程序库文件(即二进制文件),最后通过编译工具链接为可执行程序;这些独立的库文件在需要内存时一般会从堆(heap)中申请内存,由于人为的原因,开发人员不一定会在内存使用结束后将其释放,从而造成内存泄漏。

针对内存泄漏这种问题,常用的内存工具有:virgrand和address sanatizer,virgrand会占用大量CPU资源,严重降低程序运行速度,在嵌入式设备上运行大型程序时,很难用virgrand排查内存泄漏点;address sanatizer虽然也会略微降低程序运行速度,但是对编译器的版本有一定的要求,另外,还需要程序能完整的退出,在嵌入式设备中,很多程序是随设备启动一直保持运行的,这种情况下就不能用address sanatizer排查内存泄漏问题了。相关技术中虽然也有一些方案能够解决内存泄露的问题,但是存在统计易出现遗漏、需要对待测试的程序进行源码修改或编译链接不能同时统计可执行程序和动态链接库的内存分配情况等问题。为了解决相关技术中的问题,本申请提供了一种新的方案,下面对本申请采用的技术方案进行详细描述。

请参阅图1,图1是本申请提供的内存统计方法一实施例的流程示意图,该方法包括:

S11:获取目标函数的地址。

目标函数包括预设动态库中的函数和/或可执行程序中的函数;具体地,以目标函数为预设动态库中的函数(记作动态库函数)为例,为了获取动态库函数的真实地址,可以先采用第一预设函数将预设动态库加载到内存中;然后采用第二预设函数获取目标函数的地址;例如,假设动态库函数为malloc函数,预设动态库为libdl.so库,第一预设函数为dlopen函数,第二预设函数为dlsym函数,在linux环境中,libdl.so库提供了dlopen函数,其用于手动加载动态库,比如:将libc.so加载到内存中,然后通过dlsym函数获取malloc函数的地址。

在一具体的实施例中,为了减少处理任务的数量,可执行过滤操作,即对所有加载到内存的动态库进行遍历,并在第四预设函数中使用预设字符串名称对所有函数进行过滤,得到目标函数。例如,第四预设函数为“dl_iterate_phdr”,在linux环境下,libdl.so库中提供了“dl_iterate_phdr”函数,其用于遍历所有已加载到内存的动态库;然后在回调函数中用预设字符串名称对不感兴趣的函数作过滤处理,得到感兴趣的函数(即目标函数)。

S12:基于目标函数的原型,构建自定义函数。

为了实现用户自定义的函数(记作自定义函数),可先获取与目标函数对应的原型(即设定的规则或格式),然后利用目标函数的原型,来构建自定义函数,并在自定义函数可调用目标函数;具体地,以目标函数为动态库函数为例,可以根据要打桩函数(即动态库函数)原型定义自定义函数,并在该自定义函数中调用动态库函数,动态库函数的地址在S11中已经得到。

S13:获取目标函数在全局偏移表中的位置,得到重定位地址。

先获取全局偏移表(global offset table,got),然后获取目标函数在全局偏移表中的位置(即重定位地址);具体地,全局偏移表包括多个重定位条目,当汇编器生成一个目标模块时,它并不知道数据和代码最终在内存中的具体存放位置,也不知道该目标模块引用的任何外部定义的函数或者全局变量的的位置,因此汇编器遇到对最终位置未知的目标引用,将生成一个重定位条目,告诉链接器在目标文件合并时再修改引用;当程序加载到内存运行时,它所依赖的动态库也会加载到内存,在外部符号被调用时会完成动态链接,修改这些目标引用为真实外部地址。

在一具体的实施例中,以目标函数为动态库函数为例,在获取重定位地址的过程中可采用动态库函数的的调用机制,下面对该调用机制进行介绍:

当一个程序被加载进内存时,动态链接器会把需要的动态库加载并绑定到该进程的地址空间中;随后在调用某个函数时,对该函数的地址进行解析,以达到调用该函数的目的。调用动态库中的函数与调用可执行程序的内部函数不同,动态库函数的地址在动态库加载前是未知的,所以可执行程序的代码段中并未指定真实的待调用函数的地址,而是采用了一种延迟绑定机制来实现,在函数调用时进行符号解析和地址绑定,具体来说涉及到两个表:过程连接表(procedure linkage table,plt)和全局偏移表,全局偏移表位于数据段,每一个表项存储的都是一个地址;plt表位于代码段,每一个表项表示与要绑定函数相关的若干指令,存储的是用于延迟绑定的代码,里面包含了jmp指令,用于跳转到对应全局偏移表项存放的地址。

例如,如图2所示的程序跳转示意图,假设在“a.out”程序中有个函数test调用了malloc函数,函数调用的过程如下所示:

在第一次调用时,全局偏移表的第二项存放的是plt表中指令的地址,跳转到该地址并执行后续的链接处理,链接完成后再跳转到真实的glibc库中执行malloc函数,跳转路径为1-2-3-4-5,简要描述为:

test-->malloc@plt-->dl_runtime_resolve-->malloc@glibc

在第二次调用时,先跳转到malloc@plt,再跳转到全局偏移表中的相应地址,由于已经链接过了,全局偏移表存放的是glibc库中malloc函数的地址,然后就可以执行malloc函数了,跳转路径为1-2-5,简要描述为:

test-->malloc@plt-->malloc@glibc

依据上述动态库函数的延迟绑定机制,在程序启动后可以修改全局偏移表中的值(即plt指令的跳转地址)为用户自定义的函数地址,例如:my_malloc函数,从而达到为malloc函数打桩的目的。

可以理解地,上述方法适用于可执行程序和动态库;为了便于表述,本实施例以malloc函数举例,但并不仅限于此,该方法适用于任意动态库中的外部函数调用。

S14:基于重定位地址,对全局偏移表进行重定位处理。

在获取到重定位地址后,利用该重定位地址对全局偏移表进行修改;例如,将全局偏移表中与重定位地址对应的值修改为用户自定义的自定义函数的地址,完成重定位。

S15:基于自定义函数与重定位后的全局偏移表,得到目标函数的内存用量。

在获取到经过重定位处理后的全局偏移表后,通过对该全局偏移表与自定义函数进行处理,生成与目标函数对应的内存用量,实现内存的自动检测。

本实施例提供了一种基于动态链接的模块内存统计方法,能够应用于linux内存管理领域,涉及动态链接、内存管理、重定位以及延迟绑定技术;本方案能够同时对可执行程序和动态库进行打桩,从而实现进程中的各个模块的内存消耗的完整统计;另外,本方案中的待重定位的模块包括可执行文件和动态库,它们是逐个进行的,通过名称比对可过滤掉不关心的模块,从而有针对性地统计某些模块的内存数据,节省资源,且提升了统计效率。

请参阅图3,图3是本申请提供的内存统计方法另一实施例的流程示意图,该方法包括:

S31:获取目标函数的地址。

S32:基于目标函数的原型,构建自定义函数。

S31-S32与上述实施例中S11-S12相同,在此不再赘述。

S33:基于重定位条目表、动态链接符号表以及动态链接字符串表,生成重定位地址;基于重定位地址,对全局偏移表进行重定位处理。

动态链接需要用到可执行程序中的三个表:重定位条目表、动态链接符号表、动态链接字符串表,因此先获取重定位条目表、动态链接符号表以及动态链接字符串表;然后,从重定位条目表中选取一条重定位信息,得到当前重定位信息;将当前重定位信息与动态链接符号表进行匹配,得到符号名称;将符号名称与动态链接字符串表进行匹配,得到当前字符串;基于当前字符串,生成重定位地址。具体地,可判断当前字符串是否与目标函数的名称是否相同;若当前字符串是否与目标函数的名称相同,则对当前字符串所在的位置进行处理,得到重定位地址;若当前字符串是否与目标函数的名称不同,则返回从重定位条目表中选取一条重定位信息的步骤,直至对重定位条目表遍历完毕或当前字符串与目标函数的名称相同。

进一步地,获取重定位条目表中与当前字符串所在的位置对应的重定位信息的位置,得到偏移地址;将预设动态库中的基址与偏移地址叠加,得到重定位地址,预设动态库的基址为预设动态库的首地址。

在一实施方式中,以动态库函数为malloc函数为例,使用上述三个表(即重定位条目表、动态链接符号表以及动态链接字符串表)就可以找到malloc函数在全局偏移表中的对应位置,依据延迟绑定机制,修改全局偏移表中的值为自定义函数的地址,完成重定位。具体地,三个表之间的关系如图4所示,重定位条目表、动态链接符号表以及动态链接字符串表分别记作“.rela.dyn”、“.dynsym”以及“.dynstr”,重定位malloc函数的过程如下所示:

对“.rela.dyn”表进行遍历,通过“r_info”字段获取“.dynsym”表,通过“.dynsym”表的“st_name”字段在“.dynstr”表中获取字符串,如果该字符串与malloc相同,则成功找到了相匹配的重定位表项(即重定位信息),结束对“.rela.dyn”的遍历,然后使用该重定位表项计算重定位地址,计算方式如下所示:

重定位地址=动态库的基址+重定位表项中的偏移地址

然后,将自定义函数的地址写入该重定位地址,完成重定位。

在一实施方式中,还通过执行程序地址空间映射来获取动态库的名称,即基于预设动态库的地址与预设地址映射表,生成预设动态库的名称,具体地,对预设文件进行读取,得到预设地址映射表,该预设地址映射表包括多个对应关系;从预设地址映射表中过滤出预设动态库的对应关系,得到目标对应关系;基于目标对应关系,得到预设动态库的名称,从而找到目标函数到底属于哪一个动态库。

例如,在linux环境下,通过读“/proc/${PID}/maps”文件可以得到程序的预设地址映射表,然后从预设地址映射表中过滤出预设动态库的地址映射,基于该地址映射可以得到预设动态库的基址,比如,如下所示:

7b9e128000-7b9e12e000r--p00000000fd:00 3397stem/lib64/liblog.so

可以理解地,在获取到重定位后的全局偏移表后,可以利用该全局偏移表对自定义函数进行重定位。

S34:在自定义函数中调用第三预设函数,以获取目标函数的调用者的地址;在自定义函数中调用目标函数,以获取目标函数的内存用量。

在自定义函数中调用第三预设函数,自定义函数用于对目标函数进行栈回溯。例如,在自定义的my_malloc函数中,先进行栈回溯,在代码里调用内置函数“__builtin_return_address”,可获取调用者(即my_malloc函数中调用其他函数的主体)的地址,然后再调用真实的malloc函数。

S35:基于调用者的地址与目标函数的内存用量,生成内存统计文件。

可将目标函数的调用者的地址与所有动态库的地址进行匹配,获取调用者所属的动态库,得到目标动态库,即检查调用者的地址落在了哪个动态库的地址范围内;将目标函数的内存用量(即内存分配大小)添加至目标动态库对应的信息中,得到内存统计文件,比如:通过定时触发或者手动触发的方式,打印这些内存用量的信息到目标动态库对应的文件中,生成内存统计文件。

进一步地,以预设时间间隔获取目标函数的内存用量,得到内存统计数据,内存统计数据包括多个内存用量;基于内存统计数据,生成内存曲线图,内存曲线图包括多个采集次数以及与采集次数对应的内存用量;基于内存曲线图,判断是否出现内存泄露事件;若识别到出现内存泄露事件,则生成提醒消息,提醒信息用于表示当前出现内存泄露事件。具体地,可以计算内存曲线图中相邻两个采集次数对应的内存用量的差值;判断差值是否大于预设值;若该差值大于预设值,则确定出现内存泄露事件;若该差值小于或等于预设值,则确定未出现内存泄露事件。

在一实施方式中,可以定时输出内存统计数据到某一文件,通过脚本提取这些数据并绘制出折线图,如图6所示,可以看到各个模块的内存变化趋势,其中,纵坐标为字节数,横坐标为采集次数;在实际应用中,可以使用python脚本绘制“libstdc++.so.6.0.24”库中的内存用量。

可以理解地,在执行内存释放时,可以调用自定义的free函数,然后用待释放的地址查询到内存分配时记录的相关信息,按照相关信息适应性删除即可。

本实施例通过栈回溯的方式标记每一处内存分配,经数据分析可预测具体是哪一行代码存在内存泄漏,检测内存泄露的准确率较高,防止出现内存泄露的现象;另外,本实施例所提供的装置(即一个可链接的库文件)直接链接在库文件中即可生效,通过环境变量来配置功能,无需修改被测对象的源码,不会造成任何破坏,简单实现且安全性较高;而且,重定位过程全部发生在本装置中,无需被测对象干预,重定位时修改的是被测对象的全局偏移表,由于该操作也是在运行过程中完成的,所以开发人员无需修改源码,简化开发人员的开发、使用难度;此外,由于可执行程序和动态库中都有重定位信息,依据重定位信息可以对外部函数malloc和free进行重定位,扩大了使用范围。

请参阅图7,图7是本申请提供的内存统计装置一实施例的结构示意图,内存统计装置70包括互相连接的存储器71和处理器72,存储器71用于存储计算机程序,计算机程序在被处理器72执行时,用于实现上述实施例中的内存统计方法,内存统计装置70可以为服务器或嵌入式设备等。

本实施例可用于统计闭源模块的内存大小;而且,本实施例所采用的方案依据动态链接的原理,可过滤被测模块,重点监测某个或多个被测模块的内存变化情况,针对性较强,减轻处理压力;此外,还可以依据动态链接的原理,对被测模块中的分配器函数进行重定位,无需修改被测模块源码,可跨平台使用。

请参阅图8,图8是本申请提供的计算机可读存储介质一实施例的结构示意图,计算机可读存储介质80用于存储计算机程序81,计算机程序81在被处理器执行时,用于实现上述实施例中的内存统计方法。

计算机可读存储介质80可以是服务端、U盘、移动硬盘、只读存储器(ROM,Read-Only Memory)、随机存取存储器(RAM,Random Access Memory)、磁碟或者光盘等各种可以存储程序代码的介质。

在本申请所提供的几个实施方式中,应该理解到,所揭露的方法以及设备,可以通过其它的方式实现。例如,以上所描述的设备实施方式仅仅是示意性的,例如,模块或单元的划分,仅仅为一种逻辑功能划分,实际实现时可以有另外的划分方式,例如多个单元或组件可以结合或者可以集成到另一个系统,或一些特征可以忽略,或不执行。

作为分离部件说明的单元可以是或者也可以不是物理上分开的,作为单元显示的部件可以是或者也可以不是物理单元,即可以位于一个地方,或者也可以分布到多个网络单元上。可以根据实际的需要选择其中的部分或者全部单元来实现本实施方式方案的目的。

另外,在本申请各个实施方式中的各功能单元可以集成在一个处理单元中,也可以是各个单元单独物理存在,也可以两个或两个以上单元集成在一个单元中。上述集成的单元既可以采用硬件的形式实现,也可以采用软件功能单元的形式实现。

以上所述仅为本申请的实施例,并非因此限制本申请的专利范围,凡是利用本申请说明书及附图内容所作的等效结构或等效流程变换,或直接或间接运用在其他相关的技术领域,均同理包括在本申请的专利保护范围内。

去获取专利,查看全文>

相似文献

  • 专利
  • 中文文献
  • 外文文献
获取专利

客服邮箱:kefu@zhangqiaokeyan.com

京公网安备:11010802029741号 ICP备案号:京ICP备15016152号-6 六维联合信息科技 (北京) 有限公司©版权所有
  • 客服微信

  • 服务号