Files
TransPyC/includes/memhub.vp
T

1162 lines
58 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import t, c
from stdint import *
import string
import atom
import viperio
import stdio
import stdlib
# ============================================================
# memhub - Unified memory management library
# MemManager (polymorphic base class, vtable)
# ├─ MemPool (arena bump allocator, no support for individual free)
# ├─ MemSlab (fixed-size block pool, alloc/free tracked via bitmap)
# └─ MemBuddy (buddy system, supports alloc/free/realloc with coalescing)
# All subclasses override alloc/free/reset via vtable, invokable polymorphically through MemManager pointer
# ============================================================
MEMHUB_ALIGN: t.CDefine = 8
# MemSlab bitmap constants
MEMSLAB_MIN_BLOCK: t.CDefine = 16
MEMSLAB_BITMAP_BYTES: t.CDefine = 256
# MemBuddy buddy constants
MEMBUDDY_MIN_BLOCK: t.CDefine = 32
MEMBUDDY_MAX_ORDERS: t.CDefine = 32
MEMBUDDY_HEADER_SIZE: t.CDefine = 8
# 块尾 canary:alloc 时在块尾(用户数据区之外)写 8 字节哨兵,
# check_alive_blocks 校验——越界写穿数据区会破坏 canary,把
# "崩溃点漂移/事发地遥远"变成"确定性定位到越界写发生的文件"
MEMBUDDY_CANARY: t.CDefine = 8
# 分配守卫(诊断,SlotsBugTest 二代编 list 头覆盖排查):
# alloc 时把用户数据区填充 0xCD 哨兵。若对象初始化(list.__init__ 等)
# 未执行,其字段保持 0xCDCD...,CTORDBG/VDBG 打印字段时一眼可辨
# "未初始化(0xCDCD)" 与 "被覆盖(其他值)"。0=关闭(性能),1=开启。
MEMBUDDY_FILL_GUARD: t.CDefine = 1
MEMBUDDY_FILL_PATTERN: t.CDefine = 0xCD
# [DET] 分配检测(alloc 前后 2 字节 0xCDCD 标记 + 记录表探测越界写):
# 检测模式(MemBuddy(..., detection=1))下每次 alloc 布局为
# [0xCDCD][请求大小内存][0xCDCD](实际比请求多 4 字节)
# 返回指针 = 实际用户区 + 2(前标记之后);回收时向前偏移 2 字节。
# 每次 alloc 后探测记录表全部后标记(ptr+size 处)——被破坏 = 越界写,
# 立即汇报 ptr/size 并终止(定位"存活块被部分覆盖"的写入者,
# 如 SlotsBugTest KwEntry cargs 只写 Data/IterIndex 的块归属)。
MEMBUDDY_DET_MARKER: t.CDefine = 0xCDCD
# 检测记录区默认大小(32MB,每条记录 {ptr u64, size u64} = 16B)
MEMBUDDY_DET_RECORD_SIZE: t.CDefine = 33554432
# 采样周期(2 的幂):每 N 次 alloc 做一次全表扫描(分配重叠 + 探测)。
# 记录追加本身 O(1) 不受影响;损坏是持久性的,不依赖即时扫描,采样
# 不损失检测能力——2M 次 alloc 若每次全表 O(n) 扫描 = O(n²) 太慢
# (用户反馈 detection 模式过慢),采样把扫描次数降为 1/N。
MEMBUDDY_DET_PROBE_PERIOD: t.CDefine = 32
# [MEMDBG] 临时插桩(2026-09-04,定位 free list 重复 put / 悬空 free / alloc
# 重复出块;定位后整体移除,勿长期保留)。改动点:
# 1. alloc:采样分支内校验"新出块仍残留于同 order free list";
# 2. free :归还前校验"同 order free list 已含本块"(double-put 实锤);
# 3. free :无效/悬空指针(池内但 header 偶数=已释放)不再静默忽略;
# 4. 既有 [DET]/[MEM] 终止路径补 stdio.fflush(None)(stdout 重定向丢输出)。
MEMBUDDY_DBG: t.CDefine = 1
# [MEMDBG] 运行时开关:默认开(随 MEMBUDDY_DBG 编译默认值)。
# 需要让负向用例(如 BuddyTest 故意双重 free,期望"静默拒绝")不终止时,
# 可在运行期关闭(沿用 ast._mbuddy/sys._mbuddy 式跨模块全局写法):
# import memhub
# memhub._memdbg_enable = 0
_memdbg_enable: int = MEMBUDDY_DBG
def _canary_pattern() -> t.CUInt64T:
"""块尾 canary 值(0xA5A5A5A5A5A5A5A5,64 位)"""
lo: t.CUInt64T = t.CUInt64T(0xA5A5A5A5)
return (lo << 32) | lo
def _align_up(val: t.CSizeT, align: t.CSizeT) -> t.CSizeT:
if align == 0: return val
return (val + align - 1) & ~(align - 1)
def _largest_pow2_le(val: t.CSizeT) -> t.CSizeT:
if val == 0: return 0
p: t.CSizeT = 1
while p * 2 <= val:
p = p * 2
return p
def _block_size_at_order(order: t.CInt) -> t.CSizeT:
bs: t.CSizeT = MEMBUDDY_MIN_BLOCK
i: t.CInt
for i in range(order):
bs = bs << 1
return bs
# ============================================================
# MemManager - Polymorphic base class
# @t.CVTable explicit annotation to ensure recognition as vtable class during cross-module imports
# (HandlesImports only detects IsCVTable via decorator; automatic inference is unavailable across modules)
# ============================================================
@t.CVTable
class MemManager:
__provides__: list[str] = ['__memmgr__']
base: t.CPtr # Starting address of memory region
size: t.CSizeT # Total size of memory region
def __init__(self, base: t.CPtr, size: t.CSizeT):
self.base = base
self.size = size
# === Virtual functions (overridden by subclasses) ===
def alloc(self, size: t.CSizeT) -> t.CPtr:
# Default: allocation not supported
return None
def free(self, ptr: t.CPtr) -> int:
# Default: no operation
return 0
def reset(self) -> int:
# Default: no operation
return 0
# === Concrete methods (invoke virtual functions for automatic polymorphic dispatch) ===
def calloc(self, count: t.CSizeT, size: t.CSizeT) -> t.CPtr:
total: t.CSizeT = count * size
ptr: t.CVoid | t.CPtr = self.alloc(total)
if ptr is not None:
string.memset(ptr, 0, total)
return ptr
def realloc(self, ptr: t.CVoid | t.CPtr, new_size: t.CSizeT) -> t.CVoid | t.CPtr:
if ptr is None:
return self.alloc(new_size)
if new_size == 0:
self.free(ptr)
return None
new_ptr: t.CVoid | t.CPtr = self.alloc(new_size)
if new_ptr is None:
return ptr
# Base class cannot track original allocation size; data will not be copied. Subclasses may override to preserve data
self.free(ptr)
return new_ptr
def __enter__(self) -> 'MemManager' | t.CPtr:
return self
def __exit__(self):
self.reset()
def alloc_buf(self, capacity: t.CSizeT) -> viperio.Buf | t.CPtr:
buf: viperio.Buf | t.CPtr = self.alloc(viperio.Buf.__sizeof__())
if buf is None:
return None
data_ptr: t.CVoid | t.CPtr = self.alloc(capacity)
if data_ptr is None:
return None
buf.__before_init__()
buf.__init__(t.CChar(t.CUInt64T(data_ptr), t.CPtr), capacity)
return buf
# ============================================================
# MemPool - Arena bump allocator
# Individual free operations are unsupported; reset reclaims all memory at once
# ============================================================
class MemPool(MemManager):
offset: t.CSizeT # Bump pointer offset
high_water: t.CSizeT # High-water mark
def __init__(self, base: t.CVoid | t.CPtr, size: t.CSizeT):
self.base = base
self.size = size
self.offset = 0
self.high_water = 0
def alloc(self, size: t.CSizeT) -> t.CVoid | t.CPtr:
if size == 0: return None
aligned: t.CSizeT = _align_up(size, MEMHUB_ALIGN)
if self.offset + aligned > self.size:
stdio.printf("\n[OOM] 内存不足 (Out of Memory)\n")
stdio.printf("[OOM] 尝试分配: %zu 字节,失败\n", size)
stdio.printf("[OOM] MemPool: total=%zu used=%zu (bump allocator, 无释放)\n",
self.size, self.offset)
stdio.printf("[OOM] 说明: 内存池已耗尽。可能原因: (1) 池大小不足,需增大 POOL_SIZE; (2) 存在内存泄漏。\n")
stdlib.exit(1)
ptr: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(self.base) + self.offset, t.CPtr)
self.offset += aligned
if self.offset > self.high_water:
self.high_water = self.offset
return ptr
def free(self, ptr: t.CVoid | t.CPtr) -> t.CInt:
# Bump allocator does not support individual free operations
return 1
def reset(self) -> t.CInt:
self.offset = 0
self.high_water = 0
return 1
# ============================================================
# MemSlab - Fixed-size block pool
# Bitmap stored at the start of arena; subsequent area split into block_size units managed by free list
# ============================================================
class MemSlab(MemManager):
block_size: t.CSizeT # Block size (aligned)
block_count: t.CSizeT # Total block count
used_count: t.CSizeT # Used block count
free_list: t.CVoid | t.CPtr # Free list head
alloc_map: t.CUInt8T | t.CPtr # Allocation bitmap (at start of arena)
alloc_map_size: t.CSizeT # Bitmap byte count
usable: t.CVoid | t.CPtr # Usable block area start (after bitmap)
usable_size: t.CSizeT # Usable block area size
def __init__(self, base: t.CVoid | t.CPtr, size: t.CSizeT, block_size: t.CSizeT):
self.base = base
self.size = size
self.block_size = 0
self.block_count = 0
self.used_count = 0
self.free_list = None
self.alloc_map = None
self.alloc_map_size = 0
self.usable = None
self.usable_size = 0
if base is None: return
bs: t.CSizeT = _align_up(block_size, MEMHUB_ALIGN)
if bs < MEMSLAB_MIN_BLOCK: bs = MEMSLAB_MIN_BLOCK
map_bytes: t.CSizeT = _align_up(MEMSLAB_BITMAP_BYTES, MEMHUB_ALIGN)
if size < map_bytes + bs: return
self.alloc_map = base
self.alloc_map_size = map_bytes
self.usable = t.CVoid(t.CUInt64T(base) + map_bytes, t.CPtr)
self.usable_size = size - map_bytes
self.block_size = bs
# Clear bitmap
idx: t.CSizeT = 0
while idx < map_bytes:
self.alloc_map[idx] = 0
idx += 1
self.block_count = self.usable_size / bs
if self.block_count == 0: return
# Construct free list: the first 8 bytes of each block store pointer to next block
self.free_list = None
i: t.CSizeT = 0
while i < self.block_count:
block: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(self.usable) + i * bs, t.CPtr)
c.DerefAs(block, self.free_list)
self.free_list = block
i += 1
def alloc(self, size: t.CSizeT) -> t.CVoid | t.CPtr:
# Slab mode: allocate one block only if requested size <= block_size
if self.free_list is None:
stdio.printf("\n[OOM] 内存不足 (Out of Memory)\n")
stdio.printf("[OOM] 尝试分配: %zu 字节,失败(free_list 已空)\n", size)
stdio.printf("[OOM] MemSlab: block_size=%zu block_count=%zu used=%zu\n",
self.block_size, self.block_count, self.used_count)
stdio.printf("[OOM] 说明: 内存池已耗尽。可能原因: (1) 池大小不足,需增大 POOL_SIZE; (2) 存在内存泄漏。\n")
stdlib.exit(1)
if size > self.block_size:
# 超块大小请求 = 调用方错误:优雅失败返回 NULL(类 malloc 语义,调用方自行判断)。
# 与 free_list 耗尽(真正的 OOM,打印后 exit(1))区分开。
stdio.printf("[WARN] MemSlab: 请求 %zu 字节超过 block_size=%zu,分配失败返回 NULL;应改用 MemBuddy/MemPool。\n",
size, self.block_size)
return None
block: t.CVoid | t.CPtr = self.free_list
self.free_list = t.CVoid(c.Deref(t.CUInt64T(block, t.CPtr)), t.CPtr)
self.used_count += 1
idx: t.CSizeT = (t.CUInt64T(block) - t.CUInt64T(self.usable)) / self.block_size
self.alloc_map[idx / 8] = self.alloc_map[idx / 8] | t.CUInt8T(1 << (idx % 8))
return block
def free(self, ptr: t.CVoid | t.CPtr) -> t.CInt:
if ptr is None: return 0
p: t.CUInt64T = t.CUInt64T(ptr)
if p < t.CUInt64T(self.usable): return 0
if p >= t.CUInt64T(self.usable) + self.block_count * self.block_size: return 0
if (p - t.CUInt64T(self.usable)) % self.block_size != 0: return 0
idx: t.CSizeT = (p - t.CUInt64T(self.usable)) / self.block_size
byte_idx: t.CSizeT = idx / 8
bit_idx: t.CInt = t.CInt(idx % 8)
if (self.alloc_map[byte_idx] >> bit_idx) & 1 == 0: return 0
self.alloc_map[byte_idx] = self.alloc_map[byte_idx] & t.CUInt8T(~(1 << bit_idx))
c.DerefAs(ptr, self.free_list)
self.free_list = ptr
self.used_count -= 1
return 1
def reset(self) -> t.CInt:
self.used_count = 0
self.free_list = None
i: t.CSizeT = 0
while i < self.block_count:
block: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(self.usable) + i * self.block_size, t.CPtr)
c.DerefAs(block, self.free_list)
self.free_list = block
if i / 8 < self.alloc_map_size:
self.alloc_map[i / 8] = 0
i += 1
return 1
# ============================================================
# MemBuddy - Buddy system allocator
# Free list head array stored at arena start; subsequent memory managed in power-of-two sized blocks
# Thread-safe (spinlock enabled), supports block coalescing
#
# Checkpoint 支持(供编译器等长任务回收阶段性分配):
# mark() → 返回当前分配水位(不分配内存)
# reset_to(m) → 释放水位 m 之后分配的所有块(回收到 free list)
# 记录栈从 usable 顶部预留 _CHK_RESERVE 字节,不参与 buddy 分配。
# ============================================================
class MemBuddy(MemManager):
# Override parent __provides__: MemBuddy provides both __mbuddy__ and __memmgr__.
# Within `with MemBuddy(...)` context, classes (e.g. _str) with __requires__=['__mbuddy__']
# can be automatically injected via _find_provider
__provides__: list[str] = ['__mbuddy__', '__memmgr__']
max_order: t.CInt # Maximum order
free_lists: t.CUInt64T | t.CPtr # Free list head array (located at arena start)
lock_val: t.CVolatile | t.CInt # Spinlock
usable: t.CVoid | t.CPtr # Start of usable memory region (skips free_lists)
usable_size: t.CSizeT # Size of usable memory (power of two)
chk_stack: t.CUInt64T | t.CPtr # Checkpoint 分配记录栈(存 block 地址)
chk_cap: t.CSizeT # 记录栈容量(条目数)
chk_depth: t.CSizeT # 当前记录深度
chk_base: t.CVoid | t.CPtr # 记录栈起始地址(由 stdlib.malloc 分配,独立于 buddy 区)
big_alloc_count: t.CSizeT # ≥8KB 大分配计数(OOM 诊断:定位大缓冲反复分配)
# [DET] 分配检测(默认关,排查越界写/存活块覆盖时开启):
# detection=1 → alloc 前后 2 字节 0xCDCD 标记 + 记录表 + 每次 alloc 后探测
detection: 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})
_det_count: t.CSizeT = 0 # 已记录条数
_det_cap: t.CSizeT = 0 # 记录容量(条)
def __init__(self, base: t.CVoid | t.CPtr, size: t.CSizeT,
detection: t.CInt = 0):
self.base = base
self.size = size
self.detection = detection
# 显式赋值(不能依赖字段默认值——TPC 类字段默认值对 CDefine 常量
# 可能不生效,垃圾 detection_size → malloc(垃圾) 1-byte 记录区 →
# 扩容 realloc double-free,ASAN 已捕获)
self.detection_size = MEMBUDDY_DET_RECORD_SIZE
self._det_arena = None
self._det_records = None
self._det_count = 0
self._det_cap = 0
self.max_order = 0
self.free_lists = None
self.lock_val = 0
self.usable = None
self.usable_size = 0
self.chk_stack = None
self.chk_cap = 0
self.chk_depth = 0
self.chk_base = None
self.big_alloc_count = 0
self.lock_val = 0
fl_bytes: t.CSizeT = (MEMBUDDY_MAX_ORDERS + 1) * 8
self.free_lists = base
# Initialize all free list heads to be 0
i: t.CInt
for i in range(MEMBUDDY_MAX_ORDERS + 1):
self.free_lists[i] = 0
if size <= fl_bytes:
self.usable = None
self.usable_size = 0
self.max_order = 0
return
remaining: t.CSizeT = size - fl_bytes
# 注意:不能在 __init__ 里预留 checkpoint 记录栈!
# 记录栈改为 mark() 时从 buddy 池懒分配(见 mark())。
#
# 非 2 的幂 arena 支持:主池 1.2GB(非 2 幂)不再被
# _largest_pow2_le 砍成 1GB 浪费 0.2GB。整个区域按二进制
# 分解拆成若干个 2 的幂空闲块压入对应 free list;buddy 合并
# 按 offset^bs 匹配,跨分解边界的块因 order 不匹配自然停止
# 合并,不会破坏 buddy 一致性。remaining 恰为 2 的幂时分解
# 结果与原实现(单个 max_order 块)完全等价。
max_blk: t.CSizeT = _largest_pow2_le(remaining)
if max_blk < MEMBUDDY_MIN_BLOCK:
self.usable = None
self.usable_size = 0
self.max_order = 0
return
self.usable = t.CVoid(t.CUInt64T(base) + fl_bytes, t.CPtr)
# 计算 max_order: log2(max_blk / MIN_BLOCK)
self.max_order = 0
bs: t.CSizeT = MEMBUDDY_MIN_BLOCK
while bs < max_blk:
bs = bs << 1
self.max_order += 1
# 二进制分解:把 [usable, usable+remaining) 拆成 2 的幂空闲块。
# 尾部碎片(< MIN_BLOCK)不入 free list;usable_size 记实际
# 可分配字节(used/free 统计不含碎片)。
pos: t.CSizeT = 0
rem: t.CSizeT = remaining
used_area: t.CSizeT = 0
while rem >= MEMBUDDY_MIN_BLOCK:
blk: t.CSizeT = _largest_pow2_le(rem)
o: t.CInt = 0
ob: t.CSizeT = MEMBUDDY_MIN_BLOCK
while ob < blk:
ob = ob << 1
o += 1
self._fl_push(o, t.CVoid(t.CUInt64T(self.usable) + pos, t.CPtr))
pos += blk
used_area += blk
rem -= blk
self.usable_size = used_area
# 分配记录区:stdlib.malloc 独立分配(不占用 buddy 区,
# 与 chk_stack 同策略)。容量 = detection_size // 16(每条 {ptr,size})。
# 2026-09-04:记录+重叠检测从 detection 解耦——非 detection 也分配
# 记录区并启用同地址重复分配检测(不做前后标记、不改 alloc 布局),
# 定位 free-list/重复分配类损坏(memhub 翻译早期容器 Count 槽被写坏)。
det_arena: t.CVoid | t.CPtr = t.CVoid(
stdlib.malloc(self.detection_size), t.CPtr)
if det_arena is not None:
self._det_arena = det_arena
self._det_records = (t.CUInt64T | t.CPtr)(t.CVoid(det_arena, t.CPtr))
self._det_cap = self.detection_size // 16
self._det_count = 0
# === Checkpoint support ===
def mark(self) -> t.CSizeT:
"""返回当前分配水位(checkpoint)
记录栈用 stdlib.malloc 分配(独立于 buddy 区,避免占用 buddy 池、
避免 reset_to 误释放栈自身,也避免栈内存与 buddy 块重叠)。
初始容量 2M 条目(16MB):单文件翻译(parse AST + 生成 IR)可产生
几十万次 alloc,64K 初始容量会在首个大文件就触发 realloc 扩容,
扩容失败时块不再记录 → reset_to 永久 no-op → 所有分配泄漏 →
1GB 主池 OOM(NewAstTest Phase2 根因)。
"""
if self.chk_stack is None:
cap: t.CSizeT = 2 * 1024 * 1024
base: t.CVoid | t.CPtr = t.CVoid(stdlib.malloc(cap * 8), t.CPtr)
if base is None:
return 0
self.chk_base = base
self.chk_stack = (t.CUInt64T | t.CPtr)(t.CVoid(base, t.CPtr))
self.chk_cap = cap
self.chk_depth = 0
return self.chk_depth
def reset_to(self, mark: t.CSizeT):
"""释放水位 mark 之后分配的所有块,回收到 free list
栈中每个 block:若 header 仍标记为已分配(奇数),_coalesce 归还;
若已被单独 free(header==0),跳过避免重复归还。
"""
if self.chk_stack is None:
return
if mark > self.chk_depth:
# mark 大于当前深度:调用方 mark 值异常(可能读错字段/跨实例),
# reset 将 no-op → 之后分配全泄漏 → 1GB 主池 OOM。fail-fast 暴露。
stdio.printf(
"[MEM] reset_to mark=%zu > chk_depth=%zu(mark 异常,reset 失效,终止)\n",
mark, self.chk_depth)
return
if mark >= self.chk_depth:
return
self._lock()
while self.chk_depth > mark:
self.chk_depth -= 1
block: t.CVoid | t.CPtr = t.CVoid(self.chk_stack[self.chk_depth], t.CPtr)
if block is None:
continue
stored: t.CVoid | t.CPtr = t.CVoid(c.Deref(t.CUInt64T(block, t.CPtr)), t.CPtr)
stored_val: t.CUInt64T = t.CUInt64T(stored)
if (stored_val & 1) == 0:
continue # 已被单独 free,跳过
order: t.CInt = t.CInt(stored_val >> 1)
if order < 0 or order > self.max_order:
continue
self._coalesce(block, order)
# [DET] reset-aware:记录表与 chk 栈等长(每次 alloc 各追加一条,
# free 只置标志不删),截断 chk_depth 必须同步截断 _det_count,
# 否则 reset 后旧记录仍标 active → 下一轮 alloc 复用同一块被
# 误报为"分配重叠/越界写"(SlotsBugTest 7B buf + 72B Token 误报根因)。
# 2026-09-04 记录表已与 detection 解耦(detection=0 也启用记录+重叠
# 检测),截断条件不能挂 detection——否则检测模式下 reset_to 后
# 旧记录残留 active,复用块被误报重叠(记录[647] sz=72 误报例)。
if self._det_records is not None:
self._det_count = mark
self._unlock()
def reset_all(self):
"""回收全部已分配块(等价 reset_to(0))"""
if self.chk_stack is None:
return
self.reset_to(0)
# === Free list operations ===
def _fl_push(self, order: t.CInt, block: t.CVoid | t.CPtr):
old_head: t.CUInt64T = self.free_lists[order]
c.DerefAs(block, t.CVoid(old_head, t.CPtr))
self.free_lists[order] = t.CUInt64T(block)
def _fl_pop(self, order: t.CInt) -> t.CVoid | t.CPtr:
head_val: t.CUInt64T = self.free_lists[order]
if head_val == 0:
return None
block: t.CVoid | t.CPtr = t.CVoid(head_val, t.CPtr)
next_ptr: t.CVoid | t.CPtr = t.CVoid(c.Deref(t.CUInt64T(block, t.CPtr)), t.CPtr)
self.free_lists[order] = t.CUInt64T(next_ptr)
return block
def _fl_find_and_remove(self, order: t.CInt, target: t.CVoid | t.CPtr) -> t.CInt:
head_val: t.CUInt64T = self.free_lists[order]
if head_val == 0:
return 0
head: t.CVoid | t.CPtr = t.CVoid(head_val, t.CPtr)
if t.CUInt64T(head) == t.CUInt64T(target):
next_ptr: t.CVoid | t.CPtr = t.CVoid(c.Deref(t.CUInt64T(head, t.CPtr)), t.CPtr)
self.free_lists[order] = t.CUInt64T(next_ptr)
return 1
prev: t.CVoid | t.CPtr = head
cur: t.CVoid | t.CPtr = t.CVoid(c.Deref(t.CUInt64T(head, t.CPtr)), t.CPtr)
while t.CUInt64T(cur) != 0:
if t.CUInt64T(cur) == t.CUInt64T(target):
next_ptr: t.CVoid | t.CPtr = t.CVoid(c.Deref(t.CUInt64T(cur, t.CPtr)), t.CPtr)
c.DerefAs(prev, next_ptr)
return 1
prev = cur
cur = t.CVoid(c.Deref(t.CUInt64T(cur, t.CPtr)), t.CPtr)
return 0
# === Buddy system core ===
def _buddy_of(self, block: t.CVoid | t.CPtr, order: t.CInt) -> t.CVoid | t.CPtr:
offset: t.CSizeT = t.CUInt64T(block) - t.CUInt64T(self.usable)
bs: t.CSizeT = _block_size_at_order(order)
buddy_offset: t.CSizeT = offset ^ bs
return t.CVoid(t.CUInt64T(self.usable) + buddy_offset, t.CPtr)
def _order_for_size(self, size: t.CSizeT) -> t.CInt:
order: t.CInt = 0
bs: t.CSizeT = MEMBUDDY_MIN_BLOCK
while bs < size:
bs = bs << 1
order += 1
return order
def _split_to_order(self, to_order: t.CInt) -> t.CVoid | t.CPtr:
found_order: t.CInt = to_order
while found_order <= self.max_order:
if self.free_lists[found_order] != 0:
break
found_order += 1
if found_order > self.max_order:
return None
block: t.CVoid | t.CPtr = self._fl_pop(found_order)
while found_order > to_order:
found_order -= 1
bs: t.CSizeT = _block_size_at_order(found_order)
buddy: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(block) + bs, t.CPtr)
self._fl_push(found_order, buddy)
return block
def _coalesce(self, block: t.CVoid | t.CPtr, order: t.CInt):
while order < self.max_order:
buddy: t.CVoid | t.CPtr = self._buddy_of(block, order)
found: t.CInt = self._fl_find_and_remove(order, buddy)
if found == 0:
break
if t.CUInt64T(buddy) < t.CUInt64T(block):
block = buddy
order += 1
self._fl_push(order, block)
def _is_valid_ptr(self, ptr: t.CVoid | t.CPtr) -> t.CInt:
if ptr is None: return 0
if self.usable is None: return 0
block: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(ptr) - MEMBUDDY_HEADER_SIZE, t.CPtr)
if t.CUInt64T(block) < t.CUInt64T(self.usable): return 0
if t.CUInt64T(block) >= t.CUInt64T(self.usable) + self.usable_size: return 0
offset: t.CSizeT = t.CUInt64T(block) - t.CUInt64T(self.usable)
if offset % MEMBUDDY_MIN_BLOCK != 0: return 0
stored: t.CVoid | t.CPtr = t.CVoid(c.Deref(t.CUInt64T(block, t.CPtr)), t.CPtr)
stored_val: t.CUInt64T = t.CUInt64T(stored)
if (stored_val & 1) == 0: return 0
order: t.CInt = t.CInt(stored_val >> 1)
if order < 0: return 0
if order > self.max_order: return 0
# 记录栈一致性校验:分配时(alloc)每个 block 地址都会压入 chk_stack。
# reset_to(mark) 会把 [mark, chk_depth) 的块弹栈并归还 free list,
# 之后这些地址会被重新分配。若一个 header 恰好是合法奇数(假阳性),
# 但地址不在当前 chk_stack[0..chk_depth) 存活区间内,说明已被回收,
# 不能当作有效指针(否则 _format_func_type_str/TypePrint 会沿垃圾链
# 无限分配导致 OOM)。
# 注意:chk_stack 容量满(64K)后 alloc 不再记录新块(chk_depth==chk_cap),
# 此时无法区分,跳过校验(退化到仅 header 校验,避免误判正常 free)。
if self.chk_stack is not None and self.chk_depth < self.chk_cap:
alive: t.CInt = 0
ci: t.CSizeT = 0
while ci < self.chk_depth:
if self.chk_stack[ci] == t.CUInt64T(block):
alive = 1
break
ci += 1
if alive == 0:
return 0
return 1
# === Spinlock ===
def _lock(self): # can use the spinlock library for decoupling
while atom.__atomic_test_and_set(c.Addr(self.lock_val), atom.ATOMIC_ACQUIRE):
pass
def _unlock(self):
atom.__atomic_clear(c.Addr(self.lock_val), atom.ATOMIC_RELEASE)
# === Virtual function overrides ===
def alloc(self, size: t.CSizeT) -> t.CVoid | t.CPtr:
self._lock()
result: t.CVoid | t.CPtr = None
if size != 0 and self.usable is not None:
# +MEMBUDDY_CANARY:预留块尾 8 字节哨兵(用户数据区之外),越界写定位用
needed: t.CSizeT = size + MEMBUDDY_HEADER_SIZE + MEMBUDDY_CANARY
if self.detection != 0:
# [DET] 前后各 2 字节 0xCDCD 标记,实际比请求多 4 字节
needed = needed + 4
order: t.CInt = self._order_for_size(needed)
if order <= self.max_order:
block: t.CVoid | t.CPtr = self._split_to_order(order)
if block is not None:
c.DerefAs(block, t.CVoid(t.CUInt64T((order << 1) | 1), t.CPtr))
result = t.CVoid(t.CUInt64T(block) + MEMBUDDY_HEADER_SIZE, t.CPtr)
# 写块尾 canary(block + block_size - CANARY)
bs: t.CSizeT = _block_size_at_order(order)
c.DerefAs(t.CVoid(t.CUInt64T(block) + bs - MEMBUDDY_CANARY, t.CPtr),
t.CVoid(_canary_pattern(), t.CPtr))
# 分配守卫:填充用户数据区 0xCD 哨兵(未初始化检测)。
# 调用方初始化(list.__init__ 等)覆盖前,字段保持 0xCDCD...
# CTORDBG 打印 d0-d40 时:0xCDCD...=未初始化,其他=被覆盖。
if MEMBUDDY_FILL_GUARD != 0:
fill_size: t.CSizeT = bs - MEMBUDDY_HEADER_SIZE - MEMBUDDY_CANARY
if fill_size > 0:
string.memset(result, MEMBUDDY_FILL_PATTERN, fill_size)
# 记录到 checkpoint 栈(供 reset_to 回收)
# 栈满时 stdlib.realloc 翻倍扩容(防爆满后记录缺失
# → reset 不回收 + _is_valid_ptr 栈校验退化的连锁问题)
if self.chk_stack is not None:
if self.chk_depth >= self.chk_cap:
new_cap: t.CSizeT = self.chk_cap * 2
new_stack: t.CVoid | t.CPtr = t.CVoid(
stdlib.realloc(self.chk_base, new_cap * 8), t.CPtr)
if new_stack is not None:
self.chk_base = new_stack
self.chk_stack = (t.CUInt64T | t.CPtr)(t.CVoid(new_stack, t.CPtr))
self.chk_cap = new_cap
else:
# realloc 失败:块不再记录 → reset_to 无法回收该块 →
# 渐进泄漏最终 1GB 主池 OOM。fail-fast 暴露而非静默。
stdio.printf(
"[MEM] 记录栈 realloc 失败 cap=%zu depth=%zu(reset 将失效,终止)\n",
self.chk_cap, self.chk_depth)
if self.chk_depth < self.chk_cap:
self.chk_stack[self.chk_depth] = t.CUInt64T(block)
self.chk_depth += 1
if result is not None and self._det_records is not None:
base_ret_d: t.CUInt64T = t.CUInt64T(result)
# 返回指针(detection 布局下 = 用户区 + 2,前标记之后)
rec_ptr_d: t.CUInt64T = base_ret_d
if self.detection != 0:
# [DET] 布局 [0xCDCD][请求大小内存][0xCDCD]:返回指针 = 用户区 + 2
# (前标记之后);记录 {返回 ptr, size} 到独立记录区;随后探测全部
# 存活块的后标记——被破坏 = 越界写,立即汇报并终止。
m_f_d: t.CUInt16T | t.CPtr = (t.CUInt16T | t.CPtr)(t.CVoid(base_ret_d, t.CPtr))
m_f_d[0] = t.CUInt16T(MEMBUDDY_DET_MARKER)
tail_d: t.CUInt64T = base_ret_d + 2 + t.CUInt64T(size)
m_t_d: t.CUInt16T | t.CPtr = (t.CUInt16T | t.CPtr)(t.CVoid(tail_d, t.CPtr))
m_t_d[0] = t.CUInt16T(MEMBUDDY_DET_MARKER)
rec_ptr_d = base_ret_d + 2
# 采样:每 MEMBUDDY_DET_PROBE_PERIOD 次 alloc 做一次全表扫描
# (分配重叠 + 探测),其余只追加记录(O(1))。损坏是持久性的,
# 不依赖即时扫描——2M 次 alloc 若每次全表 O(n) = O(n²) 太慢
# (用户反馈 detection 模式过慢),采样把扫描降为 O(n²/PERIOD)。
if (self._det_count & (MEMBUDDY_DET_PROBE_PERIOD - 1)) == 0:
# 分配重叠即时检测:新块地址是否已在 active 记录(同一块重复
# 分配实锤——SlotsBugTest 7B buf 与 72B Token 同 ptr、cargs
# 只写 0/40 根因)。重叠 = MemBuddy free list 损坏(active 块
# 混入 free list),在第一次重叠处拦截并打印两个 size。
ff_d: t.CUInt64T = t.CUInt64T(1) << 63
dup_i: t.CSizeT = 0
while dup_i < self._det_count:
drp: t.CUInt64T = self._det_records[dup_i * 2]
drs: t.CUInt64T = self._det_records[dup_i * 2 + 1]
if drp == rec_ptr_d and (drs & ff_d) == 0:
stdio.printf("[DET] 分配重叠: 新alloc(size=%llu)与记录[%zu](sz=%llu)同地址 ptr=%llu(同一块重复分配,free list 损坏)\n",
t.CUInt64T(size), dup_i, drs & (ff_d - 1), rec_ptr_d)
stdio.fflush(None)
stdlib.exit(1)
dup_i += 1
if _memdbg_enable != 0:
# [MEMDBG] 新出块是否仍残留于同 order free list(此前的重复 put
# 使链表含两条本块 → pop 后还有残条 → 本块将被二次分配,双向写穿)。
# 记录表重叠检测要等下次 pop 同地址才撞见,这里在出块当场提前拦截。
dbg_block: t.CVoid | t.CPtr = t.CVoid(base_ret_d - MEMBUDDY_HEADER_SIZE, t.CPtr)
dbg_hdr: t.CVoid | t.CPtr = t.CVoid(c.Deref(t.CUInt64T(dbg_block, t.CPtr)), t.CPtr)
dbg_order: t.CInt = t.CInt(t.CUInt64T(dbg_hdr) >> 1)
dbg_cur: t.CVoid | t.CPtr = t.CVoid(self.free_lists[dbg_order], t.CPtr)
dbg_n: t.CSizeT = 0
while t.CUInt64T(dbg_cur) != 0:
if t.CUInt64T(dbg_cur) == t.CUInt64T(dbg_block):
stdio.printf("[MEMDBG] alloc 重复出块: size=%llu block=%llu 仍残留 free list order=%d(重复 put 实锤,终止)\n",
t.CUInt64T(size), t.CUInt64T(dbg_block), dbg_order)
stdio.fflush(None)
stdlib.exit(1)
dbg_n += 1
if dbg_n > self.usable_size / MEMBUDDY_MIN_BLOCK + 2:
stdio.printf("[MEMDBG] alloc 扫描 free list order=%d 成环/超长(自检异常,终止)\n", dbg_order)
stdio.fflush(None)
stdlib.exit(1)
dbg_cur = t.CVoid(c.Deref(t.CUInt64T(dbg_cur, t.CPtr)), t.CPtr)
if self._det_count >= self._det_cap:
# 记录表满:stdlib.realloc 翻倍扩容(防 is_alive/_det_probe
# 因容量耗尽漏检——SlotsBugTest 编译 includes 超 2M 次 alloc,
# 满后新块无记录 → is_alive 误报"读取悬空")
new_cap: t.CSizeT = self._det_cap * 2
new_rec: t.CVoid | t.CPtr = t.CVoid(
stdlib.realloc(self._det_arena, new_cap * 16), t.CPtr)
if new_rec is not None:
self._det_arena = new_rec
self._det_records = (t.CUInt64T | t.CPtr)(t.CVoid(new_rec, t.CPtr))
self._det_cap = new_cap
if self._det_count < self._det_cap:
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:
self._det_probe()
if self.detection != 0:
result = t.CVoid(base_ret_d + 2, t.CPtr)
if result is None:
# 分配失败诊断(定位"异常 size/池耗尽导致 None → 调用方解引用段错误")
if size != 0 and self.usable is not None:
needed2: t.CSizeT = size + MEMBUDDY_HEADER_SIZE + MEMBUDDY_CANARY
order2: t.CInt = self._order_for_size(needed2)
stdio.printf("[MEM] alloc 失败 size=%zu needed=%zu order=%d max_order=%d used=%zu pool_base=%llu\n",
size, needed2, order2, self.max_order, self.used_bytes(),
t.CUInt64T(self.usable))
# 越界写诊断:free list 结构损坏(self_check!=0)说明有越界写穿
# 破坏块头/free list next 指针 → alloc 找不到块(used 虚高非真实耗尽)
sc: t.CInt = self.self_check()
stdio.printf("[MEM] alloc 失败 self_check=%d free_bytes=%zu free_count=%zu chk_depth=%zu big=%zu\n",
sc, self.free_bytes(), self.free_count(), self.chk_depth, self.big_alloc_count)
# chk_depth 巨大(数百万)→ 分配次数爆炸(每语句/每调用反复分配);
# chk_depth 不大但 used 巨大 → 单次大分配(缓冲/表扩容)反复发生
if order2 > self.max_order:
stdio.printf("[MEM] 原因:请求 size 超 pool 最大块(order %d > max %d)\n",
order2, self.max_order)
else:
stdio.printf("[MEM] 原因:pool 耗尽(空闲 %zu 字节不足)\n", self.free_bytes())
# 分配失败(alloc(0) 或池耗尽):解锁并返回 None,由调用者决定后续处理。
# 不能 exit——测试依赖 alloc 失败返回 None 后继续 free+重试
# (如 OOM section: 耗尽后 free 一块再 alloc 应成功)。
self._unlock()
return None
self._unlock()
return result
def free(self, ptr: t.CVoid | t.CPtr) -> t.CInt:
self._lock()
if ptr is not None:
p_eff: t.CVoid | t.CPtr = ptr
if self.detection != 0:
# [DET] 返回指针比实际用户区靠后 2 字节,回收时向前偏移 2
p_eff = t.CVoid(t.CUInt64T(ptr) - 2, t.CPtr)
# 记录表置 free 标志(解耦于 detection,2026-09-04):非 detection
# 也需置位,否则 free 后同地址再 alloc(正常 reuse)被误报重叠。
if self._det_records is not None:
# 从记录表取本条真实 size(detection 下 free 后填 0xCD,UAF
# 写入检测:free 后块被写穿 → 探测时 0xCD 被破坏 → 报警);
# 记录保留并置 free 标志(探测时对仍 free 的块验证 0xCD 完整)。
ri: t.CSizeT = 0
while ri < self._det_count:
rp: t.CUInt64T = self._det_records[ri * 2]
rs: t.CUInt64T = self._det_records[ri * 2 + 1]
if rp == t.CUInt64T(ptr) and (rs & (t.CUInt64T(1) << 63)) == 0:
if self.detection != 0 and rs > 0:
string.memset(ptr, MEMBUDDY_FILL_PATTERN, rs)
self._det_records[ri * 2 + 1] = rs | (t.CUInt64T(1) << 63)
break
ri += 1
if self._is_valid_ptr(p_eff) != 0:
block: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(p_eff) - MEMBUDDY_HEADER_SIZE, t.CPtr)
stored: t.CVoid | t.CPtr = t.CVoid(c.Deref(t.CUInt64T(block, t.CPtr)), t.CPtr)
stored_val: t.CUInt64T = t.CUInt64T(stored)
order: t.CInt = t.CInt(stored_val >> 1)
if (stored_val & 1) == 0:
# 双重 free:块已释放(header 偶)。debug 关 = 按原语义静默忽略
# (不再次 coalesce,避免 free list 重复 put 污染);debug 开 =
# 终止并暴露悬空/重复 free 调用点。
if _memdbg_enable != 0:
stdio.printf("[MEMDBG] 悬空/重复 free: ptr=%llu block=%llu header=0x%llX(偶数=已释放/未初始化,终止)\n",
t.CUInt64T(ptr), t.CUInt64T(block), stored_val)
stdio.fflush(None)
stdlib.exit(1)
self._unlock()
return 1
if _memdbg_enable != 0:
# [MEMDBG] 重复归还检测:活跃块(header 奇数)绝不该已在本 order
# free list;命中 = 同块被 put 两次(free list 重复 put 实锤)。
dbg_f: t.CVoid | t.CPtr = t.CVoid(self.free_lists[order], t.CPtr)
dbg_fn: t.CSizeT = 0
while t.CUInt64T(dbg_f) != 0:
if t.CUInt64T(dbg_f) == t.CUInt64T(block):
stdio.printf("[MEMDBG] 重复归还: free(ptr=%llu) block=%llu 已在 free list order=%d(double-put 实锤,终止)\n",
t.CUInt64T(ptr), t.CUInt64T(block), order)
stdio.fflush(None)
stdlib.exit(1)
dbg_fn += 1
if dbg_fn > self.usable_size / MEMBUDDY_MIN_BLOCK + 2:
stdio.printf("[MEMDBG] free 扫描 free list order=%d 成环/超长(自检异常,终止)\n", order)
stdio.fflush(None)
stdlib.exit(1)
dbg_f = t.CVoid(c.Deref(t.CUInt64T(dbg_f, t.CPtr)), t.CPtr)
c.DerefAs(block, t.CVoid(0, t.CPtr))
self._coalesce(block, order)
elif _memdbg_enable != 0:
# [MEMDBG] 无效/悬空 free(原实现静默忽略):块在本池范围内但 header
# 为偶 = 已被释放/未初始化 → 悬空重复 free(stale 指针)。范围外
# (非本池指针)是正常多池场景,不报。
dbg_eff: t.CUInt64T = t.CUInt64T(p_eff)
dbg_blk: t.CUInt64T = dbg_eff - MEMBUDDY_HEADER_SIZE
if (dbg_eff >= MEMBUDDY_HEADER_SIZE and dbg_blk >= t.CUInt64T(self.usable)
and dbg_blk < t.CUInt64T(self.usable) + self.usable_size):
dbg_hv: t.CUInt64T = t.CUInt64T(t.CVoid(c.Deref(t.CUInt64T(t.CVoid(dbg_blk, t.CPtr), t.CPtr)), t.CPtr))
if (dbg_hv & 1) == 0:
stdio.printf("[MEMDBG] 悬空/重复 free: ptr=%llu block=%llu header=0x%llX(偶数=已释放/未初始化,终止)\n",
t.CUInt64T(ptr), dbg_blk, dbg_hv)
stdio.fflush(None)
stdlib.exit(1)
self._unlock()
return 1
def _rebuild_free_lists(self):
"""按二进制分解重建空闲链表(reset 恢复初始分配格局)
与 __init__ 的初始化分解一致:非 2 的幂 arena 会拆成多个
2 的幂空闲块(buddy 合并跨分解边界时因 order 不匹配自然
停止,安全);2 的幂 arena 等价于单个 max_order 块。
"""
i: t.CInt
for i in range(MEMBUDDY_MAX_ORDERS + 1):
self.free_lists[i] = 0
pos: t.CSizeT = 0
rem: t.CSizeT = self.usable_size
while rem >= MEMBUDDY_MIN_BLOCK:
blk: t.CSizeT = _largest_pow2_le(rem)
o: t.CInt = 0
ob: t.CSizeT = MEMBUDDY_MIN_BLOCK
while ob < blk:
ob = ob << 1
o += 1
self._fl_push(o, t.CVoid(t.CUInt64T(self.usable) + pos, t.CPtr))
pos += blk
rem -= blk
def reset(self) -> t.CInt:
if self.usable is None:
return 0
self._rebuild_free_lists()
# [DET] reset 重建全部 free list = 全部块释放:记录表同步清空,
# 防止后续 alloc 复用旧块被误报(与 reset_to 截断同因)。
# 2026-09-04 记录表已与 detection 解耦,清空条件同样不能挂 detection。
if self._det_records is not None:
self._det_count = 0
return 1
# realloc override: buddy system reads original order from block header; old_size is not required
def realloc(self, ptr: t.CVoid | t.CPtr, new_size: t.CSizeT) -> t.CVoid | t.CPtr:
if ptr is None:
return self.alloc(new_size)
if new_size == 0:
self.free(ptr)
return None
p_eff: t.CVoid | t.CPtr = ptr
if self.detection != 0:
# [DET] 与 free 一致:先向前偏移 2 字节再做块级计算
p_eff = t.CVoid(t.CUInt64T(ptr) - 2, t.CPtr)
if self._is_valid_ptr(p_eff) == 0:
return None
block: t.CVoid | t.CPtr = t.CVoid(t.CUInt64T(p_eff) - MEMBUDDY_HEADER_SIZE, t.CPtr)
stored: t.CVoid | t.CPtr = t.CVoid(c.Deref(t.CUInt64T(block, t.CPtr)), t.CPtr)
stored_val: t.CUInt64T = t.CUInt64T(stored)
old_order: t.CInt = t.CInt(stored_val >> 1)
needed: t.CSizeT = new_size + MEMBUDDY_HEADER_SIZE
if self.detection != 0:
needed = needed + 4 # [DET] 前后标记
new_order: t.CInt = self._order_for_size(needed)
if new_order <= old_order:
return ptr
new_ptr: t.CVoid | t.CPtr = self.alloc(new_size)
if new_ptr is None:
return ptr
old_block_size: t.CSizeT = _block_size_at_order(old_order)
old_data_size: t.CSizeT = old_block_size - MEMBUDDY_HEADER_SIZE
cp_len: t.CSizeT = old_data_size
if self.detection != 0:
cp_len = old_data_size - 4 # [DET] 排除前后 2 字节标记
string.memcpy(new_ptr, ptr, cp_len)
self.free(ptr)
return new_ptr
# ============================================================
# [DET] is_alive - 读取侧存活校验:ptr 是否在当前记录表的 active
# 记录中(alloc 后未 free)。返回 1=存活 / 0=悬空(不在记录或已
# free)。detection 关闭或记录表不可用时返回 1(不拦截)。
# 补 _det_probe 盲区:写入侧检测(标记/0xCD)拦不住 use-after-free
# 读取(如 SlotsBugTest KwEntry cargs=cl.args 悬空指向复用块)。
# ============================================================
def is_alive(self, ptr: t.CVoid | t.CPtr) -> t.CInt:
if self.detection == 0:
return 1
if self._det_records is None:
return 1
target: t.CUInt64T = t.CUInt64T(ptr)
free_flag: t.CUInt64T = t.CUInt64T(1) << 63
i: t.CSizeT = 0
while i < self._det_count:
rp: t.CUInt64T = self._det_records[i * 2]
rs: t.CUInt64T = self._det_records[i * 2 + 1]
if rp == target and (rs & free_flag) == 0:
return 1
i += 1
return 0
# ============================================================
# [DET] _det_probe - 分配检测探测:遍历记录表,验证每个存活块的
# 后标记(返回 ptr + size 处 2 字节)仍为 0xCDCD。
# 被破坏 = 越界写(数据区写穿),立即汇报 ptr/size 并终止——
# 把"随机崩溃/字段被覆盖"变成"确定性定位到越界写发生的分配块"。
# ============================================================
def _det_probe(self):
if self._det_records is None:
return
free_flag: t.CUInt64T = t.CUInt64T(1) << 63
i: t.CSizeT = 0
while i < self._det_count:
p: t.CUInt64T = self._det_records[i * 2]
s: t.CUInt64T = self._det_records[i * 2 + 1]
if (s & free_flag) != 0:
# free 记录:跳过——buddy 分配器合并/重分配使 free 块内存不排他
# (块合并进更大块后,新分配的用户区可覆盖旧记录的前标记区而旧块
# 头仍为 0),前标记校验会误报(SlotsBugTest 1024B 块 hdr=0x0、
# 前标记被 2048 合并分配数据写零的误报例,与 w0/w1 0xCD 校验同类)。
# 只校验 active 记录(存活块独占,header LSB=1 不会合并,
# 前/后标记可靠)。
i += 1
continue
if s == 0:
i += 1
continue # 无 size 记录(理论不发生,防御)
# active 记录:前标记(下溢检测)+ 后标记(上溢检测)
m_fa: t.CUInt16T | t.CPtr = (t.CUInt16T | t.CPtr)(t.CVoid(p - 2, t.CPtr))
mfv_a: t.CUInt16T = m_fa[0]
if mfv_a != t.CUInt16T(MEMBUDDY_DET_MARKER):
stdio.printf("[DET] 下溢写: ptr=%llu size=%llu 前标记=0x%04X(期望 0xCDCD)\n",
p, s, t.CUInt32T(mfv_a))
stdio.fflush(None)
stdlib.exit(1)
m16: t.CUInt16T | t.CPtr = (t.CUInt16T | t.CPtr)(t.CVoid(p + s, t.CPtr))
mv: t.CUInt16T = m16[0]
if mv != t.CUInt16T(MEMBUDDY_DET_MARKER):
stdio.printf("[DET] 越界写: ptr=%llu size=%llu 后标记=0x%04X(期望 0xCDCD)块内容前16B: ",
p, s, t.CUInt32T(mv))
bi3: t.CInt = 0
while bi3 < 16:
bv3: t.CUInt8T = (t.CUInt8T | t.CPtr)(t.CVoid(p + t.CUInt64T(bi3), t.CPtr))[0]
stdio.printf("%02X ", t.CUInt32T(bv3))
bi3 += 1
# 越界区(ptr+size 起 24 字节):0x00=被写穿 0xCD=未写 其它=写入值,
# 用于判断写入者写多长、往哪个方向
stdio.printf("越界区: ")
bj3: t.CInt = 0
while bj3 < 24:
bvj: t.CUInt8T = (t.CUInt8T | t.CPtr)(t.CVoid(p + s + t.CUInt64T(bj3), t.CPtr))[0]
stdio.printf("%02X ", t.CUInt32T(bvj))
bj3 += 1
# 记录表上下文:该条前 3 + 后 3 的 {ptr,size},推断越界块分配阶段
stdio.printf("\n记录上下文: ")
ci2: t.CInt = -3
while ci2 <= 3:
idx2: t.CSizeT = t.CSizeT(t.CInt(i) + ci2)
if idx2 < self._det_count:
cip: t.CUInt64T = self._det_records[idx2 * 2]
cis: t.CUInt64T = self._det_records[idx2 * 2 + 1]
stdio.printf("[%d]ptr=%llu sz=%llu ", ci2, cip, cis & (free_flag - 1))
ci2 += 1
# 重叠检测:同一 ptr 是否有多条记录(alloc 重叠实锤——不同 size
# 请求拿到同一块 → 互相覆盖;如 Token(72B) 与字符串 buf(7B) 共用块)
stdio.printf("\n同ptr记录: ")
ci3: t.CSizeT = 0
while ci3 < self._det_count:
cp3: t.CUInt64T = self._det_records[ci3 * 2]
cs3: t.CUInt64T = self._det_records[ci3 * 2 + 1]
if cp3 == p and (cs3 & free_flag) == 0:
stdio.printf("[%zu]sz=%llu ", ci3, cs3 & (free_flag - 1))
ci3 += 1
stdio.printf("\n")
stdio.fflush(None)
stdlib.exit(1)
i += 1
# === Status Queries ===
@property
def mem_size(self) -> t.CSizeT:
# Total usable region size (power of two). Maximum allocatable size = mem_size - MEMBUDDY_HEADER_SIZE
return self.usable_size
def stats(self) -> t.CSizeT:
# Return total usable region size, for status statistics
return self.usable_size
def free_count(self) -> t.CSizeT:
# Count total blocks in all order lists for status statistics
count: t.CSizeT = 0
i: t.CInt
for i in range(MEMBUDDY_MAX_ORDERS + 1):
head_val: t.CUInt64T = self.free_lists[i]
cur: t.CVoid | t.CPtr = t.CVoid(head_val, t.CPtr)
while t.CUInt64T(cur) != 0:
count += 1
cur = t.CVoid(c.Deref(t.CUInt64T(cur, t.CPtr)), t.CPtr)
return count
def free_bytes(self) -> t.CSizeT:
# Count total bytes in all free blocks for status statistics
total: t.CSizeT = 0
i: t.CInt
for i in range(MEMBUDDY_MAX_ORDERS + 1):
head_val: t.CUInt64T = self.free_lists[i]
cur: t.CVoid | t.CPtr = t.CVoid(head_val, t.CPtr)
while t.CUInt64T(cur) != 0:
total += _block_size_at_order(i)
cur = t.CVoid(c.Deref(t.CUInt64T(cur, t.CPtr)), t.CPtr)
return total
def used_bytes(self) -> t.CSizeT:
# Used bytes = total usable region size - free bytes in all free blocks
return self.usable_size - self.free_bytes()
def dump_stats(self, label: t.CChar | t.CPtr):
# Print memory usage statistics
fb: t.CSizeT = self.free_bytes()
ub: t.CSizeT = self.usable_size - fb
stdio.printf("[MEM] %s: total=%zu used=%zu (%zu%%) free=%zu free_blocks=%zu\n",
label, self.usable_size, ub,
(ub * 100) / self.usable_size if self.usable_size > 0 else 0,
fb, self.free_count())
def self_check(self) -> t.CInt:
# Validate buddy allocator internal consistency, return 0=OK, non-0=damage
if self.usable is None:
return 0
# 环检测:free list 被越界写破坏 next 指针后可能成环,
# 无步数上限会导致无限循环(崩溃点漂移排查哨兵必须能终止)。
max_steps: t.CSizeT = self.usable_size / MEMBUDDY_MIN_BLOCK + 2
i: t.CInt
for i in range(MEMBUDDY_MAX_ORDERS + 1):
head_val: t.CUInt64T = self.free_lists[i]
cur: t.CVoid | t.CPtr = t.CVoid(head_val, t.CPtr)
steps: t.CSizeT = 0
while t.CUInt64T(cur) != 0:
steps += 1
if steps > max_steps:
return 4 # free list 成环或节点数超上限(next 指针被破坏)
# Check pointer is in usable region range
if t.CUInt64T(cur) < t.CUInt64T(self.usable):
return 1
if t.CUInt64T(cur) >= t.CUInt64T(self.usable) + self.usable_size:
return 2
# Check alignment: order i blocks must be aligned to size _block_size_at_order(i)
offset: t.CSizeT = t.CUInt64T(cur) - t.CUInt64T(self.usable)
bs: t.CSizeT = _block_size_at_order(i)
if offset % bs != 0:
return 3
cur = t.CVoid(c.Deref(t.CUInt64T(cur, t.CPtr)), t.CPtr)
return 0
def check_alive_blocks(self, from_mark: t.CSizeT) -> t.CInt:
# 检查 [from_mark, chk_depth) 记录的所有已分配块 header 完整性。
# 越界写(参数错位/下标 GEP 越界)会破坏相邻块 header(奇偶位/order 错乱),
# 返回 0=OK,非 0=损坏(rc 语义见调用方注释)。
# 用于 reset_to 前哨兵:把"崩溃点漂移"变成"确定性定位到损坏发生的文件"。
if self.chk_stack is None:
return 0
if from_mark > self.chk_depth:
# from_mark 大于当前深度:调用方 mark 值异常(可能读错字段/跨实例),
# 校验将 no-op → 越界写漏检。fail-fast 暴露。
stdio.printf(
"[MEM] check_alive_blocks from_mark=%zu > chk_depth=%zu(mark 异常)\n",
from_mark, self.chk_depth)
return 0
if from_mark >= self.chk_depth:
return 0
if self.usable is None:
return 0
ci: t.CSizeT = from_mark
while ci < self.chk_depth:
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):
return 1
if t.CUInt64T(block) >= t.CUInt64T(self.usable) + self.usable_size:
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)
if (stored_val & 1) != 0:
# 活块:header 必须是 (order<<1)|1,order 合法且块对齐到该 order 大小
order: t.CInt = t.CInt(stored_val >> 1)
if order < 0:
return 3
if order > self.max_order:
return 4
offset: t.CSizeT = t.CUInt64T(block) - t.CUInt64T(self.usable)
bs: t.CSizeT = _block_size_at_order(order)
if offset % bs != 0:
return 5
# 块尾 canary 校验:越界写穿数据区会破坏块尾哨兵,
# 立即定位到越界写发生的文件(崩溃点漂移的关键哨兵)
can_stored: t.CVoid | t.CPtr = t.CVoid(
c.Deref(t.CUInt64T(t.CUInt64T(block) + bs - MEMBUDDY_CANARY, t.CPtr), t.CPtr),
t.CPtr)
if t.CUInt64T(can_stored) != _canary_pattern():
return 6
ci += 1
return 0