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:
+38 -8
View File
@@ -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(