五千字從反匯編一步步詳細講述函式堆疊幀的創建與銷毀
這篇文章帶你深入了解函式引數的傳遞和銷毀程序,形參實參的關系,和區域變數的創建等等,
------------------------------------------------------------文章較長,滿滿干貨---------------------------------------------------------------

文章所用環境:windows x64 VS2013
文章目錄
- 五千字從反匯編一步步詳細講述函式堆疊幀的創建與銷毀
- 預備知識
- 1 . 記憶體四區
- 2 . 堆疊幀-----摘自百度百科
- 3 . 暫存器
- ebp和esp這兩個暫存器是存放函式地址的 , 專門來維護堆疊幀
- 一、main函式堆疊幀的開辟
- 1) 地基:main函式也是被其他函式呼叫的
- 2) 壓堆疊:從已有地基打起從高地址往低地址蓋大樓
- 二、代碼中add函式堆疊幀的開辟
- 1 ) 傳參
- 2 ) add函式:
- 三、add函式堆疊幀的銷毀
- 形參的銷毀
- 流程圖
預備知識
1 . 記憶體四區
在介紹函式堆疊幀開辟之前先草草畫一個記憶體四區的圖方便后續理解:

我們知道堆區和堆疊區是相對著使用空間的,堆區從低地址到高地址,而堆疊區從高地址到低地址
2 . 堆疊幀-----摘自百度百科
堆疊幀也叫程序活動記錄,是編譯器用來實作程序/函式呼叫的一種資料結構,
從邏輯上講,堆疊幀就是一個函式執行的環境:函式引數、函式的區域變數、函式執行完后回傳到哪里等等,
---------------------------------說白了就是用來記錄的,
3 . 暫存器
在我們了解了堆疊區的性質之后,接下來需要大概說明一下計算機至關重要的元件:暫存器
這里想要具體了解的可以去看看百度百科https://baike.baidu.com/item/%E5%AF%84%E5%AD%98%E5%99%A8
以下都是暫存器的一些種類:
其中最重要的就是ebp和esp,
重要的事情說三遍!!
ebp和esp這兩個暫存器是存放函式地址的 , 專門來維護堆疊幀
ebp和esp這兩個暫存器是存放函式地址的 , 專門來維護堆疊幀
ebp和esp這兩個暫存器是存放函式地址的 , 專門來維護堆疊幀
而堆疊是從高地址向低地址延伸的,每個函式的每次呼叫,都有它自己獨立的一個堆疊幀,這個堆疊幀中維持著所需要的各種資訊,
暫存器ebp指向當前的堆疊幀的底部(高地址),暫存器esp指向當前的堆疊幀的頂部(低地址),
那么接下來我們通過一段代碼和對應的匯編語言來說明
#include<stdio.h>
int add(int a, int b);
int main()
{
int a = 10;
int b = 20;
int ret = add(a, b);
printf("%d", ret);
return 0;
}
int add(int a, int b)
{
return a + b;
}
int main()
{
00C61400 push ebp
00C61401 mov ebp,esp
00C61403 sub esp,0E4h
00C61409 push ebx
00C6140A push esi
00C6140B push edi
00C6140C lea edi,[ebp+FFFFFF1Ch]
00C61412 mov ecx,39h
00C61417 mov eax,0CCCCCCCCh
00C6141C rep stos dword ptr es:[edi]
int a = 10;
00C6141E mov dword ptr [ebp-8],0Ah
int b = 20;
00C61425 mov dword ptr [ebp-14h],14h
int c = add(a, b);
00C6142C mov eax,dword ptr [ebp-14h]
00C6142F push eax
00C61430 mov ecx,dword ptr [ebp-8]
00C61433 push ecx
00C61434 call 00C61096
00C61439 add esp,8
00C6143C mov dword ptr [ebp-20h],eax
printf("%d", c);
00C6143F mov esi,esp
00C61441 mov eax,dword ptr [ebp-20h]
00C61444 push eax
00C61445 push 0C65858h
00C6144A call dword ptr ds:[00C69114h]
00C61450 add esp,8
00C61453 cmp esi,esp
00C61455 call 00C6113B
return 0;
00C6145A xor eax,eax
}
00C6145C pop edi
}
00C6145D pop esi
00C6145E pop ebx
00C6145F add esp,0E4h
00C61465 cmp ebp,esp
00C61467 call 00C6113B
00C6146C mov esp,ebp
00C6146E pop ebp
00C6146F ret
這里想要了解匯編指令可以了解這篇文章:
https://blog.csdn.net/sinat_27382047/article/details/72810788反匯編指令
一、main函式堆疊幀的開辟
1) 地基:main函式也是被其他函式呼叫的
- c語言中的main函式是被_start函式呼叫的,
- 函式都是在堆疊上開辟堆疊幀的, 那么有堆疊幀必然需要ebp和esp去維護堆疊幀,
ebp指向堆疊幀底部(高地址),esp指向堆疊幀頂部(低地址)

