fix(memhub): canary 校验现场补位宽诊断打印,新增 deep_detect 深诊断开关

- rc=1..6 现场打印高低 32 位分列的 canary 读回值与期望常量,现场可直接分辨 写入端高位为 0 与 比较端符号扩展 两类位宽不一致

- 新增 MemBuddy.deep_detect(构造形参默认 0):仅 --detection-deep 时启用每次分配全表探测与周期扫描,默认路径无额外开销
This commit is contained in:
TermiNexus committed 2026-09-15 19:11:59 +08:00
1 parent 746ca689a9
commit 0c85e30739
1 file changed
+84 -7
+84 -7
View File
@@ -57,10 +57,15 @@ MEMBUDDY_DET_RECORD_SIZE: t.CDefine = 33554432
# 记录追加本身 O(1) 不受影响;损坏是持久性的,不依赖即时扫描,采样
# 不损失检测能力——2M 次 alloc 若每次全表 O(n) 扫描 = O(n²) 太慢
# (用户反馈 detection 模式过慢),采样把扫描次数降为 1/N。
# 运行期覆盖:MemBuddy 实例字段 deep_detect != 0(命令行 --detection-deep,
# 见 main.vp 的 strstr 取值与 MemBuddy(...) 传参)时忽略本周期、等价于 1
# (= 每次 alloc 都探测),判定点见本文件 alloc 内 L856(_det_probe)与
# L800-801(周期全表扫描)两处。默认 32:未开该开关时掩码 x & 31 与历史
# 实现逐字节一致(deep_detect 恒 0,见 L801 / L856 两处判定写法)。
MEMBUDDY_DET_PROBE_PERIOD: t.CDefine = 32
# [热点] 周期全表扫描总开关(编译期,默认 0=关闭;范式同 MEMBUDDY_THREAD_SAFE/
# MEMBUDDY_FILL_GUARD——常量比较在 HandleIf 处折叠,未取分支整体不生成代码)。
# MEMBUDDY_FILL_GUARD——常量比较不依赖运行期取值,默认 0 时该分支不执行)。
# alloc 每 MEMBUDDY_DET_PROBE_PERIOD 次分配会执行两项 O(n) 全扫描,其耗时全部
# 落在 MemBuddy.alloc 的 self 内(无外部调用可分摊):
# 1. 记录表重叠检测:while 遍历 _det_records[0 .. _det_count),_det_count ≈ 未回收
@@ -72,10 +77,14 @@ MEMBUDDY_DET_PROBE_PERIOD: t.CDefine = 32
# 采样块每 32 次才进入一次 ⇒ 单次采样块约 8.6e5 tick,alloc 函数体其余部分
# (O(1) 块头/canary 写入 + chk_stack/记录追加)占比可忽略;按 32 采样只把次数
# 降为 1/32,并不改变 O(n²/PERIOD) 的量级(n=_det_count)。
# ⇒ 默认 0:不生成该分支,alloc 回到纯 O(1) 分配路径。排查重复分配 / free list
# ⇒ 默认 0:alloc 回到纯 O(1) 分配路径(该分支不执行)。排查重复分配 / free list
# 残留 / 越界写时置 1(同时需 _memdbg_enable 才有第 2 项检查)。
# 置 1 时该分支与历史实现逐字节一致;该分支除诊断 printf+fflush+exit 外无任何
# 状态写入,故关闭只少诊断、不改变分配器语义。
# 运行期覆盖:MemBuddy 实例字段 deep_detect != 0(命令行 --detection-deep)可在
# 不改本编译期常量的前提下打开该扫描,判定点见本文件 alloc 内 L800 的
# "MEMBUDDY_PERIOD_SCAN != 0 or self.deep_detect != 0"(配合 L801 掩码短路
# → 每次 alloc 都跑该全扫描)。默认 0 = 与历史实现一致:该诊断分支不执行。
MEMBUDDY_PERIOD_SCAN: t.CDefine = 0
# [MEMDBG] 临时插桩(2026-09-04,定位 free list 重复 put / 悬空 free / alloc
@@ -394,6 +403,11 @@ class MemBuddy(MemManager):
# [DET] 分配检测(默认关,排查越界写/存活块覆盖时开启):
# detection=1 → alloc 前后 2 字节 0xCDCD 标记 + 记录表 + 每次 alloc 后探测
detection: t.CInt = 0
# 运行期重诊断开关(命令行 --detection-deep → main.vp strstr → 构造传参):
# deep_detect=1 ⇒ 忽略 MEMBUDDY_DET_PROBE_PERIOD(等价 1,每次 alloc 都探测)
# + 打开 MEMBUDDY_PERIOD_SCAN 的周期全表扫描。仅诊断用,极慢。
# 默认 0 = 与历史实现完全一致(范式同上方 detection 字段)。
deep_detect: t.CInt = 0
detection_size: t.CSizeT = MEMBUDDY_DET_RECORD_SIZE
_det_arena: t.CVoid | t.CPtr = None # 记录区基址(stdlib.malloc,供 realloc 扩容)
_det_records: t.CUInt64T | t.CPtr = None # 记录表(每 2×u64 = 1 条:{ptr, size})
@@ -401,10 +415,12 @@ class MemBuddy(MemManager):
_det_cap: t.CSizeT = 0 # 记录容量(条)
def __init__(self, base: t.CVoid | t.CPtr, size: t.CSizeT,
detection: t.CInt = 0):
detection: t.CInt = 0, deep_detect: t.CInt = 0):
self.base = base
self.size = size
self.detection = detection
# 默认形参(范式同上 detection):既有全部构造点(2 参 / 3 参)无需改动
self.deep_detect = deep_detect
# 显式赋值(不能依赖字段默认值——TPC 类字段默认值对 CDefine 常量
# 可能不生效,垃圾 detection_size → malloc(垃圾) 1-byte 记录区 →
# 扩容 realloc double-free,ASAN 已捕获)
@@ -776,9 +792,13 @@ class MemBuddy(MemManager):
# (分配重叠 + [MEMDBG] 残留探测),其余只追加记录(O(1))。损坏是
# 持久性的,不依赖即时扫描——若每次全表 O(n) 则整体 O(n²)。该采样块
# 即 MemBuddy.alloc 的 O(n) 热点(量级见 MEMBUDDY_PERIOD_SCAN 定义),
# 故整体挂在编译期总开关下,默认 0=不生成该分支。
if MEMBUDDY_PERIOD_SCAN != 0:
if (self._det_count & (MEMBUDDY_DET_PROBE_PERIOD - 1)) == 0:
# 故整体挂在编译期总开关下,默认 0 = 不执行该分支(历史行为)。
# 运行期重诊断(--detection-deep → self.deep_detect != 0)额外开此
# 扫描,并把采样掩码短路成"每次都扫":两个判定点(此处总开关 +
# 下方采样)各加一个 deep_detect 分支,deep_detect 恒 0 时两条
# 表达式与历史实现逐字节相同。
if MEMBUDDY_PERIOD_SCAN != 0 or self.deep_detect != 0:
if (self._det_count & (MEMBUDDY_DET_PROBE_PERIOD - 1)) == 0 or self.deep_detect != 0:
# 分配重叠即时检测:新块地址是否已在 active 记录(同一块重复
# 分配实锤——SlotsBugTest 7B buf 与 72B Token 同 ptr、cargs
# 只写 0/40 根因)。重叠 = MemBuddy free list 损坏(active 块
@@ -830,7 +850,10 @@ class MemBuddy(MemManager):
self._det_records[self._det_count * 2] = rec_ptr_d
self._det_records[self._det_count * 2 + 1] = t.CUInt64T(size)
self._det_count += 1
if self.detection != 0 and (self._det_count & (MEMBUDDY_DET_PROBE_PERIOD - 1)) == 0:
# 探测判定:detection 关闭时不进入(故未开 --detection 的默认路径
# 不额外读 deep_detect);开 detection 时 deep_detect != 0 → 每次
# alloc 都探测,否则退回历史采样(每 MEMBUDDY_DET_PROBE_PERIOD 次)。
if self.detection != 0 and ((self._det_count & (MEMBUDDY_DET_PROBE_PERIOD - 1)) == 0 or self.deep_detect != 0):
self._det_probe()
if self.detection != 0:
result = t.CVoid(base_ret_d + 2, t.CPtr)
@@ -1217,8 +1240,14 @@ class MemBuddy(MemManager):
block: t.CVoid | t.CPtr = t.CVoid(self.chk_stack[ci], t.CPtr)
if block is not None:
if t.CUInt64T(block) < t.CUInt64T(self.usable):
# [TEMP-DIAG] 现场:块地址低于池起点(%llu/%zu 格式先例见 L861/L1216)
stdio.printf("[MEM] 存活块损坏 rc=1(块地址低于 usable): block=%llu usable=%llu ci=%zu from_mark=%zu\n",
t.CUInt64T(block), t.CUInt64T(self.usable), ci, from_mark)
return 1
if t.CUInt64T(block) >= t.CUInt64T(self.usable) + self.usable_size:
# [TEMP-DIAG] 现场:块地址超出池末尾
stdio.printf("[MEM] 存活块损坏 rc=2(块地址超出池末尾): block=%llu usable=%llu ci=%zu from_mark=%zu\n",
t.CUInt64T(block), t.CUInt64T(self.usable), ci, from_mark)
return 2
stored: t.CVoid | t.CPtr = t.CVoid(c.Deref(t.CUInt64T(block, t.CPtr)), t.CPtr)
stored_val: t.CUInt64T = t.CUInt64T(stored)
@@ -1226,12 +1255,21 @@ class MemBuddy(MemManager):
# 活块:header 必须是 (order<<1)|1,order 合法且块对齐到该 order 大小
order: t.CInt = t.CInt(stored_val >> 1)
if order < 0:
# [TEMP-DIAG] 现场:order 为负(header 被写穿)
stdio.printf("[MEM] 存活块损坏 rc=3(order 为负): block=%llu order=%d ci=%zu from_mark=%zu\n",
t.CUInt64T(block), order, ci, from_mark)
return 3
if order > self.max_order:
# [TEMP-DIAG] 现场:order 超 max_order(%d 先例见 L861 max_order=%d)
stdio.printf("[MEM] 存活块损坏 rc=4(order 超 max_order): block=%llu order=%d max_order=%d ci=%zu from_mark=%zu\n",
t.CUInt64T(block), order, self.max_order, ci, from_mark)
return 4
offset: t.CSizeT = t.CUInt64T(block) - t.CUInt64T(self.usable)
bs: t.CSizeT = _block_size_at_order(order)
if offset % bs != 0:
# [TEMP-DIAG] 现场:块未对齐到该 order 的块大小
stdio.printf("[MEM] 存活块损坏 rc=5(块未对齐 order 大小): block=%llu order=%d bs=%zu offset=%zu ci=%zu from_mark=%zu\n",
t.CUInt64T(block), order, bs, offset, ci, from_mark)
return 5
# 块尾 canary 校验:越界写穿数据区会破坏块尾哨兵,
# 立即定位到越界写发生的文件(崩溃点漂移的关键哨兵)。
@@ -1240,6 +1278,45 @@ class MemBuddy(MemManager):
c.Deref(t.CUInt64T(t.CUInt64T(block) + bs - MEMBUDDY_CANARY, t.CPtr), t.CPtr),
t.CPtr)
if t.CUInt64T(can_stored) != t.CUInt64T(MEMBUDDY_CANARY_PATTERN):
# [TEMP-DIAG] 越界写现场:块尾 canary 被写穿(rc=6 的判据)。
# 原实现只回 rc(int),调用方拿不到块/阶/实际值 → 无法定位。
# 此处补齐现场字段(全部为当前作用域既有变量,不新增取值路径):
# block/order/bs 见上方 L1241-1253 计算;ci/from_mark 为
# 校验下标与窗口起点;canary vs 期望均以十六进制打印。
# printf 形式与格式符沿用本文件既有先例:
# stdio.printf(...) / stdio.fflush(None) 见 L800-802、L816-817
# %zu ← t.CSizeT(L1216 from_mark/chk_depth、L765 chk_cap/depth)
# %llu ← t.CUInt64T(L800 size/ptr、L861 pool_base)
# %d ← t.CInt(L816 dbg_order、L822)
# %llx ← t.CUInt64T 十六进制(L917 header=0x%llX)
stdio.printf(
"[MEM] canary 损坏: block=%llu order=%d bs=%zu ci=%zu from_mark=%zu canary=0x%llx 期望=0x%llx\n",
t.CUInt64T(block), order, bs, ci, from_mark,
t.CUInt64T(can_stored), t.CUInt64T(MEMBUDDY_CANARY_PATTERN))
# [TEMP-DIAG] 位宽诊断:把上面同一对值(canary 实际读回值 / 期望
# 常量)再按高 32 位、低 32 位分别打印,用于在现场直接分辨:
# (i) 写入端只落了低 32 位(高 32 位为 0):canary_hi == 0x0;
# (ii) 比较端把 32 位常量做了符号扩展:期望_hi == 0xffffffff;
# 若 canary_hi 与 期望_hi 不一致,即"同一个编译期常量被两种不同
# 扩展方式抬到 64 位"的代码生成缺陷实锤(既有字段与 rc 不变)。
# 写法先例(均为本文件既有):
# t.CUInt64T 局部变量 + 移位:L806/L1056 ff_d: t.CUInt64T = t.CUInt64T(1) << 63
# CUInt64T 域内算术:L916 (rs & (t.CUInt64T(1) << 63))、L919 rs | ...
# %llx ← t.CUInt64T 十六进制:上行 canary=0x%llx / L932 header=0x%llX
# 低 32 位用"减去高半部"求得,不引入 >0x7FFFFFFF 的大字面量
# (本仓库踩过大字面量被解析截断的坑,见本文件 L120-122)。
can_d: t.CUInt64T = t.CUInt64T(can_stored)
exp_d: t.CUInt64T = t.CUInt64T(MEMBUDDY_CANARY_PATTERN)
can_hi: t.CUInt64T = can_d >> 32
can_lo: t.CUInt64T = can_d - (can_hi << 32)
exp_hi: t.CUInt64T = exp_d >> 32
exp_lo: t.CUInt64T = exp_d - (exp_hi << 32)
stdio.printf(
"[MEM] canary 位宽诊断: canary_lo=0x%llx canary_hi=0x%llx 期望_lo=0x%llx 期望_hi=0x%llx\n",
can_lo, can_hi, exp_lo, exp_hi)
# 调用方(Phase1Main)随后打印并 exit;此处先 fflush 保证
# 重定向下现场不丢(同 [DET] 终止路径,本文件 L802/L818)。
stdio.fflush(None)
return 6
ci += 1
return 0