簡體   English   中英

如何獲取基本堆棧指針的地址

[英]How to get address of base stack pointer

我正在將應用程序從x86移植到x64。 我正在使用Visual Studio 2009; 大多數代碼都是C ++,有些部分是純粹的C.在編譯x64時我們不支持__asm關鍵字,而我們的應用程序包含一些內聯匯編程序。 我沒有寫這段代碼所以我不確切知道應該做什么:

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

EBP是指向當前函數堆棧的基指針。 有沒有辦法在不使用內聯asm的情況下獲取堆棧指針? 我一直在關注微軟提供的內在函數作為內聯asm的替代品,但我找不到任何能給我帶來幫助的東西。 有任何想法嗎?

安德烈亞斯詢問用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計算機上運行。 我們完全控制部署,因為我們自己安裝/配置它並將整個產品交付給我們的客戶。

真正正確的做法是重寫此函數所做的任何事情,以便它不需要訪問實際的幀指針。 這絕對是不好的行為。

但是,要做你想要的,你應該能夠做到:

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的地址,我們真正得到了這個函數的堆棧幀的起始地址。

注意:某些優化可能會導致刪除“Frame”變量。 可能不是,但要小心。

第二個注意:當“pFrame”本身在堆棧上時,您的原始代碼以及此代碼將操縱“pFrame”指向的數據。 有可能在這里意外覆蓋pFrame然后你會有一個壞指針,並可能會得到一些奇怪的行為。 當從x86移動到x64時要特別注意這一點,因為pFrame現在是8個字節而不是4個,所以如果舊的“使用pFrame做的東西”代碼在占用內存之前考慮了Frame和pFrame的大小,那么你將會需要考慮新的,更大的尺寸。

您可以使用_AddressOfReturnAddress()內在函數來確定當前幀指針中的位置,假設它尚未完全優化。 我假設如果你明確地引用它,編譯器將阻止該函數優化掉幀指針。 或者,如果您只使用單個線程,則可以使用IMAGE_NT_HEADER.OptionalHeader.SizeOfStackReserveIMAGE_NT_HEADER.OptionalHeader.SizeOfStackCommit來確定主線程的堆棧大小。 對如何訪問IMAGE_NT_HEADER當前圖像。

我還建議不要使用IsBadWritePtr來確定堆棧的結束。 至少你可能會導致籌碼增長,直到你擊中預備隊,因為你會去一個警衛頁面。 如果您確實想要查找堆棧的當前大小,請將VirtualQuery與您正在檢查的地址一起使用。

如果最初的用途是走棧,你可以使用StackWalk64

Microsoft提供了一個庫(DbgHelp)來處理堆棧行走 ,你應該使用而不是依賴於程序集技巧。 例如,如果存在PDB文件,它也可以遍歷優化的堆棧幀(那些不使用EBP幀)。

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

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

無法保證RBP(x64相當於EBP)實際上是指向callstack中當前幀的指針。 我猜微軟決定盡管有幾個新的通用寄存器,它們需要另外一個被釋放,所以RBP僅用作調用alloca()的函數中的framepointer,在某些其他情況下。 因此,即使支持內聯匯編,也不是可行的方法。

如果您只想回溯,則需要在dbghelp.dll中使用StackWalk64。 它位於XP附帶的dbghelp.dll中,並且XP之前沒有64位支持,因此您不需要隨應用程序發送dll。

對於32位版本,只需使用當前的方法。 你自己的方法可能比dbghelp的導入庫小,更不用說內存中的實際dll了,所以這是一個明確的優化(個人經驗:我已經為x86實現了一個Glibc風格的回溯和backtrace_symbols,不到一個 - dbghelp導入庫的大小的十分之一)。

此外,如果您將其用於進程內調試或發布后崩潰報告生成,我強烈建議您只使用提供給異常處理程序的CONTEXT結構。

也許有一天我會決定認真對待x64,並找出一個可以分享我可以共享的StackWalk64的廉價方式,但是因為我仍然以我的所有項目為目標,所以我沒有打擾過。

如果您需要精確的“基指針”,那么內聯匯編是唯一的方法。

令人驚訝的是,編寫使用相對較少的平台特定代碼來驅動堆棧的代碼是可能的,但是很難完全避免匯編(取決於您正在做什么)。

如果你要做的就是避免溢出堆棧,你可以只取任何局部變量的地址。

.code

PUBLIC getStackFrameADDR _getStackFrameADDR
getStackFrameADDR:
    mov RAX, RBP
    ret 0

END

這樣的東西對你有用。

用ml64或jwasm編譯它,並在你的代碼extern“C”中使用它來調用它.void getstackFrameADDR(void);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM