2009-12-04 75 views
14

我正在將應用程序從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的替代品的內在因素,但是我找不到任何能給我帶來有用的東西。有任何想法嗎?

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計算機上運行。我們完全控制部署,因爲我們自己安裝/配置它,並將整個事情交付給我們的客戶。

+0

什麼東西是與P幀做了什麼? –

回答

10

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

但是,做你在找什麼,你應該能夠做到:

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的大小,需要考慮新的,更大的尺寸。

+6

我不認爲這個變量可以被刪除,如果你把它的地址。但是,編譯器可以自由地重新排列變量,但它喜歡。從技術上講,沒有語言級別的保證框架甚至位於棧上(但實際上我希望這可以很好地工作)。 –

+0

我的想法,我正在提交類似的答​​案,但我阻止自己,因爲它感覺如此脆弱。 –

+3

如果你把它變成'volatile'會怎麼樣? –

0

如果您需要精確的「基本指針」,那麼內聯彙編是唯一的方法。

令人驚訝的是,可以用相對較少的特定於平臺的代碼編寫代碼堆棧的代碼,但很難完全避免彙編(取決於您在做什麼)。

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

+0

看來你必須使用一個專用的彙編程序(ml64)來創建一個檢查堆棧指針的例程,發現前一個堆棧幀的地址(以便在調用匯編程序時解釋堆棧指針中的移動),並返回它(我不知道你會怎麼做)。然後將其鏈接到您的C程序。 –

4

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

我也建議不要使用IsBadWritePtr來確定棧的結束。至少你可能會導致堆棧增長,直到你打到預備隊,因爲你會絆倒警衛頁面。如果您確實想要查找堆棧的當前大小,請使用VirtualQuery以及您正在檢查的地址。

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

+0

它沒有看到剩下多少空間,它正在回溯到之前的堆棧幀。基本上做一個回溯。 – caf

+0

如果是這種情況,那麼有一個API:StackWalk64。 – MSN

+0

我建議你增加一個新的答案,聽起來好像它可能是OP所需要的。 – caf

0
.code 

PUBLIC getStackFrameADDR _getStackFrameADDR 
getStackFrameADDR: 
    mov RAX, RBP 
    ret 0 

END 

類似的東西可以爲你工作。

使用ml64或jwasm編譯它,並在代碼中使用它來調用它 extern「C」void getstackFrameADDR(void);

+1

默認情況下,x86-64代碼通常不會有幀指針。 GCC和MSVC都沒有編譯。 –

0

無法保證RBP(x64的EBP等價物)實際上是指向調用堆棧中當前幀的指針。我想微軟決定,儘管有幾個新的通用寄存器,他們需要另一個釋放,所以RBP僅用作調用alloca()的函數中的幀指針,以及在某些其他情況下。所以,即使內聯彙編得到支持,它也不會走。

如果你只是想回溯,你需要在dbghelp.dll中使用StackWalk64。它位於XP附帶的dbghelp.dll中,XP之前沒有64位支持,因此您不需要將DLL與應用程序一起發送。

對於您的32位版本,只需使用您當前的方法。你自己的方法可能會比dbghelp的導入庫小,更不用說內存中的實際DLL了,所以這是一個明確的優化(個人經驗:我已經實現了一個Glibc風格的backtrace和backtrace_symbols for x86,是dbghelp導入庫大小的十分之一)。另外,如果你正在使用它進行進程內調試或發佈後發佈崩潰報告,我強烈建議只使用提供給異常處理程序的CONTEXT結構。

也許有一天我會決定嚴肅對待x64,並且找出使用我可以共享的StackWalk64的便宜方法,但是因爲我仍然針對所有我沒有打擾的項目使用x86。