diff --git a/memhub.vp b/memhub.vp index 76cf6c5..67a9e59 100644 --- a/memhub.vp +++ b/memhub.vp @@ -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