先看一下以下的程式碼,以及用他來編譯出來的組合語言
// add.c
#include <stdio.h>
long add(long a, long b)
{
return a + b;
}
int main()
{
int a, b, c;
a = -1;
b = -2;
c = add(a, b);
printf("%d\n", c);
return 0;
}
在終端機輸入:
$ gcc -Og -S add.c #產生組合語言
雖然產生出來的組合語言看起來很雜亂,但等等只要看看重點的幾行就好
1 .file "add.c"
2 .text
3 .globl add
4 .type add, @function
5 add:
6 .LFB11:
7 .cfi_startproc
8 leaq (%rdi,%rsi), %rax
9 ret
10 .cfi_endproc
11 .LFE11:
12 .size add, .-add
13 .section .rodata.str1.1,"aMS",@progbits,1
14 .LC0:
15 .string "%d\n"
16 .text
17 .globl main
18 .type main, @function
19 main:
20 .LFB12:
21 .cfi_startproc
22 subq $8, %rsp
23 .cfi_def_cfa_offset 16
24 movq $-2, %rsi
25 movq $-1, %rdi
26 call add
27 movq %rax, %rsi
28 leaq .LC0(%rip), %rdi
29 movl $0, %eax
30 call printf@PLT
31 movl $0, %eax
32 addq $8, %rsp
33 .cfi_def_cfa_offset 8
34 ret
35 .cfi_endproc
36 .LFE12:
37 .size main, .-main
38 .ident "GCC: (Debian 10.2.1-6) 10.2.1 20210110"
39 .section .note.GNU-stack,"",@progbits
重點的幾行:
5 add:
...
8 leaq (%rdi,%rsi), %rax # a + b
9 ret
19 main:
...
24 movq $-2, %rsi # b = -2
25 movq $-1, %rdi # a = -1
26 call add # add(a, b)
...
從這幾行可以看到c語言中的function call在組合語言中是如何被實作的:
1.先把-1丟進%rdi
、-2丟進%rsi
(24,25行)
2.call add
(26行)
3.add
執行%rdi
* rsi
的動作,並把內容放到%rax
中
4.從add
return回main
add
這個subroutine之所以拿%rdi
跟%rsi
這兩個暫存器來做相乘,是因為他相信第一個跟第二個參數分別被放在%rdi
跟%rsi
中
而這種信任的合作模式,通常書上會使用convention(習俗、習慣)來稱呼
我的疑問:
既然這是習俗,應該代表並不是強制上必須要這麼做的吧?
習慣上用%rdi及%rsi來當第1第2個參數,但用其他的暫存器(如%r8,%r9)應該也可以吧?
所以來做個小小的實驗,來修改gcc產生出來的add.s
檔:
5 add:
...
8 leaq (%r8,%r9), %rax # a + b (改成r8乘r9)
9 ret
19 main:
...
24 movq $-2, %r8 # b = -2 (從%rsi改成%r9)
25 movq $-1, %r9 # a = -1 (從%rdi改成%r8)
26 call add # add(a, b)
...
# 編譯修改後的add.s
$ gcc -c add.s -Og
$ gcc -o add add.o -Og
執行:
$ ./add
-3
可以看到就算使用了其他的暫存器,也一樣是可以的,gcc也沒有給出任何的警告或錯誤
會用到那些暫存器
是由編譯器去決定的
如果你有可能在C/C++裡混用組合語言的話
就得自己保存暫存器的值到堆疊
以免因為暫存器的值被覆蓋
導致程式錯亂掉
喔喔感謝~~~