From 9717ce0107dd14c6989d023f20a72b09b1b47cef Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sun, 4 Oct 2026 22:38:02 +0800 Subject: [PATCH] =?UTF-8?q?feat(traceback):=20=E8=BF=90=E8=A1=8C=E6=9C=9F?= =?UTF-8?q?=E8=B0=83=E7=94=A8=E6=A0=88=E6=98=BE=E7=A4=BA=20callsite=20?= =?UTF-8?q?=E8=A1=8C=E5=8F=B7=EF=BC=88=E7=BC=96=E8=AF=91=E5=99=A8=E6=97=81?= =?UTF-8?q?=E6=96=87=E4=BB=B6=20+=20=E9=93=BE=E6=8E=A5=E5=90=8E=20objdump?= =?UTF-8?q?=20=E5=BD=92=E4=B8=80=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MLIR 后端下 mlirlite 只记录 DI 不输出、且 mlir-translate 不产 !dbg(实测), 导致我方 .vp 函数在最终 exe 无任何 .debug_line(.stackmap 只剩 mingw CRT 的 C 源记录)→ 用户代码帧只能显示 line 0。 改为 Route A:--traceback 时编译器记录每函数(F)与每次调用(C,含调用点行), 写 .vpcall;链接后 WriteStackMap 用 llvm-objdump 反汇编,把 callq 的 返回地址 RVA 按 (调用方,被调方,出现序号) 匹配到调用点行,并在函数入口写定义行 兜底;合并进现有 .stackmap 格式。运行期 traceback.vp 不改。 验收:PtrOpProbe --traceback 生成 427 条我方记录(抽查 main 的 malloc/printf 等行号与源码逐一吻合);非 --traceback 构建零旁文件、产物与运行结果不变。 --- App/lib/Phase/BuildPipeline.vp | 631 ++++++++++++++++++++++++++++- App/lib/Phase/Phase1/Phase1Main.vp | 8 + App/lib/Phase/Phase2/__init__.vp | 15 +- App/lib/core/Gen/DebugInfoGen.vp | 22 +- 4 files changed, 667 insertions(+), 9 deletions(-) diff --git a/App/lib/Phase/BuildPipeline.vp b/App/lib/Phase/BuildPipeline.vp index 16159ff..6a80ee7 100644 --- a/App/lib/Phase/BuildPipeline.vp +++ b/App/lib/Phase/BuildPipeline.vp @@ -120,6 +120,61 @@ def _append_traceback_dbghelp(linker_flags: str) -> str: return buf +# ============================================================ +# --traceback 调用点旁文件(.vpcall) +# +# 编译器(mlirlite 调试调用点记录 + DebugInfoGen 转达)在翻译每个模块时累积 +# 「F/C」记录文本;Phase1/Phase2 在模块翻译完成、dump 之后调用 WriteCallSidecar +# 写盘并登记路径。WriteStackMap 链接后按登记表读取,结合 llvm-objdump 归一地址。 +# 仅 --traceback 时产生;关闭时不写任何旁文件、不登记。 +# ============================================================ +TB_VPCALL_MAX: t.CDefine = 8192 +_tb_vpcall_buf: bytes = None # TB_VPCALL_MAX * 8:每槽一个 char*(stdlib 分配,进程内一次性) +_tb_vpcall_n: int = 0 + + +def _tb_register_vpcall(path: str) -> int: + """把已写出的 .vpcall 路径登记进全局表(供 WriteStackMap 稍后读取)。""" + global _tb_vpcall_buf, _tb_vpcall_n + if path is None: + return 0 + if _tb_vpcall_buf is None: + _tb_vpcall_buf = stdlib.malloc(sizet(TB_VPCALL_MAX) * 8) + if _tb_vpcall_buf is None: + VLogger.error("--traceback: .vpcall 路径表分配失败,终止编译", "TRACEBACK") + return 1 + _tb_vpcall_n = 0 + if _tb_vpcall_n >= TB_VPCALL_MAX: + VLogger.error("--traceback: .vpcall 路径表溢出(模块数超上限),终止编译", "TRACEBACK") + return 1 + slot: str | t.CPtr = (str | t.CPtr)(uint64(_tb_vpcall_buf) + sizet(_tb_vpcall_n) * 8) + slot[0] = path + _tb_vpcall_n += 1 + return 0 + + +def WriteCallSidecar(temp_dir: str, sha1: str, text: str) -> int: + """--traceback: 把本模块的调试调用点文本写入 //.vpcall。 + + text 为 None 时跳过(该模块无 define/无调用)。路径随后登记到全局表,交由 + WriteStackMap 读取;本函数不释放路径(进程生命周期内一直有效)。 + """ + if temp_dir is None or sha1 is None or text is None: + return 0 + vpath: str = StubMergerCollect._sliced_path(temp_dir, string.strlen(temp_dir), sha1, "vpcall") + if vpath is None: + VLogger.error(f"--traceback: .vpcall 路径构造失败(sha1={sha1}),终止编译", "TRACEBACK") + return 1 + vf: rawptr[fileio.File] = fileio.File(vpath, fileio.MODE.W) + if vf.closed: + VLogger.error(f"--traceback: 写出 .vpcall 失败(无法打开 {vpath}),终止编译", "TRACEBACK") + stdlib.free(vpath) + return 1 + vf.write(text, string.strlen(text)) + vf.close() + return _tb_register_vpcall(vpath) + + # ============================================================ # _tb_basename_is_traceback - is this source file includes/traceback.vp # ============================================================ @@ -1343,10 +1398,13 @@ def link_objs_to_exe(obj_paths: str, obj_paths_len: sizet, # Every failure aborts the build (VLogger.error exits); there is no fallback. # ============================================================ -# row record: u64 rva | u64 file_path | u32 line | u32 pad -TB_ROW_SIZE: t.CDefine = 24 +# row record: u64 rva | u64 file_path | u32 line | u32 pad | u64 func_name(可空) +# func_name 仅我方 .vpcall 派生行携带(objdump 标签名);DWARF 行写 0,序列化时 +# 回退到 DWARF 子程序表查名。 +TB_ROW_SIZE: t.CDefine = 32 TB_ROW_OFF_FILE: t.CDefine = 8 TB_ROW_OFF_LINE: t.CDefine = 16 +TB_ROW_OFF_FUNC: t.CDefine = 24 # func record: u64 low_rva | u64 high_rva | u64 name TB_FUNC_SIZE: t.CDefine = 24 # dir record: u32 idx | u32 pad | u64 name @@ -1726,7 +1784,7 @@ def _tb_grow_rec(mb: memhub.MemBuddy*, buf: bytes, old_cap: int, def _tb_emit_row(mb: memhub.MemBuddy*, d: TbDump | t.CPtr, - addr: uint64, path: str, line: int) -> int: + addr: uint64, path: str, line: int, fname: str = None) -> int: if d is None: return 1 if d.RowCount >= d.RowCap: @@ -1741,6 +1799,10 @@ def _tb_emit_row(mb: memhub.MemBuddy*, d: TbDump | t.CPtr, _tb_wr_u64(d.RowBuf, off, addr) _tb_wr_u64(d.RowBuf, off + TB_ROW_OFF_FILE, t.CUInt64T(path)) _tb_wr_u32(d.RowBuf, off + TB_ROW_OFF_LINE, uint64(line)) + fv: uint64 = 0 + if fname is not None: + fv = t.CUInt64T(fname) + _tb_wr_u64(d.RowBuf, off + TB_ROW_OFF_FUNC, fv) d.RowCount = d.RowCount + 1 d.RawRowCount = d.RawRowCount + 1 return 0 @@ -2100,6 +2162,517 @@ def _tb_parse_dump(mb: memhub.MemBuddy*, dump: str) -> TbDump | t.CPtr: return d +# ============================================================ +# --traceback 调用点归一:.vpcall + llvm-objdump +# +# MLIR 后端不产 DWARF(我方 .vp 函数在 exe 里没有 .debug_line),故用编译器在翻译期 +# 落盘的 .vpcall 文本 + 链接后对 exe 的 `llvm-objdump -d` 反汇编,把每个直接 +# callq 的**返回地址**(调用方帧的 PC)还原为「文件 + 调用点行 + 调用方函数名」。 +# +# 匹配口径:objdump 的函数标签地址 → 下一标签地址 构成函数区间;对每条指令行 +# `^([0-9a-f]+): (.. )+\s+(\S+)\s*(.*)$`,`callq 0xADDR ` 的返回地址 = 下一条 +# 指令(或下一个标签)的地址。用「所在函数 + callee 符号 + 该 (caller,callee) 对内的 +# 出现序号」匹配编译器 C 记录(同一 (caller,callee) 在构建序与地址序一致,llc -O0)。 +# 另为我方每个函数在入口地址生成一条兜底记录(F 记录 → (文件, 定义行, 函数名))。 +# ============================================================ + +# 调用记录:u64 caller | u64 callee | u64 file | u32 line | u32 next(组内下一个, NONE=尾) +TB_CR_SIZE: t.CDefine = 32 +TB_CR_OFF_CALLER: t.CDefine = 0 +TB_CR_OFF_CALLEE: t.CDefine = 8 +TB_CR_OFF_FILE: t.CDefine = 16 +TB_CR_OFF_LINE: t.CDefine = 24 +TB_CR_OFF_NEXT: t.CDefine = 28 +# 函数记录:u64 sym | u64 file | u32 defline | u32 pad +TB_FR_SIZE: t.CDefine = 24 +TB_FR_OFF_SYM: t.CDefine = 0 +TB_FR_OFF_FILE: t.CDefine = 8 +TB_FR_OFF_DEFLINE: t.CDefine = 16 +# 组哈希槽(以 (caller,callee) 为键;开放寻址 + 线性探测): +# u64 caller | u64 callee | u32 used | u32 head | u32 tail | u32 cursor +TB_GRP_SIZE: t.CDefine = 32 +TB_GRP_OFF_CALLER: t.CDefine = 0 +TB_GRP_OFF_CALLEE: t.CDefine = 8 +TB_GRP_OFF_USED: t.CDefine = 16 +TB_GRP_OFF_HEAD: t.CDefine = 20 +TB_GRP_OFF_TAIL: t.CDefine = 24 +TB_GRP_OFF_CURSOR: t.CDefine = 28 +# 无效索引哨兵(u32 全 1) +TB_IDX_NONE: uint64 = 0xFFFFFFFF + + +class TbCallParse: + Cr: bytes # TB_CR_SIZE 记录数组 + CrCap: int + CrN: int + Fr: bytes # TB_FR_SIZE 记录数组 + FrCap: int + FrN: int + Grp: bytes # TB_GRP_SIZE 槽数组 + GrpCap: int + + def __new__(self) -> t.CPtr: + return t.CPtr(_mbuddy.alloc(TbCallParse.__sizeof__())) + + def __init__(self): + self.Cr = None + self.CrCap = 0 + self.CrN = 0 + self.Fr = None + self.FrCap = 0 + self.FrN = 0 + self.Grp = None + self.GrpCap = 0 + + +def _tb_next_pow2(n: int) -> int: + p: int = 16 + while p < n: + p = p * 2 + return p + + +def _tb_strhash(s: str) -> uint64: + """FNV-1a(32 位起点 + 64 位素数,仅用于哈希表寻址)。""" + h: uint64 = 0x811C9DC5 + if s is None: + return h + i: sizet = 0 + while _tb_cc(s, i) != 0: + h = (h ^ uint64(_tb_cc(s, i))) * 0x100000001B3 + i += 1 + return h + + +def _tb_grp_get(grp: bytes, cap: int, caller: str, callee: str, do_insert: int) -> int: + """按 (caller,callee) 查槽;do_insert=1 时未命中就地插入。返回槽号或 -1。""" + if grp is None or cap <= 0 or caller is None or callee is None: + return -1 + mask: uint64 = uint64(cap - 1) + hv: uint64 = (_tb_strhash(caller) * 0x100000001B3 + _tb_strhash(callee)) & mask + i: int = 0 + while i < cap: + slot: int = int((hv + uint64(i)) & mask) + off: sizet = sizet(slot) * TB_GRP_SIZE + if uint64(_tb_rd_u32(grp, off + TB_GRP_OFF_USED)) == 0: + if do_insert == 0: + return -1 + _tb_wr_u32(grp, off + TB_GRP_OFF_USED, uint64(1)) + _tb_wr_u64(grp, off + TB_GRP_OFF_CALLER, t.CUInt64T(caller)) + _tb_wr_u64(grp, off + TB_GRP_OFF_CALLEE, t.CUInt64T(callee)) + _tb_wr_u32(grp, off + TB_GRP_OFF_HEAD, uint64(TB_IDX_NONE)) + _tb_wr_u32(grp, off + TB_GRP_OFF_TAIL, uint64(TB_IDX_NONE)) + _tb_wr_u32(grp, off + TB_GRP_OFF_CURSOR, uint64(TB_IDX_NONE)) + return slot + sc: str = rawptr(_tb_rd_u64(grp, off + TB_GRP_OFF_CALLER)) + sk: str = rawptr(_tb_rd_u64(grp, off + TB_GRP_OFF_CALLEE)) + if sc is not None and sk is not None \ + and string.strcmp(sc, caller) == 0 and string.strcmp(sk, callee) == 0: + return slot + i += 1 + return -1 + + +def _tb_cp_init(mb: memhub.MemBuddy*) -> TbCallParse | t.CPtr: + cp: TbCallParse | t.CPtr = TbCallParse() + if cp is None: + return None + cp.CrCap = 1024 + cp.FrCap = 1024 + cp.Cr = mb.alloc(sizet(cp.CrCap) * TB_CR_SIZE) + cp.Fr = mb.alloc(sizet(cp.FrCap) * TB_FR_SIZE) + if cp.Cr is None or cp.Fr is None: + return None + cp.CrN = 0 + cp.FrN = 0 + return cp + + +def _tb_cp_add_cr(mb: memhub.MemBuddy*, cp: TbCallParse | t.CPtr, + caller: str, callee: str, file: str, line: int) -> int: + if cp.CrN >= cp.CrCap: + ncap: int = cp.CrCap * 2 + nb: bytes = _tb_grow_rec(mb, cp.Cr, cp.CrCap, TB_CR_SIZE, ncap) + if nb is None: + VLogger.error("--traceback: 调用记录表扩容失败,终止编译", "TRACEBACK") + return 1 + cp.Cr = nb + cp.CrCap = ncap + off: sizet = sizet(cp.CrN) * TB_CR_SIZE + _tb_wr_u64(cp.Cr, off + TB_CR_OFF_CALLER, t.CUInt64T(caller)) + _tb_wr_u64(cp.Cr, off + TB_CR_OFF_CALLEE, t.CUInt64T(callee)) + _tb_wr_u64(cp.Cr, off + TB_CR_OFF_FILE, t.CUInt64T(file)) + _tb_wr_u32(cp.Cr, off + TB_CR_OFF_LINE, uint64(line)) + _tb_wr_u32(cp.Cr, off + TB_CR_OFF_NEXT, uint64(TB_IDX_NONE)) + cp.CrN = cp.CrN + 1 + return 0 + + +def _tb_cp_add_fr(mb: memhub.MemBuddy*, cp: TbCallParse | t.CPtr, + sym: str, file: str, defline: int) -> int: + if cp.FrN >= cp.FrCap: + ncap: int = cp.FrCap * 2 + nb: bytes = _tb_grow_rec(mb, cp.Fr, cp.FrCap, TB_FR_SIZE, ncap) + if nb is None: + VLogger.error("--traceback: 函数记录表扩容失败,终止编译", "TRACEBACK") + return 1 + cp.Fr = nb + cp.FrCap = ncap + off: sizet = sizet(cp.FrN) * TB_FR_SIZE + _tb_wr_u64(cp.Fr, off + TB_FR_OFF_SYM, t.CUInt64T(sym)) + _tb_wr_u64(cp.Fr, off + TB_FR_OFF_FILE, t.CUInt64T(file)) + _tb_wr_u32(cp.Fr, off + TB_FR_OFF_DEFLINE, uint64(defline)) + cp.FrN = cp.FrN + 1 + return 0 + + +def _tb_read_whole(mb: memhub.MemBuddy*, path: str) -> str: + """读整个文件为 NUL 结尾字符串(失败返回 None)。""" + f: rawptr[fileio.File] = fileio.File(path, fileio.MODE.R) + if f.closed: + return None + sz: LONGLONG = f.size() + if sz < 0: + f.close() + return None + buf: str = mb.alloc(sizet(sz) + 2) + if buf is None: + f.close() + return None + got: LONG = f.read_all(buf, ULONG(sz) + ULONG(2)) + f.close() + if got < 0: + return None + return buf + + +def _tb_parse_f_line(mb: memhub.MemBuddy*, s: str, a: sizet, b: sizet, + cp: TbCallParse | t.CPtr) -> int: + """解析 `F\t\t\t`。""" + p: sizet = a + while p < b and _tb_cc(s, p) != 9: + p += 1 + if p >= b: + return 0 + p += 1 + f2: sizet = p + while f2 < b and _tb_cc(s, f2) != 9: + f2 += 1 + if f2 >= b: + return 0 + sym: str = _tb_dup_span(mb, s + p, f2 - p) + p = f2 + 1 + f3: sizet = p + while f3 < b and _tb_cc(s, f3) != 9: + f3 += 1 + if f3 >= b: + return 0 + defline: int = _tb_parse_dec(s + p) + p = f3 + 1 + file: str = _tb_dup_span(mb, s + p, b - p) + if sym is None or file is None: + return 0 + return _tb_cp_add_fr(mb, cp, sym, file, defline) + + +def _tb_parse_c_line(mb: memhub.MemBuddy*, s: str, a: sizet, b: sizet, + cp: TbCallParse | t.CPtr) -> int: + """解析 `C\t\t\t\t`。""" + p: sizet = a + while p < b and _tb_cc(s, p) != 9: + p += 1 + if p >= b: + return 0 + p += 1 + e2: sizet = p + while e2 < b and _tb_cc(s, e2) != 9: + e2 += 1 + if e2 >= b: + return 0 + caller: str = _tb_dup_span(mb, s + p, e2 - p) + p = e2 + 1 + e3: sizet = p + while e3 < b and _tb_cc(s, e3) != 9: + e3 += 1 + if e3 >= b: + return 0 + callee: str = _tb_dup_span(mb, s + p, e3 - p) + p = e3 + 1 + e4: sizet = p + while e4 < b and _tb_cc(s, e4) != 9: + e4 += 1 + if e4 >= b: + return 0 + line: int = _tb_parse_dec(s + p) + p = e4 + 1 + file: str = _tb_dup_span(mb, s + p, b - p) + if caller is None or callee is None or file is None: + return 0 + return _tb_cp_add_cr(mb, cp, caller, callee, file, line) + + +def _tb_parse_vpcall_file(mb: memhub.MemBuddy*, path: str, + cp: TbCallParse | t.CPtr) -> int: + whole: str = _tb_read_whole(mb, path) + if whole is None: + VLogger.error(f"--traceback: 读取 .vpcall 失败:{path},终止编译", "TRACEBACK") + return 1 + n: sizet = string.strlen(whole) + pos: sizet = 0 + while pos < n: + ls: sizet = pos + while pos < n and _tb_cc(whole, pos) != 10: + pos += 1 + le: sizet = pos + if pos < n: + pos += 1 + if le > ls and _tb_cc(whole, le - 1) == 13: + le -= 1 + if le > ls: + typ: int = _tb_cc(whole, ls) + if typ == 70: + if _tb_parse_f_line(mb, whole, ls, le, cp) != 0: + return 1 + elif typ == 67: + if _tb_parse_c_line(mb, whole, ls, le, cp) != 0: + return 1 + return 0 + + +def _tb_load_vpcalls(mb: memhub.MemBuddy*, cp: TbCallParse | t.CPtr) -> int: + if _tb_vpcall_buf is None or _tb_vpcall_n <= 0: + return 0 + k: int = 0 + while k < _tb_vpcall_n: + slot: str | t.CPtr = (str | t.CPtr)(uint64(_tb_vpcall_buf) + sizet(k) * 8) + path: str = slot[0] + if path is not None: + if _tb_parse_vpcall_file(mb, path, cp) != 0: + return 1 + k += 1 + return 0 + + +def _tb_build_groups(mb: memhub.MemBuddy*, cp: TbCallParse | t.CPtr) -> int: + """把所有 C 记录按 (caller,callee) 串成组链表(组槽存 head/tail/cursor)。""" + if cp.CrN <= 0: + return 0 + cp.GrpCap = _tb_next_pow2(cp.CrN * 2 + 16) + cp.Grp = mb.alloc(sizet(cp.GrpCap) * TB_GRP_SIZE) + if cp.Grp is None: + VLogger.error("--traceback: 调用点分组表分配失败,终止编译", "TRACEBACK") + return 1 + string.memset(cp.Grp, 0, sizet(cp.GrpCap) * TB_GRP_SIZE) + i: int = 0 + while i < cp.CrN: + off: sizet = sizet(i) * TB_CR_SIZE + caller: str = rawptr(_tb_rd_u64(cp.Cr, off + TB_CR_OFF_CALLER)) + callee: str = rawptr(_tb_rd_u64(cp.Cr, off + TB_CR_OFF_CALLEE)) + slot: int = _tb_grp_get(cp.Grp, cp.GrpCap, caller, callee, 1) + if slot < 0: + VLogger.error("--traceback: 调用点分组哈希表溢出,终止编译", "TRACEBACK") + return 1 + goff: sizet = sizet(slot) * TB_GRP_SIZE + tailv: uint64 = uint64(_tb_rd_u32(cp.Grp, goff + TB_GRP_OFF_TAIL)) + if tailv == uint64(TB_IDX_NONE): + _tb_wr_u32(cp.Grp, goff + TB_GRP_OFF_HEAD, uint64(i)) + _tb_wr_u32(cp.Grp, goff + TB_GRP_OFF_CURSOR, uint64(i)) + else: + toff: sizet = sizet(int(tailv)) * TB_CR_SIZE + _tb_wr_u32(cp.Cr, toff + TB_CR_OFF_NEXT, uint64(i)) + _tb_wr_u32(cp.Cr, off + TB_CR_OFF_NEXT, uint64(TB_IDX_NONE)) + _tb_wr_u32(cp.Grp, goff + TB_GRP_OFF_TAIL, uint64(i)) + i += 1 + return 0 + + +def _tb_emit_call_row(mb: memhub.MemBuddy*, d: TbDump | t.CPtr, + cp: TbCallParse | t.CPtr, img_lo: uint64, + caller: str, callee: str, ret_va: uint64) -> int: + """按 (caller,callee) 的下一条未消费 C 记录为返回地址生成一条行记录。""" + if caller is None or callee is None or cp.Grp is None or ret_va < img_lo: + return 0 + slot: int = _tb_grp_get(cp.Grp, cp.GrpCap, caller, callee, 0) + if slot < 0: + return 0 + goff: sizet = sizet(slot) * TB_GRP_SIZE + curv: uint64 = uint64(_tb_rd_u32(cp.Grp, goff + TB_GRP_OFF_CURSOR)) + if curv == uint64(TB_IDX_NONE): + return 0 + ci: int = int(curv) + coff: sizet = sizet(ci) * TB_CR_SIZE + file: str = rawptr(_tb_rd_u64(cp.Cr, coff + TB_CR_OFF_FILE)) + line: int = int(_tb_rd_u32(cp.Cr, coff + TB_CR_OFF_LINE)) + nxt: uint64 = uint64(_tb_rd_u32(cp.Cr, coff + TB_CR_OFF_NEXT)) + _tb_wr_u32(cp.Grp, goff + TB_GRP_OFF_CURSOR, nxt) + return _tb_emit_row(mb, d, ret_va, file, line, caller) + + +def _tb_emit_entry_row(mb: memhub.MemBuddy*, d: TbDump | t.CPtr, + cp: TbCallParse | t.CPtr, img_lo: uint64, + addr: uint64, sym: str) -> int: + """为 objdump 标签 `sym` 生成函数入口兜底行(文件/定义行取自 F 记录)。""" + if sym is None or cp.Fr is None or cp.FrN <= 0 or addr < img_lo: + return 0 + i: int = 0 + while i < cp.FrN: + off: sizet = sizet(i) * TB_FR_SIZE + s: str = rawptr(_tb_rd_u64(cp.Fr, off + TB_FR_OFF_SYM)) + if s is not None and string.strcmp(s, sym) == 0: + file: str = rawptr(_tb_rd_u64(cp.Fr, off + TB_FR_OFF_FILE)) + dl: int = int(_tb_rd_u32(cp.Fr, off + TB_FR_OFF_DEFLINE)) + return _tb_emit_row(mb, d, addr, file, dl, sym) + i += 1 + return 0 + + +def _tb_hex_span(s: str, a: sizet, b: sizet) -> uint64: + v: uint64 = 0 + i: sizet = a + while i < b: + hd: int = _tb_hex_digit(_tb_cc(s, i)) + if hd < 0: + break + v = (v << 4) | uint64(hd) + i += 1 + return v + + +def _tb_run_objdump(mb: memhub.MemBuddy*, exe_path: str) -> str: + cl: sizet = string.strlen(exe_path) + 64 + cmd: bytes = mb.alloc(cl) + if cmd is None: + VLogger.error("--traceback: objdump 命令缓冲分配失败,终止编译", "TRACEBACK") + return None + viperlib.snprintf(cmd, cl, "llvm-objdump -d \"%s\"", exe_path) + res: rawptr[subprocess.CompletedProcess] = subprocess.run(cmd, True, True) + if res is None or res.returncode != 0: + rc: int = -1 + if res is not None: + rc = res.returncode + VLogger.error(f"--traceback: 调用 llvm-objdump 失败(返回码 {rc};请确认 PATH 内含 LLVM 工具),终止编译", "TRACEBACK") + return None + dump: str = res.stdout + if dump is None: + VLogger.error("--traceback: llvm-objdump 无输出,终止编译", "TRACEBACK") + return None + return dump + + +def _tb_scan_objdump(mb: memhub.MemBuddy*, dump: str, img_lo: uint64, + d: TbDump | t.CPtr, cp: TbCallParse | t.CPtr) -> int: + """解析 `llvm-objdump -d` 文本,匹配 C/F 记录并把我方行写入 d。""" + dlen: sizet = string.strlen(dump) + pos: sizet = 0 + in_text: int = 0 + cur_func: str = None + prev_is_call: int = 0 + prev_callee: str = None + prev_func: str = None + while pos < dlen: + ls: sizet = pos + while pos < dlen and _tb_cc(dump, pos) != 10: + pos += 1 + le: sizet = pos + if pos < dlen: + pos += 1 + if le > ls and _tb_cc(dump, le - 1) == 13: + le -= 1 + if le <= ls: + continue + if _tb_startswith(dump + ls, "Disassembly of section") != 0: + # 跨段:上一个段末尾未决的 callq 无有效返回地址,丢弃 + prev_is_call = 0 + if _tb_contains(dump + ls, ".text") != 0: + in_text = 1 + else: + in_text = 0 + continue + if in_text == 0: + continue + c0: int = _tb_cc(dump, ls) + is_hex0: int = 0 + if (c0 >= 48 and c0 <= 57) or (c0 >= 97 and c0 <= 102) or (c0 >= 65 and c0 <= 70): + is_hex0 = 1 + if is_hex0 == 0: + continue + # 首个空格 + sp: sizet = ls + while sp < le and _tb_cc(dump, sp) != 32: + sp += 1 + if sp + 1 < le and _tb_cc(dump, sp + 1) == 60: + # ---- 标签行 `ADDR :` ---- + laddr: uint64 = _tb_hex_span(dump, ls, sp) + if prev_is_call != 0: + _tb_emit_call_row(mb, d, cp, img_lo, prev_func, prev_callee, laddr) + prev_is_call = 0 + lb: sizet = sp + 2 + j: sizet = lb + while j < le and _tb_cc(dump, j) != 62: + j += 1 + cur_func = _tb_dup_span(mb, dump + lb, j - lb) + if cur_func is not None: + _tb_emit_entry_row(mb, d, cp, img_lo, laddr, cur_func) + continue + # ---- 指令行 `ADDR: bytes mnemonic operands` ---- + k: sizet = ls + while k < le and _tb_cc(dump, k) != 58: + k += 1 + if k >= le: + continue + iaddr: uint64 = _tb_hex_span(dump, ls, k) + if prev_is_call != 0: + _tb_emit_call_row(mb, d, cp, img_lo, prev_func, prev_callee, iaddr) + prev_is_call = 0 + # 跳过字节 token(恰好 2 个 hex 字符),定位 mnemonic 起点 + p: sizet = k + 1 + while p < le and (_tb_cc(dump, p) == 32 or _tb_cc(dump, p) == 9): + p += 1 + ms: sizet = p + while p < le: + ts: sizet = p + while p < le and _tb_cc(dump, p) != 32 and _tb_cc(dump, p) != 9: + p += 1 + tl: sizet = p - ts + ishex: int = 0 + if tl == 2 and _tb_hex_digit(_tb_cc(dump, ts)) >= 0 \ + and _tb_hex_digit(_tb_cc(dump, ts + 1)) >= 0: + ishex = 1 + ms = ts + if ishex == 0: + break + while p < le and (_tb_cc(dump, p) == 32 or _tb_cc(dump, p) == 9): + p += 1 + me: sizet = p + mlen: sizet = me - ms + is_call: int = 0 + if mlen == 5 and string.strncmp(dump + ms, "callq", 5) == 0: + is_call = 1 + elif mlen == 4 and string.strncmp(dump + ms, "call", 4) == 0: + is_call = 1 + if is_call != 0: + os: sizet = me + while os < le and (_tb_cc(dump, os) == 32 or _tb_cc(dump, os) == 9): + os += 1 + if os + 1 < le and _tb_cc(dump, os) == 48 and _tb_cc(dump, os + 1) == 120: + # 直接调用:提取 `` + ab: sizet = os + while ab < le and _tb_cc(dump, ab) != 60: + ab += 1 + if ab < le: + cb: sizet = ab + 1 + ce: sizet = cb + while ce < le and _tb_cc(dump, ce) != 62: + ce += 1 + xe: sizet = cb + while xe < ce and _tb_cc(dump, xe) != 43: + xe += 1 + prev_callee = _tb_dup_span(mb, dump + cb, xe - cb) + prev_func = cur_func + prev_is_call = 1 + return 0 + + def WriteStackMap(mb: memhub.MemBuddy*, exe_path: str) -> int: """--traceback: export .stackmap after a successful link. 0 = ok.""" if mb is None or exe_path is None: @@ -2140,8 +2713,35 @@ def WriteStackMap(mb: memhub.MemBuddy*, exe_path: str) -> int: d: TbDump | t.CPtr = _tb_parse_dump(mb, dump) if d is None: return 1 + + # ---- 1b) 我方调用点行记录:.vpcall + llvm-objdump ---- + # DWARF 行仅覆盖链接进来的 mingw CRT(我方 .vp 函数无 .debug_line);这里用编译器 + # 落盘的 .vpcall + objdump 反汇编补上「返回地址 → 调用点行 / 函数入口行」。 + if _tb_vpcall_n > 0: + cp: TbCallParse | t.CPtr = _tb_cp_init(mb) + if cp is None: + VLogger.error("--traceback: 调用点解析状态分配失败,终止编译", "TRACEBACK") + return 1 + if _tb_load_vpcalls(mb, cp) != 0: + return 1 + if _tb_build_groups(mb, cp) != 0: + return 1 + odump: str = _tb_run_objdump(mb, exe_path) + if odump is None: + return 1 + our_before: int = d.RowCount + if _tb_scan_objdump(mb, odump, img_lo, d, cp) != 0: + return 1 + our_added: int = d.RowCount - our_before + if our_added > 0: + VLogger.success(f"--traceback: 从 {_tb_vpcall_n} 个 .vpcall 生成 {our_added} 条我方调用点记录(F={cp.FrN} 函数, C={cp.CrN} 调用)", "TRACEBACK") + else: + VLogger.warning(f"--traceback: 读到 {_tb_vpcall_n} 个 .vpcall(F={cp.FrN}, C={cp.CrN})但未匹配到任何 objdump 调用点——我方帧行号仍缺,退化到 DWARF", "TRACEBACK") + else: + VLogger.warning("--traceback: 未登记任何 .vpcall(我方模块无调用点记录),退化到 DWARF 行为", "TRACEBACK") + if d.RawRowCount == 0: - VLogger.error(f"--traceback: 未能从 {exe_path} 解析出任何 DWARF 行记录(.debug_line 为空?这是 --traceback 的要求)", "TRACEBACK") + VLogger.error(f"--traceback: 未能从 {exe_path} 解析出任何行记录(DWARF .debug_line 为空且无我方 .vpcall 记录,这是 --traceback 的要求)", "TRACEBACK") return 1 # ---- 2) rows: normalise to RVA, same rva keeps the largest line ---- @@ -2161,6 +2761,8 @@ def WriteStackMap(mb: memhub.MemBuddy*, exe_path: str) -> int: _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FILE)) _tb_wr_u32(d.RowBuf, sizet(kept) * TB_ROW_SIZE + TB_ROW_OFF_LINE, uint64(_tb_rd_u32(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_LINE))) + _tb_wr_u64(d.RowBuf, sizet(kept) * TB_ROW_SIZE + TB_ROW_OFF_FUNC, + _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FUNC)) else: _tb_wr_u64(d.RowBuf, sizet(kept) * TB_ROW_SIZE, a - img_lo) kept += 1 @@ -2187,17 +2789,20 @@ def WriteStackMap(mb: memhub.MemBuddy*, exe_path: str) -> int: rv: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE) bl: uint64 = uint64(_tb_rd_u32(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_LINE)) bp: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FILE) + bf: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FUNC) j: int = i + 1 while j < kept and _tb_rd_u64(d.RowBuf, sizet(j) * TB_ROW_SIZE) == rv: lj: uint64 = uint64(_tb_rd_u32(d.RowBuf, sizet(j) * TB_ROW_SIZE + TB_ROW_OFF_LINE)) if lj > bl: bl = lj bp = _tb_rd_u64(d.RowBuf, sizet(j) * TB_ROW_SIZE + TB_ROW_OFF_FILE) + bf = _tb_rd_u64(d.RowBuf, sizet(j) * TB_ROW_SIZE + TB_ROW_OFF_FUNC) j += 1 if uniq != i: _tb_wr_u64(d.RowBuf, sizet(uniq) * TB_ROW_SIZE, rv) _tb_wr_u64(d.RowBuf, sizet(uniq) * TB_ROW_SIZE + TB_ROW_OFF_FILE, bp) _tb_wr_u32(d.RowBuf, sizet(uniq) * TB_ROW_SIZE + TB_ROW_OFF_LINE, bl) + _tb_wr_u64(d.RowBuf, sizet(uniq) * TB_ROW_SIZE + TB_ROW_OFF_FUNC, bf) uniq += 1 i = j if dropped_rows > 0: @@ -2256,6 +2861,15 @@ def WriteStackMap(mb: memhub.MemBuddy*, exe_path: str) -> int: pth: str = rawptr(_tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FILE)) pool_need += string.strlen(pth) + 1 i += 1 + # 我方行直接携带函数名(走字符串池);DWARF 行名来自子程序表。 + i = 0 + while i < uniq: + fp: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FUNC) + if fp != 0: + fns: str = rawptr(fp) + if fns is not None: + pool_need += string.strlen(fns) + 1 + i += 1 i = 0 while i < fkept: nm0: str = rawptr(_tb_rd_u64(d.FuncBuf, sizet(i) * TB_FUNC_SIZE + 16)) @@ -2267,7 +2881,7 @@ def WriteStackMap(mb: memhub.MemBuddy*, exe_path: str) -> int: VLogger.error("--traceback: 字符串池对象分配失败,终止编译", "TRACEBACK") return 1 p.Pool = mb.alloc(pool_need) - p.Offs = mb.alloc(sizet(uniq + fkept) * 8 + 64) + p.Offs = mb.alloc((sizet(uniq) * 2 + sizet(fkept)) * 8 + 64) if p.Pool is None or p.Offs is None: VLogger.error("--traceback: 字符串池缓冲分配失败,终止编译", "TRACEBACK") return 1 @@ -2286,7 +2900,12 @@ def WriteStackMap(mb: memhub.MemBuddy*, exe_path: str) -> int: foff: uint64 = _tb_intern(p, pth2) noff: uint64 = 0 rv2: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE) - nm2: str = _tb_func_name_for(d.FuncBuf, fkept, rv2) + fp2: uint64 = _tb_rd_u64(d.RowBuf, sizet(i) * TB_ROW_SIZE + TB_ROW_OFF_FUNC) + nm2: str = None + if fp2 != 0: + nm2 = rawptr(fp2) + else: + nm2 = _tb_func_name_for(d.FuncBuf, fkept, rv2) if nm2 is not None: noff = _tb_intern(p, nm2) _tb_wr_u32(ro, sizet(i) * 8, foff) diff --git a/App/lib/Phase/Phase1/Phase1Main.vp b/App/lib/Phase/Phase1/Phase1Main.vp index 6d724bf..434efe5 100644 --- a/App/lib/Phase/Phase1/Phase1Main.vp +++ b/App/lib/Phase/Phase1/Phase1Main.vp @@ -1169,6 +1169,14 @@ def RunPhase1(mb: memhub.MemBuddy*, includes_dir: str, temp_dir: str, stdlib.free(full_path_p1) stdlib.free(full_buf_p1) + # --traceback: 取出本模块的调试调用点记录(mlirlite 累积)并写 .vpcall, + # 供链接后 WriteStackMap 结合 objdump 还原调用点行号。关闭时不产生任何旁文件。 + if BuildPipeline._traceback_mode != 0: + vpcall_text_p1: str = mlirlite.DebugTakeText() + if vpcall_text_p1 is not None: + BuildPipeline.WriteCallSidecar(temp_dir, sha1, vpcall_text_p1) + stdlib.free(vpcall_text_p1) + # 计时点 3:dump(stub+text)完成;输出单文件三段耗时 t_f3: uint64 = w32.win32base.GetTickCount64() VLogger.info(f"[{i+1}/{loop_n_b}] 翻译: {file_path} (parse={t_f1-t_f0} ms, translate={t_f2-t_f1} ms, dump={t_f3-t_f2} ms, total={t_f3-t_f0} ms)", "Phase1") diff --git a/App/lib/Phase/Phase2/__init__.vp b/App/lib/Phase/Phase2/__init__.vp index 8d332df..1d52326 100644 --- a/App/lib/Phase/Phase2/__init__.vp +++ b/App/lib/Phase/Phase2/__init__.vp @@ -1117,6 +1117,13 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, stdlib.free(full_path_a) stdlib.free(full_buf_a) + # --traceback: 取出本模块的调试调用点记录并写 .vpcall。 + if BuildPipeline._traceback_mode != 0: + vpcall_text_a: str = mlirlite.DebugTakeText() + if vpcall_text_a is not None: + BuildPipeline.WriteCallSidecar(temp_dir, ent.Sha1, vpcall_text_a) + stdlib.free(vpcall_text_a) + # save dependencies (_imported_modules) for Phase B (切片路径) deps_path_a: str = StubMergerCollect._sliced_path(temp_dir, td_len_pa, ent.Sha1, "deps.txt") if deps_path_a is not None: @@ -1522,7 +1529,13 @@ def RunMultiFileProject(mb: memhub.MemBuddy*, ifff.write(inc_full_buf, inc_full_len) ifff.close() stdlib.free(inc_full_path) - stdlib.free(inc_full_buf) + stdlib.free(inc_full_buf) + # --traceback: 取出本模块的调试调用点记录并写 .vpcall。 + if BuildPipeline._traceback_mode != 0: + vpcall_text_mi: str = mlirlite.DebugTakeText() + if vpcall_text_mi is not None: + BuildPipeline.WriteCallSidecar(temp_dir, inc_sha1_mi, vpcall_text_mi) + stdlib.free(vpcall_text_mi) # 重试读 full.ll inc_combined_len_mi = StubMergerBuild.LoadFullIR(temp_dir, inc_sha1_mi, inc_combined_mi, COMBINED_IR_SIZE) # OOM 清理:stub/text/meta/full 已写盘,回收本 includes 文件翻译瞬时数据 diff --git a/App/lib/core/Gen/DebugInfoGen.vp b/App/lib/core/Gen/DebugInfoGen.vp index efcfa4f..d1d14bd 100644 --- a/App/lib/core/Gen/DebugInfoGen.vp +++ b/App/lib/core/Gen/DebugInfoGen.vp @@ -33,6 +33,7 @@ _g_enabled: int = 0 _g_finalized: int = 0 _g_mod: mlirlite.LLVMModule* = None _g_file_path: str = None +_g_abs_file_path: str = None _g_file_id: int = -1 _g_null_id: int = -1 _g_subtype_id: int = -1 @@ -72,6 +73,9 @@ def SetEnabled(flag: bool) -> int: _g_enabled = 1 else: _g_enabled = 0 + # 把同一开关转达给 mlirlite 的调试调用点记录(--traceback 时我们自产 + # .vpcall 旁文件,供 WriteStackMap 结合 objdump 还原调用点行号)。 + mlirlite.DebugSetEnabled(flag) return 0 @@ -124,7 +128,7 @@ def _RefOf(pool: memhub.MemBuddy*, node_id: int) -> str: # ============================================================ def BeginModule(mod: mlirlite.LLVMModule*, file_path: str) -> int: """Reset per-module DI state. Call once at the start of each module.""" - global _g_mod, _g_file_path, _g_finalized, _g_file_id, _g_null_id + global _g_mod, _g_file_path, _g_abs_file_path, _g_finalized, _g_file_id, _g_null_id global _g_subtype_id, _g_cu_id, _g_cur_sp, _g_cur_entry_ref, _g_cur_builder, _g_depth if _g_enabled == 0: return 0 @@ -132,6 +136,10 @@ def BeginModule(mod: mlirlite.LLVMModule*, file_path: str) -> int: return 1 _g_mod = mod _g_file_path = file_path + _g_abs_file_path = None + # 模块边界兜底:翻译中途失败可能残留未 Pop 的调试函数上下文,先清空, + # 避免把上一个模块的 caller 误算到本模块的调用点上。 + mlirlite.DebugResetFunc() _g_finalized = 0 _g_file_id = -1 _g_null_id = -1 @@ -151,7 +159,7 @@ def BeginModule(mod: mlirlite.LLVMModule*, file_path: str) -> int: # (created lazily at the first define, once per module) # ============================================================ def _EnsureModuleNodes(pool: memhub.MemBuddy*) -> int: - global _g_file_id, _g_null_id, _g_subtype_id, _g_cu_id + global _g_file_id, _g_null_id, _g_subtype_id, _g_cu_id, _g_abs_file_path if _g_cu_id >= 0: return 0 mod: mlirlite.LLVMModule* = _g_mod @@ -196,6 +204,9 @@ def _EnsureModuleNodes(pool: memhub.MemBuddy*) -> int: for ni in range(flen): if nbuf[ni] == '\\': nbuf[ni] = '/' + # 保存绝对路径供 mlirlite 调试调用点记录复用(.vpcall 旁文件里的文件路径口径 + # 必须与 DIFile 一致:绝对 + 正斜杠,运行期才能按路径重读源码行)。 + _g_abs_file_path = nbuf # filename 取整条路径;directory 另存其目录前缀(两段各自 NUL 结尾,互不污染) fname: str = nbuf directory: str = "" @@ -367,6 +378,9 @@ def PushFunction(pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, return 0 _g_cur_entry_ref = entry_ref mlirlite.builder_set_debug_location(builder, entry_ref) + # 进入本 define:通知 mlirlite 调试调用点记录当前函数符号/文件/定义行, + # 并写一条 F 记录(供 .vpcall 旁文件 + WriteStackMap 生成函数入口兜底行)。 + mlirlite.DebugNoteFunc(name, _g_abs_file_path, dline) return 1 @@ -387,6 +401,8 @@ def PopFunction(builder: mlirlite.IRBuilder*) -> int: _g_cur_builder = (mlirlite.IRBuilder*)(bslot[0]) if builder is not None: mlirlite.builder_clear_debug_location(builder) + # 离开本 define:恢复 mlirlite 记录的外层函数上下文(嵌套 define 用)。 + mlirlite.DebugClearFunc() return 0 @@ -402,6 +418,8 @@ def SetStatement(pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, """ if _g_enabled == 0 or pool is None or builder is None: return 0 + # 记录当前语句行:随后本语句内 build_call 产生的 C 记录以它作为调用点行。 + mlirlite.DebugNoteLine(line) if _g_cur_sp < 0: return 0 ref: str = _EnsureLocation(pool, _g_cur_sp, line, col)