如何使StackWalk64()在x64上成功工作?

时间:2020-03-06 14:45:21  来源:igfitidea点击:

我有一个C ++工具,可将调用堆栈移到某一点。在代码中,它首先获取实时CPU寄存器的副本(通过RtlCaptureContext()),然后使用一些" #ifdef ..."块将特定于CPU的寄存器名称保存到stackstack.AddrPC.Offset中。 ,...AddrStack...和...AddrFrame` ...;同样,对于上面的3个Addr ...成员,每个成员都设置stackstack.Addr ... Mode = AddrModeFlat。 (这是我前一段时间遇到的一些示例代码的借用。)

使用x86二进制文件,效果很好。但是,对于x64二进制文件,StackWalk64()会传回伪造的地址。 (第一次调用该API时,唯一的虚假地址值出现在AddrReturn(==0xFFFFFFFF'FFFFFFFE-aka StackWalk64()的第3个arg,即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上却有问题?关于如何解决它的任何建议?

解决方案

鉴于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项目。

FWIW,我已经切换到使用CaptureStackBackTrace(),现在它可以正常工作了。