這裡是x86-64 GNU/Linux ABI的重點整理。
核心界面(系統呼叫)
函數呼叫
參數傳遞
系統呼叫號碼(system call number)以rax
來傳遞。參數依照下面的順序傳遞:
rdi, rsi, rdx, r10, r8, r9
暫存器的使用
用作參數傳遞的六個暫存器中的資料不會被kernel保留,此外,rcx
和r11
的資料不會被kernel保留。
資料回傳
System call回傳的資料由rax
傳回。小於0的數值代表在system call的過程有錯誤,錯誤碼為rax * -1
使用者界面(User-level applications)
函數呼叫
參數傳遞
指標或整數型態的參數由下列暫存器傳遞:
rdi, rsi, rdx, rcx, r8, r9
暫存器的使用
被呼叫者只有義務保存r12~r15, rbx, rsp, rbp
以及x87 control word中的資料。其他暫存器中資料的命運未定。所謂的保存義務是說,函數回傳後,這些必須被保存的暫存器的數字必須和呼叫前一樣。
堆疊管理
ABI規定在進行函數呼叫的時候,rsp必須是16的倍數。換句話說,堆疊記憶體必須和16-byte對齊。見下:
xor %rax, %rax /**此時rsp % 16 == 0*/ call foo根據觀察:
start: /*此時rsp % 16 == 0*/ ... call __libc_start_main ...以及
main: /*或其他C語言相容的函數*/ /*此時(rsp + 8) % 16 == 0*/對於大部分讀者們會經手的user-mode組合語言撰寫過程,會對rsp做下面的處理。
/*這是方法一。傳統的stack frame*/ .equ main_frame_size, 32 /*是16的倍數的話,會比較方便管理*/ main: push %rbp sub $main_frame_size, %rsp /*~做點事情,像是泡咖啡~*/ pop %rbp ret另一種處理方法是:
/* 這是方法二,比較新被提出的方法。 好處是,多了rbp可以利用。 壞處是,*據說*這種方法比較慢(一丁點) */ .equ main_frame_size, 40 /*是16n + 8的話,會比較方便管理*/ main: sub $main_frame_size, %rsp /*~做點事情,像是泡牛奶~*/ add $main_frame_size %rsp ret這兩種方法要用哪一種都可以。
總結
總之在撰寫C語言相容函數的時候一定要注意:
- 在呼叫其他函數之前,請記得保存在暫存器中的資料,以免發生遺憾。
-
在撰寫一個C語言相容的函數時,如果要使用
r12~r15, rbx, rsp, rbp
,記得先做備份。在離開函數之前請務必把裡面的資料還原回去。
沒有留言:
張貼留言