fix(memhub): canary 校验现场补位宽诊断打印,新增 deep_detect 深诊断开关
- rc=1..6 现场打印高低 32 位分列的 canary 读回值与期望常量,现场可直接分辨 写入端高位为 0 与 比较端符号扩展 两类位宽不一致 - 新增 MemBuddy.deep_detect(构造形参默认 0):仅 --detection-deep 时启用每次分配全表探测与周期扫描,默认路径无额外开销
This commit is contained in:
1 parent
746ca689a9
commit
0c85e30739
1 file changed
+84
-7
@@ -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
|
||||
|
||||
Reference in new issue
Block a user