2) 壓堆疊:從已有地基打起從高地址往低地址蓋大樓
008C1400 push ebp
008C1401 mov ebp,esp
008C1403 sub esp,0E4h
008C1409 push ebx
008C140A push esi
008C140B push edi
008C140C lea edi,[ebp-0E4h]
008C1412 mov ecx,39h
008C1417 mov eax,0CCCCCCCCh
008C141C rep stos dword ptr es:[edi]
PUSH 指令:首先減少ESP的值,再將源運算元復制到堆疊,運算元是16位的,則ESP減2(位元組),運算元是32位的,則 ESP減4(位元組),esp通常是指向堆疊頂的(這里要指出的是:學過單片機的同學請注意單片機種的堆疊與Windows下的堆疊是不同的,請參考相應資料),這里頂部是地址小的區域,那么,壓入堆疊的資料越多,esp也就越來越小;
- 首先看第一段進行push指令壓堆疊操作,從高地址往低地址把ebp壓堆疊

并且esp調整指向堆疊幀頂部(也就是減少ESP的值,16位的,則ESP減2位元組,32位的,則 ESP減4位元組)

接下來是move指令:mov ebp,esp , 就是把第二個引數拷貝到第一個引數,
即:把ebp的地址指向改為esp的地址,esp,ebp指向同一塊

sub esp,0E4h
(這里sub是減,0E4h 是十六進制E4,也就是esp — E4,esp存放的地址減E4)
那么此時esp和ebp之間就分配了一定空間,即main函式的堆疊幀.

push ebx
push esi
push edi
這三個都是暫存器,依然壓堆疊操作,并且esp減少指向堆疊頂(低地址)

lea:取得第二個引數地址后放入到前面的暫存器(第一個引數)中,
然而lea也同樣可以實作mov的操作,例如:lea edi,[ebp-24h]
方括號表示存盤單元,也就是提取方括號中的資料所指向的內容,然而lea提取內容的地址,這樣就實作了把地址(ebp-24h)放入到了edi
lea edi,[ebp-E4h]
008C140C lea edi,[ebp-0E4h]
008C1412 mov ecx,39h
008C1417 mov eax,0CCCCCCCCh
008C141C rep stos dword ptres:[edi]
- 這段的作用就是把ebp - 0E4h的地址賦給edi,39(十六進制)賦給ecx,把CCCCCCCC(十六進制)賦給eax
并且從edi開始的以下39h個4位元組都變成eax也就是CCCCCCCC
注意這里39h = 57,57個4位元組也就是57 × 4 = 228轉化成十六進制就是0E4h,也就是從ebp到ebp - 0E4h的那段空間全都變成CCCCCCCC
而CCCCCCCC也就是燙所對應的亂碼

