Kernel Memory Leak Detector : kmemleak 사용법

커널 개발에서 메모리 누수는 심각한 문제이다. 메모리 누수는 시스템의 성능을 저하시키고, 장기적으로는 시스템이 불안정해지는 원인이 될 수 있기 때문이다. 이를 해결하기 위해 Linux 커널은 Kernel Memory Leak Detector인 kmemleak 툴을 제공한다. kmemleak는 커널에서 발생하는 메모리 누수를 탐지하고 보고하는 도구이다. 이번 글에서는 kmemleak의 사용법에 대해 알아본다.

Kmemleak 적용

Enable CONFIG_DEBUG_KMEMLEAK

kmemleak을 사용하기 위해서는 “Kernel hacking” CONFIG_DEBUG_KMEMLEAK 을 활성화 해야 한다.

$ ./scripts/config --enable DEBUG_KMEMLEAK
$ cat .config| grep KMEMLEAK
CONFIG_HAVE_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK_MEM_POOL_SIZE=16000
# CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF is not set
CONFIG_DEBUG_KMEMLEAK_AUTO_SCAN=y

커널 쓰레드는 기본적으로 10분 마다 메모리를 캔하고 발견된 참조되지 않은 object들을 출력한다.

kmemleak sysfs 인터페이스

kmemleak sysfs 인터페이스를 사용하려면 아래와 같이 debugfs를 마운트 한다.

mount -t debugfs nodev /sys/kernel/debug

커널 쓰레드는 기본적으로 10분 마다 메모리를 캔하고 발견된 참조되지 않은 object들을 출력한다. debugfs 마운트 후, kmemleak 인터페이스를 확인하고 메모리 누수에 대한 정보를 보기위해 다음을 수행한다.

cat /sys/kernel/debug/kmemleak

중간에 메모리 스캔을 트리거 하려면 다음과 같이 kmemleak 인터페이스에 scan을 입력한다.

echo scan > /sys/kernel/debug/kmemleak

현재 가능한 모든 kmemleak 리스트를 지우려면 다음과 같이 clear를 입력한다.

echo clear > /sys/kernel/debug/kmemleak

kmemleak 파라미터

Runtime 시 kmemleak 파라미터를 조정하여 kmemleak 동작을 제어 할 수 있다.

  • off : kmemleak 비활성화 (되돌릴수 없음)
  • stack=on : 스택 검사 활성화 (default)
  • stack=off : 스택 검사 비활성화
  • scan=on : 자동 메모리 스캐닝 쓰레드 시작 (default)
  • scan=off : 자동 메모리 스캐닝 쓰레드 중지
  • scan=<secs> : 자동 메모리 스캐닝 시간을 초단위로 설정 (default 600, 자동 스캔 중지하려면 0)
  • scan: 메모리 스캔 트리거
  • clear : 현재 보고된 unreferenced 객체를 모두 gray로 표시하여 메모리 leak 의심 목록을 지움
  • dump=<addr> : <addr>에서 발견된 객체에 대한 정보 덤프

kmemleak 예제

// 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 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_MODULES
        pr_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));
#endif
        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));
        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;
}

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_init(kmemleak_test_init);
module_exit(kmemleak_test_exit);
MODULE_LICENSE("GPL");

다음은 위 kmemleak 예제 소스를 컴파일 한 ko파일을 라즈베리파이에서 insmod 시 dmesg 에 추가된 메세지 이다.

