wordpress 在線(xiàn)留言關(guān)鍵詞優(yōu)化工具
????????函數(shù),一般都有返回值,函數(shù)名,參數(shù),再下來(lái)還有什么mian函數(shù),函數(shù)寫(xiě)出來(lái)就是要被調(diào)用的,上面圖片上的代碼,main函數(shù)和myadd函數(shù),都要在自己的棧結(jié)構(gòu)什么形成自己的棧,可以幫我們理解局部變量,為什么是臨時(shí),棧上開(kāi)辟空間,棧的地址是向下增長(zhǎng),堆也學(xué)了,堆向上增長(zhǎng),每調(diào)一個(gè)函數(shù),就是形成棧幀的一個(gè)過(guò)程,返回函數(shù)是棧幀被釋放的一個(gè)過(guò)程,釋放不是真正的釋放,而是讓這塊空間無(wú)效,下一次創(chuàng)建棧幀的時(shí)候可以覆蓋掉。
? ? ? ? 局部變量為什么具有臨時(shí)性,是因?yàn)樵趯?duì)應(yīng)的函數(shù)內(nèi)棧幀上面創(chuàng)建的,函數(shù)被返回時(shí),棧幀結(jié)構(gòu)都被釋放了,賴(lài)以生存的環(huán)境都沒(méi)有了,所以變量也被釋放了。
? ? ? ? 今天就來(lái)看看棧幀的形成與釋放。
? ? ? ? 把一個(gè)函數(shù)的生命周期理解,其他都好理解了,因?yàn)閺膍ain函數(shù)開(kāi)始都是函數(shù)調(diào)用,一個(gè)函數(shù)的生命周期搞定了,整個(gè)生命周期都搞定了。
? ? ? ? 我直接從myadd開(kāi)始,就預(yù)設(shè)main函數(shù)有自己的棧幀了,只要myadd理解透了,其他的都搞定了。
? ? ? ? 先來(lái)了解一下東西寄存器和一些簡(jiǎn)單的匯編一下命令
?寄存器最重要的就是后三個(gè),ebp、esp,有了這兩個(gè)寄存器,就可以有效指向一塊內(nèi)存,eip保存著當(dāng)前指令的下一條指令的地址,本質(zhì)就是衡量我們指向到了某個(gè)位置
?先把代碼和圖畫(huà)出來(lái),畫(huà)出內(nèi)存分布圖,我們棧幀結(jié)構(gòu)主要研究棧區(qū),所以把棧區(qū)放大,棧區(qū)地址是往小的增的。
? ? ? ?代碼啟動(dòng)調(diào)試之后一路f10,進(jìn)來(lái)?,看圖片的右上角,出現(xiàn)了幾個(gè)隱藏的函數(shù),所以main函數(shù)也是一個(gè)函數(shù)。main函數(shù)是被誰(shuí)調(diào)用的。
?是被_tmianCRTStartup()調(diào)用的,但是它也是函數(shù)也誰(shuí)調(diào)用它
它也是被 mainCRTStartup調(diào)用,只是做了一下cookie
?mainCRTStartup它也是函數(shù)呀,它被誰(shuí)調(diào)用?記住一件事,要被執(zhí)行,第一步加載到內(nèi)存,第二步就要開(kāi)始執(zhí)行,開(kāi)始之前永遠(yuǎn)都是,操作系統(tǒng)來(lái)做,調(diào)mainCRTStartup,這里就是調(diào)用邊界了。
所以我先研究add的棧幀,就是那個(gè)綠色的方格,只要一個(gè)清楚了其他的都清楚了,好,準(zhǔn)備工作完成。
我重新畫(huà)一張圖,除了棧,還有cpu和三個(gè)寄存器,前面說(shuō)了
?esp和ebp指向main的起始位置和結(jié)束的位置,也就是地址,
?
eip先代表著執(zhí)行者main的代碼?
好前期工作完成 ,啟動(dòng)反匯編,
直接來(lái)到這里 ,意思是esp-8個(gè)字節(jié)定位的位置吧0Ah,放到我們的棧幀當(dāng)中,一條匯編做了兩件事,開(kāi)辟空間,完成初始化
?看eip寄存器指向的位置,還沒(méi)有開(kāi)始執(zhí)行,
程序從main開(kāi)始執(zhí)行,這一步完成之后,ebp-8就是棧底向上偏移8,所以x的位置就在這里,就是下面這樣的,ebp-8是這塊棧內(nèi)存里面的一個(gè)偏移。x區(qū)域的起始地址,在上面位置因?yàn)閮?nèi)存排布是有低到高的?,但是這里的內(nèi)存是反著的,看地址排布,
起始地址是往上減少的,??臻g就往底地址增長(zhǎng)的。?
所以有一點(diǎn)要注意的是的起始地址是在上面
eip的是執(zhí)行當(dāng)前main函數(shù)的代碼
?然后下一條,[ebp-14h],ebp不變,減14,把y放到棧上
?這一步完成之后,是這樣的但是有一個(gè)注意的地方就是黑色的線(xiàn)是esp-14的位置,但是內(nèi)存指向的話(huà)應(yīng)該是指向最最小的內(nèi)存,起始地址是從小的開(kāi)始,所以y的起始地址,在上面位置因?yàn)閮?nèi)存排布是有低到高的?,但是這里的內(nèi)存是反著的,看地址排布,
下一步,f10,把0,放到ebp-20的這個(gè)位置,eip指向了1883的地址處,
?執(zhí)行完之后,然后z也入棧了
?但是會(huì)發(fā)現(xiàn)一個(gè)問(wèn)題就是y和z是靠著的,x和y是分開(kāi)的,這是和編譯器有關(guān)的。因?yàn)榭臻g是隨機(jī)的,所以也有可能是鏤空的,應(yīng)該是防止程序員猜測(cè)一些地址。
在接下來(lái)。到了,myadd的位置,可以發(fā)現(xiàn)一條C語(yǔ)言的指令,可能是有多條匯編指令
?接下來(lái),一個(gè)是eax是一個(gè)寄存器,還有一個(gè)是ebp-14
下面可以看到,ebp-14是y的地址,也是說(shuō)要將y的里面的值放到eax的寄存器里面
?
?來(lái)看一下,這里要把內(nèi)存窗口打開(kāi),當(dāng)我們按一下f10,到了push這里,push就要進(jìn)棧,看一下esp的位置,也就是棧頂,
?按一下f10,注意到esp的位置右8c變成了88,減少了4個(gè)字節(jié),并且0xB已經(jīng)在88位置,也發(fā)現(xiàn)了一個(gè)問(wèn)題ebp的88已經(jīng)變成新的棧了,B值已經(jīng)壓到了棧上了
?
?所以,棧頂?shù)奈恢?#xff0c;由內(nèi)存圖可以看到,是直接壓在原來(lái)?xiàng)m斨系奈恢?#xff0c;棧頂?shù)奈恢靡舶l(fā)生了改變,也壓進(jìn)來(lái)了。
?相當(dāng)于,把y的值拿到了寄存器里面,再?gòu)募拇嫫鞣诺较乱粋€(gè)位置。好像就是y的一份拷貝
?下一步,這里和上面一樣,需要一個(gè)寄存器ecx,ebp-8的位置是x的地址,也是把x的值放到ecx里面。
?中途遇到了點(diǎn)問(wèn)題,重啟之后,地址變了。
中途遇到了點(diǎn)問(wèn)題,重新了調(diào)試按一下f10,注意到esp的位置右c8變成了c4,減少了4個(gè)字節(jié),也可以看一下ECX的寄存器值是00000A了,放進(jìn)去寄存器里面了并且0xA已經(jīng)在c4位置,也發(fā)現(xiàn)了一個(gè)問(wèn)題ebp的c4已經(jīng)變成新的棧了,A值已經(jīng)壓到了棧上了,
??所以,棧頂?shù)奈恢?#xff0c;由內(nèi)存圖可以看到,是直接壓在原來(lái)?xiàng)m斨系奈恢?#xff0c;棧頂?shù)奈恢靡舶l(fā)生了改變,也壓進(jìn)來(lái)了。?相當(dāng)于,把X的值拿到了寄存器里面,再?gòu)募拇嫫鞣诺较乱粋€(gè)位置。好像就是X的一份拷貝
?所以下面這四個(gè)動(dòng)作,是做了一個(gè)臨時(shí)拷貝
?
?兩個(gè)總結(jié):
1.臨時(shí)變量的形成是在函數(shù)正式被調(diào)用之前就形成了的
2.形參實(shí)例化的順序是從右到左的
接著下來(lái),到了call了,call的作用就是函數(shù)調(diào)用,1.壓入返回值地址 2.轉(zhuǎn)入目標(biāo)函數(shù)
call調(diào)用函數(shù),只需要修改eip的值就可以了,但是調(diào)完之后我還要返回,所以要不保存返回值地址,返回到call后面的add,為什么要壓入返回值地址:根本原因就是函數(shù)掉完,之后需要返回。
看圖片,解析,ESP會(huì)保存00f110f0de1這個(gè)跳轉(zhuǎn)地址,并且把a(bǔ)dd的地址壓棧,壓到c0位置
?按f11,發(fā)現(xiàn)返回值已經(jīng)把棧的位置已經(jīng)壓入了,這里的地址壓進(jìn)去了,eip也變成了00f110f0,
?這里要jmp, jmp就是跳轉(zhuǎn)到函數(shù),eip上的地址就是jmp前的地址,jmp后eip的地址就要變成00方18f0,
?jmp后,eip就是00f118f0的地址就是,就是已經(jīng)進(jìn)來(lái)函數(shù)里面了,從現(xiàn)在就正式進(jìn)來(lái)了,可以知道00f118f0就是函數(shù)的入口
完成后,?eip位置就不再是mian了而是mydd了,已經(jīng)進(jìn)到myadd里面了
到這里,就已經(jīng)完成了函數(shù)調(diào)用前的準(zhǔn)備了,?形參列表初始化,形參列表完成,返回值入棧,eip跳轉(zhuǎn)函數(shù),怎么運(yùn)行,下面準(zhǔn)備開(kāi)始myadd函數(shù),接下來(lái)重點(diǎn)就是前三個(gè)。這三個(gè)就是棧幀最核心的內(nèi)容。
第一步, push? ebp是什么意思, ebp是什么?ebp不就是main函數(shù)的棧底嗎,所以這里要做的就是把main函數(shù)的棧底入棧,
按一下f10,看到了把。已經(jīng)是壓進(jìn)去了,
所以圖就變成這個(gè)樣子了,進(jìn)去之后,esp的位置一定要改變,
第二步mov ebp,esp,意思就是把esp里面的內(nèi)容,放到ebp里面去,esp原本是指向是main函數(shù)的棧頂,那如果直接放進(jìn)去不會(huì)覆蓋原來(lái)的嗎,那到時(shí)候怎么找回來(lái),不用擔(dān)心,因?yàn)閑bp的值在剛剛已經(jīng)入棧了,
按f10 ,看到esp和ebp一樣了,所以是是放進(jìn)去了
?所以,ebp不在執(zhí)行main函數(shù)的棧底,所以棧頂和棧底都指向棧頂
?sub是減的意思, 0CCh,意思是棧頂esp減去0CCh這個(gè)空間,然后把結(jié)構(gòu)放到esp,0cch這里這里減多大,是和我自己當(dāng)前的函數(shù)規(guī)有關(guān)系的,多變量就減的比較大,反之就少。
目前esp是00A2F794,按f10,就變成了00A2F6C84
所以esp減少了,是不是就指向了一段新的空間,ebp不變,變成如下這個(gè)樣子?
esp減去了一段范圍,所以就變成了這個(gè)樣子了?,形成了myadd的棧區(qū),esp和ebp也不是main的棧了
?所以回到這張圖,main有自己的棧幀,myadd有自己的棧幀
?所以接下來(lái)直接到int c = 0這里,中間那些事完成一些初始化的問(wèn)題,就不看了。
C是myadd里面定義的一個(gè)變量,和前面一樣, 在ebp-8的地方把c放進(jìn)去,ebp-8就是以ebp為參照物減去8個(gè)字節(jié)。
?所以0就進(jìn)來(lái)了
?接下來(lái),下一條指令,就是eax,dword ptr [ebp+8],是什么意思呢?
?ebp-8就是以ebp為參考減去8個(gè)字節(jié)向上走,那加就是反著走,所以ebp+8就是剛剛找到a的值,并且放到eax里面,
重點(diǎn):內(nèi)存取出來(lái)的一定是連續(xù)地址最小的哪一個(gè)
??add ? ? ? ? eax,dword ptr [ebp+0Ch],就是找到ebp+12的值和eax的想加,那這個(gè)位置是誰(shuí),剛好是b的值,eax剛好把a(bǔ)的值拿出來(lái),最后兩個(gè)相加,然后結(jié)果放回到eax
重點(diǎn):內(nèi)存取出來(lái)的一定是連續(xù)地址最小的哪一個(gè)
?ea寄存器已經(jīng)是15了,15是16進(jìn)制,轉(zhuǎn)回10進(jìn)制就是21.
?所以下面這一步是將eax的值寫(xiě)回到ebp-8的位置上,而前面ebp-8,就是c的位置
?所以執(zhí)行后是這個(gè)樣子的
?到此棧幀已經(jīng)創(chuàng)建結(jié)束了,下一步就是return 返回了
經(jīng)過(guò)前面分析知道函數(shù)棧幀是自己形成的,但是這里的sub減多少是誰(shuí)決定的?答案是編譯器決定的,只要你定義變量就要有類(lèi)型,只要知道類(lèi)型了編譯器就知道給你開(kāi)多大了,所以sizeof是在之前就計(jì)算好的了
開(kāi)始return返回,如何理解return,放回值,都是通過(guò)eax和ecx等等這種通用這些寄存器返回的,
?我只看這四條指令,中間那些pop只是配置一些寄存器的和上面的push只是一樣的,重點(diǎn)是圈起來(lái)這四個(gè)
mov ? ?eax,dword ptr [ebp-8]這個(gè)很簡(jiǎn)單,就是把值放到了eax上面?
下面三條才是重點(diǎn)
mov esp, ebp就是把ebp的值放到esp里面,ebp是myadd棧底,所以把myadd的棧底放到esp里面
?相當(dāng)于esp和ebp都是向了同一個(gè)地方,,所以相當(dāng)于就是把myadd的空間給釋放了,所以只需要讓esp執(zhí)行ebp的地方myadd的整個(gè)空間就釋放了,
?
?
接下來(lái)就到了這個(gè)了?pop ? ebp 把當(dāng)前棧頂?shù)闹祻棾鋈?#xff0c;彈到ebp哪里去,
彈出去后,棧頂就指向了下一個(gè)地方了。
?彈出后,棧頂就指向了返回值哪里,代表著什么
?ebp指向原來(lái)的main棧底了
?
?
?
ret就是將返回值地址返回到eip,這個(gè)返回值地址就是但是call的下一條指令地址,因?yàn)楹蚿op一樣,所以棧頂也要退回下一個(gè)所以變成這個(gè)樣子
?下圖可以發(fā)現(xiàn)ret之后棧頂是變大了
?可以分享已經(jīng)返回到了這里
?
?打×的地方就是棧已經(jīng)釋放了?
?那還有兩個(gè)怎么辦?可以這里 add esp,8,意思是說(shuō)棧頂?shù)奈恢孟蛳录?個(gè)字節(jié)
?就這樣就把兩個(gè)空間給釋放了
?
?所以這就回到了main棧幀,main函數(shù)了,所以一次完整棧幀創(chuàng)建和釋放