如何获取基堆栈指针的地址

Posted

技术标签:

【中文标题】如何获取基堆栈指针的地址【英文标题】:How to get address of base stack pointer 【发布时间】:2010-12-23 06:06:18 【问题描述】:

我正在将应用程序从 x86 移植到 x64。我正在使用 Visual Studio 2009;大部分代码是 C++,有些部分是纯 C。编译到 x64 时不支持 __asm 关键字,并且我们的应用程序包含一些内联汇编程序部分。我没有写这段代码,所以我不知道 et 应该做什么:

int CallStackSize() 
    DWORD Frame;
    PDWORD pFrame;
    __asm
        
            mov EAX, EBP
            mov Frame, EAX
        
    pFrame = (PDWORD)Frame;
    /*... do stuff with pFrame here*/

EBP 是指向当前函数堆栈的基指针。有什么方法可以在不使用内联 asm 的情况下获取堆栈指针?我一直在研究 Microsoft 提供的作为内联 asm 替代品的内在函数,但我找不到任何对我有用的东西。有什么想法吗?

Andreas 询问 pFrame 做了什么。这是完整的功能:

int CallStackSize(DWORD frameEBP = 0)

    DWORD pc;
    int tmpint = 0;
    DWORD Frame;
    PDWORD pFrame, pPrevFrame;

    if(!frameEBP) // No frame supplied. Use current.
    
        __asm
        
            mov EAX, EBP
            mov Frame, EAX
        
    
    else Frame = frameEBP;

    pFrame = (PDWORD)Frame;
    do
    
        pc = pFrame[1];
        pPrevFrame = pFrame;
        pFrame = (PDWORD)pFrame[0]; // precede to next higher frame on stack

        if ((DWORD)pFrame & 3) // Frame pointer must be aligned on a DWORD boundary. Bail if not so.
        break;

        if (pFrame <= pPrevFrame)
        break;

        // Can two DWORDs be read from the supposed frame address?
        if(IsBadWritePtr(pFrame, sizeof(PVOID)*2))
        break;

        tmpint++;
     while (true);
    return tmpint;

未使用变量 pc。看起来这个函数沿着堆栈向下走,直到它失败。它假定它无法在应用程序堆栈之外读取,因此当它失败时,它已经测量了调用堆栈的深度。此代码不需要在 _EVERY_SINGLE 编译器上编译。只是VS2009。该应用程序不需要在 EVERY_SINGLE 计算机上运行。我们可以完全控制部署,因为我们自己安装/配置并将整个事情交付给我们的客户。

【问题讨论】:

用 pFrame 做了什么? 【参考方案1】:

真正正确的做法是重写这个函数所做的一切,这样它就不需要访问实际的帧指针。这绝对是不好的行为。

但是,要做你正在寻找的事情,你应该能够做到:

int CallStackSize() 
    __int64 Frame = 0; /* MUST be the very first thing in the function */
    PDWORD pFrame;

    Frame++; /* make sure that Frame doesn't get optimized out */

    pFrame = (PDWORD)(&Frame);
    /*... do stuff with pFrame here*/

这样做的原因是,在 C 语言中,函数所做的第一件事通常是在分配局部变量之前保存基指针 (ebp) 的位置。通过创建一个局部变量(Frame)然后获取 if 的地址,我们就真正得到了这个函数的栈帧的起始地址。

注意:某些优化可能会导致“框架”变量被删除。可能不会,但要小心。

第二个注意事项:当“pFrame”本身在堆栈上时,您的原始代码以及此代码会操纵“pFrame”指向的数据。可能会在此处意外覆盖 pFrame,然后您的指针会出错,并且可能会出现一些奇怪的行为。在从 x86 迁移到 x64 时要特别注意这一点,因为 pFrame 现在是 8 个字节而不是 4 个字节,所以如果您的旧“使用 pFrame 处理”代码在弄乱内存之前考虑了 Frame 和 pFrame 的大小,您将需要考虑新的更大尺寸。

【讨论】:

我不认为如果你获取它的地址变量可以被删除。但是,编译器可以随意重新排列变量。从技术上讲,没有语言级别的保证 Frame 甚至位于堆栈上(但实际上我希望这可以正常工作)。 我的想法完全正确,我正准备提交类似的答​​案,但我阻止了自己,因为它感觉很脆弱。 如果你做到了volatile呢? 这在应用程序代码中应该是非常罕见的,但在系统代码中却很常见。保守的垃圾收集器可以利用它。 Mozilla 的 javascript 引擎出于不同的原因使用它:避免溢出 C 堆栈。 mxr.mozilla.org/mozilla-central/ident?i=JS_CHECK_STACK_SIZE @JoshLee: 是的,volatile int Frame = 0; 会避免编译器将0 的存储区优化为Frame。如果启用优化,Frame++ 根本没有帮助。但我们实际上并不需要 MSVC 或 GCC。使用该地址足以让编译器获取空间地址,它可以在此堆栈帧中的某处存储本地地址。 x64 MSVC 首先使用影子空间(在返回地址之上)。针对 Linux 的 x86-64 GCC 使用 RSP 下面的红色区域。 32 位 MSVC 保留实际空间。【参考方案2】:

