kmemleak工具

news/2023/6/7 23:40:50

kmemleak工具

  • 前言
  • KMEMLEAK原理
  • 开启KMEMLEAK功能
  • 测试用例
    • kmemleak-test.c
    • Makefile
    • 运行结果

前言

内存泄漏分为用户态的内存泄漏和内核态的内存泄漏,我们本文主要关注的是内核态的内存泄漏。工作中比较常见的内存泄漏按照发生泄漏的频率可以划分以下几种类型:
1、一次性内存泄漏,只在初始化过程中或某一次条件触发产生的内存泄漏。
2、偶发性内存泄漏,在某种条件下偶尔触发产生的内存泄漏。
3、频发性内存泄漏,内存泄漏点被频繁的触发。

对于频发性内存泄漏我们有比较多的调试手段去定位,比如我们可以先通过/proc/meminfo信息大致确定下内存泄漏发生在哪个模块中,再通过其他手段进一步定位。如果观察到vmalloc异常,可以通过/proc/vmallocinfo信息分析定位。如果观察到slab内存异常,可以通过slabinfo和/sys/kernel/slab/*/alloc_calls或free_calls去辅助定位问题。而对于一次性的或者偶发性的内存泄漏确很难去通过/proc/meminfo信息快速分析定位,且大量的一次性或偶发性内存泄漏,同样给系统造成额外的内存压力。而本文介绍的kmemleak工具为各种类型的内存泄漏提供了一种检测方法。

KMEMLEAK原理

kmemleak(kernel memory leak detector)是检测内核空间的内存泄漏的调试工具。检测对象是memblock_alloc、kmalloc、vmalloc、kmem_cache_alloc等函数分配的内存块,该内存块由struct kmemleak_object来描述(简称为object)。kmemleak的实现原理非常简单,通过暴力扫描内存(假定内存中存放的都是指针,以ARM64为例,每次扫描8个字节),如果找不到指向起始地址或者内存块任何位置的指针,则分配的内存块被认为是孤立的。这意味着内核可能无法将分配内存块的地址传递给释放函数,因此该内存块被视为内存泄漏。内存块(object)有3种颜色,分别为黑色、白色、灰色, 通过count和min_count区分不同颜色的object。

  • 黑色: min_count = -1,表示被忽略的object,此object不包含对别人的引用,也不会存在内存泄漏,比如代码段会标记为黑色。

  • 白色: count < min_count,孤立的object,没有足够的引用指向这个object,一轮扫描结束后被认为泄漏的内存块。

  • 灰色: min_count = 0,表示不是孤立的object,即不存在内存泄漏的object,如代码中主动标记object为灰色,防止误报(如data、bss、ro_after_init)。或者count >= min_count,对该object有足够的指针引用,认为不存在内存泄漏的内存块。

具体检测步骤如下:

1、通过struct kmemleak_object(简称为object)描述kmalloc、vmalloc、kmem_cache_alloc等函数申请的内存块,记录申请内存的起始地址,大小、call trace等信息。同时把object加入到红黑树object_tree_root和双向链表object_list中,红黑树中的key值为内存块的起始地址。

2、遍历双向链表object_list,把所有的object的count计数清0,即在新的一轮扫描前,尽可能的把能复位成白色的object标记为白色。然后判断object是否是灰色(默认data、bss、ro_after_init段会被标记为灰色),如果是灰色的object则把object加入到灰色链表gray_list中。

3、扫描内存中可能存放指针的内存区域(per-cpu段、struct page的内容、内核栈、灰色链表),根据挂在红黑树中所有的object的地址范围进行对比。如果有指针指向某一个object(指向该object的起始地址或者指向object地址范围内),会把object对应的count字段增加1,如果object变成灰色,则会把object加入到灰色链表中。

4、扫描object_list中的白色对象的object,判断object所描述的地址范围的内容的crc值是否发生变化,如果发生变化,则同样把object加入到灰色链表gray_list中。说明通过间接的方式访问了object描述的地址范围,不是内存泄漏,减少误报。

5、重新扫描灰色链表,因为步骤4中,可能有些白色的object加入到了灰色链表中,需要重新扫描。

6、经过上述一系列的扫描,剩余白色的object就是可疑的内存泄漏点。

开启KMEMLEAK功能

如果定义了CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF宏或者通过cmdline设置为kmemleak=off,则默认关闭kmemleak。如果cmdline中设置kmemleak=on,则表示默认开启kmemleak功能。如果没有定义CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF宏,则默认是开启kmemleak功能

9155 CONFIG_HAVE_DEBUG_KMEMLEAK=y
9156 CONFIG_DEBUG_KMEMLEAK=y
9157 CONFIG_DEBUG_KMEMLEAK_MEM_POOL_SIZE=16000
9158 # CONFIG_DEBUG_KMEMLEAK_TEST is not set
9159 # CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF is not set
9160 # CONFIG_DEBUG_KMEMLEAK_AUTO_SCAN is not set

测试用例

kmemleak-test.c

// SPDX-License-Identifier: GPL-2.0-only
/** samples/kmemleak/kmemleak-test.c** Copyright (C) 2008 ARM Limited* Written by Catalin Marinas <catalin.marinas@arm.com>*/#define pr_fmt(fmt) "kmemleak: " fmt#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/list.h>
#include <linux/percpu.h>
#include <linux/fdtable.h>#include <linux/kmemleak.h>struct test_node {long header[25];struct list_head list;long footer[25];
};static LIST_HEAD(test_list);
static DEFINE_PER_CPU(void *, kmemleak_test_pointer);/** Some very simple testing. This function needs to be extended for* proper testing.*/
static int __init kmemleak_test_init(void)
{struct test_node *elem;int i;pr_info("Kmemleak testing\n");/* make some orphan objects */pr_info("kmalloc(32) = %p\n", kmalloc(32, GFP_KERNEL));pr_info("kmalloc(32) = %p\n", kmalloc(32, GFP_KERNEL));pr_info("kmalloc(1024) = %p\n", kmalloc(1024, GFP_KERNEL));pr_info("kmalloc(1024) = %p\n", kmalloc(1024, GFP_KERNEL));pr_info("kmalloc(2048) = %p\n", kmalloc(2048, GFP_KERNEL));pr_info("kmalloc(2048) = %p\n", kmalloc(2048, GFP_KERNEL));pr_info("kmalloc(4096) = %p\n", kmalloc(4096, GFP_KERNEL));pr_info("kmalloc(4096) = %p\n", kmalloc(4096, GFP_KERNEL));
#ifndef CONFIG_MODULESpr_info("kmem_cache_alloc(files_cachep) = %p\n",kmem_cache_alloc(files_cachep, GFP_KERNEL));pr_info("kmem_cache_alloc(files_cachep) = %p\n",kmem_cache_alloc(files_cachep, GFP_KERNEL));
#endifpr_info("vmalloc(64) = %p\n", vmalloc(64));pr_info("vmalloc(64) = %p\n", vmalloc(64));pr_info("vmalloc(64) = %p\n", vmalloc(64));pr_info("vmalloc(64) = %p\n", vmalloc(64));pr_info("vmalloc(64) = %p\n", vmalloc(64));/** Add elements to a list. They should only appear as orphan* after the module is removed.*/for (i = 0; i < 10; i++) {elem = kzalloc(sizeof(*elem), GFP_KERNEL);pr_info("kzalloc(sizeof(*elem)) = %p\n", elem);if (!elem)return -ENOMEM;INIT_LIST_HEAD(&elem->list);list_add_tail(&elem->list, &test_list);}for_each_possible_cpu(i) {per_cpu(kmemleak_test_pointer, i) = kmalloc(129, GFP_KERNEL);pr_info("kmalloc(129) = %p\n",per_cpu(kmemleak_test_pointer, i));}return 0;
}
module_init(kmemleak_test_init);static void __exit kmemleak_test_exit(void)
{struct test_node *elem, *tmp;/** Remove the list elements without actually freeing the* memory.*/list_for_each_entry_safe(elem, tmp, &test_list, list)list_del(&elem->list);
}
module_exit(kmemleak_test_exit);MODULE_LICENSE("GPL");

