c語言函式的棧幀
阿新 • • 發佈:2022-06-05
非靜態區域性變數如何在棧上分配?c語言中的函式是如何傳引數?如何呼叫?如何返回的?
(1)、sum01.c生成32位彙編程式,進行靜態分析;
(2)、將sum01.c編譯連線成32位的可執行檔案sum01.exe,然後拖入OD軟體,在main函式入口出設定斷點,進行單步跟蹤,動態分析。
引數傳遞:通過堆疊傳遞(c語言傳參是從右向左傳);
函式呼叫:call 函式名;
函式返回:ret指令(返回值一般在EAX暫存器中);
c語言中,函式名本質就是一個地址(該函式的第一條指令在記憶體中儲存的偏移地址)。
函式名對應的地址:printf("add of sum is :%p,add of main :%p\n",sum,main);
c語言中每個函式呼叫:
(1)、傳引數:從右到左,存放到堆疊棧頂;
(2)、發出call指令:call 被呼叫函式名;(將call指令的下一條指令的地址推入堆疊棧頂,然後將被呼叫的函式的第一條指令的地址自動賦值給EIP暫存器-------段內呼叫;
如果是段間呼叫,則會自動將call指令的下一條指令的地址(段地址CS:偏移地址EIP),推入堆疊棧頂,然後將被呼叫的函式的第一條指令的地址(段地址CS:偏移地址EIP)自動賦值給相應的CS和EIP暫存器)
以下面程式碼為例子:
int sum(int x, int y) { int z; z = x + y; return z; } int main(void) { int a = 10; int b = 20; printf("sum=%d\n", sum(a, b)); return 0; } /*彙編程式碼*/ _sum: push ebp mov ebp, esp sub esp, 4 mov eax, DWORD PTR [ebp+12] add eax, DWORD PTR [ebp+8] mov DWORD PTR [ebp-4], eax mov eax, DWORD PTR [ebp-4] leave ret .def ___main; .scl 2; .type 32; .endef LC0: .ascii "sum=%d\12\0" .globl _main .def _main; .scl 2; .type 32; .endef _main: push ebp mov ebp, esp sub esp, 24 and esp, -16 mov eax, 0 mov DWORD PTR [ebp-12], eax mov eax, DWORD PTR [ebp-12] call __alloca call ___main mov DWORD PTR [ebp-4], 10 mov DWORD PTR [ebp-8], 20 mov eax, DWORD PTR [ebp-8] mov DWORD PTR [esp+4], eax mov eax, DWORD PTR [ebp-4] mov DWORD PTR [esp], eax call _sum mov DWORD PTR [esp+4], eax mov DWORD PTR [esp], OFFSET FLAT:LC0 call _printf mov eax, 0 leave ret .def _printf; .scl 2; .type 32; .endef
被呼叫函式內部:
(1)建立自己的棧幀底部:
push ebp ;儲存上一個棧幀的基地址(棧底地址)
mov ebp,esp ;讓ebp暫存器執行當前的棧幀的棧底
(2)為函式內部定義的非靜態區域性變數分配儲存空間:
and esp ,-16 ;將棧頂指標esp進行對齊,保證能被16整除
sub esp ,32 ;為函式內部的非靜態區域性變數分配儲存空間,分配的空間一般多餘區域性變數所需的,防止溢位。
(3)函式內部要呼叫別的函式:
A. 傳引數(從右向左傳,存放到堆疊棧頂) B. 發出call指令
(4)如何訪問自己的區域性變數呢?
mov DWORD PTR [esp+28], 10 (esp+28---->某個區域性變數) mov DWORD PTR [esp+24], 20 (esp+24---->某個區域性變數) 或者通過ebp來訪問傳遞給自己的引數:ebp-4--->第一個引數,ebp-8--->第二個引數
一個函式內部完整的棧幀結構:(ebp指向當前函式棧幀的底部,而esp指向當前函式棧幀的頂部)
呼叫當前函式的那個函式的棧底指標ebp(也是當前函式的棧底)。 <-----ebp(棧底)
函式內部的區域性變數。
當前函式呼叫別的函式傳的引數。
當前函式呼叫別的函式的返回地址。 <----esp(棧頂)
(5)函式結束,返回返回值(一般通過eax暫存器返回),進行平衡堆疊操作:拋棄當前的棧幀,恢復ebp和esp原來的值。
leave (leave指令的功能:mov esp, ebp以及pop ebp,本質是拋棄當前函式棧幀,恢復上一個函式的棧幀)
ret (噹噹前棧頂的返回地址彈出送到eip中,如果是段間返回,則彈出棧頂到cs和eip中)
堆疊程式碼分析:
EBP=0022FFB0
ESP=0022FF84
push ebp
EBP=0022FFB0
ESP=0022FF80 --->(EBP)=0022FFB0
mov ebp, esp
EBP=0022FF80
ESP=0022FF80 --->(EBP)=0022FFB0
sub esp, 18
EBP=0022FF80
ESP=0022FF68
and esp, fffffff0 (-16) //地址對齊操作,讓32位地址的最右邊4位為0,也就是該地址能被16整除。
EBP=0022FF80
ESP=0022FF68 & fffffff0 = 0022ff60
mov eax, 0
MOV DWORD PTR SS:[EBP-C],EAX ss:0022FF74--(00000000)
MOV DWORD PTR SS:[EBP-4],0A EBP-4-->a
MOV DWORD PTR SS:[EBP-8],14 EBP-8-->b
MOV EAX,DWORD PTR SS:[EBP-8]
MOV DWORD PTR SS:[ESP+4],EAX b--->棧頂ESP+4
MOV EAX,DWORD PTR SS:[EBP-4] a--->棧頂ESP
MOV DWORD PTR SS:[ESP],EAX
ESP = 0022ff60
CALL _sum
ESP=0022FF5C 0022FF5C--->004012EA 函式呼叫的返回地址(call指令下一條指令的地址)
設定新的EIP為被呼叫的函式中第一條指令的地址:00401290。 00401290--->push ebp (sum函式中的第一條指令)
ESP=0022FF58 執行了PUSH EBP EBP=0022FF80-->棧頂(儲存main函式裡的ebp,因為sum函式要建立自己的棧幀ebp)
棧幀:就是呼叫到某個函式時該函式使用的一段棧上的連續的儲存空間,ebp指向棧幀底部,esp指向棧幀頂部。
EBP=0022FF58 ESP=0022FF58 執行了MOV EBP,ESP
SUB ESP,4 實際上是為sum函式中的區域性變數z分配儲存空間。此時ESP=0022FF54
MOV EAX,DWORD PTR SS:[EBP+C] EBP+C--->引數y,存放的是b變數的值
ADD EAX,DWORD PTR SS:[EBP+8] EBP+8--->引數x,存放的是a變數的值
MOV DWORD PTR SS:[EBP-4],EAX 和--->z中 0022FF54(z)<--- 30(1E)
MOV EAX,DWORD PTR SS:[EBP-4] 返回值(和30)----> EAX (返回值一般都通過EAX暫存器返回)
LEAVE指令執行:恢復原來的棧幀(EBP、ESP):執行前EBP=0022FF58,ESP=0022FF54 執行後EBP=0022FF80,ESP=0022FF5C
此時的ESP棧頂裡存放的是返回地址004012EA(main函式中call _sum的下一條指令的地址)
執行sum函式的最後一條指令:RETN 返回指令(從棧頂彈出返回地址,送入EIP)。
MOV DWORD PTR SS:[ESP+4],EAX將返回值EAX(和30)放入棧頂(傳引數,準備執行printf函式)
MOV DWORD PTR SS:[ESP], 004012A4(字串常量"sum=%d\n"的偏移地址)放入棧頂
CALL _printf呼叫printf函式輸出計算的和。
MOV EAX,0 main函式返回給作業系統的值
LEAVE
RETN main函式返回 return 0;