[  277.226418] kmemleak_test: loading out-of-tree module taints kernel.
[  277.227683] kmemleak: Kmemleak testing
[  277.227703] kmemleak: kmalloc(32) = 000000005ce19789
[  277.227720] kmemleak: kmalloc(32) = 00000000f03d1900
[  277.227735] kmemleak: kmalloc(1024) = 00000000ea3776ef
[  277.227747] kmemleak: kmalloc(1024) = 00000000b254aa2f
[  277.227763] kmemleak: kmalloc(2048) = 000000008e4ea6cc
[  277.227782] kmemleak: kmalloc(2048) = 0000000072e7071d
[  277.227799] kmemleak: kmalloc(4096) = 000000000ebe1d0e
[  277.227811] kmemleak: kmalloc(4096) = 00000000f7fa0ab7
[  277.227878] kmemleak: vmalloc(64) = 00000000b8e6f08f
[  277.227929] kmemleak: vmalloc(64) = 0000000013efdec3
[  277.227968] kmemleak: vmalloc(64) = 0000000072874f6e
[  277.228014] kmemleak: vmalloc(64) = 00000000eaf41b31
[  277.228118] kmemleak: vmalloc(64) = 000000001a742bab
[  277.228136] kmemleak: kzalloc(sizeof(*elem)) = 00000000c16622eb
[  277.228147] kmemleak: kzalloc(sizeof(*elem)) = 00000000fde86096
[  277.228157] kmemleak: kzalloc(sizeof(*elem)) = 00000000d6b3949f
[  277.228167] kmemleak: kzalloc(sizeof(*elem)) = 0000000098b8ab2f
[  277.228178] kmemleak: kzalloc(sizeof(*elem)) = 0000000008de7dc5
[  277.228189] kmemleak: kzalloc(sizeof(*elem)) = 00000000a81b48b5
[  277.228200] kmemleak: kzalloc(sizeof(*elem)) = 000000004e66a918
[  277.228210] kmemleak: kzalloc(sizeof(*elem)) = 000000000aa2105c
[  277.228220] kmemleak: kzalloc(sizeof(*elem)) = 000000009e9731b0
[  277.228230] kmemleak: kzalloc(sizeof(*elem)) = 00000000af5d3653
[  277.228247] kmemleak: kmalloc(129) = 00000000a7ced7f1
[  277.228257] kmemleak: kmalloc(129) = 0000000090866847
[  277.228268] kmemleak: kmalloc(129) = 000000007ce7e406
[  277.228279] kmemleak: kmalloc(129) = 00000000d664c79d

다음은 kmemleak scan 시 결과이다. unreference 객체 정보를 출력하여 memory leak 의심 지점을 참고할 수 있다.

root@raspberrypi:~# echo scan > /sys/kernel/debug/kmemleak
root@raspberrypi:~# cat /sys/kernel/debug/kmemleak
unreferenced object 0xffffffc08098b000 (size 4096):
  comm "insmod", pid 1914, jiffies 4294961601 (age 318.968s)
  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:
    [<00000000b2480bbd>] __vmalloc_node_range+0x710/0x770
    [<00000000d3c5750d>] vmalloc+0x64/0x80
    [<000000003c8d839c>] kmemleak_test_init+0x180/0x2b0 [kmemleak_test]
    [<00000000a8d17a97>] do_one_initcall+0x4c/0x298
    [<000000003cc1a023>] do_init_module+0x60/0x218
    [<00000000869479ce>] load_module+0x1c08/0x1d40
    [<000000008ec63e18>] init_module_from_file+0x8c/0xd8
    [<00000000f772d9a4>] __arm64_sys_finit_module+0x1c0/0x290
    [<0000000029b60f0f>] invoke_syscall+0x50/0x128
    [<000000005fb76f4e>] el0_svc_common.constprop.0+0x48/0xf8
    [<00000000e19b1d1f>] do_el0_svc+0x28/0x40
    [<00000000228b4432>] el0_svc+0x40/0xf8
    [<00000000ec15273e>] el0t_64_sync_handler+0x13c/0x158
    [<000000006a7dbf03>] el0t_64_sync+0x190/0x198
unreferenced object 0xffffffc0809c3000 (size 4096):
  comm "insmod", pid 1914, jiffies 4294961601 (age 318.972s)
  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:
    [<00000000b2480bbd>] __vmalloc_node_range+0x710/0x770
    [<00000000d3c5750d>] vmalloc+0x64/0x80
    [<00000000b3d9490c>] kmemleak_test_init+0x194/0x2b0 [kmemleak_test]
    [<00000000a8d17a97>] do_one_initcall+0x4c/0x298
    [<000000003cc1a023>] do_init_module+0x60/0x218
    [<00000000869479ce>] load_module+0x1c08/0x1d40
    [<000000008ec63e18>] init_module_from_file+0x8c/0xd8
    [<00000000f772d9a4>] __arm64_sys_finit_module+0x1c0/0x290
    [<0000000029b60f0f>] invoke_syscall+0x50/0x128
    [<000000005fb76f4e>] el0_svc_common.constprop.0+0x48/0xf8
    [<00000000e19b1d1f>] do_el0_svc+0x28/0x40
    [<00000000228b4432>] el0_svc+0x40/0xf8
    [<00000000ec15273e>] el0t_64_sync_handler+0x13c/0x158
    [<000000006a7dbf03>] el0t_64_sync+0x190/0x198

참고

답글 남기기