内存检测凭什么"看见"越界?——影子内存的账本逻辑
系列二 · 第 3 篇
上篇末尾留了个问号:msSanitizer 凭什么"看见"了那多出来的 768 字节?答案是它给算子的内存建了一份平行的"账本",专业名字叫影子内存(Shadow Memory)。
先说原理。越界也好、未初始化也好,本质都是"某块内存处于不该被访问的状态"。但真实内存里存的只有数据本身,一个字节是 0 还是 1,并不会告诉你"我还没被写过"或者"这里已经越界了"。工具要做出判断,就得额外记一份状态——这就是影子内存:真实内存的每个字节,在账本里都有一格,专门记录它的状态。
这套思路不是昇腾首创。CPU 圈的 AddressSanitizer 早就这么干,有篇经典论文叫《How to Shadow Every Byte of Memory Used by a Program》(如何给程序用到的每个字节都配上影子),名字说的就是方法本身。msSanitizer 继承了这份巧思,又为昇腾硬件加了点私货。
在 msSanitizer 里,每个影子字节被拆成两部分:低 2 位记录状态,高 6 位记录"这个字节当前归哪个核管"。状态一共四种,正好塞进 2 位:不可访问(NOACCESS),碰了就是越界;可访问但没写过(UNDEFINED),读了就是未初始化;可访问且已写入(DEFINED),正常状态;还有被踩塌的(ERROR),这个下一篇见。
账本怎么用?逻辑朴素得很。写内存时,把对应影子格置成 DEFINED,相当于记一笔"这里来过货了";读内存时,翻一下账本——如果是 UNDEFINED,说明你在读一件从没被写入的东西;如果是 NOACCESS,说明你伸手进了没分配的地盘。越界、未初始化,一次判明。
看到这你可能要问:上百 GB 的 GM 内存,每个字节都配一格账本,光账本就得额外吃掉等量的空间,这谁受得了?这正是工程上的精妙处。真实内存绝大部分是"整片同状态"的——一大块没分配的,一大块刚写完的。所以账本不做成平铺的,而做成多级目录:先按大块建索引,整块状态一致的,一格就记一片;只有真正被用到、状态开始"花"起来的区域,才展开成逐字节的细账。GM 用了三级目录,片上内存两级,大头开销就这么省了下来。
这套账本,是内存检测的地基。越界和未初始化都靠它,但它只回答"某个字节现在什么状态"。还有一类问题——内存的生命周期,什么时候被谁申请、该不该释放、用完没有——得靠另一套台账。那正是下一篇要聊的东西。
- 点赞
- 收藏
- 关注作者
评论(0)