正常
调试档案 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 问题。 - 修复:
init里LCR=0x03对齐 QEMU 默认。 - 防复发:串口初始化用常量(
LCR=0x03/FCR=0xC7/MCR=0x03)而非裸0x数,并对齐模拟器默认配置;串口本身没通时,用 debugcon(0xE9)打面包屑定位 init 卡在哪一步。
案例三:换行后逐行右错,呈阶梯状
- 症状:多行输出每换一行就往右挪一点,整体斜着走,读不成句。
- 原因:
puts直接吐\n,而串口终端把\n只当"换行"不"回车",光标不回到行首。于是下一行从上一行结尾的正下方开始,逐行右移。 - 定位:肉眼即可识别——阶梯状错位几乎只此一个原因。
- 修复:
puts遇到\n先putc('\r')再putc('\n'),补回车。 - 防复发:串口文本输出统一走一个处理
\r\n的puts,别让调用方自己拼。
案例四:全局对象的构造函数没跑(对象库 / .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_array用KEEP(*(.init_array .init_array.*)),并正确导出起止符号。 - 防复发:任何含全局对象的内核,链接脚本里
.init_array一律 KEEP;配一条"全局对象构造"的内核测试专门盯它。
一句话总结
这一章的坑分两类:一类是算法边界(INT64_MIN)——靠把纯逻辑抽出来配 host 单测来抓;一类是工具链没接对(串口配置、\r\n、.init_array 被 KEEP)——靠常量化配置和专门的运行时测试来防。把"纯算法可 host 测"和"运行时靠 QEMU 测"这两条轨都铺好,后面堆功能才有底气。