fix(traceback): 撤掉 caller_ret([RBP+8] 在 .vp 帧里不是返回地址)
实测 _TypePrintDepth:RBP = rsp+0x70(locals base),入参返回地址在 RBP+0x38;[RBP+8] 只是无关局部量(值落在 .data 区),打印它会误导。调用者一律以 .stackmap 走栈结果为准 文档第十四节 C 同步;regs/av_addr 两条保留
This commit is contained in:
1 parent
1379b8814c
commit
677dd13320
1 file changed
+11
-19
+11
-19
@@ -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
|
||||
|
||||
Reference in new issue
Block a user