From 677dd13320d4d9beeb323d2e7902af570d2ef567 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 28 Sep 2026 12:21:26 +0800 Subject: [PATCH] =?UTF-8?q?fix(traceback):=20=E6=92=A4=E6=8E=89=20caller?= =?UTF-8?q?=5Fret=EF=BC=88[RBP+8]=20=E5=9C=A8=20.vp=20=E5=B8=A7=E9=87=8C?= =?UTF-8?q?=E4=B8=8D=E6=98=AF=E8=BF=94=E5=9B=9E=E5=9C=B0=E5=9D=80=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实测 _TypePrintDepth:RBP = rsp+0x70(locals base),入参返回地址在 RBP+0x38;[RBP+8] 只是无关局部量(值落在 .data 区),打印它会误导。调用者一律以 .stackmap 走栈结果为准 文档第十四节 C 同步;regs/av_addr 两条保留 --- traceback.vp | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/traceback.vp b/traceback.vp index 3922de6..a849c61 100644 --- a/traceback.vp +++ b/traceback.vp @@ -410,16 +410,16 @@ import w32.win32dbghelp as dbg # 输出形状:`=== TPC crash dump (unhandled exception; the process keeps terminating) ===` # → ` Trigger=unhandled-exception code=0x… addr=0x… av_kind=… av_addr=0x… crashthread=…` # → ` exe base=… RIP=… RSP=… RBP=…` → ` regs: rcx=… rdx=… r8=… r9=… r12=…` -# → ` caller_ret=0x…`(出错帧 [RBP+8],能读到时才打)→ 与 Ctrl+C 逐字同形的 Traceback 帧 -# (含源码行回读)→ ` frames=N` → 落文件提示 → `=== crash dump end ===`。 +# → 与 Ctrl+C 逐字同形的 Traceback 帧(含源码行回读)→ ` frames=N` → 落文件提示 +# → `=== crash dump end ===`。 # * `av_kind`/`av_addr` 取自 EXCEPTION_RECORD 的 ExceptionInformation[0/1](0=读 1=写 8=执行; # 数据地址),只在 0xC0000005 类访问违规时有效——崩溃现场正是"原始数字胜过猜测"的场合; # * `regs` 给的是**出错帧的实参现场**(Win64 前 4 个整型实参 = rcx/rdx/r8/r9;r12 是该帧 # 用来解引用指针的寄存器):一条就能区分「实参传错」与「实参对、指针所指内容错」, -# 不必再来一轮重编; -# * `caller_ret` 是**出错帧自己的返回地址**([RBP+8]):它不依赖 .stackmap,可独立反汇编核对, -# 用来判定"走栈给出的第一个调用者帧"是真帧还是 unwind 造出来的假帧。Ctrl+C 路径不打印 -# 这些(那里没有"出错指令的算子"可看)。 +# 不必再来一轮重编。Ctrl+C 路径不打印它(那里没有"出错指令的算子"可看)。 +# * **不打印 `[RBP+8]` 当返回地址**:.vp 帧的 RBP 是 locals base(`leaq N(%rsp), %rbp`), +# 返回地址在 RBP + 帧字节数 - 8(逐函数不同;实测 _TypePrintDepth 是 RBP+0x38), +# `[RBP+8]` 只是无关局部量。调用者一律以 .stackmap 走栈结果为准。 # 中文件头(栈转储/触发=)只属于 Ctrl+C 路径,崩溃路径不动它。 # D. 已知边界:ASan 的 VEH 优先于未处理异常过滤器——若某次崩溃由 ASan 正常报告(它自带堆栈), # 本路径不会被调用;本路径覆盖的正是「ASan 报不出来 / 没开 ASan」那两类(实测的两个退出码 @@ -1911,19 +1911,11 @@ def DumpCrashThread(Info: VOIDPTR) -> int: uint64(ctx.Rcx), uint64(ctx.Rdx), uint64(ctx.R8), uint64(ctx.R9), uint64(ctx.R12)) EmitLine(N) - # Return address of the faulting frame, read from [RBP+8] (the standard frame-pointer slot). - # Printed on its own line only when that read succeeds: it is the caller's resume address, so - # it can be mapped independently of the .stackmap and proves whether the walker's first - # recovered frame is the real caller (a missing line means "not available", never a fake 0). - RetWord: t.CArray[uint64, 1] - RetWord[0] = 0 - if Rbp != 0: - if wm.ReadProcessMemory(HProc, t.CPtr(Rbp + 8), &RetWord, sizet(8), &Rd) != 0 \ - and t.CSizeT(Rd) >= t.CSizeT(8): - N = stdio.snprintf(_State.LineBuf, t.CSizeT(TRACEBACK_LINE_CAP), - " caller_ret=0x%llx (faulting frame's return address)\n", - uint64(RetWord[0])) - EmitLine(N) + # NOTE: [RBP+8] is deliberately NOT read as a "return address". In .vp frames the compiler emits + # `leaq N(%rsp), %rbp` (RBP = a locals base, N = the spill area size), so the incoming return + # address sits at RBP + callee_frame_bytes - 8 - a per-function offset (measured on + # _TypePrintDepth: RBP = rsp+0x70 and the return address is at RBP+0x38, so [RBP+8] is an + # unrelated local, not the caller). The .stackmap walker is the authority for callers. # dbghelp first: a fault usually sits inside a system DLL, where the RBP chain has nothing to # follow. The RBP chain and the stack scan stay as fallbacks, exactly as in the Ctrl+C path. Frames: int = 0