找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2466

积分

0

好友

330

主题
发表于 2 小时前 | 查看: 3| 回复: 0

在Linux系统开发和内核驱动调试中,内存错误是常见且棘手的问题。典型的内存访问错误包括:

  • 越界访问 (out of bounds)
  • 访问已经释放的内存 (use after free)
  • 重复释放
  • 内存泄露 (memory leak)
  • 栈溢出 (stack overflow)

针对这些问题,Linux内核提供了多种检测工具,各有侧重。kmemleak 专门用于发现潜在的内存泄露。slub_debugkasan 功能有所重叠,都能检测越界、释放后使用等问题。部分 slub_debug 的问题需要借助 slabinfo 工具来发现;而 kasan 速度更快,所有问题都能独立上报,缺点是依赖较高版本的GCC支持(gcc 4.9.2 或 gcc 5.0 以上)。

一、测试环境准备

首先需要准备一个用于测试的内核编译环境。以下是基于ARM64架构的编译示例:

git clone https://github.com/arnoldlu/linux.git -b running_kernel_4.4
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
make defconfig
make bzImage -j4 ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

二、slub_debug

关键词:Red Zone、Padding、Object Layout。

Linux内核中,小块内存的分配大量使用slab/slub分配器。slub_debug 为这个分配器提供了一系列内存检测功能,主要针对以下几种易错场景:

  • 访问已经释放的内存
  • 越界访问
  • 重复释放内存

2.1 编译支持slub_debug内核

要使用 slub_debug,首先需要在内核配置中启用相关选项。路径为:General setup -> Enable SLUB debugging support,然后再选择 Kernel hacking -> Memory Debugging -> SLUB debugging on by default

关键的配置项如下:

CONFIG_SLUB=y
CONFIG_SLUB_DEBUG=y
CONFIG_SLUB_DEBUG_ON=y
CONFIG_SLUB_STATS=y

2.3 测试环境:slabinfo、slub.ko

我们将通过一个测试内核模块 slub.ko 来模拟各种内存异常访问。有些错误可以直接触发并显示,有些则需要通过 slabinfo 工具来查看。

slabinfo 工具位于内核源码的 tools/vm 目录下,使用以下命令编译生成静态链接的可执行文件,以便放入根文件系统:

make slabinfo CFLAGS=-static ARCH=arm CROSS_COMPILE=arm-linux-gnueabi-

将编译好的 slabinfo 放入 _install/sbin 目录,并打包到最终的镜像中。

测试代码位于:https://github.com/arnoldlu/linux/tree/running_kernel_4.4/test_code/slub_debug
在目录下执行 make.sh,将生成的 slub.ko / slub2.ko / slub3.ko 放入根文件系统的 data 目录。

2.4 进行测试

使用QEMU启动测试内核,并在内核命令行中启用 slub_debug

qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine type=virt -smp 2 -m 2048 -kernel arch/arm64/boot/Image --append "rdinit=/linuxrc console=ttyAMA0 loglevel=8 slub_debug=UFPZ" -nographic

这里 slub_debug 的参数含义如下:

  • F:在 free (释放) 时执行检查。
  • Z:表示启用 Red Zone (红色区域)。
  • P:是 Poison (毒化) 的意思,用于标记已释放内存。
  • U:会记录slab的使用者信息(分配/释放的栈回溯)。

当打开 SLAB_STORE_USER 选项(对应参数 U)后,可以在错误报告中清晰地看到问题发生点的函数调用栈。

2.5 测试结果

slub_debug 可以检测到的错误类型包括:

  • 内存越界访问:分为 Redzone overwrittenObject padding overwritten
  • 重复释放:对应 Object already free
  • 访问已释放内存:对应 Poison overwritten

2.5.1 Redzone overwritten

测试代码逻辑

static void create_slub_error(void)
{
  buf = kmalloc(32, GFP_KERNEL);
  if(buf) {
    memset(buf, 0x55, 80); // 虽然只申请了32字节,但实际分配的是kmalloc-64(64字节)。设置80字节访问以触发异常。
  }
}