Makefile

# SPDX-License-Identifier: GPL-2.0-only
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-KERNEL_DIR ?=~/linux_rt/linux-rt-5.15/
obj-m := kmemleak-test.omodules:$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modulesclean:$(MAKE) -C $(KERNEL_DIR) M=$(PWD) cleaninstall:cp *.ko $(KERNEL_DIR)/kmodules

运行结果

加载模块,触发内存泄漏检测:

insmod kmemleak-test.ko
echo scan > /sys/kernel/debug/kmemleak

查看结果如下:

[root@liebao kmemleak]# cat /sys/kernel/debug/kmemleak
unreferenced object 0xffff000006d3a400 (size 1024):comm "insmod", pid 1040, jiffies 4295348039 (age 1046.512s)hex dump (first 32 bytes):00 e0 b2 45 00 00 00 00 17 00 00 00 01 00 01 00  ...E............00 90 b2 45 00 00 00 00 0b 00 00 00 03 00 02 00  ...E............backtrace:[<(____ptrval____)>] slab_post_alloc_hook+0x7c/0x260[<(____ptrval____)>] kmem_cache_alloc_trace+0x188/0x358[<(____ptrval____)>] 0xffff8000010950a0[<(____ptrval____)>] do_one_initcall+0x50/0x2b0[<(____ptrval____)>] do_init_module+0x50/0x1e8[<(____ptrval____)>] load_module+0x2184/0x2708[<(____ptrval____)>] __do_sys_init_module+0x1e8/0x228[<(____ptrval____)>] __arm64_sys_init_module+0x24/0x30[<(____ptrval____)>] invoke_syscall+0x54/0x118[<(____ptrval____)>] el0_svc_common.constprop.3+0x90/0x118[<(____ptrval____)>] do_el0_svc+0x34/0xa0[<(____ptrval____)>] el0_svc+0x20/0x60[<(____ptrval____)>] el0t_64_sync_handler+0x88/0xb0[<(____ptrval____)>] el0t_64_sync+0x16c/0x170
unreferenced object 0xffff000006d3a800 (size 1024):comm "insmod", pid 1040, jiffies 4295348039 (age 1046.512s)hex dump (first 32 bytes):00 00 e0 48 00 00 00 00 0b 00 00 00 01 00 01 00  ...H............00 00 e6 48 00 00 00 00 00 10 00 00 03 00 02 00  ...H............backtrace:[<(____ptrval____)>] slab_post_alloc_hook+0x7c/0x260[<(____ptrval____)>] kmem_cache_alloc_trace+0x188/0x358[<(____ptrval____)>] 0xffff8000010950bc[<(____ptrval____)>] do_one_initcall+0x50/0x2b0[<(____ptrval____)>] do_init_module+0x50/0x1e8[<(____ptrval____)>] load_module+0x2184/0x2708[<(____ptrval____)>] __do_sys_init_module+0x1e8/0x228[<(____ptrval____)>] __arm64_sys_init_module+0x24/0x30[<(____ptrval____)>] invoke_syscall+0x54/0x118[<(____ptrval____)>] el0_svc_common.constprop.3+0x90/0x118[<(____ptrval____)>] do_el0_svc+0x34/0xa0[<(____ptrval____)>] el0_svc+0x20/0x60[<(____ptrval____)>] el0t_64_sync_handler+0x88/0xb0[<(____ptrval____)>] el0t_64_sync+0x16c/0x170
。。。

