函數調用過程分析
1. 靜態變量和初始化
int static_variable = 5;
--------------------------------------------
.data ;進入程序的數據區
.even ;確保變量開始於內存的偶數地址
.global _static_variable ;聲明變量為全局類型
_static_variable:
.long 5 ;創建空間,初始化
2. 堆棧幀
一個函數分為:函數序、函數體、函數跋
函數序:執行啟動工作,如:為局部變量保存堆棧中的內存
函數跋:在函數即將返回之前清理堆棧。
函數體:執行工作的地方
void f()
{
register int i1, i2, i3, i4, i5,
i6, i7, i8, i9, i10;
register char *c1, *c2, *c3, *c4, *c5,
*c6, *c7, *c8, *c9, *c10;
extern int a_very_long_name_to_see_...
double dbl;
int func_ret_int();
double func_ret_double();
char *func_ret_char_ptr();
-------------------------------------------------------
.text ;進入程序代碼段
.global _f ;函數的全局聲明
_f: link a6, #-88 ;創建堆棧幀,堆棧幀是堆棧中的一個區域,存儲變量和其他值
moveml 0x3cfc, sp@ ;選定寄存器中的值復制到堆棧中
0x3cfc表示寄存器d2至d7、a2到a5中的值需要被保存
局部變量聲明和函數原型不會產生任何匯編代碼。但局部變量聲明時進行了初始化,也會出現指令用於賦值操作
3. 寄存器變量
i1 = 1; i2 = 2; i3 = 3; i4 = 4; i5 = 5;
i6 = 6; i7 = 7; i8 = 8; i9 = 9; i10 = 10;
c1 = (char *)110; c2 = (char *)120;
c3 = (char *)130; c4 = (char *)140;
c5 = (char *)150; c6 = (char *)160;
c7 = (char *)170; c8 = (char *)180;
c9 = (char *)190; c10 = (char *)200;
-------------------------------------------
moveq #1,d7
moveq #2,d6
moveq #3,d5
moveq #4,d4
moveq #5,d3
moveq #6,d2
movl #7,a6@(-4)
movl #8,a6@(-8)
movl #9,a6@(-12)
movl #10,a6@(-16)
movl #110,a5
movl #120,a4
movl #130,a3
movl #140,a2
movl #150,a6@(-20)
movl #160,a6@(-24)
movl #170,a6@(-28)
movl #180,a6@(-32)
movl #190,a6@(-36)
movl #200,a6@(-40)
值1至6被放在數據寄存器,7至10放在其他地方
指針變量前4個存放在寄存器
其他變量,機器執行間接尋址和索引操作。a6稱為幀指針,指向堆棧幀內部的一個引用位置
這台機器(motorola 68000),a6用作幀指針,a7是堆棧指針sp,d0和d1用於存函數返回值
4. 堆棧幀的布局
運行時堆棧保存了每個函數運行時所需要的數據,包括它的自動變量和返回地址。
4.1 傳遞函數參數
i2 = func_ret_int(10, i1, i10);
---------------------------------
movl a6@(-16),sp@-
movl d7,sp@-
pea 10
jbsr _func_ret_int
前3條指令把函數的參數壓入堆棧中。以參數列表相反的次序逐個壓入棧。
接下來跳轉子程序,把返回地址壓入堆棧中,並跳轉到_func_ret_int的起始位置,當被調用函數結束任務返回調用位置時,就需要用到壓入堆棧的返回地址。
4.2 函數序
int func_ret_int(int a, int b, register int c)
{
int d;
---------------------------------------------------
.global _func_ret_int
_func_ret_int:
link a6,#-8
moveml #0x80,sp@
movl a6@(16),d7
link指令分成幾個步驟:
-
a6的內容被壓入堆棧中
-
堆棧指針的當前值被復制到a6
-
link指令從堆棧指針中減去8
-
這將創建空間用於保存局部變量和被保存的寄存器值*
下一條指令把單一寄存器保存到堆棧幀,操作數0x80指定寄存器d7。寄存器存儲在堆棧的頂部,剩余部分必然是局部變量存儲的地方。如上圖右邊。
函數序最后才能夠堆棧復制一個值到d7,函數把第三個參數聲明為寄存器變量。
4.3 堆棧中的參數次序
被調用函數使用幀指針加一個偏移量來訪問參數,當參數以反序壓入到堆棧時,參數列表的第一個參數便位於堆棧中這堆參數的頂部,它距離幀指針的偏移量是一個常數。
4.4 最終的堆棧幀布局
d = b - 6;
return a + b + c;
--------------------------------
movl a6@(12),d0
subl #6,d0
movl d0,a6@(-4)
movl a6@(8),d0
movl a6@(12),d0
addl d7,d0
moveml a6@(-8),#0x80
unlk a6
rts
4.5 函數跋
-
mveml恢復以前被保存的寄存器值
-
unkl(unlink)把a6的值復制給堆棧指針並把從堆棧中彈出的a6舊值裝入a6中。清除堆棧幀中返回地址以上的那部分內容
-
rts指令通過把返回地址從堆棧中彈出到程序計數器,從而從該函數返回。
現在,執行流從調用程序的地點繼續。堆棧尚未完全清理
i2 = func_ret_int(10, i1, i10);
------------------------------------
lea sp@(12),sp
movl d0,d6
第1條指令把參數從堆棧中彈出,此時堆棧的狀態就和調用前狀態完全一樣了。
4.6 返回值
函數跋沒有使用d0,因此依然保存函數的返回值。第二條指令把d0復制給d6,后者存放變量i2的存放位置。
dbl = func_ret_double();
c1 = func_ret_char_ptr();
-------------------------------------
jbsr _func_ret_double
movl d0,a6@(-48)
movl d1,a6@(-44)
pea a5@
jbsr _func_ret_char_ptr
addqw #4,sp
movl d0,a5
第一個函數double是8字節,無法放入一個寄存器中,因此返回值需要d0和d1兩個寄存器。
最后那個函數調用說明了指針變量時如何從函數中返回的:也是通過d0進行傳遞的。
參考:C和指針-第18章 運行時環境