彙編基礎四 --函式呼叫與堆疊平衡
阿新 • • 發佈:2020-07-23
函式
將高階語言中定義的函式,被編譯位彙編程式碼執行時,會被編譯為一堆指令的集合,用來實現特定的功能,並獲得執行後的結果。如果不關注函式中的具體實現,就可以將一個函式看作一個整體,函式呼叫過程等同於執行了一個操作,只不過這個操作比較複雜而已。
彙編中實現一個函式可以使用JMP 和 CALL 指令完成。
函式是一堆完成特定功能的指令集,這些指令集同樣需要按照順序依次執行,所以只要知道函式執行的第一條指令的地址(函式的首地址),函式將會依次執行這些指令,完成函式。
計算兩數之和
我們使用C語言實現簡單的加法函式
函式分為帶參函式和無參函式,對於無參函式,直接使用CALL指令跳轉到函式首地址然後開始執行即可,並且CALL指令會將下一行指令地址入棧儲存,用於函式結束返回時跳回到原地址。
// 函式體 首地址 指令 00401019 ADD EAX, 4
0040101D RETN // 將棧中的地址 00401038 取出, 賦值給ESI,下次執行回到00401038地址處執行
... ... 00401034 CALL 00401019 // 將下一行指令地址 00401038存入棧空間,然後執行 00401019地址處的函式
00401038 ...
如果函式帶引數,我們使用C語言實現簡單的加法函式為例
int add(int x, int y) { int z = 0; z = x + y; return z; } void main() {int x = 1; int y = 2; int z; z = add(x, y) }
該函式執行時可以分為以下的步驟。
- add函式需要x, y兩個引數至,所以在呼叫函式前,將引數push 到棧中,為了在函式中使用,多個引數多次執行push即可。 push 1, push 2
- 執行CALL 指令,該指令將下一指令地址存入棧中,並將函式首地址寫入EIP暫存器,下一次將會執行函式體中的指令。
- 函式中的指令開始執行,但是執行我們加法邏輯前,會執行一些操作
- 首先進行堆疊提升,並開啟一段緩衝區空間用於儲存函式中的臨時變數,使用 SUB ESP, 40h指令,將ESP向上偏移40個數據寬度。堆疊提升後,使用EBP位置進行定址。
- 將三個暫存器中的值寫入棧中,函式執行的過程中會覆蓋暫存器的值,儲存在棧中後,函式結束時可以從該處恢復。
- 開始執行函式的主邏輯
- 獲取棧中 x,y的值進行加法操作。通過EBP暫存器中儲存的位置,函式引數在棧中的位置分別為,EBP+8和EBP+C(16進位制)
- 執行加法操作
- MOV EAX, 0 -- EAX暫存器中的值設定為0
- ADD EAX, ptr ds:[ EBP+8 ] -- EAX暫存器中的值 + 記憶體地址 EBP +8位置值,結果儲存在EAX中
- ADD EAX, ptr ds:[ EBP+C] -- EAX暫存器中的值 + 記憶體地址 EBP +C 位置值,結果儲存在EAX中
- 執行結束後,結果被儲存到了EAX暫存器中。
- 函式執行結束,開始恢復堆疊,清除函式棧中的資訊,保證函式執行前的堆疊資訊和函式執行後的堆疊相同,也就是滿足堆疊平衡。
- 恢復三個暫存器edi, esi, ebx中值,pop edi, pop esi, pop ebx
- 清除緩衝區,這裡的清除並不刪除其中的資料,將ESP指標恢復即可,這樣緩衝區空間會被作為未使用區域,新的資料寫入時候,將原來的資料覆蓋。由於ESP提升執行了SUB,所以ADD ESP, 40h即可
- EBP恢復到原EBP位置,此時棧中儲存了原EBP中的位置,所以 POP EBP ,將堆疊中的原EBP地址儲存到EBP中,EBP地址恢復。
- 下一行指令地址賦值給EPI暫存器。到此為止,函式執行結束,並回到了CALL指令的下一行指令,但是函式引數空間還沒有清除,所以需要在函式外部恢復堆疊平衡。
- CALL指令的下一行,清除函式引數。同樣的,ESP向下移動,ADD EBP, C 即可。
上面是執行過程,彙編程式碼為。
// 主程式入口 。。。 push 1 push 2 call 0040107D -- 呼叫函式 add esp, 8 -- 清楚堆疊中的引數值,恢復堆疊
-- 執行結果在eaxz中,需要使用時,獲取即可 。。。
// 此處為函式首地址為 0040107D push ebp -- 堆疊提升,儲存原ebp值,然後將esp賦值給esp mov ebp, esp sub esp, 40h push ebx push esi push edi mov eax, 0 add eax, ptr ds:[ebp+8] add eax, ptr ds:[ebp+c] pop edi pop esi pop ebx add esp, 40h cmp ebp esp -- 比較esp和ebp是否相同,清除棧資訊後應該相,否則說明棧中的內容沒有被清除。堆疊不平衡 pop ebp -- 從棧中取出原ebp值,存入ebp即恢復原ebp值 ret
函式執行過程中的堆疊空間是在函式執行時才分配的,這裡發生了一次堆疊提升,所以我們總是說函式執行時有獨立的棧空間,也是通過這種方式實現。函式執行前後始終需要保證堆疊平衡。
總結
總結函式的執行過程
- 引數入棧
- 儲存當前執行指令的地址,入棧
- 進入函式,
- ESP 和 EBP 分別進行堆疊提升
- sub esp 開闢緩衝區空間,緩衝區空間地址為 EBP + 4 -> ESP
- 三個暫存器值儲存到棧中
- 執行函式中內容,EBP為基址,獲取通過+8 +C獲取函式引數,+4位置為函式返回時跳轉的地址
- 函式執行結束:
- 恢復三個暫存器
- add esp恢復 緩衝區,
- ESP和EBP位置恢復
- ret
- 函式外部add ebp 恢復引數空間。
- 在eax中獲取函式返回值即可。