Skip to content

调试档案 005 · 内核输出与可测性的几个边界坑

notes/005/ 提炼,配套 005 · 内核会说话了。这一章给内核接上了串口和 kprintf,并第一次搭起测试。下面几个坑都和"边界值"或"工具链没接对"有关。

案例一:打印 INT64_MIN 得到一串错的数字

  • 症状:kprintf("%d", INT64_MIN) 打出来不是 -9223372036854775808,而是乱七八糟的值或溢出后的数字。
  • 原因:format_decimal 把负数转正时做 value = -value。但 INT64_MIN 的绝对值比 INT64_MAX 大 1,-INT64_MIN 在补码下溢出,结果还是它自己(仍为负),后面逐位取模全乱。
  • 定位:这是纯算法 bug,host 单测 ASSERT_EQ(format_decimal(INT64_MIN,...), "-9223372036854775808") 一条就能抓到——在内核里反而极难触发(谁会专门打 INT64_MIN)。
  • 修复:对 INT64_MIN 单独特判,直接拷贝常量串 "-9223372036854775808"
  • 防复发:凡涉及"负数取绝对值再转换"的算法,都要特判最小负数;并且这种纯算法一律配 host 单测覆盖边界,别只靠 QEMU 跑。

案例二:串口满屏乱码

  • 症状:内核确实在发数据(终端能看到字符刷动),但全是乱码,看不出原文。
  • 原因:UART 的线路配置(LCR:数据位/校验/停止位)和 Baud 跟接收端对不上。Cinux 设 LCR=0x03(8N1),必须和 QEMU -serial 默认的 115200 8N1 一致;配错一位,位帧错位,解出来全是垃圾。
  • 定位:先只发一个固定字符(如 A),终端收到 A 就说明线路对,收到乱码就是 LCR/Baud 问题。
  • 修复:initLCR=0x03 对齐 QEMU 默认。
  • 防复发:串口初始化用常量(LCR=0x03/FCR=0xC7/MCR=0x03)而非裸 0x 数,并对齐模拟器默认配置;串口本身没通时,用 debugcon(0xE9)打面包屑定位 init 卡在哪一步。

案例三:换行后逐行右错,呈阶梯状

  • 症状:多行输出每换一行就往右挪一点,整体斜着走,读不成句。
  • 原因:puts 直接吐 \n,而串口终端把 \n 只当"换行"不"回车",光标不回到行首。于是下一行从上一行结尾的正下方开始,逐行右移。
  • 定位:肉眼即可识别——阶梯状错位几乎只此一个原因。
  • 修复:puts 遇到 \nputc('\r')putc('\n'),补回车。
  • 防复发:串口文本输出统一走一个处理 \r\nputs,别让调用方自己拼。

案例四:全局对象的构造函数没跑(对象库 / .init_array 被裁)

  • 症状:内核测试里"全局对象构造"那条挂,或量产代码里全局对象状态是默认 0 而非构造函数设的值。
  • 原因:format.cpp 等被编成单独的静态库再链进内核,如果链接脚本没用 KEEP 保护 .init_array,链接器可能把它当未引用的垃圾段裁掉。于是 _init_global_ctors 遍历 __init_array_start..end 时拿到的是空,全局构造全跳过。
  • 定位:GDB 看 __init_array_start__init_array_end 是否相等(相等=段被裁空);或看链接产物里有没有 .init_array
  • 修复:linker.ld.init_arrayKEEP(*(.init_array .init_array.*)),并正确导出起止符号。
  • 防复发:任何含全局对象的内核,链接脚本里 .init_array 一律 KEEP;配一条"全局对象构造"的内核测试专门盯它。

一句话总结

这一章的坑分两类:一类是算法边界(INT64_MIN)——靠把纯逻辑抽出来配 host 单测来抓;一类是工具链没接对(串口配置、\r\n.init_array 被 KEEP)——靠常量化配置和专门的运行时测试来防。把"纯算法可 host 测"和"运行时靠 QEMU 测"这两条轨都铺好,后面堆功能才有底气。

035_multi_terminal-40-g5d72b8b · 5d72b8b · 2026-06-26