跳到主要内容

1.8 看见差异——生产内核与调试内核的配置对决

上一节我们把调试内核里能开的开关都打开了——这很好,但这也引来了一个问题:我们到底改了什么?或者说,这一堆 yn 的跳动,到底让这颗内核和它的生产版兄弟产生了多大的分歧?

这个问题比你想象的要重要。如果你不清楚两者的边界,将来调试的时候,你可能会把一个「只有调试内核才有的 Bug」当成通用 Bug 来修,或者更糟——在生产环境里试图加载一个只有调试符号才存在的模块。

让我们把看不见的变成看得见的。

对比配置的显微镜:diffconfig

内核源码树里藏着一个很好用的脚本,叫 scripts/diffconfig。它就像是一台显微镜,专门用来对比两份 .config 文件,并把差异放大给你看。

现在,假设我们手上有两份配置文件路径:

  • 生产内核配置:~/lkd_kernels/kconfig_prod01
  • 调试内核配置:~/lkd_kernels/kconfig_dbg01

我们在调试内核的源码目录下执行这个脚本,把差异输出到一个文本文件里:

scripts/diffconfig ~/lkd_kernels/kconfig_prod01 ~/lkd_kernels/kconfig_dbg01 > ../../kconfig_diff_prod_to_debug.txt

这一行命令跑完,你就拥有了一份完整的「体检报告」。

在我的系统上,这份报告超过了 200 行。这意味着调试内核并不是「稍微」改了一下,而是发生了基因层面的突变

解读差异报告

用编辑器打开 kconfig_diff_prod_to_debug.txt。你会发现文件里的行被分成了三类,每一类都在讲一个不同的故事。

第一类:被移除的功能(前缀 -

- 开头的行,代表这些功能在生产内核里是有的,但在调试内核里被我们剔除了。

-BPF_LSM y
-DEFAULT_SECURITY_APPARMOR y
-DEFAULT_SECURITY_SELINUX n
-DEFAULT_SECURITY_SMACK n
[ … ]

看到这个列表你可能会愣一下:等等,调试内核为什么要把安全模块关掉?

这听起来很反直觉,但这里有个很实际的权衡。某些安全机制(比如 LSM 框架下的某些模块)会对系统调用进行深度拦截,这会极大地拖慢系统速度,甚至干扰调试器的正常追踪。在专门用来抓 Bug 的内核上,为了性能和纯粹性,有时候不得不暂时卸下这些盾牌。

第二类:被修改的开关(n -> yy -> n

这是最核心的部分。这里显示的是那些「状态发生翻转」的选项。你会发现,绝大多数箭头都是指向 y 的。

DEBUG_ATOMIC_SLEEP n -> y
DEBUG_BOOT_PARAMS n -> y
DEBUG_INFO n -> y
DEBUG_KMEMLEAK n -> y
DEBUG_LOCK_ALLOC n -> y
DEBUG_MUTEXES n -> y
DEBUG_PLIST n -> y
DEBUG_RT_MUTEXES n -> y
DEBUG_RWSEMS n -> y
DEBUG_SPINLOCK n -> y
[ … ]

每一行 n -> y 都意味着我们在调试内核里埋下了一双眼睛。

  • DEBUG_ATOMIC_SLEEP:原本不许在原子上下文睡觉,现在不仅不许,还会大喊大叫。
  • DEBUG_INFO:原本为了节省空间不给符号,现在全给,为了 gdb 能看懂。
  • DEBUG_LOCK_ALLOCDEBUG_MUTEXESDEBUG_SPINLOCK:锁相关的检查全开。

在这里,你也看到了我们之前特意修改的名字后缀:

LKDTM n -> m
LOCALVERSION "-prod01" -> "-dbg01"
LOCK_STAT n -> y
MMIOTRACE n -> y
MODULE_SIG y -> n
[ … ]
  • LOCALVERSION-prod01 变成了 -dbg01。这正是为什么你在启动信息里能看到 5.10.17-dbg01 而不是 5.10.17-prod01
  • MODULE_SIGy 变成了 n。这里有一个微妙的点:生产内核通常要求模块签名(y),防止加载恶意模块;但调试内核为了方便自己折腾测试模块,通常把强制签名关掉(n),否则你自己写个测试模块加载还得折腾签名证书,简直是给自己找罪受。

第三类:新增的功能(前缀 +

+ 开头的行,代表这些是全新的、在生产内核配置里压根不存在的项目。

+ARCH_HAS_EARLY_DEBUG y
+BITFIELD_KUNIT n
[ … ]
+IKCONFIG m
+KASAN_GENERIC y
[ … ]

注意最后那个 KASAN_GENERIC。这是 Kernel Address SANitizer,这是内核调试里的大杀器,专门用来发现内存越界和「释放后使用」这类烂内存 Bug。它会极大地拖慢系统速度(可能让系统慢 2 倍甚至更多),所以在生产内核里它绝对是个禁项,但在调试内核里,它是无价之宝。

回到现实:你的项目可能不一样

虽然这里我们演示了一种「标准」的生产 vs 调试的配对方式,但我必须诚实地说:

特定的内核配置只是代表性的。

你的项目或产品可能有一大堆奇葩需求。也许你的生产内核需要某些特殊的实时补丁,也许你的调试环境需要跑在一个奇怪的虚拟化平台上。

另外,如果你是在做现代的嵌入式 Linux,你大概率不是在命令行里敲 make menuconfig,而是在用 Yocto 或者 Buildroot 这种重量级构建系统。

如果是那样,你刚才学的这些「手动修改 .config」的功夫就要换个地方用了。在 Yocto 里,你通常得写一个 bbappend 的 recipe 文件来把你的调试配置塞进去。那是一个更复杂的坑,但核心逻辑是一样的:一个是瘦身的运动员,一个是浑身贴满传感器的实验体。

最终检查

说到这里,我隐秘地希望你已经老实照做了——也就是说,你现在手里应该真的有两颗编译好的内核:一颗生产,一颗调试。

如果没有,我真的建议你现在就去把编译这一步跑完。不要只看不练。

如果一切顺利,你现在应该拥有了定制的 5.10 LTS 生产内核和调试内核,随时准备上战场。接下来的章节里,我们就会真的让它们跑起来,看看它们在出问题的时候到底有什么不一样表现。

在那之前,让我们先用最后一节的内容,给你装上几件调试用的「贴身装备」。