我們常常會使用函數調用來實現某種操作,只知道從調用函數跳到被調函數,被調函數執行完又返回到主調函數,可能會有以下的疑問: 1.形參開闢空間嗎?
2.參數由誰來開闢空間
3.形參的入棧順序
4.返回值由誰傳出來
5.怎麼回到調用函數
6.如何知道下一行指令
爲了解決以上的問題,以一段簡單的代碼爲例,以下是代碼的反彙編:
調用函數:
mov 指移動數值
lea 指移動地址
ebp 指向棧底指針
esp 指向棧頂指針
eax ebx ecx edx 都是存儲數據的寄存器
先從這部分彙編指令開始,
int rt = 0;
0123141E mov dword ptr [rt],0
int a = 10;
01231425 mov dword ptr [a],0Ah
int b = 20;
0123142C mov dword ptr [b],14h
這三個彙編指令都是對變量進行初始化,如 Int rt = 0;是根據棧底指針的偏移量確定存儲0這個數字的位置
0123141E mov dword ptr [rt],0
0123141E 是指令的虛擬地址,
mov是對數值的移動,
dword ptr [rt] 實際上是dword ptr [ebp-4](棧底指針的偏移量)
簡單來講,意思就是將數值0移動到爲ebp-4的地址;int a = 10;與int b = 20亦如此入棧
rt = Sum(a,b);
01231433 mov eax,dword ptr [b]
01231436 push eax
01231437 mov ecx,dword ptr [a]
0123143A push ecx
0123143B call Sum (012311C7h)
01231440 add esp,8
01231443 mov dword ptr [rt],eax
將b的值(ptr[ebp-12])保存在eax寄存器中,然後壓棧,將a的值(ptr[ebp-8])保存在ecx的寄存器中,將ecx壓棧;我們發現這裏的a和b也就是形參,可知,參數是在主調函數中開闢空間的
call:靜態相對位移,call是跳轉到被調函數的指令地址包含以下兩個步驟:
1.將下一條指令壓棧;
2.跳轉到被調函數;Sum(012311C7h)括號裏就是被調函數的指令地址
被調函數
會注意到被調函數中的前面一部分和主調函數中的除了個別數據外,基本一致;其實每個函數前面都會這麼一個部分,現在來看這些指令到底是做什麼的;
012313D0 push ebp
012313D1 mov ebp,esp
012313D3 sub esp,0C0h
將主調函數的棧底指針的值壓棧,然後將ebp移動到esp的位置;sub意思是讓esp減等0c0,也就是爲新棧開闢了192個字節;這三步主要就是先將主調函數的棧頂指針入棧,然後爲新棧開闢空間;
012313D9 push ebx
012313DA push esi
012313DB push edi
分別將寄存器ebx、esi、edi依次入棧
012313DC lea edi,[ebp-0C0h]
使edi指向ebp-0C0h的位置;
012313E2 mov ecx,30h
012313E7 mov eax,0CCCCCCCCh
012313EC rep stos dword ptr es:[edi]
這三行指令的意思就是循環30h十進制是48次(48*4就是新棧開闢的空間大小),從棧頂到棧底將數據賦值爲0xcccccccch
012313EE mov eax,dword ptr [a]
012313F1 add eax,dword ptr [b]
在ptr[ebp+8]中將a的值放在寄存器eax中,add是寄存器eax等於eax中的值加上ptr[ebp+c]中的值,也就是計算a+b的值,將結果放在eax中,所以結果是由寄存器帶出來的;如果返回值是小於等於四個字節,則由一個寄存器帶出來,如果大於4小於8的字節,由兩個字節帶出來,大於8個字節時,在調用方開闢一個返回值臨時量的空間,將return回來的值循環拷貝在臨時量區;
銷燬棧
013613F4 pop edi
013613F5 pop esi
013613F6 pop ebx
013613F7 mov esp,ebp
013613F9 pop ebp
先將三個寄存器出棧,將esp移動到ebp,這時esp和ebp都是指向新棧的棧底指針,最後將ebp出棧,ebp就再次指向主調函數的棧底;
回到主調函數中 01361440 add esp,8
01361443 mov dword ptr [rt],eax esp指向的還是新棧的棧底,所以add就是將esp向下移動兩個字節,指向主調函數的棧頂 將寄存器eax的值賦給rt(ptr[ebp-4])的位置
整個過程就是這個樣子了,再回到開始的幾個問題,在這個過程中已經解決了
1.形參開闢空間嗎?形參開闢空間
2.參數由誰來開闢空間?參數由主調函數開闢
3.形參的入棧順序?由右向左
4.返回值由誰傳出來?由寄存器傳出來
5.怎麼回到調用函數?將主調函數的棧底指針的值入棧到被調函數中,被調函數的棧底指針釋放後,回到主調函數中
6.如何知道下一行指令?call指令的作用就是將下一行指令地址入棧