虽然 kmalloc 申请了32字节,但内核实际分配的是下一个大小的缓存 kmalloc-64(64字节)。因此 memset 36字节不会报错,需要设置超过64(如80)才能触发右侧越界,覆盖到对象尾部的 Redzone 区域。

执行与输出
执行 insmod data/slub.ko,然后使用 slabinfo -v 查看结果。输出报告非常详细,通常包含四大部分:

  1. 问题描述:指出是哪个slab缓存(如 kmalloc-64)和错误类型(Redzone overwritten)。
  2. 栈回溯信息:显示该内存块分配(Allocated in)和释放(Freed in)时的调用栈,帮助定位代码位置。
  3. 内存对象布局与内容:以十六进制和ASCII形式dump出问题对象及其周围的内存,包括对象本身(Object)、红色警戒区(Redzone)和对齐填充区(Padding)。可以清晰看到被覆盖的 Redzone 区域(原本应为 0xcc,被写成了 0x55)。
  4. 问题发现路径及修复:显示是哪个进程(如 slabinfo)通过何种调用路径检查到了问题,并报告内核尝试进行的修复(如将 Redzone 恢复为 0xcc)。

这种详细的报告机制是 Debugging 复杂内存问题的重要依据。

2.5.2 Object padding overwritten

测试代码逻辑(向左越界)

void create_slub_error(void)
{
  int i;
  buf = kmalloc(32, GFP_KERNEL);
  if(buf) {
    buf[-1] = 0x55; // 向左越界访问,覆盖了前一个对象的 Padding 区域
    kfree(buf);
  }
}

执行与输出
执行 insmod data/slub4.ko。错误类型报告为 Object padding overwritten。在内存dump中,可以看到对应对象的 Padding 区域末尾字节被从 0x5a 改写为 0x55

2.5.3 Object already free

测试代码逻辑(重复释放)

void create_slub_error(void)
{
  buf = kmalloc(32, GFP_KERNEL);
  if(buf) {
    memset(buf, 0x55, 32);
    kfree(buf); // 第一次释放
    printk("al: Object already freed");
    kfree(buf); // 第二次释放,触发错误
  }
}

在内核中,kfree 的执行会经过 slab_free -> __slab_free -> free_debug_processing -> on_freelist 等路径,在第二次释放时会检查到该对象已在空闲链表上。

执行与输出
执行 insmod data/slub2.ko。报告错误类型为 Object already free。报告会分别给出该对象第一次分配和第一次释放的栈回溯。最终处理结果是该slab对象没有被再次释放。

2.5.4 Poison overwritten

测试代码逻辑(释放后使用)

static void create_slub_error(void)
{
  buf = kmalloc(32, GFP_KERNEL); // 分配后,buf内容初始化为0x6b (POISON_FREE)
  if(buf) {
    kfree(buf); // 释放,内存被“毒化”
    printk("al: Access after free");
    memset(buf, 0x55, 32); // 在已释放的内存上进行写操作,触发错误
  }
}

执行与输出
执行 insmod data/slub3.ko,然后使用 slabinfo -v 触发检查。报告错误类型为 Poison overwritten。在内存dump中,可以看到对象前32字节已被成功修改为 0x55,而后面未被修改的部分仍为释放后的“毒化”值 0x6b。报告同时给出了分配和释放的栈回溯。

三、kmemleak

kmemleak 是内核提供的一种用于检测内存泄露的工具。它会启动一个内核线程,周期性地扫描内存,寻找并报告那些不可达(即没有指针指向)但尚未释放的内存对象。

3.1 支持kmemleak内核选项

要启用 kmemleak,需要配置以下内核选项(位于 Kernel hacking -> Memory Debugging -> Kernel memory leak detector):

CONFIG_HAVE_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE=400
# CONFIG_DEBUG_KMEMLEAK_TEST is not set
CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF=y // 如果关闭此项,则内核默认开启,无需命令行参数

3.2 构造测试环境

如果使用了 CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF,需要在启动内核时通过命令行参数 kmemleak=on 来激活它。

