Skip to content

第七十八章:内存调试

元婴后期

涉及内核源码:

林小源在 Slab 作坊的角落里,发现了一扇暗门。

门上积满了灰尘,门缝里透出微弱的光。他推开门,里面是一间昏暗的工作室。墙壁上挂满了各种工具——放大镜、探针、示波器——每一件都散发着淡淡的银光。

一位老者坐在工作台前,手里拿着一块内存方块,正用放大镜仔细观察。

"你在看什么?"林小源问。

"虫子。"老者头也不回地说。

"虫子?"

"内存里的虫子。"老者放下放大镜,转过身来。他的眼睛布满血丝,像是很久没有睡觉。"越界访问、使用已释放内存、未初始化内存——这些 bug 就像虫子,藏在内存的缝隙里,你找不到它们,但它们会慢慢地啃噬你的系统。"

林小源走近工作台。老者手中的内存方块表面看起来完好无损,但在放大镜下,他看到了一些细小的裂缝——裂缝里有什么东西在蠕动。

"这是什么 bug?"

"越界访问。"老者说,"int arr[10]; arr[10] = 42;——访问了第 11 个元素,但数组只有 10 个。写入的 42 覆盖了相邻的内存。程序可能不会立刻崩溃,但数据已经悄悄损坏了。"

老者从墙上取下一面半透明的镜子,递给林小源。

"这是 KASAN——Kernel Address Sanitizer。"老者说,"内核地址消毒器。"

林小源接过镜子,往内存方块上一照。镜子映出了方块内部的结构——每一块内存旁边都有一个"影子",影子的颜色代表内存的状态:绿色表示有效,红色表示无效,灰色表示未初始化。

"影子内存。"老者说,"KASAN 给每 8 字节内存分配 1 字节影子内存。每次内存访问,KASAN 都会先检查影子——如果访问了红色区域,立刻报告。"

林小源用镜子扫描了整个方块。他发现了三个红色区域——三个 bug。

"这三个分别是什么?"

"第一个是越界访问,"老者说,"写入超出了数组的边界。第二个是使用已释放内存——free(ptr); *ptr = 42;,ptr 指向的内存已经还给系统了,但代码还在用。第三个是未初始化内存——int arr[10]; printf('%d', arr[0]);,arr[0] 的值是随机的。"

林小源放下镜子,看着老者继续工作。

"KASAN 会让内核变慢多少?"

"2 到 3 倍。"老者说,"每次内存访问都要检查影子,这有开销。调试工具不适合在生产环境用——但开发阶段,它能帮你找到那些藏得很深的虫子。"

他顿了顿,又说:"还有其他工具。KMSAN 检测未初始化内存,KMEMLEAK 检测内存泄漏,DEBUG_PAGEALLOC 把释放的页面标记为不可访问——使用已释放内存会立刻崩溃。"

"为什么不总是开着?"

"因为代价。"老者说,"调试工具让系统变慢、内存变大。生产环境需要的是性能,不是调试。但在开发阶段——"他指了指墙壁上的工具,"这些工具能救你的命。"

林小源看着那些发光的工具,心里多了一层敬畏。内存 bug 是最难找的 bug——它们不会立刻崩溃,而是悄悄损坏数据,等到你发现的时候,已经太晚了。

说到底,"看不见的虫子"比"看得见的敌人"更可怕。


c
printf("=== 内存调试 — 找到隐藏的 bug ===\n\n");

printf("常见的内存 bug:\n\n");

printf("1. 越界访问 (Out-of-bounds)\n");
printf("   int arr[10];\n");
printf("   arr[10] = 42;  // 访问第 11 个元素\n");
printf("   可能破坏相邻数据\n\n");

printf("2. 使用已释放内存 (Use-after-free)\n");
printf("   ptr = malloc(size);\n");
printf("   free(ptr);\n");
printf("   *ptr = 42;  // 使用已释放的内存\n");
printf("   可能破坏新分配的数据\n\n");

printf("3. 未初始化内存\n");
printf("   int arr[10];\n");
printf("   printf(\"%%d\", arr[0]);  // 随机值\n");
printf("   可能导致不确定行为\n\n");

printf("4. 双重释放 (Double free)\n");
printf("   free(ptr);\n");
printf("   free(ptr);  // 释放两次\n");
printf("   可能破坏内存分配器\n\n");

printf("--- 内核调试工具 ---\n\n");

printf("KASAN (Kernel Address Sanitizer):\n");
printf("  检测越界访问和使用已释放内存\n");
printf("  使用影子内存跟踪每字节状态\n");
printf("  开销: 约 2-3 倍\n\n");

printf("KMSAN (Kernel Memory Sanitizer):\n");
printf("  检测未初始化内存使用\n\n");

printf("KMEMLEAK:\n");
printf("  检测内存泄漏\n\n");

printf("DEBUG_PAGEALLOC:\n");
printf("  释放的页面标记为不可访问\n");
printf("  使用已释放内存立即崩溃\n\n");

printf("--- 使用 KASAN ---\n");
printf("编译内核时启用:\n");
printf("  CONFIG_KASAN=y\n");
printf("  CONFIG_KASAN_INLINE=y\n\n");
printf("运行时查看报告:\n");
printf("  dmesg | grep KASAN\n");

道藏笔记

内核启示

内核提供多种内存调试工具。

常见的内存 bug:

  • 越界访问
  • 使用已释放内存
  • 未初始化内存
  • 双重释放

调试工具:

  • KASAN — 越界访问和使用已释放内存
  • KMSAN — 未初始化内存
  • KMEMLEAK — 内存泄漏
  • DEBUG_PAGEALLOC — 使用已释放内存

KASAN 的原理:

  • 使用影子内存跟踪每字节状态
  • 每次访问检查影子内存
  • 发现问题立即报告

调试工具是"照妖镜"——让隐藏的 bug 无处遁形。


破关试炼

内存调试之试

本章讲内存调试时,哪一个配置会在释放页面后撤销映射以尽早暴露越界访问?

答对后才能继续滑动和进入下一章。

以修仙之名,悟内核之道