新聞中心
Linux系統(tǒng)中堆棧的ESP/EIP解析

在計算機系統(tǒng)中,棧是一種常見的數(shù)據(jù)結(jié)構(gòu),常作為程序的運行空間之一,其中堆棧是最常用的一種。Linux系統(tǒng)中的棧是用C語言實現(xiàn)的,并且在內(nèi)存中以“后進先出”的方式進行存儲和管理。在程序運行時,棧被用來保存函數(shù)調(diào)用的返回地址和局部變量,還用來作為內(nèi)存緩存圈和臨時存儲區(qū)域。在本文中,將詳細分析Linux系統(tǒng)中堆棧的ESP/EIP解析。
ESP是指棧指針寄存器,EIP是指指令指針寄存器,它們都是CPU寄存器中的一種。ESP通常用來記錄棧的棧頂位置,而EIP則用來記錄程序?qū)⒁獔?zhí)行的下一條指令的地址。它們在一起被用來為CPU提供一個基于棧的函數(shù)調(diào)用機制。
當程序執(zhí)行到函數(shù)調(diào)用指令時,會將當前指令的地址壓入棧中,同時將ESP指針向下移動,指向新的棧頂。接著程序會跳轉(zhuǎn)到指定的函數(shù)地址,并繼續(xù)執(zhí)行函數(shù)中的代碼。在函數(shù)執(zhí)行過程中,棧會被用來存放函數(shù)的參數(shù)和局部變量。在函數(shù)執(zhí)行完畢后,原來的棧頂?shù)刂繁粡棾觯瑫rESP指針向上移動,指向新的棧頂。此時,程序會繼續(xù)執(zhí)行上一層函數(shù)中的代碼,通過EIP指針跳轉(zhuǎn)到之前保存的返回地址中。
在多層函數(shù)調(diào)用時,棧會不斷地被創(chuàng)建和銷毀,同時ESP和EIP指針也會有相應(yīng)的變化。當一個函數(shù)A調(diào)用另一個函數(shù)B時,程序必須將A函數(shù)的ESP和EIP指針保存下來,以便在B函數(shù)執(zhí)行完畢后,能夠正確地返回到A函數(shù)中。這種保存ESP和EIP指針的機制稱為棧幀,是用來管理棧中各個函數(shù)調(diào)用過程的重要數(shù)據(jù)結(jié)構(gòu)。
在Linux系統(tǒng)中,ESP和EIP指針的值是由操作系統(tǒng)內(nèi)核進行管理的。當一個程序通過系統(tǒng)調(diào)用向操作系統(tǒng)請求分配內(nèi)存時,內(nèi)核會自動將堆棧分配給該程序,并分配相應(yīng)的ESP和EIP寄存器。同時,內(nèi)核還會為程序分配一個初始的棧幀,用來存儲程序執(zhí)行過程中的基本信息。隨著程序的執(zhí)行,內(nèi)核會根據(jù)需要動態(tài)地創(chuàng)建和銷毀棧幀,并更新ESP和EIP指針的值。這樣,程序在運行過程中就可以訪問正確的??臻g,同時能夠正確地返回到上一層函數(shù)中。
在分析Linux系統(tǒng)中的ESP/EIP解析時,需要注意的是,ESP指針向下移動表示棧頂向下移動,而EIP指針向上移動表示程序?qū)⒁獔?zhí)行的下一條指令向上移動。它們在整個程序執(zhí)行過程中都是相對變化的,而不是絕對變化的。因此,在編寫Linux程序時,需要明確掌握棧和棧幀的相關(guān)知識,正確使用ESP和EIP指針,以確保程序能夠正確地運行和返回。
綜上所述,Linux系統(tǒng)中的ESP和EIP指針是用來管理棧和棧幀的重要寄存器。它們在程序的函數(shù)調(diào)用和返回過程中發(fā)揮重要的作用,是程序正確執(zhí)行的關(guān)鍵。在編寫Linux程序時,需要充分理解ESP和EIP指針在程序中的作用,合理管理棧和棧幀,以確保程序能夠正確運行。
相關(guān)問題拓展閱讀:
- linux c 引用傳遞參數(shù)
- 從用戶狀態(tài)轉(zhuǎn)換到核心狀態(tài)是通過什么實現(xiàn)的?
linux c 引用傳遞參數(shù)
C/C++函數(shù)參數(shù)的傳遞方式有三種:值傳遞(pass by value)、指針傳遞(pass bypointer)、引用傳遞(pass by reference)。
C/C++函數(shù)參數(shù)的傳遞通道是通過堆棧傳遞,默認遵循弊改__cdecl(C聲明方式),參數(shù)由改檔調(diào)用者從右往左逐個壓入堆棧,在函數(shù)調(diào)用完成之后再由調(diào)用者恢復(fù)堆棧。(Win32API遵循stdcall傳參規(guī)范的,不在本文討論范圍)
下面是測試代碼
void Swap(__int64* _pnX, __int64* _pnY)
{
__int64 nTemp = *_pnX;
*_pnX = *_pnY;
*_pnY = nTemp;
}
void Swap(__int64& _nX, __int64& _nY)
{
__int64 nTemp = _nX;
_nX = _nY;
_nY = nTemp;
}
void SetValue(__int64 _nX)
{
__int64 nTemp = _nX;
}
// Test001
void GetMemory(__int64* _pBuff)
{
_pBuff = new __int64;
}
// Test002
void GetMemory(__int64** _ppBuff)
{
*_ppBuff = new __int64;
}
int _tmain(int argc, _TCHAR* argv)
{
__int64 nA = 0x10;
__int64 nB = 0x20;
// Test to pass by pointer
Swap(&nA, &nB);
// Test to pass by reference
Swap(nA, nB);
// Test to pass by value
SetValue(nA);
// Test the pointer that points the pointer
__int64* _pArray = NULL;
GetMemory(&_pArray);
delete _pArray;
_pArray = NULL;
//租殲判 Test the pointer
GetMemory(_pArray);
return 0;
}
指針傳遞和引用傳遞
// 下面看一下對應(yīng)的反匯編的代碼(VS版)
__int64 nA = 0x10;
E movdword ptr ,10h
movdword ptr ,0
__int64 nB = 0x20;
C movdword ptr ,20h
movdword ptr ,0
// Test to pass by pointer
Swap(&nA, &nB);
A leaeax,
D pusheax
E leaecx,
pushecx
callSwap (4111E5h)
addesp,8
// Test to pass by reference
Swap(nA, nB);
A leaeax,
D pusheax
E leaecx,
pushecx
callSwap (4111E0h)
addesp,8
// GCC版
0x: lea eax,
0x: mov DWORD PTR ,eax
0xa : lea eax,
0xe : mov DWORD PTR ,eax
0x: call 0x
0x: lea eax,
0xa : mov DWORD PTR ,eax
0xe : lea eax,
0x004015a2 : mov DWORD PTR ,eax
0x004015a5 : call 0x
通過上面的反匯編代碼,我們可以看出指針傳遞和引用傳遞在機制是一樣的,都是將指針值(即地址)壓入棧中,調(diào)用函數(shù),然后恢復(fù)棧。Swap(nA, nB)和Swap(&nA, &nB);在實際上的匯編代碼也基本上一模一樣,都是從棧中取出地址來。由此可以看出引用和指針在效率上是一樣的。這也是為什么指針和引用都可以達到多態(tài)的效果。指針傳遞和引用傳遞其實都是改變的地址指向的內(nèi)存上的值來達到修改參數(shù)的效果。
值傳遞
下面是值傳遞對應(yīng)的反匯編代碼
// Test to pass by value
SetValue(nA);
A moveax,dword ptr
D pusheax
E movecx,dword ptr
pushecx
callSetValue (4111EAh)
addesp,8
因為我的機器是32位的CPU,從上面的匯編代碼可以看64Bit的變量被分成2個32Bit的參數(shù)壓入棧中。這也是我們常說的,值傳遞會形成一個拷貝。如果是一個自定義的結(jié)構(gòu)類型,并且有很多參數(shù),那么如果用值傳遞,這個結(jié)構(gòu)體將被分割為非常多個32Bit的逐個拷貝到棧中去,這樣的參數(shù)傳遞效率是非常慢的。所以結(jié)構(gòu)體等自定義類型,都使用引用傳遞,如果不希望別人修改結(jié)構(gòu)體變量,可以加上const修飾,如(const MY_STRUCT& _value);
下面來看一下Test001函數(shù)對應(yīng)的反匯編代碼的參數(shù)傳遞
__int64* _pArray = NULL;
004137E0 movdword ptr ,0
// Test the pointer
GetMemory(_pArray);
moveax,dword ptr
pusheax
callGetMemory (411203h)
B addesp,4
從上面的匯編代碼可以看出,其實是0被壓入到棧中作為參數(shù),所以GetMemory(_pArray)無論做什么事,其實都與指針變量_pArray無關(guān)。GetMemory()分配的空間是讓棧中的臨時變量指向的,當函數(shù)退出時,棧得到恢復(fù),結(jié)果申請的空間沒有人管,就產(chǎn)生內(nèi)存泄露的問題了?!禖++ Primer》將參數(shù)傳遞分為引用傳遞和非引用傳遞兩種,非引用傳遞其實可以理解為值傳遞。這樣看來,指針傳遞在某種意義上也是值傳遞,因為傳遞的是指針的值(1個4BYTE的值)。值傳遞都不會改變傳入實參的值的。而且普通的指針傳遞其實是改變的指針變量指向的內(nèi)容。
下面再看一下Test002函數(shù)對應(yīng)的反匯編代碼的參數(shù)傳遞
__int64* _pArray = NULL;
004137E0 movdword ptr ,0
GetMemory(&_pArray);
004137E7 leaeax,
004137EA pusheax
004137EB callGetMemory (4111FEh)
004137F0 addesp,4
從上面的匯編代碼lea eax, 可以看出,_pArray的地址被壓入到棧中去了。
然后看一看GetMemory(&_pArray)的實現(xiàn)匯編代碼。
0xb :push ebp
0xc :mov ebp,esp
0xe :sub esp,0x18
0x004015a1 :mov DWORD PTR ,0x20
0x004015a8 :call 0x473ef0
0x004015ad :mov edx,DWORD PTR
0x004015b0 :mov DWORD PTR ,eax
0x004015b2 :leave
0x004015b3 :ret
藍色的代碼是分配臨時變量空間,然后調(diào)用分配空間函數(shù)分配空間,得到的空間指針即eax.
然后紅色的匯編代碼即從ebp+0x8的棧上取到上面壓入棧中的參數(shù)_pArray的地址.
mov DWORD PTR ,eax即相當于把分配的空間指針eax讓edx指向,也即讓_pArray指向分配的空間eax.
從用戶狀態(tài)轉(zhuǎn)換到核心狀態(tài)是通過什么實現(xiàn)的?
用戶態(tài)和內(nèi)核態(tài)的轉(zhuǎn)換
1)用戶態(tài)切換到內(nèi)核態(tài)的3種方式
a. 系統(tǒng)調(diào)用 這是用戶態(tài)進程主動要求切換到內(nèi)核態(tài)的一種方式,用戶態(tài)進程通過系統(tǒng)調(diào)用申請使用操作系統(tǒng)提供的服務(wù)程序完成工作,比如前例中fork()實際上就是執(zhí)行了一個創(chuàng)建新進程的系統(tǒng)調(diào)用。而系統(tǒng)調(diào)用的機制其核心還是使用了操作系統(tǒng)為用戶特別開放的一個中斷來實現(xiàn),例如Linux的int 80h中斷。系統(tǒng)調(diào)用實質(zhì)上是一個中斷,而匯編指令int 就可以實現(xiàn)用戶態(tài)向內(nèi)核態(tài)切換,iret實現(xiàn)內(nèi)核態(tài)向用戶態(tài)切換 b. 異常 當CPU在執(zhí)行運行在用戶態(tài)下的程序時,發(fā)生了某些事先不可知的異常,這時會觸發(fā)由當前運行進程切換到處理此異常的內(nèi)核相關(guān)程序中,也就轉(zhuǎn)到了內(nèi)核態(tài),比如缺頁異常。
c. 外圍設(shè)備的中斷 當外圍設(shè)備完成用戶請求的操作后,會向CPU發(fā)出相應(yīng)的中斷信號,這時CPU會暫停執(zhí)行下一條即將要執(zhí)行的指令轉(zhuǎn)而去執(zhí)行與中斷信號對應(yīng)的處理程序,如果先前執(zhí)行的指令是用戶態(tài)下的程序,那么這個轉(zhuǎn)換慎嫌的過程自然也就發(fā)生銀孝慧了由用戶態(tài)到內(nèi)核態(tài)的切換。比如硬盤讀寫操作完成,系統(tǒng)會切換到硬盤讀寫的中斷處理程序中執(zhí)行后續(xù)操作等。 這3種方式是系統(tǒng)在運行時由用戶態(tài)轉(zhuǎn)到內(nèi)核態(tài)的最主要方式,其中系統(tǒng)調(diào)用可以認為是用戶進程主動發(fā)起的,異常和外圍設(shè)備中斷則是被動的。
2)具體的切換操作 從觸發(fā)方式上看,可以認為存在前述3種不同的類型,但是從最終實際完成由用戶態(tài)到內(nèi)核態(tài)的切換操作上來說,涉及的關(guān)鍵步驟是完全一致的,沒有任何區(qū)別,都相當于執(zhí)行了一個中斷響應(yīng)的過程,因為系統(tǒng)調(diào)用實際上最終是中斷機制實現(xiàn)的,而異常和中鋒答斷的處理機制基本上也是一致的,關(guān)于它們的具體區(qū)別這里不再贅述。關(guān)于中斷處理機制的細節(jié)和步驟這里也不做過多分析,涉及到由用戶態(tài)切換到內(nèi)核態(tài)的步驟主要包括:
從當前進程的描述符中提取其內(nèi)核棧的ss0及esp0信息。
使用ss0和esp0指向的內(nèi)核棧將當前進程的cs,eip,eflags,ss,esp信息保存起來,這個 過程也完成了由用戶棧到內(nèi)核棧的切換過程,同時保存了被暫停執(zhí)行的程序的下一條指令。
將先前由中斷向量檢索得到的中斷處理程序的cs,eip信息裝入相應(yīng)的
寄存器
,開始 執(zhí)行中斷處理程序,這時就轉(zhuǎn)到了內(nèi)核態(tài)的程序執(zhí)行了。
關(guān)于linux堆棧espeip的介紹到此就結(jié)束了,不知道你從中找到你需要的信息了嗎 ?如果你還想了解更多這方面的信息,記得收藏關(guān)注本站。
香港服務(wù)器選創(chuàng)新互聯(lián),2H2G首月10元開通。
創(chuàng)新互聯(lián)(www.cdcxhl.com)互聯(lián)網(wǎng)服務(wù)提供商,擁有超過10年的服務(wù)器租用、服務(wù)器托管、云服務(wù)器、虛擬主機、網(wǎng)站系統(tǒng)開發(fā)經(jīng)驗。專業(yè)提供云主機、虛擬主機、域名注冊、VPS主機、云服務(wù)器、香港云服務(wù)器、免備案服務(wù)器等。
當前題目:Linux系統(tǒng)中堆棧的espeip解析(linux堆棧espeip)
網(wǎng)頁URL:http://m.5511xx.com/article/dpphejj.html


咨詢
建站咨詢
