未初始化检测——追着"从没被写过"这件事不放
系列二 · 第 6 篇
写过算子的同学都知道,拿到一块新内存,里面的内容是不确定的——可能是上一轮算子的残留,也可能是随机垃圾。要是不小心读了这种内存,读出来的值就是隐藏的地雷。msSanitizer 的 initcheck,就是专门抓"读了从没写过的东西"的。
它的底气,还是来自第 3 篇那张影子账本。回想一下,账本里每个字节的状态有几个关键取值:NOACCESS(不可访问)、UNDEFINED(可访问但没写过)、DEFINED(写过,已初始化)。未初始化检测的逻辑,一句话就能说清——一块内存刚分配出来,账本标成 UNDEFINED,意思是"这片地方空着,还没人写过";谁往里写了数据,账本就改成 DEFINED;谁要读它,工具先翻账本,一看是 UNDEFINED,抓个正着,这就是"未初始化读"。
所以 initcheck 本质上是影子内存里"UNDEFINED → DEFINED"这条状态流转的守望者。听起来简单,真做起来有个绕不开的坎:顺序。
想这样一个场景:核 A 写了某块 GM,核 B 之后读它——这是正常的生产者-消费者协作,不是 bug。可工具收到的读写记录是乱序落盘、按 block 分头报上来的,如果只看"有写、有读",很容易把这种正常协作误判成"读了没写的东西"。要分辨"读发生在写之前还是之后",得先把事件按真实执行顺序排好——这正是工具里那个"流水线回放器"(PipelineReplayer)的职责。它像放录像带一样,把乱序的记录按同步关系还原成真实顺序,再交给检测。
这也解释了为什么 initcheck 比纯越界检测更"重":越界不看顺序,谁碰了不该碰的边界一眼便知;未初始化却必须弄清先后,多一整套回放的功夫。
最后有两个边界得说清楚。其一,initcheck 只能发现"从没被写过就读"的内存;只要一块内存被写过、哪怕是脏数据,工具也认定它"已初始化",不会报警——它要的是"定义"本身,不关心写得对不对。其二,别把 initcheck 和 registercheck 弄混:前者管内存字节,后者管硬件特殊寄存器是不是复位到了默认值,完全是两码事,registercheck 我们放到系列二的最后再聊。
- 点赞
- 收藏
- 关注作者
评论(0)