這篇文章是用 x86-64 的架構作為例子,因為多數人的電腦是用 x86-64 的架構
只要依照 在linux中看gcc產生出來的組合語言 這篇文章
就可以把 C 語言的檔案編議成 x86-64 的組合語言
編譯時請記得加上 -Og
這個 flag ,因為較低的優化程度比較好觀察 gcc
的行為
如上圖,x86-64 共有 16 個 64-bit register ,(不過這張圖並沒有把 program counter
畫上去)
以圖中左上的 register 為例,%rax
代表的是整個 64-bit ,
而寫 %eax
時,代表的是較低位元的 32-bit
除了用紅色標示的 %rsp
有特別的用途之外 (register stack pointer),
其他的 15 個 register 都算是 general purpose 的 register
這種狀況是最簡單的,考慮以下的 C 語言程式碼:
void callee()
{
/*...
...*/
}
void caller()
{
/*...
...*/
callee();
/*...
...*/
}
編譯成組合語言:
callee:
...
...
...
...
ret
caller:
...
...
call callee
...
...
call callee
這行代表讓 program counter 指到 callee
的位置ret
這行代表回到 call callee
的下一行繼續執行
幾乎就跟 C 語言的運作邏輯相同
回傳值的過程大概可以列成以下步驟:
caller
呼叫 callee
callee
執行到最後面時,把回傳值放到 %rax
裡callee
執行 ret
回到 caller
繼續執行caller
從 %rax
拿取回傳值(因為 callee
已經把回傳值放到%rax
了)透過 callee
把回傳值放到 %rax
caller
從 %rax
拿取回傳值的模式,
就成功的實作出 C 語言裡 function 回傳值的功能,
當然不用 %rax
用其他的 register 也可以
但習慣上就是會用 %rax
所以說 C 裡的 return value 並不是用硬體來實作的行為,而是一種軟體上的設計
考慮以下的 C 語言程式碼:
long callee()
{
return -0x87;
}
long caller()
{
long result;
result = callee() + 1;
return result;
}
編譯出的組合語言:
callee:
mov $0xffffffffffffff79,%rax
retq
caller:
callq callee
add $0x1,%rax
retq
傳參數的方法跟傳回傳值的方法差不多
就跟回傳時要約定好要用 %rax
來當中介一樣
傳參數也要約定好要用哪些 register 來中介:
第幾個參數|register
----------+-------
1|%rdi
2|%rsi
3|%rdx
4|%rcx
5|%r8
6|%r9
考慮以下的 C 語言程式碼:
long callee(long a1, long a2, long a3, long a4, long a5, long a6)
{
return a1 + a2 + a3 + a4 + a5 + a6;
}
long caller()
{
long result, a1, a2, a3, a4, a5, a6;
a1 = -1;
a2 = -2;
a3 = -3;
a4 = -4;
a5 = -5;
a6 = -6;
result = callee(a1, a2, a3, a4, a5, a6) - 0x87;
return result;
}
編譯出的組合語言:
callee:
add %rsi,%rdi
add %rdx,%rdi
add %rcx,%rdi
add %r8,%rdi
lea (%rdi,%r9,1),%rax (%rax = %rdi + %r9 * 1)
retq
caller:
mov $0xfffffffffffffffa,%r9 (two's complement)
mov $0xfffffffffffffffb,%r8
mov $0xfffffffffffffffc,%rcx
mov $0xfffffffffffffffd,%rdx
mov $0xfffffffffffffffe,%rsi
mov $0xffffffffffffffff,%rdi
callq 40 <caller+0x2f>
sub $0x87,%rax
retq
來不及發文了,這個就留到明天在說!
Computer Systems: A Programmer's Perspective, 3/E (CS:APP3e)