通过kmemleak report的输出信息,我们可以获取如下信息:
1、产生泄漏内存块对应的object的起始地址为0xffff000006d3a400 ,泄漏的大小为1024个字节。
2、进程名为insmod,pid为1040,创建object时的jiffies为1046.512s。
3、泄漏内存块的前32字节数据。
4、泄漏点的backtrace信息。

卸载模块,因没有free内存,会有新的内存泄漏

[root@liebao kmemleak]# rmmod kmemleak_test
[root@liebao kmemleak]# echo scan > /sys/kernel/debug/kmemleak
[10015.161611] kmemleak: 14 new suspected memory leaks (see /sys/kernel/debug/kmemleak)
[root@liebao kmemleak]# cat /sys/kernel/debug/kmemleak
unreferenced object 0xffff000008c72c00 (size 256):comm "insmod", pid 1040, jiffies 4295348048 (age 8201.876s)hex dump (first 32 bytes):00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................backtrace:[<(____ptrval____)>] slab_post_alloc_hook+0x7c/0x260[<(____ptrval____)>] kmem_cache_alloc_trace+0x188/0x358[<(____ptrval____)>] 0xffff800001095268[<(____ptrval____)>] do_one_initcall+0x50/0x2b0[<(____ptrval____)>] do_init_module+0x50/0x1e8[<(____ptrval____)>] load_module+0x2184/0x2708[<(____ptrval____)>] __do_sys_init_module+0x1e8/0x228[<(____ptrval____)>] __arm64_sys_init_module+0x24/0x30[<(____ptrval____)>] invoke_syscall+0x54/0x118[<(____ptrval____)>] el0_svc_common.constprop.3+0x90/0x118[<(____ptrval____)>] do_el0_svc+0x34/0xa0[<(____ptrval____)>] el0_svc+0x20/0x60[<(____ptrval____)>] el0t_64_sync_handler+0x88/0xb0[<(____ptrval____)>] el0t_64_sync+0x16c/0x170
unreferenced object 0xffff000008c72d00 (size 256):comm "insmod", pid 1040, jiffies 4295348048 (age 8201.924s)hex dump (first 32 bytes):00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................backtrace:[<(____ptrval____)>] slab_post_alloc_hook+0x7c/0x260[<(____ptrval____)>] kmem_cache_alloc_trace+0x188/0x358[<(____ptrval____)>] 0xffff800001095268[<(____ptrval____)>] do_one_initcall+0x50/0x2b0[<(____ptrval____)>] do_init_module+0x50/0x1e8[<(____ptrval____)>] load_module+0x2184/0x2708[<(____ptrval____)>] __do_sys_init_module+0x1e8/0x228[<(____ptrval____)>] __arm64_sys_init_module+0x24/0x30[<(____ptrval____)>] invoke_syscall+0x54/0x118[<(____ptrval____)>] el0_svc_common.constprop.3+0x90/0x118[<(____ptrval____)>] do_el0_svc+0x34/0xa0[<(____ptrval____)>] el0_svc+0x20/0x60[<(____ptrval____)>] el0t_64_sync_handler+0x88/0xb0[<(____ptrval____)>] el0t_64_sync+0x16c/0x170
。。。