int a = 10;
008C141E mov dword ptr [a],0Ah
int b = 20;
008C1425 mov dword ptr [b],14h
那么我們關閉反匯編中的符號顯示:
int a = 10;
008C141E mov dword ptr [ebp-8],0Ah
int b = 20;
008C1425 mov dword ptr [ebp-14h],14h
接下來把十六進制0A也就是10拷貝給a,十六進制14也就是16+4=20拷貝給b
而a 的地址就是ebp - 8h,b的地址就是ebp - 14h也就是ebp - 20

二、代碼中add函式堆疊幀的開辟
int ret = add(a, b);
00951943 mov eax,dword ptr [b]
00951946 push eax
00951947 mov ecx,dword ptr [a]
0095194A push ecx
0095194B call 009513B1
00951950 add esp,8
00951953 mov dword ptr [ebp-20h],eax
1 ) 傳參
00951943 mov eax,dword ptr [b]
00951946 push eax
00951947 mov ecx,dword ptr [a]
0095194A push ecx
方括號表示提取方括號中的資料所指向的內容.
把b的值拷貝給eax,并且把eax壓堆疊,同理也把a的值拷貝給ecx,并壓堆疊

> 008C1434 call 008C1096
> call指令進行呼叫函式并保存地址

2 ) add函式:
代碼如下(示例):
int add(int a,int b)
{
00C613C0 push ebp
00C613C1 mov ebp,esp
00C613C3 sub esp,0C0h
00C613C9 push ebx
00C613CA push esi
00C613CB push edi
00C613CC lea edi,[ebp+FFFFFF40h]
00C613D2 mov ecx,30h
00C613D7 mov eax,0CCCCCCCCh
00C613DC rep stos dword ptr es:[edi]
return a + b;
00C613DE mov eax,dword ptr [ebp+8]
00C613E1 add eax,dword ptr [ebp+0Ch]
}
00C613E4 pop edi
00C613E5 pop esi
00C613E6 pop ebx
00C613E7 mov esp,ebp
00C613E9 pop ebp
00C613EA ret
- 還是一樣進行add函式堆疊幀的開辟
00C613C0 push ebp
00C613C1 mov ebp,esp
00C613C3 sub esp,0C0h
00C613C9 push ebx
00C613CA push esi
00C613CB push edi
00C613CC lea edi,[ebp+FFFFFF40h]
00C613D2 mov ecx,30h
00C613D7 mov eax,0CCCCCCCCh
00C613DC rep stos dword ptr es:[edi]
同main函式一樣不再贅述,這里注意存放ebp的是main的堆疊幀底部,并且此時esp ebp用來維護add函式的堆疊幀
return a + b;
00C613DE mov eax,dword ptr [ebp+8]
00C613E1 add eax,dword ptr [ebp+0Ch]
- 接下來把ebp+ 8 那塊地址所對應的值(如圖也就是10)拷貝給暫存器eax
- 并且把ebp +0Ch 的地址所對應 的值(也就是20)加到 eax

三、add函式堆疊幀的銷毀
00C613E4 pop edi
00C613E5 pop esi
00C613E6 pop ebx
00C613E7 mov esp,ebp
00C613E9 pop ebp
00C613EA ret
pop:與push相反,esp每次加4(位元組),資料出堆疊并放到后邊暫存器里,
這里依次彈出也就是edi放到edi里,esi放到esi,ebx放到ebx
- 此時edi,esi,ebx出堆疊,并把ebp拷貝給esp


- 接著繼續出堆疊,把堆疊頂彈出放到ebp里邊,而此時的堆疊頂就是之前存放的Main函式的ebp
00C613E9 pop bp

- pop之后ebp指向main函式的堆疊底

ret:跳轉會呼叫函式的地方,對應于call,回傳到對應的call呼叫的下一條指令,若有回傳值,則放入eax中
此時彈出堆疊頂(call指令下一條地址)并回傳到該地址

形參的銷毀
00C61439 add esp,8
- 緊接著esp往下加8個位元組


- 此刻形參銷毀
流程圖

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/292783.html
標籤:其他
下一篇:函式的創建和銷毀(細)



