3

我有一个 C++ 工具,它可以一次遍历调用堆栈。在代码中,它首先获取实时 CPU 寄存器的副本(通过 RtlCaptureContext()),然后使用一些 " #ifdef ..." 块将 CPU 特定的寄存器名称保存到stackframe.AddrPC.Offset、 ... AddrStack... 和 ... 中AddrFrame。 ..; 此外,对于上述 3 个Addr... 成员中的每一个,它都设置了stackframe.Addr... .Mode = AddrModeFlat。(这是从我不久前遇到的一些示例代码中借来的。)

使用 x86 二进制文件,这很好用。但是,对于 x64 二进制文件,StackWalk64() 会传回虚假地址。 (第一次调用 API 时,唯一公然伪造的地址值出现在AddrReturn(== 0xFFFFFFFF'FFFFFFFE-- aka StackWalk64() 的第三个参数,GetCurrentThread() 返回的伪句柄)中。如果第二次调用 API ,但是,所有Addr... 变量都会收到虚假地址。) 无论如何AddrFrame设置都会发生这种情况:

  • 使用任一推荐的 x64“基/帧指针”CPU 寄存器:rbp(= 0xf) 或rdi(= 0x0)
  • 使用rsp (没想到它会起作用,但还是尝试了)
  • 设置AddrPCAddrStack正常,但保持AddrFrame归零(在其他示例代码中看到)
  • 清零所有Addr... 值,让 StackWalk64() 从传入的 CPU 寄存器上下文中填充它们(在其他示例代码中看到)

FWIW,物理堆栈缓冲区的内容在 x64 和 x86 上也不同(当然,在考虑了不同的指针宽度和堆栈缓冲区位置之后)。不管是什么原因, StackWalk64() 应该仍然能够正确地遍历调用堆栈——哎呀,调试器仍然能够遍历调用堆栈,而且它似乎在幕后使用 StackWalk64() 本身。奇怪的是,调试器报告的(正确的)调用堆栈包含基地址和返回地址指针值,其组成字节实际上并不存在于堆栈缓冲区中(低于或高于当前堆栈指针)。

(FWIW #2:鉴于上面的堆栈缓冲区奇怪,我确实尝试禁用 ASLR ( /dynamicbase:no) 以查看它是否有所作为,但二进制文件仍然表现出相同的行为。)

所以。任何想法为什么这在 x86 上可以正常工作,但在 x64 上会出现问题?关于如何修复它的任何建议?

4

3 回答 3

2

鉴于 fs.sf 是一个 STACKFRAME64 结构,您需要在将其传递给 StackWalk64 之前像这样初始化它:(c 是一个 CONTEXT 结构)

  DWORD machine = IMAGE_FILE_MACHINE_AMD64;
  RtlCaptureContext (&c);
  fs.sf.AddrPC.Offset = c.Rip;
  fs.sf.AddrFrame.Offset = c.Rsp;
  fs.sf.AddrStack.Offset = c.Rsp;
  fs.sf.AddrPC.Mode = AddrModeFlat;
  fs.sf.AddrFrame.Mode = AddrModeFlat;
  fs.sf.AddrStack.Mode = AddrModeFlat;

此代码取自 ACE(自适应通信环境),改编自 CodeProject 上的 StackWalker 项目。

于 2008-09-25T23:46:02.973 回答
2

SymInitialize(process, nullptr, TRUE) must be called (once) before StackWalk64().

于 2020-09-20T09:55:51.933 回答
1

FWIW,我已经切换到 using CaptureStackBackTrace(),现在它工作得很好。

于 2008-10-01T21:29:42.817 回答