feat(callgenmixin): len/调用前/callee 三级 can 断点,定位 4 字节错位

StringIterTest can=0x229_0000_0000=[cargs高32][count低32],IR 显示 len/调用均正确(i64),但运行时 callee 收到垃圾——需三级 printf 断点确定错位发生在 len 计算/调用传参/callee 读取哪一级。

门控(can>=2^32 才打印):①len结果异常(len 后立即)②调用前 can(L352 前)③callee 槽位悬空已打印 can。一次运行锁定。
This commit is contained in:
TermiNexus committed 2026-08-25 17:29:33 +08:00
1 parent d7ed91e3e5
commit b3546b05a5
1 file changed
+83 -14
+83 -14
View File
@@ -85,6 +85,13 @@ class CallGenMixin(CallGenBase.CallGenBase):
can: t.CSizeT = 0
if cargs is not None:
can = len(cargs)
# [DET] len() 结果核对(SlotsBugTest/StringIterTest can 垃圾高位定位:
# can=0x229_0000_0000=[cargs高32][count低32],怀疑 len() 生成代码 4 字节
# 错位。门控:真实 count 远小于 2^32,can>=2^32 即异常,打印定位)。
if (t.CUInt64T(can) & (t.CUInt64T(1) << 32)) != 0:
lh_l: t.CUInt64T | t.CPtr = (t.CUInt64T | t.CPtr)(t.CVoid(cargs, t.CPtr))
stdio.printf("[DET] len结果异常: can=%llu cargs=%llu data=%llu count=%llu cap=%llu\n",
t.CUInt64T(can), t.CUInt64T(cargs), lh_l[0], lh_l[1], lh_l[2])
# super()/ctraits 检测:void 方法(如 __init__)命中后 build_call 返回
# None 属预期(void call 无 SSA 值),必须用 hit_box 区分"命中已处理"
@@ -149,7 +156,7 @@ class CallGenMixin(CallGenBase.CallGenBase):
# BinOp 节点作为 Call func: 联合类型注解用作类型转换
if cl.func is not None and cl.func.kind() == ast.ASTKind.BinOp and trans is not None:
binop_ty: llvmlite.LLVMType | t.CPtr = self.Gen.TypeGenResolve.resolve_annotation_type(
pool, cl.func, trans._imported_modules, trans._from_imports, trans)
pool, cl.func, trans._imported_modules, trans._from_imports, trans, 0)
if binop_ty is not None:
binop_args: list[ast.AST | t.CPtr] | t.CPtr = cl.args
if binop_args is not None and len(binop_args) == 1:
@@ -341,6 +348,11 @@ class CallGenMixin(CallGenBase.CallGenBase):
pool, builder, mod, struct_ty, func_name, cargs, can,
keywords, kw_count, trans)
# 仅位置参数:按字段顺序构建
# [DET] 调用前 can 核对(与 len结果/callee 收到的 can 三级对照,
# 定位 4 字节错位发生在 len 计算/调用传参/callee 读取哪一级)
if (t.CUInt64T(can) & (t.CUInt64T(1) << 32)) != 0:
stdio.printf("[DET] 调用前 can=%llu cargs=%llu\n",
t.CUInt64T(can), t.CUInt64T(cargs))
return self.Gen.CallGenCtor._translate_struct_ctor(
pool, builder, mod, struct_ty, func_name, cargs, can, trans)
@@ -751,8 +763,22 @@ class CallGenMixin(CallGenBase.CallGenBase):
for ai in range(can):
arg: ast.AST | t.CPtr = cargs.get(ai)
if arg is not None:
# 先取形参类型:单引号单字符字面量(CONST_CHAR)传给 str/指针
# 形参时需物化 1 字符字符串(translate_constant 依据 target_ty
# 判定),否则 coerce inttoptr 把字符值当指针(如 '"'=0x22)→
# strlen 访问低地址 ASan 崩溃(二代编 StringTest PyI 生成根因)。
fp: llvmlite.Param | t.CPtr = None
fpty: llvmlite.LLVMType | t.CPtr = None
if found_func is not None:
fp = llvmlite.function_get_param_head(found_func)
fpi: int = 0
while fp is not None and fpi < ai:
fp = llvmlite.param_get_next(fp)
fpi += 1
if fp is not None:
fpty = llvmlite.param_get_ty(fp)
arg_val: llvmlite.Value | t.CPtr = self.Gen.ExprGen.translate_value(
builder, pool, mod, arg, None, 0, trans)
builder, pool, mod, arg, None, 0, trans, fpty)
if arg_val is not None:
# 参数类型提升:当 found_func 为 None(stub 未注入)时,
# 根据外部函数签名推断期望类型,提升字面量(如 i32→i64)
@@ -772,6 +798,15 @@ class CallGenMixin(CallGenBase.CallGenBase):
arg_val = decayed_fc
case _:
pass
# 已知变参 C 库函数(snprintf/sprintf/vsprintf)固定参数
# 按签名 coerce:stub 未注入时跨模块签名不可见,但固定
# 参数类型是 C ABI 硬约束(snprintf size 为 i64),不提升
# 则 call(i32 1024) 与 declare(i64 size) 不匹配,且 Win64
# 变参槽高 4 字节残留栈垃圾(clean 日志 0x7ff700000052 根因)。
kvc_ty: llvmlite.LLVMType | t.CPtr = self.Gen.CallGenVa._get_known_vararg_cfunc_param_ty(
pool, func_name, ai)
if kvc_ty is not None:
arg_val = self.Gen.ExprGenBase.coerce_to_type(builder, arg_val, kvc_ty, arg)
ext_param_ty: llvmlite.LLVMType | t.CPtr = self.Gen.CallGenLib._get_external_func_param_ty(
pool, func_name, ai)
if ext_param_ty is not None:
@@ -785,13 +820,7 @@ class CallGenMixin(CallGenBase.CallGenBase):
elif found_func is not None:
# found_func 存在:按形参类型 coerce(如 stdlib.malloc(size: t.CSizeT))
# 修复: CDefine/i32 常量传给 i64 形参未提升,导致 call(i32) 与 define(i64) 不匹配
fp: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(found_func)
fpi: int = 0
while fp is not None and fpi < ai:
fp = llvmlite.param_get_next(fp)
fpi += 1
if fp is not None:
fpty: llvmlite.LLVMType | t.CPtr = llvmlite.param_get_ty(fp)
if fpty is not None:
arg_val = self.Gen.ExprGenBase.coerce_to_type(
builder, arg_val, fpty, arg)
@@ -871,6 +900,52 @@ class CallGenMixin(CallGenBase.CallGenBase):
else:
tail.Next = df_arg_val
tail = df_arg_val
elif found_func is not None:
# 默认参数缺失/表信息不全时的零值兜底:
# 1) df_param_count>0 但 df_defaults 为空(函数表有参数数、无默认值记录)
# → 现有 if 不满足(要求 df_defaults 非 None 且 default_count>0),
# 缺参调用按 x64 约定读 [rsp+0x28] 栈参数区垃圾 → ASan 崩溃。
# 2) 调用方文件先于定义文件编译(编译顺序问题,如 Phase1Main 调
# StubMergerBuild._BuildReachableSha1Set 缺 app_deps),
# find_func_entry_in_table/find_func_defaults 均查不到(df_param_count=0)
# → 同样缺参崩溃。
# 两种情况都用 found_func 实际参数数兜底,缺参填零值
# (对齐 TPC _fill_default_args 的零值兜底语义;found_func 是 stub
# declare,参数数与 define 一致,按它填充安全)。
# 注意排除 EH 输出参数(%__eh_msg_out__/%__eh_code_out__):
# 含 raise 的函数定义尾部注入 2 个 EH 形参,若计入 fc_cnt 会为
# raise_disk() 这类无参调用补 2 个 zeroinitializer 占位,随后
# _emit_eh_guarded_call 又追加真实 EH 实参 → call 4 参 vs define
# 2 参 → x64 ABI 错位 → 函数写 null 输出指针 → 0xC0000005
# (TestFBProject raise_disk 崩溃根因)。
fc_cnt: int = 0
fc_p: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(found_func)
while fc_p is not None:
fp_nm_z: str = llvmlite.param_get_name(fc_p)
if fp_nm_z is None or (string.strcmp(fp_nm_z, "%__eh_msg_out__") != 0
and string.strcmp(fp_nm_z, "%__eh_code_out__") != 0):
fc_cnt += 1
fc_p = llvmlite.param_get_next(fc_p)
if fc_cnt == 0:
fc_cnt = df_param_count if df_param_count > 0 else 0
if can < fc_cnt:
fp_z: llvmlite.Param | t.CPtr = llvmlite.function_get_param_head(found_func)
fpi_z: int = 0
while fp_z is not None and fpi_z < can:
fp_z = llvmlite.param_get_next(fp_z)
fpi_z += 1
while fp_z is not None and fpi_z < fc_cnt:
zero_ty_z: llvmlite.LLVMType | t.CPtr = llvmlite.param_get_ty(fp_z)
zero_val_z: llvmlite.Value | t.CPtr = llvmlite.ConstZero(pool, zero_ty_z)
if zero_val_z is not None:
zero_val_z.Next = None
if head is None:
head = zero_val_z
else:
tail.Next = zero_val_z
tail = zero_val_z
fp_z = llvmlite.param_get_next(fp_z)
fpi_z += 1
# 使用模块中的实际函数名(可能带 SHA1 前缀,如 "83210a72e829a086.atoi")
# t.CExtern/t.State/t.CExport 函数不加 SHA1 前缀,使用裸名
@@ -1164,12 +1239,6 @@ class CallGenMixin(CallGenBase.CallGenBase):
else:
if trans is not None:
trans._deps_pending = 1
# [DEPDBG] 临时诊断:跨模块函数依赖未就绪即 pending 的根因(用后移除)
dbg_ms5: str = trans.ModuleSha1 if (trans is not None and trans.ModuleSha1 is not None) else "?"
stdio.printf("[DEPDBG] pending(cross_fn): mod=%s call=%s sha1=%s text=%d\n",
dbg_ms5, call_name,
cf_sha1 if cf_sha1 is not None else "none",
self._dep_text_ll_exists(pool, cf_sha1) if cf_sha1 is not None else -1)
cf_dbg: str = VLogger.fmt_buf()
if cf_dbg is not None:
viperlib.snprintf(