如何使StackWalk64()在x64上成功运行?

时间:2008-09-25 23:01:31

标签: c++ windows winapi 64-bit callstack

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

使用x86二进制文件,这非常有用。但是,使用x64二进制文件,StackWalk64()会传回伪造的地址。 (第一次调用API时,唯一明显的虚假地址值出现在AddrReturn(== 0xFFFFFFFF'FFFFFFFE - 又名StackWalk64()的第3个arg中,伪句柄返回GetCurrentThread())。但是,如果第二次调用API,则所有Addr ...变量都会收到虚假地址。)无论AddrFrame如何设置,都会发生这种情况:

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

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

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

因此。任何想法为什么这可以在x86上正常工作,但在x64上有问题?有关如何修复它的任何建议吗?

3 个答案:

答案 0 :(得分: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项目。

答案 1 :(得分:1)

FWIW,我已切换到使用CaptureStackBackTrace(),现在它运作正常。

答案 2 :(得分:1)

SymInitialize(process, nullptr, TRUE)必须在StackWalk64()之前被调用一次。