fix(includes): ASan 集成支持 + 越界写封堵 + TypePrint 类型名截断 40→255

1. Phase2Translator 增加 TPC_ASAN 支持(opt asan 插桩+链接 -fsanitize=address),配合 clang64 定位 0xbe 崩溃。2. llvmlite/memhub/viperio/viperlib 多处理无界 strcpy/strcat/下溢/递归守卫/栈数组钳制。3. memhub alloc 块尾 canary(MEMBUDDY_CANARY)+check_alive_blocks 校验+chk_stack realloc 扩容。4. TypePrint Ptr(Struct) 类型名截断上限 40→255:含 sha1 前缀类型名(如 bbdf3bbd4c3bc28c.CONSOLE_SCREEN_BUFFER_INFO=42)被截断产生未定义类型引用(llc undefined type)。
This commit is contained in:
TermiNexus committed 2026-08-10 18:09:13 +08:00
1 parent cb557c84ce
commit d2d319eb21
10 files changed
+195 -49

No files matched your search

+4
View File
@@ -1813,6 +1813,10 @@ def _append_cstr(dst: t.CChar | t.CPtr, dst_size: t.CSizeT, src: t.CChar | t.CPt
"""将 C 字符串 src 追加到 dst 末尾"""
if dst is None or src is None: return
dlen: t.CSizeT = string.strlen(dst)
# 无符号下溢防护:dlen 超过缓冲大小时(缓冲已被破坏/悬空)直接返回,
# 否则 remain = dst_size - dlen 下溢成巨值会把 dst[dlen+i] 写穿到缓冲外
if dlen >= dst_size:
return
slen: t.CSizeT = string.strlen(src)
remain: t.CSizeT = dst_size - dlen
if remain <= 0: return
+23 -16
View File
@@ -489,24 +489,27 @@ def _replace_ssa_numbers(text: t.CChar | t.CPtr,
nxt: t.CChar | t.CPtr = src_p + 1
if nxt is not None and _is_digit_ch(nxt[0]) != 0:
num: t.CInt = string.atoi(nxt)
new_num: t.CInt = _map_get_i32(map_arr, num)
if num >= 0 and num <= max_n and new_num != -1:
# 写入 '%'
new_buf[dst_idx] = '%'
dst_idx += 1
# 写入新编号
num_buf[0] = '\0'
viperlib.snprintf(num_buf, 16, "%d", new_num)
k: t.CSizeT = 0
while num_buf[k] != '\0' and dst_idx + 1 < new_size:
new_buf[dst_idx] = num_buf[k]
# 先做范围检查再读 map_arr:num 超 max_n 时 map_arr 只有
# (max_n+1)*4 字节,先读后查会越界读远端内存
if num >= 0 and num <= max_n:
new_num: t.CInt = _map_get_i32(map_arr, num)
if new_num != -1:
# 写入 '%'
new_buf[dst_idx] = '%'
dst_idx += 1
k += 1
# 跳过旧数字字符
src_p = src_p + 1
while src_p is not None and _is_digit_ch(src_p[0]) != 0:
# 写入新编号
num_buf[0] = '\0'
viperlib.snprintf(num_buf, 16, "%d", new_num)
k: t.CSizeT = 0
while num_buf[k] != '\0' and dst_idx + 1 < new_size:
new_buf[dst_idx] = num_buf[k]
dst_idx += 1
k += 1
# 跳过旧数字字符
src_p = src_p + 1
continue
while src_p is not None and _is_digit_ch(src_p[0]) != 0:
src_p = src_p + 1
continue
# 普通字符:直接复制
new_buf[dst_idx] = src_p[0]
dst_idx += 1
@@ -713,6 +716,10 @@ def _append_cstr(dst: t.CChar | t.CPtr, dst_size: t.CSizeT, src: t.CChar | t.CPt
"""将 C 字符串 src 追加到 dst 末尾"""
if dst is None or src is None: return
dlen: t.CSizeT = string.strlen(dst)
# 无符号下溢防护:dlen 超过缓冲大小时(缓冲已被破坏/悬空)直接返回,
# 否则 remain = dst_size - dlen 下溢成巨值会把 dst[dlen+i] 写穿到缓冲外
if dlen >= dst_size:
return
slen: t.CSizeT = string.strlen(src)
remain: t.CSizeT = dst_size - dlen
if remain <= 0: return
+14 -5
View File
@@ -405,10 +405,15 @@ def _is_type_defined_in_module(mod: LLVMModule | t.CPtr, name: t.CChar | t.CPtr)
def _scan_type_for_cross_module_refs(pool: memhub.MemBuddy | t.CPtr,
mod: LLVMModule | t.CPtr,
ty: LLVMType | t.CPtr):
ty: LLVMType | t.CPtr,
depth: t.CInt = 0):
"""递归扫描类型树,为跨模块结构体引用添加 opaque 声明"""
if ty is None or mod is None or pool is None:
return
# 深度守卫:自引用结构体(链表/树节点,如 Node{...; Ptr(Node) next})
# 会无限递归栈溢出,与其他递归(TypePrint/DeepCopyType)一致设上限
if depth >= 100:
return
match ty:
case LLVMType.Ptr(pointee):
if pointee is not None:
@@ -429,21 +434,21 @@ def _scan_type_for_cross_module_refs(pool: memhub.MemBuddy | t.CPtr,
fld: ParamNode | t.CPtr = sfields
while fld is not None:
if fld.Ty is not None:
_scan_type_for_cross_module_refs(pool, mod, fld.Ty)
_scan_type_for_cross_module_refs(pool, mod, fld.Ty, depth + 1)
fld = fld.Next
case _:
_scan_type_for_cross_module_refs(pool, mod, pointee)
_scan_type_for_cross_module_refs(pool, mod, pointee, depth + 1)
case LLVMType.Struct(sfields2, sfcount2, sname2):
# 顶层结构体(非指针):递归扫描字段
if sfields2 is not None:
fld2: ParamNode | t.CPtr = sfields2
while fld2 is not None:
if fld2.Ty is not None:
_scan_type_for_cross_module_refs(pool, mod, fld2.Ty)
_scan_type_for_cross_module_refs(pool, mod, fld2.Ty, depth + 1)
fld2 = fld2.Next
case LLVMType.Array(elem_ty, acount):
if elem_ty is not None:
_scan_type_for_cross_module_refs(pool, mod, elem_ty)
_scan_type_for_cross_module_refs(pool, mod, elem_ty, depth + 1)
case _:
pass
@@ -590,6 +595,10 @@ def _append_cstr(dst: t.CChar | t.CPtr, dst_size: t.CSizeT, src: t.CChar | t.CPt
"""将 C 字符串 src 追加到 dst 末尾"""
if dst is None or src is None: return
dlen: t.CSizeT = string.strlen(dst)
# 无符号下溢防护:dlen 超过缓冲大小时(缓冲已被破坏/悬空)直接返回,
# 否则 remain = dst_size - dlen 下溢成巨值会把 dst[dlen+i] 写穿到缓冲外
if dlen >= dst_size:
return
slen: t.CSizeT = string.strlen(src)
remain: t.CSizeT = dst_size - dlen
if remain <= 0: return
+27 -16
View File
@@ -416,26 +416,33 @@ def _TypePrintDepth(buf: t.CChar | t.CPtr, size: t.CSizeT,
match pointee:
case LLVMType.Struct(sfields, sfcount, sname):
if sname is not None:
# 悬空防护:sname 可能指向被 reset 回收/部分复用的 pool 内存,
# 用受限打印避免读越界崩溃;完整名在 vtable 生成后由
# TypeStr 兜底(stdlib 深拷贝,跨 reset 存活)
viperlib.snprintf(buf, size, "%%\"%.40s\"*", sname)
# 悬空防护:sname 可能指向被 reset 回收/部分复用的 pool 内存。
# viperlib 的 %s 未实现 precision(%.40s 会全量读 sname),
# 手动 strncpy 限长拷贝后再打印,读侧限制在缓冲内。
# 类型名含 sha1 前缀可超 40 字符(如
# "bbdf3bbd4c3bc28c.CONSOLE_SCREEN_BUFFER_INFO"=42),
# 原 40 字节截断会产生未定义类型引用(llc: undefined type)。
sn_cp: t.CChar | t.CPtr = pool.alloc(256)
if sn_cp is not None:
string.strncpy(sn_cp, sname, 255)
sn_cp[255] = '\0'
viperlib.snprintf(buf, size, "%%\"%s\"*", sn_cp)
return
case LLVMType.Void():
# Ptr(Void) → i8*(LLVM IR void* 非法)
string.strcpy(buf, "i8*")
viperlib.snprintf(buf, size, "%s", "i8*")
return
case LLVMType.Label():
string.strcpy(buf, "i8*")
viperlib.snprintf(buf, size, "%s", "i8*")
return
case LLVMType.Metadata():
string.strcpy(buf, "i8*")
viperlib.snprintf(buf, size, "%s", "i8*")
return
case _:
pass
# pointee == None 时也兜底 Ptr(Int8) 打印 i8*
if pointee is None:
string.strcpy(buf, "i8*")
viperlib.snprintf(buf, size, "%s", "i8*")
return
# 通用路径: 递归打印 pointee 后追加 '*'
_TypePrintDepth(buf, size, pointee, pool, depth + 1)
@@ -484,24 +491,24 @@ def _TypePrintDepth(buf: t.CChar | t.CPtr, size: t.CSizeT,
first2 = 0
_strappend(buf, size, ")")
case LLVMType.Void():
string.strcpy(buf, "void")
viperlib.snprintf(buf, size, "%s", "void")
case LLVMType.Label():
string.strcpy(buf, "label")
viperlib.snprintf(buf, size, "%s", "label")
case LLVMType.Metadata():
string.strcpy(buf, "metadata")
viperlib.snprintf(buf, size, "%s", "metadata")
case LLVMType.Float(bits):
if bits == 16:
string.strcpy(buf, "half")
viperlib.snprintf(buf, size, "%s", "half")
elif bits == 32:
string.strcpy(buf, "float")
viperlib.snprintf(buf, size, "%s", "float")
elif bits == 64:
string.strcpy(buf, "double")
viperlib.snprintf(buf, size, "%s", "double")
elif bits == 128:
string.strcpy(buf, "fp128")
viperlib.snprintf(buf, size, "%s", "fp128")
else:
viperlib.snprintf(buf, size, "float%x", bits)
case _:
string.strcpy(buf, "void")
viperlib.snprintf(buf, size, "%s", "void")
# ============================================================
@@ -511,6 +518,10 @@ def _strappend(dst: t.CChar | t.CPtr, dst_size: t.CSizeT, src: t.CChar | t.CPtr)
"""将 src 追加到 dst 末尾(不超过 dst_size)"""
if dst is None or src is None: return
dlen: t.CSizeT = string.strlen(dst)
# 无符号下溢防护:dlen 超过缓冲大小时(缓冲已被破坏/悬空)直接返回,
# 否则 remain = dst_size - dlen 下溢成巨值会把 dst[dlen+i] 写穿到 GB 外
if dlen >= dst_size:
return
slen: t.CSizeT = string.strlen(src)
remain: t.CSizeT = dst_size - dlen
if remain <= 0: return
+4
View File
@@ -220,6 +220,10 @@ def _append_str(dst: t.CChar | t.CPtr, dst_size: t.CSizeT, src: t.CChar | t.CPtr
"""将 src 追加到 dst 末尾"""
if dst is None or src is None: return
dlen: t.CSizeT = string.strlen(dst)
# 无符号下溢防护:dlen 超过缓冲大小时(缓冲已被破坏/悬空)直接返回,
# 否则 remain = dst_size - dlen 下溢成巨值会把 dst[dlen+i] 写穿到缓冲外
if dlen >= dst_size:
return
slen: t.CSizeT = string.strlen(src)
remain: t.CSizeT = dst_size - dlen
if remain <= 0: return
+4
View File
@@ -82,6 +82,10 @@ def _append_cstr(dst: t.CChar | t.CPtr, dst_size: t.CSizeT, src: t.CChar | t.CPt
"""将 src 追加到 dst 末尾"""
if dst is None or src is None: return
dlen: t.CSizeT = string.strlen(dst)
# 无符号下溢防护:dlen 超过缓冲大小时(缓冲已被破坏/悬空)直接返回,
# 否则 remain = dst_size - dlen 下溢成巨值会把 dst[dlen+i] 写穿到缓冲外
if dlen >= dst_size:
return
slen: t.CSizeT = string.strlen(src)
remain: t.CSizeT = dst_size - dlen
if remain <= 0: return
+76 -1
View File
@@ -25,6 +25,16 @@ MEMSLAB_BITMAP_BYTES: t.CDefine = 256
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
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
@@ -529,13 +539,18 @@ class MemBuddy(MemManager):
self._lock()
result: t.CVoid | t.CPtr = None
if size != 0 and self.usable is not None:
needed: t.CSizeT = size + MEMBUDDY_HEADER_SIZE
# +MEMBUDDY_CANARY:预留块尾 8 字节哨兵(用户数据区之外),越界写定位用
needed: t.CSizeT = size + MEMBUDDY_HEADER_SIZE + MEMBUDDY_CANARY
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))
# 记录到 checkpoint 栈(供 reset_to 回收)
# 栈满时 stdlib.realloc 翻倍扩容(防 64K 爆满后记录缺失
# → reset 不回收 + _is_valid_ptr 栈校验退化的连锁问题)
@@ -552,6 +567,17 @@ class MemBuddy(MemManager):
self.chk_stack[self.chk_depth] = t.CUInt64T(block)
self.chk_depth += 1
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\n",
size, needed2, order2, self.max_order, self.used_bytes())
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 应成功)。
@@ -660,11 +686,18 @@ class MemBuddy(MemManager):
# 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
@@ -677,3 +710,45 @@ class MemBuddy(MemManager):
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:
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
+3 -1
View File
@@ -31,7 +31,9 @@ class Buf:
self.data[self.length + i] = src[i]
i += 1
self.length += n
self.data[self.length] = 0
# 恰好写满(n==avail)时 data[capacity] 越界 1 字节,加保护
if self.length < self.capacity:
self.data[self.length] = 0
return n
def cstr(self) -> t.CChar | t.CPtr:
+2 -2
View File
@@ -672,7 +672,7 @@ def vsnprintf(buf: t.CChar | t.CPtr, size: t.CSizeT, fmt: str, ap: t.CChar | t.C
temp[fi] = t.CChar(frac_part % 10 + 48)
fi += 1
frac_part //= 10
while fi < precision:
while fi < precision and fi < TEMP_BUF_SIZE - 1:
temp[fi] = 48
fi += 1
while fi > 0 and write_count < size - 1:
@@ -835,7 +835,7 @@ def vsnprintf(buf: t.CChar | t.CPtr, size: t.CSizeT, fmt: str, ap: t.CChar | t.C
temp[fi] = t.CChar(frac_p % 10 + 48)
fi += 1
frac_p //= 10
while fi < precision:
while fi < precision and fi < TEMP_BUF_SIZE - 1:
temp[fi] = 48
fi += 1
while fi > 0 and write_count < size - 1: