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:
1 parent
d7ed91e3e5
commit
b3546b05a5
1 file changed
+83
-14
@@ -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(
|
||||
|
||||
Reference in new issue
Block a user