启动QEMU的命令如下:

qemu-system-aarch64 -machine virt -cpu cortex-a57 -machine type=virt -smp 2 -m 2048 -kernel arch/arm64/boot/Image --append "rdinit=/linuxrc console=ttyAMA0 loglevel=8 kmemleak=on" -nographic

测试代码(模拟内存泄露)

static char *buf;
void create_kmemleak(void)
{
  buf = kmalloc(120, GFP_KERNEL); // 分配1,指针被下一行覆盖,造成泄露
  buf = vmalloc(4096); // 分配2,同样会造成之前分配的120字节泄露
}

3.3 进行测试

kmemleak 不会主动持续扫描,需要手动触发。基本操作流程如下:

  1. 触发扫描echo scan > /sys/kernel/debug/kmemleak
  2. 加载问题模块insmod data/kmemleak.ko
  3. 等待报告:内核会打印类似 kmemleak: 2 new suspected memory leaks (see /sys/kernel/debug/kmemleak) 的信息。
  4. 查看详情cat /sys/kernel/debug/kmemleak

3.4 分析测试结果

kmemleak 的报告对每个可疑泄露点都包含以下信息:

  • 泄露地址和大小
  • 相关进程信息(进程名、PID、时间戳)
  • 内存内容的前32字节hex dump
  • 分配该内存时的栈回溯(最关键的信息)

报告示例如下:

unreferenced object 0xede22dc0 (size 128): // 第一个可疑泄露,128字节
  comm “insmod“, pid 765, jiffies 4294941257 (age 104.920s) // 进程信息
  hex dump (first 32 bytes): // 内存内容快照
    6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
    6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk
  backtrace: // 栈回溯,指向分配点
    [<bf002014>] 0xbf002014
    [<c000973c>] do_one_initcall+0x90/0x1d8
    [<c00a71f4>] do_init_module+0x60/0x38c
    [<c0086898>] load_module+0x1bac/0x1e94
    [<c0086ccc>] SyS_init_module+0x14c/0x15c
    [<c000f3c0>] ret_fast_syscall+0x0/0x3c
    [<ffffffff>] 0xffffffff

通过栈回溯,开发者可以精准定位到是哪个函数分配了这块再未被引用的内存,进而检查代码逻辑。

四、kasan

KASAN (KernelAddressSANitizer) 是一个动态内存错误检测工具,其设计借鉴了用户态的ASan。它可以检查内存越界访问、使用已释放内存、重复释放以及栈溢出等。需要注意的是,kasan 暂不支持32位ARM,主要支持ARM64和x86_64架构。

4.1 使能kasan

使用 kasan 必须打开 CONFIG_KASAN 内核配置选项(Kernel hacking -> Memory debugging -> KASan: runtime memory debugger)。

关键配置如下:

CONFIG_HAVE_ARCH_KASAN=y
CONFIG_KASAN=y
# CONFIG_KASAN_OUTLINE is not set
CONFIG_KASAN_INLINE=y // 内联检测,更快但体积稍大
CONFIG_TEST_KASAN=m // 编译测试模块

4.2 代码分析

kasan 的报错路径核心为:kasan_report -> kasan_report_error -> print_error_description -> print_address_description -> print_shadow_for_address。它会利用影子内存(shadow memory)来追踪内存状态,从而实现高效检测。

4.3 测试用例及分析

内核源码中自带了一个测试模块 test_kasan.c,编译为 kasan.ko。加载此模块可以模拟多种内存错误场景。

执行测试:insmod data/kasan.ko

kasan 能够检测的错误类型非常全面,包括越界访问(slab、栈、全局变量)、释放后使用(use-after-free)等,其中重复释放的检测依赖于底层 slub_debug 的基础设施。

4.3.1 slab-out-of-bounds

测试代码(右侧越界)

static noinline void __init kmalloc_oob_right(void)
{
    char *ptr;
    size_t size = 123;
    pr_info(“out-of-bounds to right\n“);
    ptr = kmalloc(size, GFP_KERNEL);
    if (!ptr) {
        pr_err(“Allocation failed\n“);
        return;
    }
    ptr[size] = ‘x‘; // 越界写,访问了分配大小之后的第一个字节
    kfree(ptr);
}

