8位称为字节(byte),16位称为字(word),32位为双字(double words),64位为四字(quad words)
C语言基本数据类型对应的x86-64表示如下
C声明 | Intel数据类型 | 汇编代码后缀 | 大小(byte) |
---|---|---|---|
char | 字节 | b | 1 |
short | 字 | w | 2 |
int | 双字 | l | 4 |
long | 四字 | q | 8 |
char * | 四字 | q | 8 |
float | 单精度 | s | 4 |
double | 双精度 | l | 8 |
大多数GCC生成的汇编代码指令都有一个字符的后缀,表明操作数的大小。例如movb、mobw等等。l后缀可同时表示双字和双精度,因为浮点数使用的是一组完全不同的指令和寄存器,因此不会产生歧义。
一个x86-64的CPU包含一组16个存储64位值的通用寄存器,用于存储整数和指针。初始的8086有8个16位的寄存器,如下标的%ax到%sp。扩展到IA32架构时,这些寄存器也扩展到了32位寄存器,从%eax到%esp。
63 | 31 | 15 | 7 | |
---|---|---|---|---|
%rax | %eax | %ax | %al | 返回值 |
%rbx | %ebx | %bx | %bl | 被调用者保存 |
%rcx | %ecx | %cx | %cl | 参数4 |
%rdx | %edx | %dx | %dl | 参数3 |
%rsi | %esi | %si | %sil | 参数2 |
%rdi | %edi | %di | %dil | 参数1 |
%rbp | %ebp | %bp | %bpl | 被调用者保存 |
%rsp | %esp | %sp | %spl | 栈指针 |
%r8 | %r8d | %r8w | %r8b | 参数5 |
%r9 | %r9d | %r9w | %r9b | 参数6 |
%r10 | %r10d | %r10w | %r10b | 调用者保存 |
%r11 | %r11d | %r11w | %r11b | 调用者保存 |
%r12 | %r12d | %r12w | %r12b | 被调用者保存 |
%r13 | %r13d | %r13w | %r13b | 被调用者保存 |
%r14 | %r14d | %r14w | %r14b | 被调用者保存 |
%r15 | %r15d | %r15w | %r15b | 被调用者保存 |
指令可以通过上表中嵌套的寄存器表示对这十六个寄存器的低位字节进行操作。复制和生成1字节、2字节、4字节、8字节值。当以这些寄存器作为目标时,有这样的规则:生成1字节和2字节数时保持高位剩下的字节不变,生成四字节数时会把高位四字节置为0。
操作数的寻址可以分为三种类型——立即数、寄存器和内存引用。如下表所示
类型 | 格式 | 操作数值 | 名称 |
---|---|---|---|
立即数 | $Imm | Imm | 立即数寻址 |
寄存器 | ra | R[ra] | 寄存器寻址 |
存储器 | Imm | M[Imm] | 绝对寻址 |
存储器 | (ra) | M[R[ra]] | 间接寻址 |
存储器 | Imm(rb) | M[Imm + R[rb]] | 基址寻址 |
存储器 | (rb, ri) | M[R[ra] + R[rb]] | 变址寻址 |
存储器 | Imm(rb, ri) | M[Imm + R[ra] + R[ri]] | 变址寻址 |
存储器 | (, ri, s) | M[R[ri] * s] | 比例变址寻址 |
存储器 | Imm(, ri, s) | M[Imm + R[ri] * s] | 比例变址寻址 |
存储器 | (rb, ri, s) | M[R[rb] + R[ri] * s] | 比例变址寻址 |
存储器 | Imm(rb, ri, s) | M[Imm + R[rb] + R[ri] * s] | 比例变址寻址 |
最简单的数据传送指令是MOV类,movb、movw、movl、movq这四条指令执行相同的操作,不同的是他们的操作数的大小不同
指令 | 效果 | 描述 |
---|---|---|
MOV S, D | D<—S | 传送 |
movb | 传送字节 | |
movw | 传送字 | |
movl | 传送双字 | |
movq | 传送四字 | |
movabsq I, R | R<—I | 传送绝对的四字 |
源操作数指定的值是一个立即数,存储在寄存器或者内存中。目的操作数指定一个位置,要么是一个寄存器,要么是一个内存地址。x86-64增加了一条限制,两个操作数不能都指向内存位置,要将一个值从内存中的一个位置复制到另一个位置必须先将源值加载到寄存器,然后再写入目的内存地址。
常规的movq指令只能以表示为32位补码数字的立即数作为源操作数,然后把该值符号扩充得到64位的值并放到目标位置。movabsq指令能够以任意64位立即数作为源操作数,并且只能以寄存器作为目的。
在将较小的源值复制到较大的目的地时使用movz或者movs指令。
MOVZ类中的指令把剩余的字节填充为0,MOVS则填充为源操作数的最高位。两种指令格式为mov[z/s + 后缀1 + 后缀2],即第一个后缀为源操作数的大小,第二个后缀为目的地址的大小。
MOVS类中还给出了cltq指令。cltq指令没有操作数,他总以寄存器%eax作为源,%rax作为符号扩展结果的目的,也就是等效于movslq %eax, %rax
,不过编码更紧凑。
C语言代码如下
long swap(long *des, long src)
{
long tmp = *des;
*des = src;
return tmp;
}
产生的汇编代码如下,%rdi为参数一即des,%rsi为参数二src,%rax为返回值
swap:
movq (%rdi), %rax
movq %rsi, (%rdi)
ret
上面的汇编代码将%rdi中的指针des作为地址在内存中寻址,并将des指针指向的值传输到返回值%rax中即tmp。由于long类型为4字,因此使用movq。第三行同理,将%rsi寄存器中的值直接传输到%rdi寄存器中的指针des指向的内存地址,最后返回结果。