1)
不可能对MOV的两个参数使用相对基址寻址:MOVQ -8(%rbx), -8(%rbx)
。要查看是否支持寻址模式的组合,您需要阅读手册的相关说明。
2)
IMUL 指令稍有不同:它把%rax的值乘以操作数,把结果的低64位存在%rax,高64位放在%rdx (两个64位值相乘的结果是128位)。IDIV则相反,把128bit值(低64位在 %rax ,高64位在%rdx)除以指令中的操作数(为了正确处理负数,用CDQO 指令把%rax符号扩展到%rdx),商存储在%rax,余数在%rdx。
PUSH 指令首先减少 ESP 的值,再将源操作数复制到堆栈
3)
JMP指令需要编译器生成目标标签(LABEL)。标签必须唯一,并且是汇编文件内部私有,对外部不可见,除非有.globl指示。按C语言的说法,汇编中没有修饰的标签是static的,.globl修饰的标签是extern的。
4)
栈是一个辅助的数据结构,主要用来记录函数的调用历史和相关的局部变量(没有放到寄存器的)。一般栈是从高地址到低地址向下生长的。%rsp是栈指针,指向栈最底部(其实是平常所说的栈顶)元素。所以,push %rax(8字节),会把%rsp减去8,并把%rax写到 %rsp指向的位置。
SUBQ $8, %rsp
MOVQ %rax, (%rsp)
pop则刚好相反:(这个确实有道理,要理清楚输入输出才行)
MOVQ (%rsp), %rax
ADDQ $8, %rsp
要丢弃最后压入栈中的值,只需要修改%rsp的值即可:
ADDQ $8, %rsp
当然,由于压栈和出栈非常常见,所以这两个操作有两个专有指令,他们的行为和上面描述的完全相同:
PUSHQ %rax
POPQ %rax
5)
X86-64的调用方式有些不同,称作System V ABI。整个约定相当复杂,下面是简化版,但对我们来说足够了:
- 整数参数(包含指针)依次放在%rdi, %rsi, %rdx, %rcx, %r8, 和 %r9 寄存器中。
- 浮点参数依次放在寄存器%xmm0-%xmm7中。
- 寄存器不够用时,参数放到栈中。
- 可变参数函数(比如printf), 寄存器%eax需记录下浮点参数的个数。
- 被调用的函数可以使用任何寄存器,但它必须保证%rbx, %rbp, %rsp, and %r12-%r15恢复到原来的值(如果它改变了它们的值)。
- 返回值存储在 %eax中.
具体是不是这样还不知道。
6)
上面例子都有很多方式进行优化。比如,特定的代码不使用 %rbx-%r15的时候,就不需要保存。可以把参数保留在寄存器中,而不是压栈。返回值可以直接使用%eax计算,而不是保存到局部变量。手工写代码时,完成这些优化很容易,但是对编译器来说就不这么简单了。