研究編譯器運行時環境的幾個問題:
1,運行環境的限制是什麼?
2,如何使C和彙編一起工作?
1,判斷運行環境
方法:步驟一: 從編譯器獲取彙編語言代碼列表。獲取途徑:UNIX系統中,編譯器生成的*.s文件;Borland編譯器生成的*.asm文件
步驟二: 閱讀彙編代碼,瞭解每條指令工作過程。工具:描述計算機指令集的手冊
1.1測試程序(使用Motorola 68000處理器)
1.1.1 靜態變量初始化
int static_variable = 5;
彙編代碼
.data
.enen
.global _static_variable
_static_variable:
.long 5
彙編一開始是兩個指令,分別表示進入程序數據區及確保變量開始於內存的偶數地址,因爲68000處理器要求邊界對齊。
然後變量被聲明爲全局類型,變量名以‘_‘開始,許多C編譯器會在C代碼所聲明的外部名字前加一個下劃線,以免與各個庫所使用的名字衝突。
最後,編譯器爲變量創建空間,並初始化。
1.1.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 db1;
int func_ret_int();
double func_ret_double();
char *func_ret_char_ptr();
}
f()函數分三個部分:
函數序:執行函數啓動需要的工作,如爲局部變量保持堆棧中的內存。
函數體:執行有用工作的地方
函數跋:在函數即將返回之前清理堆棧
彙編指令
.text ;表示進入程序的代碼段,開始爲函數創建堆棧幀。
.globl _f ;函數名的全局聲明,注意前面也有下劃線
-f: link a6,#-88 ;link指令使在堆棧中保留88個字節的空間,用於存儲局部變量和其他值
moveml #0x3cfc,sp@ ;把選定寄存器中的值複製到堆棧中
68000處理器的寄存器:
d0~d7:數據寄存器
a0~a7:地址寄存器
局部變量聲明和函數原型不會產生任何彙編代碼,除非聲明時進行了初始化,則會產生彙編賦值代碼。
1.1.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
mov1 #7,a6@(-4)
mov1 #8,a6@(-8)
mov1 #9,a6@(-12)
mov1 #10,a6@(-16)
mov1 #110,a5
mov1 #120,a4
mov1 #130,a3
mov1 #140,a2
mov1 #150,a6@(-20)
mov1 #160,a6@(-24)
mov1 #170,a6@(-28) ;指定偏移地址爲-28.
mov1 #180,a6@(-32)
mov1 #190,a6@(-36)
mov1 #200,a6@(-40)
通過彙編可以發現當前使用的編譯器特點:
1)數據寄存器只能放6個整型值
2)有些編譯器不把字符型變量存放在寄存器中
3)特殊寄存器存放浮點值
4)最多允許4個指針變量放在寄存器中,無論指針是什麼類型,都可以放在寄存器中
其他變量的存放,機器用間接尋址和索引操作來進行。
寄存器a6爲幀指針,指向堆棧幀內部的“引用”位置,通過引用位置加上偏移量可以訪問堆棧幀中的所有值。偏移地址從-4開始,每次增加4。
通過偏移地址,可以建立映射表,準確顯示堆棧中每個值相對於幀指針a6的位置。
i1~i10是寄存器變量,存儲於寄存器d2~d7,a2~a5中,受到寄存器保存。注意在這臺機器中,a6用作幀指針,a7用作堆棧指針sp,而d0~d1,用於函數返回值,所以不用做保護寄存器。
1.1.4測試外部標識符允許的最大長度
/*外部名字*/
a_very_long_name_to_see_how_long_they_can_be = 1
彙編指令
mov1 #1 a_very_long_name_to_see_how_long_they_can_be
這段代碼顯示,該名字爲超出限制,爲了找出最大限制,可以不斷加大該名字,知道發現彙編程序揭短之。
注意:
外部名字的最終限制是鏈接器施加的,很可能接收任意長度的名字而忽略前幾個字符以外的其他字符。標準要求外部名字至少區分前6個字符。
1.1.5 判斷堆棧幀的格局
測試 1:堆棧幀的組織形式
i2 = func_ret_int(10,i1,i10);
彙編代碼
mov1 a6@(-16),sp@-
mov1 d7,sp@-
pea 10
jbsr _func_ret_int
前三條指令把函數參數壓入堆棧
第一個被壓入的參數i10,存儲於a6@(-16),
然後壓入d7,d7存儲的是i1;
最後一個壓入方式:簡單地把它的操作時壓入堆棧中,高效。
當值壓入堆棧時,堆棧向低地址生長。
最後,是跳轉子程序指令。先把返回地址壓入棧中,並跳轉至_func_ret_int的起始位置,調用結束在將返回地址出棧,繼續執行當前程序。
測試2:調用和從函數返回的協議 ,即函數序問題
func_ret_int(int a,int b,register int c)
{
int d; d = b - 6; return a + b + c;
}
彙編指令
.globl _fun_ret_int
_fun_ret_int:
link a6,#-8
movem1 #0x80,sp@
mov1 a6@(16),d7
link指令執行分幾個步驟:
1,把a6內容壓入堆棧
2,堆棧指針的當前值被複製爲a6
3,link指令從堆棧中減去8
創建空間被用於保存局部變量和被保存的寄存器值
下一條指令,把單一的寄存器保存到堆棧幀。操作數0x80指定寄存器d7,寄存器值存儲在堆棧的頂部,提示堆棧幀的頂部就是寄存器值保存的位置。堆棧幀剩餘部分必然是局部變量存儲的地方。
最後一條指令,從堆棧複製一個值到d7。c被聲明爲寄存器變量,存儲的位置是從幀指針往下16個字節。
關於堆棧中的參數次序
參數壓入棧時按照參數列表相反的順序。被調用函數使用幀指針加一個偏移量來訪問參數。當參數以反序壓入堆棧時,參數列表任何一個參數距離幀指針的偏移量是一個常數,與壓入棧的參數個數無關。
如果按照參數列表順序壓棧,第一個參數距離幀指針的偏移量就和壓入到堆棧中的參數數量有關了。雖然編譯器可以計算出該偏移量此時的大小,但是如果實際傳遞的參數數量和函數期望接收的參數數量不一致,偏移量就會不正確,函數訪問參數就會出錯。
mov1 a6@(12),d0
subq1 #6,d0
mov1 d0,a6@(-4)
mov1 a6@(8),d0
add1 a6(12),d0
add1 d7,d0
movem1 a6@(-8),#0x80
unlk a6
rts
第一條指令,把第二個參數複製到d0
第二條指令,將這個值減去6
第三條指令,把結果存儲到局部變量d0
下面三條指令,對return語句求值,結果放在d0
該函數的函數跋從movem1指令開始,用於恢復以前被保存的寄存器值。
unlk指令,把a6的值複製到堆棧指針,並把從堆棧中彈出的a6的舊值裝入到a6中,實現清除堆棧幀中返回地址以上的部分內容。
rts指令,通過吧返回地址從堆棧中彈出到程序計數器,從而從該函數返回
i2 = func_ret_int(10,i1,i10);
彙編指令
lea sp@(12),sp
mov1 d0,d6
被調用函數並未完全從堆棧中清除它的堆棧幀,原因在於只有被調用函數才知道參數列表的參數個數,參數列表的參數留在堆棧中,等待被調用後再安全清除。
db1 = func_ret_double();
c1 = fun_ret_char_ptr( c1 );
彙編指令
jbsr _fun_ret_double
mov1 d0,a6@(-48)
mov1 d1,a6@(-44)
pea a5@
jbsr _fun_ret_char_ptr
addqw #4,sp
mov1 d0,a5
函數沒有任何參數,無任何東西壓棧。double長度8個字節,無法存入一個寄存器。分d0,d1來存儲。