如果您需要精确的“基指针”,那么内联汇编是唯一的选择。

令人惊讶的是,可以使用相对较少的特定于平台的代码编写代码来处理堆栈,但很难完全避免汇编(取决于您在做什么)。

如果您只想避免堆栈溢出,您可以获取任何局部变量的地址。

【讨论】:

看来您必须使用专用的汇编程序 (ml64) 来创建一个检查堆栈指针的例程,发现前一个堆栈帧的地址(以说明堆栈指针中的移动)当调用你的汇编程序时),并返回它(我不知道你会怎么做)。然后将其链接到您的 C 程序中。【参考方案3】:

您可以使用 _AddressOfReturnAddress() 内在函数来确定当前帧指针中的位置,假设它没有被完全优化掉。我假设如果您明确引用它,编译器将阻止该函数优化帧指针。或者,如果只使用单个线程,则可以使用IMAGE_NT_HEADER.OptionalHeader.SizeOfStackReserveIMAGE_NT_HEADER.OptionalHeader.SizeOfStackCommit 来确定主线程的堆栈大小。请参阅this 了解如何访问当前图像的IMAGE_NT_HEADER

我还建议不要使用IsBadWritePtr 来确定堆栈的末尾。至少你可能会导致堆栈增长,直到你达到保留,因为你会绊倒一个保护页。如果您确实想查找堆栈的当前大小,请将VirtualQuery 与您正在检查的地址一起使用。

如果最初的用途是遍历堆栈,您可以使用StackWalk64

【讨论】:

它看不到剩余多少空间,它正在往回走之前的堆栈帧链。基本上是在做回溯。 如果是这种情况,有一个 API:StackWalk64。 我建议您将其添加为新的答案,听起来这很可能是 OP 需要的。【参考方案4】:
.code

PUBLIC getStackFrameADDR _getStackFrameADDR
getStackFrameADDR:
    mov RAX, RBP
    ret 0

END

类似的东西可能对你有用。

用 ml64 或 jwasm 编译它并在你的代码中使用它来调用它 extern "C" void getstackFrameADDR(void);

【讨论】:

x86-64 代码默认通常没有帧指针。 GCC 和 MSVC 都不需要一个编译器。【参考方案5】:

不能保证 RBP(x64 的 EBP 等效项)实际上是指向调用堆栈中当前帧的指针。我猜微软决定尽管有几个新的通用寄存器,但他们需要释放另一个,所以 RBP 仅在调用 alloca() 的函数中用作帧指针,在某些其他情况下。因此,即使支持内联汇编,也不是可行的方法。

如果你只是想回溯,你需要使用 dbghelp.dll 中的 StackWalk64。它位于 XP 附带的 dbghelp.dll 中,并且在 XP 之前没有 64 位支持,因此您不需要将 dll 与您的应用程序一起提供。

对于您的 32 位版本,只需使用您当前的方法。您自己的方法可能会比 dbghelp 的导入库小,更不用说内存中的实际 dll,因此这是一个明确的优化(个人经验:我在不到一个 - dbghelp 导入库的十分之一)。

此外,如果您将其用于进程内调试或发布后崩溃报告生成,我强烈建议您只使用提供给异常处理程序的 CONTEXT 结构。

也许有一天我会决定认真地瞄准 x64,并想出一个便宜的方法来使用我可以分享的 StackWalk64,但由于我的所有项目仍然以 x86 为目标,所以我没有打扰。

【讨论】:

【参考方案6】:

Microsoft 提供了一个库 (DbgHelp) 来处理 stack walking,您应该使用而不是依赖汇编技巧。例如,如果存在 PDB 文件,它也可以遍历优化的堆栈帧(那些不使用 EBP 的)。

CodeProject 有一篇文章解释了如何使用它:

http://www.codeproject.com/KB/threads/StackWalker.aspx

【讨论】:

以上是关于如何获取基堆栈指针的地址的主要内容,如果未能解决你的问题,请参考以下文章

调试器如何跟踪堆栈?

c++中如何获取函数名,和获取到一个基类指针后,如何判断它里面保存的是那个子类类型对象?

C++获取基类指针所指子类对象的类名

获取堆栈上控制器的指针

Qt中如何获取某个控件的指针地址

如何使用算术和掩码舍入地址?