![](/img/trans.png)
[英]How to get the value of the base pointer, stack pointer and saved base pointer
[英]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.SizeOfStackReserve
和IMAGE_NT_HEADER.OptionalHeader.SizeOfStackCommit
來確定主線程的堆棧大小。 見這對如何訪問IMAGE_NT_HEADER
當前圖像。
我還建議不要使用IsBadWritePtr
來確定堆棧的結束。 至少你可能會導致籌碼增長,直到你擊中預備隊,因為你會去一個警衛頁面。 如果您確實想要查找堆棧的當前大小,請將VirtualQuery
與您正在檢查的地址一起使用。
如果最初的用途是走棧,你可以使用StackWalk64
。
Microsoft提供了一個庫(DbgHelp)來處理堆棧行走 ,你應該使用而不是依賴於程序集技巧。 例如,如果存在PDB文件,它也可以遍歷優化的堆棧幀(那些不使用EBP
幀)。
CodeProject有一篇文章解釋了如何使用它:
無法保證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.