从调用栈看不到[<(ptrval)>] 0xffff800001095268这些地址对应的源代码,在kallsyms 搜索地址,发现在kmemleak_test模块里。另外可以借助objdump和addr2line工具看内存泄漏的代码行。
使用objdump时,发现pc值和实际代码有0x5000的偏移,不清楚是不是linux模块链接时,有没有相关操作,待确认。

[root@liebao kmemleak]# cat /proc/kallsyms | grep ffff8000010
ffff800001090000 t $x	[kmemleak_test]
ffff800001090000 t kmemleak_test_exit	[kmemleak_test]
ffff800001092000 d $d	[kmemleak_test]
ffff800001092000 d test_list	[kmemleak_test]
ffff800001091028 r $d	[kmemleak_test]
ffff800001092040 d $d	[kmemleak_test]
ffff800001091140 r $d	[kmemleak_test]
ffff800001091140 r _note_9	[kmemleak_test]
ffff800001091158 r _note_8	[kmemleak_test]
ffff800001092040 d __this_module	[kmemleak_test]
ffff800001090000 t cleanup_module	[kmemleak_test]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.exyb.cn/news/show-4558688.html

如若内容造成侵权/违法违规/事实不符,请联系郑州代理记账网进行投诉反馈,一经查实,立即删除!

相关文章

Silvaco 学习笔记——材料参数

材料参数和物理模型的选取有关&#xff0c; 常用的参数及说明如下

T67 silvaco deckbuild 调试窗口消失

silvaco deckbuild 调试窗口消失 状态窗口消失 debug窗口消失 鼠标放在下方&#xff0c;缓慢向上移动&#xff0c;知道出现&#xff0c;上下箭头加等号的符号&#xff0c;再向上拖动。

Silvaco的学习历程

听说有人想找材料&#xff1f;&#xff1f;&#xff1f;开什么玩笑&#xff1f;&#xff1f;&#xff1f; 自从从传统的电气行业转到新的专业——功率半导体器件&#xff0c;开始专攻SiC SBD/JBS后&#xff0c;自己在开始学习Silvaco的仿真&#xff0c;这里将自己的学习过程po…

Silvaco TCAD 2017 Linux 虚拟机直接运行教程

鉴于很多网友在下载了本店(https://item.taobao.com/item.htm?spma1z38n.10677092.0.0.594c1debfTspFM&id575224803877)提供的Silvaco TCAD 2017 for Linux软件套件之后&#xff0c;在虚拟机上安装时所出现的各种问题&#xff0c;经验证&#xff0c;本店提供的软件套件在所…

silvaco 量子阱程序_深度 | 量子计算技术的研究现状与未来

导读1900年 Max Planck 提出“量子”概念&#xff0c;宣告了“量子”时代的诞生。科学家发现&#xff0c;微观粒子有着与宏观世界的物理客体完全不同的特性。宏观世界的物理客体&#xff0c;要么是粒子&#xff0c;要么是波动&#xff0c;它们遵从经典物理学的运动规律&#xf…

silvaco 二维器件仿真学习笔记

20190214 Atlas仿真介绍&#xff0c;工艺流程&#xff0c;还有结构定义 都在书上&#xff0c;以后遇到了可以直接查。 主要补充一个纵向结构图的看法&#xff0c;加一个切线即可。这个故事告诉我们tonyplot用好的重要性&#xff0c;Deckbuild主要就是把结构写出来&#xff0c…

缩短饿了么tabs 组件线条宽度

前言 css 样式很多&#xff0c;相互之间配合起来其实能完成很不错的效果&#xff0c;也能减少很多JavaScript 代码。因为对样式理解少从而造成的 JavsScript 脚本 增多是得不偿失的。 案例场景 之前我们UI 设计过一个 tab 切换的效果&#xff0c;主体样式和 vue 组件库 ivie…

Android手势密码探索

Android 智能手机在全球市场有着极高的市场占有率&#xff0c;越来越受到广大消费者的青睐。但 Android 作为开源操作系统&#xff0c;且很容易可以获得系统 root 权限&#xff0c;Android 系统的安全问题也是用户和开发者最关心的问题之一。 手势密码作为手机上方便的一种安全…