1.8 看见差异——生产内核与调试内核的配置对决
上一节我们把调试内核里能开的开关都打开了——这很好,但这也引来了一个问题:我们到底改了什么?或者说,这一堆 y 和 n 的跳动,到底让这颗内核和它的生产版兄弟产生了多大的分歧?
这个问题比你想象的要重要。如果你不清楚两者的边界,将来调试的时候,你可能会把一个「只有调试内核才有的 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 -> y 或 y -> 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_ALLOC、DEBUG_MUTEXES、DEBUG_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_SIG从y变成了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 生产内核和调试内核,随时准备上战场。接下来的章节里,我们就会真的让它们跑起来,看看它们在出问题的时候到底有什么不一样表现。
在那之前,让我们先用最后一节的内容,给你装上几件调试用的「贴身装备」。