总结自《深入理解计算机系统》第三版
访问信息
x86-64的CPU包含一组16个存储64位值的通用目的寄存器,用于存放整数和指针。
注意:
- 生成1字节和2字节数字的指令会保持剩下的字节不变
- 生成4字节数字会把高位4个字节置位0,作为从IA32的扩展部分
操作数指示符
操作数类型分为
- 立即数(immediate):$后接整数,汇编器会自动选择最紧凑的方式进行数值编码。
- 寄存器(register):ra表示寄存器a,寄存器组看做是一个数组R,将ra看做索引,R[ra]即表示寄存器的值
- 内存引用:将内存看做一个数组Mb[Addr]
常用形式Imm(rb,ri,s)
基址变址寄存器rb和ri必须是64位寄存器,s只能是1、2、4、8。有效地址的计算为Addr=Imm+R[rb]+R[ri]*s,所以以操作数形式出现的Imm(rb,ri,s)表示M[Imm(rb,ri,s)]。
其他形式都是此形式的变式,Imm表示M[Imm],(rb)表示以R[rb]为有效地址的M[R[rb]]。
数据传送指令
mov指令
- 后缀’b’,‘w’,‘l’,'q’分别代表字节、字、双字、四字
- movabsq指令,将任意64位立即数送入寄存器
- 源、目的寄存器字节大小相同
- 传送指令的操作数不能都是内存,将值从一个位置到另一个位置需要两条指令——将源值加载到寄存器,将寄存器写入到目的位置
- mov指令只更新指定的寄存器字节或内存位置,而movl指令以寄存器为目的是,会将高4字节设置为0
movabsq $0x0011223344556677,%rax ;%rax=0011223344556677
movb $-1,%al ;%rax=00112233445566FF
movw $-1,%ax ;%rax=001122334455FFFF
movl $-1,%eax ;%rax=00000000FFFFFFFF
movq $-1,%rax ;%rax=FFFFFFFFFFFFFFFF
movz指令(零扩展,将较小源值复制到较大目的)
注意:在mov指令中源操作数为双字(后缀l)将高位设为0,因而movz指令缺少movzlq。
movs指令(符号扩展,将较小源值复制到较大目的)
- cltq只用于寄存器%eax和%rax,且它与movzlq %eax,%rax一致。
movabsq $0x0011223344556677,%rax ;%rax=0011223344556677
movb $0xAA,%dl ;%dl=AA
movb %dl,%al ;%rax=00112233445566AA
movzbq %dl,%rax ;%rax=00000000000000AA
movsbq %dl,%rax ;%rax=FFFFFFFFFFFFFFAA
压入和弹出栈数据
将四字值压入栈,首先将栈指针减8,然后将值写入新栈顶地址。因此下式相等,区别在于pushq只有一个字节,而两条指令要8个字节。
pushq %rax = subq $8,%rsp ;Decrement stack pointer
movq %rax,(%rsp) ;Store %rax on stack
pop %rax = movq (%rsp),%rdx ;Read %rdx from stack
addp $8,%rsp ;Increment stack pointer
例子:
注意:
- 栈向低地址方向增长,所以压栈减小栈指针%rsp的值(%rsp中存有效地址,所以存值到内存加括号),并将数据存放在内存,出栈相反。
- 栈和程序代码以及其他形式的程序数据都放在同一内存,所以程序可以用标准的内存寻址方式访问栈的任意位置,例如:movq 8(%rsp),%rax将第二个四字从栈中取出。
加载有效地址(load effective address)指令
形式是从内存读数据到寄存器,实际上没有引用内存,并不是从内存读数据带到寄存器,而是将有效位置带到寄存器,例如M[R[rb]]中的有效地址R[rb]。
两个用法:
- 为内存引用产生指针,类似C的取址符&
- 执行普通的算术操作(单纯将寄存器中的有效地址看做数),但这种方法比用算术操作方便得多
C程序代码
long scale(long x,long y,long z){
long t=x+4*y+12*z;
return t;
}
汇编代码
;long scale(long x,long y,long z)
;x in %rdi,y in %rsi,z in %rdx
scale:
leaq (%rdi,%rsi,4),%rax ;x+4*y
leaq (%rdx,%rdx,2),%rdx ;z+2*z=3*z
leaq (%rax,%rdx,4),%rax ;(x+4*y)+4*(3*z)=x+4*y+12*z
ret
算术和逻辑操作
注意:
- 第三组的二元操作数,第一个操作数可以是立即数、寄存器或内存,第二个是寄存器或内存(若为内存地址,则处理器必须从内存度读出值,执行操作,然后写回内存)。
- 移位操作的移位量可以是立即数或单字节寄存器%cl中的值。
C程序代码
long arith(long x,long y,long z){
long t1=x^y;
long t2=z*48;
long t3=t1&0x0F0F0F0F;
long t4=t2=t3;
}
汇编代码
;long arith(long x,long y,long z)
;x in %rdi,y in %rsi,z in %rdx
arith:
xorq %rsi,%rdi ;t1=x^y
leaq (%rdx,%rdx,2),%rax ;3*z
salq $4,%rax ;t2=16*(3*z)=48*z
andl $252645135,%edi ;t3=t1&0x0F0F0F0F
subq %rdi,%rax ;t2-t3
ret
上面代码可以看出
- 将乘法的一部分分解为逻辑运算
- 一个寄存器会存放多个程序值
乘除运算
汇编器可通过计算操作数数目,分辨出哪条指令。