输出分析
报告开头明确错误类型:BUG: KASAN: slab-out-of-bounds in kmalloc_oob_right+0xa4/0xe0 [kasan]
报告内容结合了 kasan 的错误类型定位和 slub_debug 格式的详细对象信息、栈回溯。最后还会输出问题地址周围的内存影子状态fc 表示不可访问,0003 等表示可访问字节数,这有助于判断越界的程度。

4.3.2 use-after-free

测试代码(释放后使用)

static noinline void __init kmalloc_uaf(void)
{
    char *ptr;
    size_t size = 10;
    pr_info(“use-after-free\n“);
    ptr = kmalloc(size, GFP_KERNEL);
    if (!ptr) {
        pr_err(“Allocation failed\n“);
        return;
    }
    kfree(ptr);
    *(ptr + 8) = ‘x‘; // 在已释放的内存上写
}

输出分析
报告类型为:BUG: KASAN: use-after-free in kmalloc_uaf+0xac/0xe0 [kasan]
影子内存状态显示问题地址周围为 fb,这通常表示这是一段已释放(free)且被毒化(poisoned)的内存区域,对其访问会被捕获。

4.3.3 stack-out-of-bounds

测试代码(栈数组越界)

static noinline void __init kasan_stack_oob(void)
{
    char stack_array[10];
    volatile int i = 0;
    char *p = &stack_array[ARRAY_SIZE(stack_array) + i]; // 指针指向栈数组末尾之后
    pr_info(“out-of-bounds on stack\n“);
    *(volatile char *)p; // 越界读
}

输出分析
报告类型为:BUG: KASAN: stack-out-of-bounds in kasan_stack_oob+0xa8/0xf0 [kasan]
这类错误在实际编程中常见且隐蔽,kasan 能有效检测栈变量的越界访问。

4.3.4 global-out-of-bounds

测试代码(全局数组越界)

static char global_array[10];
static noinline void __init kasan_global_oob(void)
{
    volatile int i = 3;
    char *p = &global_array[ARRAY_SIZE(global_array) + i]; // 指向全局数组之外
    pr_info(“out-of-bounds global variable\n“);
    *(volatile char *)p; // 越界读
}

输出分析
报告类型为:BUG: KASAN: global-out-of-bounds in kasan_global_oob+0x9c/0xe8 [kasan]
报告还会额外指出该地址属于哪个全局变量:Address belongs to variable global_array+0xd/0xffffffffffffe3f8 [kasan]

五、小结

总结一下这三个工具的特点与选型建议:

  • kmemleak:功能专注,是检测内存泄露的“独门绝技”,在需要查找内存只分配不释放的场景下不可或缺。
  • slub_debug:集成在SLUB分配器中,支持平台广泛,是调试 Kernel 内存基础问题(越界、重复释放、释放后使用)的经典工具。其输出信息极其详尽,适合深入分析。
  • kasan:更现代、效率更高的动态检测工具,基于编译插桩和影子内存,检测速度快,错误类型覆盖全(包括栈和全局变量越界)。缺点是需较新编译器和高版本内核支持,且目前主要支持ARM64/x86平台。

对于非ARM64/x86平台,slub_debug 是进行内核内存问题调试的主要工具。而在支持 kasan 的平台和较新内核版本上,它因其高效和全面性而成为更优选择。理解这些工具的原理和输出,是每一位Linux内核开发者进行高效 内存管理 和调试的必备技能。如果你想深入探讨更多内核或底层开发话题,欢迎到 云栈社区 交流分享。

原作者:ArnoldLu
原文地址:https://www.cnblogs.com/arnoldlu/p/8568090.html




上一篇:iOS高危Safari浏览器漏洞被公开,DarkSword攻击链泄露危及2.7亿设备
下一篇:DataWorks + EMR Serverless Spark 实践:构建企业级Data+AI一体化平台
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-27 04:35 , Processed in 0.627225 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表