先看一下以下的程式码,以及用他来编译出来的组合语言
// 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也没有给出任何的警告或错误