From d2d319eb21abed6912e8249fa7fb293757720ef5 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 10 Aug 2026 18:09:13 +0800 Subject: [PATCH] =?UTF-8?q?fix(includes):=20ASan=20=E9=9B=86=E6=88=90?= =?UTF-8?q?=E6=94=AF=E6=8C=81=20+=20=E8=B6=8A=E7=95=8C=E5=86=99=E5=B0=81?= =?UTF-8?q?=E5=A0=B5=20+=20TypePrint=20=E7=B1=BB=E5=9E=8B=E5=90=8D?= =?UTF-8?q?=E6=88=AA=E6=96=AD=2040=E2=86=92255?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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)。 --- includes/llvmlite/__builder.vp | 4 ++ includes/llvmlite/__function.vp | 39 +++++++++------ includes/llvmlite/__module.vp | 19 +++++-- includes/llvmlite/__types.vp | 43 ++++++++++------ includes/llvmlite/__values.vp | 4 ++ includes/llvmlite/__verify.vp | 4 ++ includes/memhub.vp | 77 ++++++++++++++++++++++++++++- includes/viperio.vp | 4 +- includes/viperlib.vp | 4 +- lib/Projectrans/Phase2Translator.py | 46 ++++++++++++++--- 10 files changed, 195 insertions(+), 49 deletions(-) diff --git a/includes/llvmlite/__builder.vp b/includes/llvmlite/__builder.vp index 2d761d0..b694501 100644 --- a/includes/llvmlite/__builder.vp +++ b/includes/llvmlite/__builder.vp @@ -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 diff --git a/includes/llvmlite/__function.vp b/includes/llvmlite/__function.vp index bb311ee..5796912 100644 --- a/includes/llvmlite/__function.vp +++ b/includes/llvmlite/__function.vp @@ -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 diff --git a/includes/llvmlite/__module.vp b/includes/llvmlite/__module.vp index 9edddb9..d64fc6c 100644 --- a/includes/llvmlite/__module.vp +++ b/includes/llvmlite/__module.vp @@ -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 diff --git a/includes/llvmlite/__types.vp b/includes/llvmlite/__types.vp index 540bb52..58ab93b 100644 --- a/includes/llvmlite/__types.vp +++ b/includes/llvmlite/__types.vp @@ -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 diff --git a/includes/llvmlite/__values.vp b/includes/llvmlite/__values.vp index 96fe07f..af44781 100644 --- a/includes/llvmlite/__values.vp +++ b/includes/llvmlite/__values.vp @@ -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 diff --git a/includes/llvmlite/__verify.vp b/includes/llvmlite/__verify.vp index 6d269f8..1c21e86 100644 --- a/includes/llvmlite/__verify.vp +++ b/includes/llvmlite/__verify.vp @@ -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 diff --git a/includes/memhub.vp b/includes/memhub.vp index 8eabae5..72e017a 100644 --- a/includes/memhub.vp +++ b/includes/memhub.vp @@ -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 diff --git a/includes/viperio.vp b/includes/viperio.vp index 9485919..48157bc 100644 --- a/includes/viperio.vp +++ b/includes/viperio.vp @@ -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: diff --git a/includes/viperlib.vp b/includes/viperlib.vp index 144fd65..f0cecb6 100644 --- a/includes/viperlib.vp +++ b/includes/viperlib.vp @@ -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: diff --git a/lib/Projectrans/Phase2Translator.py b/lib/Projectrans/Phase2Translator.py index be3f933..6b21762 100644 --- a/lib/Projectrans/Phase2Translator.py +++ b/lib/Projectrans/Phase2Translator.py @@ -160,6 +160,15 @@ class Phase2Translator: self.linker_cmd = linker_cmd self.linker_flags = linker_flags or [] self.linker_output = linker_output + # ASan 模式:TPC_ASAN=1 时对全部 .ll 做 opt -passes=asan 插桩并链接 libasan。 + # 检测 stdlib 堆/栈/全局越界与垃圾指针(MemBuddy pool 内部由 canary 覆盖)。 + # 注意:mingw64 的 clang 不提供 ASan runtime(libclang_rt.asan 缺失), + # 必须使用 clang64 环境(自带 asan_dynamic runtime),PATH 前置 clang64/bin。 + self.asan_mode = os.environ.get('TPC_ASAN') == '1' + if self.asan_mode: + _clang64_bin = r'D:\msys64\clang64\bin' + if os.path.isdir(_clang64_bin): + os.environ['PATH'] = _clang64_bin + os.pathsep + os.environ.get('PATH', '') self.slice_level = 3 self.sha1_map: dict[str, str] = {} self.sig_files: dict[str, str] = {} @@ -1185,6 +1194,29 @@ class Phase2Translator: except Exception as e: _vlog().warning(f" [警告] 解析包重导出失败 {init_pyi_path}: {e}") + def _llc_compile(self, ll_path: str, obj_path: str, + cwd: str | None = None) -> subprocess.CompletedProcess: + """编译单个 .ll 为 obj。 + + ASan 模式(TPC_ASAN=1)下先对 IR 执行 opt -passes=asan 插桩: + 检测 stdlib 堆/栈/全局越界写与垃圾指针解引用(MemBuddy pool 内部 + 由块尾 canary 覆盖,两者互补)。opt 失败返回其结果(returncode!=0)。 + """ + compile_input = ll_path + if self.asan_mode: + asan_ll = ll_path[:-3] + '_asan.ll' + # -asan-globals=false:TPC 对全局变量(如各模块 _g_gen)用直接地址寻址, + # ASan 全局 redzone 重定位后引用错位会导致读到垃圾指针(如 + # register_struct 的 self=0xbebebebe...)。先关闭全局插桩排除该因素。 + opt_cmd = ['opt', '-passes=asan', '-asan-globals=false', ll_path, '-o', asan_ll] + opt_r = subprocess.run(opt_cmd, capture_output=True, text=True, cwd=cwd) + if opt_r.returncode != 0: + _vlog().error(f" opt ASan 插桩失败: {opt_r.stderr.strip()}") + return opt_r + compile_input = asan_ll + cmd = [self.compile_cmd] + self.compile_flags + ['-o', obj_path, compile_input] + return subprocess.run(cmd, capture_output=True, text=True, cwd=cwd) + def _compile_ll_files(self, active_sha1s: set[str]) -> None: """将 output_dir 中属于 active_sha1s 的 .ll 文件编译为 .obj(跳过已编译过的 include 文件)""" # 第一次 walk:清理 stale 文件(.obj/.deps.json/纯 .ll,排除 .stub.ll/.text.ll) @@ -1307,9 +1339,7 @@ class Phase2Translator: f.write(opt_content) merged_obj = os.path.join(self.output_dir, '_merged.obj') try: - cmd = [self.compile_cmd] + self.compile_flags + ['-o', merged_obj, optimized_ll] - result = subprocess.run(cmd, capture_output=True, text=True, - cwd=self.output_dir) + result = self._llc_compile(optimized_ll, merged_obj, self.output_dir) if result.returncode == 0: _vlog().success(f" 合并模块编译完成") for ll_path in ll_files: @@ -1591,9 +1621,7 @@ class Phase2Translator: dso_ll_path = ll_path[:-3] + '_dso.ll' with open(dso_ll_path, 'w', encoding='utf-8') as f: f.write(ll_content) - cmd = [self.compile_cmd] + self.compile_flags + ['-o', obj_path, dso_ll_path] - result = subprocess.run(cmd, capture_output=True, text=True, - cwd=os.path.dirname(ll_path) or '.') + result = self._llc_compile(dso_ll_path, obj_path, os.path.dirname(ll_path) or '.') if result.returncode == 0: return (rel, 'ok', '') else: @@ -2355,8 +2383,7 @@ class Phase2Translator: def _compile_one_include(task): dso_ll_path, obj_path = task[0], task[1] - cmd = [self.compile_cmd] + self.compile_flags + ['-o', obj_path, dso_ll_path] - result = subprocess.run(cmd, capture_output=True, text=True, cwd=self.output_dir) + result = self._llc_compile(dso_ll_path, obj_path, self.output_dir) return (task, result) if len(compile_tasks) > 1: @@ -3050,6 +3077,9 @@ SECTIONS { # 其他所有标志(包括 -Wl, 选项)放在目标文件之前 non_lib_flags.append(flag) i += 1 + # ASan 模式:链接 libasan(配合各 .ll 的 opt -passes=asan 插桩) + if self.asan_mode: + non_lib_flags.append('-fsanitize=address') cmd = [self.linker_cmd] + non_lib_flags + ['-o', link_output] + all_link_files + lib_flags _vlog().info(f" 执行: {' '.join(cmd)}") result = subprocess.run(