函式是任何一門高級語言中必須要存在的,使用函式式編程可以讓程式可讀性更高,充分發揮了模塊化設計思想的精髓,今天我將帶大家一起來探索函式的實作機理,探索編譯器到底是如何對函式這個關鍵字進行實作的,并使用匯編語言模擬實作函式編程中的引數傳遞呼叫規范等,
說到函式我們必須要提起呼叫約定這個名詞,而呼叫約定離不開堆疊的支持,堆疊在記憶體中是一塊特殊的存盤空間,遵循先進后出原則,使用push與pop指令對堆疊空間執行資料壓入和彈出操作,堆疊結構在記憶體中占用一段連續存盤空間,通過esp與ebp這兩個堆疊指標暫存器來保存當前堆疊起始地址與結束地址,每4個位元組保存一個資料,
一般編譯器實作呼叫呼叫約定無外乎以下這幾種:
- CDECL:C/C++默認的呼叫約定,呼叫方平堆疊,不定引數的函式可以使用,引數通過堆疊傳遞.
- STDCALL:被調方平堆疊,不定引數的函式無法使用,引數默認全部通過堆疊傳遞.
- FASTCALL32:被調方平堆疊,不定引數的函式無法使用,前兩個引數放入(ECX, EDX),剩下的引數壓堆疊保存.
- FASTCALL64:被調方平堆疊,不定引數的函式無法使用,前四個引數放入(RCX, RDX, R8, R9),剩下的引數壓堆疊保存.
- System V:類Linux系統默認約定,前八個引數放入(RDI,RSI, RDX, RCX, R8, R9),剩下的引數壓堆疊保存.
當堆疊頂指標esp小于堆疊底指標ebp時,就形成了堆疊幀,堆疊幀中可以尋址的資料有區域變數,函式回傳地址,函式引數等,不同的兩次函式呼叫,所形成的堆疊幀也不相同,當由一個函式進入另一個函式時,就會針對呼叫的函式開辟出其所需的堆疊空間,形成此函式的獨有堆疊幀,而當呼叫結束時,則清除掉它所使用的堆疊空間,關閉堆疊幀,該程序通俗的講叫做堆疊平衡,而如果堆疊在使用結束后沒有恢復或過度恢復,則會造成堆疊的上溢或下溢,給程式帶來致命錯誤,
cdecl 呼叫者平堆疊: cdecl是C/C++默認呼叫約定,該呼叫方式在函式內不進行任何平衡引數操作,而是在退出函式后對esp執行加4操作,從而實作堆疊平衡,
該約定會采用復寫傳播優化,將每次引數平衡的操作進行歸并,在函式結束后一次性平衡堆疊頂指標esp,且不定引數函式可使用此約定,
stdcall 被呼叫者平堆疊: stdcall與cdecl只在引數平衡上有所不同,其余部分都一樣,但該約定不定引數函式無法使用,
cdecl呼叫方式的函式在同一作用域內多次被呼叫,會在效率上比stdcall高一些,因為它可以使用復寫傳播優化,而stdcall在函式內平衡堆疊,無法使用復寫傳播優化,
fastcall 被呼叫者平堆疊: fastcall效率最高,它可利用暫存器傳遞引數,一般前兩個或前四個引數用暫存器傳遞,其余引數傳遞則轉換為堆疊傳遞,此約定不定引數函式無法使用,
對于32位來說使用ecx,edx傳遞前兩個引數,后面的用堆疊傳遞,
對于64位則會使用RCX,RDX,R8,R9傳遞前四個引數,后面的用堆疊傳遞,
使用esp尋址: 在O2編譯器選項中,為了提高程式執行效率,只要堆疊頂是穩定的,就可以不再使用ebp指標,而是利用esp指標直接訪問區域變數,這樣可節省一個暫存器資源,
如下一段匯編代碼,我們找到當前ESP基地址,

可以看到,esp+18就是第一個傳入引數,那么程式在編譯時,其實已經算出來了,

使用esp尋址后,不必每次進入函式后都調整堆疊底ebp,從而減少了ebp的使用,因此可以有效提升程式執行效率,
但每次訪問都需要計算,如果在函式執行程序中esp發生了變化,再次訪問變數就需要重新計算偏移了,

參考文獻:《C++反匯編與逆向分析技術揭秘》 文章出處:https://www.cnblogs.com/LyShark/p/15901950.html
著作權宣告:本博客文章與代碼均為學習時整理的筆記,博客中除去明確標注有參考文獻的文章,其他文章 [均為原創] 作品,轉載請 [添加出處] ,您添加出處是我創作的動力!
如果您惡意轉載本人文章并被本人發現,則您的整站文章,將會變為我的原創作品,請相互尊重 !
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/426388.html
標籤:其他
