- x86-64位寄存器含有16组64位通用目的寄存器,%rax,%bx %cx %dx %rsi %rdi %rbp %rsp %r8 %r9 ….%r15,其中特殊的%rsp存储栈指针的值,%rax为返回值。
- 汇编有两种架构,现在大多数编译器如GCC等使用的都是ATT架构的,另外微软与Intel的编译器使用的是Intel的架构,两者有细微的区别。
- 汇编指令后的为操作数,一遍指令都有一个或者多个操作数,操作数的值有三种来源,一种是立即数,一种是来自寄存器,一种是来自内存。
- 立即数的书写是$后直接跟正常的整数,如movb $0x45, %bl 操作数类型为立即数与寄存器
- 寄存器类型的操作数只能是1、2、4、8字节,这样是为了向后兼容,如8字节为64位的操作数对应64位寻址能力的系统,4字节则对应32位系统等等。如 movq %rax,%rdx 的操作数类型均为寄存器
- 对于操作数为内存类型的,有多种不同的寻址模式:
- 立即数寻址—$0x54
- 寄存器寻址—%rax
- 绝对寻址—–0x56
- 间接寻址—–(%rax)
- 基址+偏移量寻址—8(%rax)
- 变址寻址—(%rax,%rbx)
- 变址寻址—7(%rax,%rbx)
- 比例变址寻址—(,%rax,4)
- 比例变址寻址—7(,%rax,8)
- 比例变址寻址—(%rax,%rbx,1)
- 比例变址寻址—7(%rbx,%rax,2)
- 比例值只能为1,2,4,8
- 数据传送指令:movb movw movl movq分别对应1,2,4,8字节的传送,有两个操作数,一个源操作数一个目的地址,如movq (%rbx),%rax 表示从寄存器%rbx中读取全部64位对应的内存地址的值存到寄存器%rax中,再比如movl $0x5495868383930205,-56(%rax),表示将32立即数$0x5495868383930205 存到寄存器%rax中的值表示的内存地址-56字节的位置上。
- movzbw movzbl movzwl movzbq movzwq,在较小的源值复制到较大的目的时运用零扩展使用这些指令,注意到这里少了movzlq,即双字扩展到4字,这是因为该方法可以通过movl自动实现,因为在写寄存器的时候凡是产生大于32位的值其寄存器高危都将被置于0,所以可以用movl提带movzlq,
- movsbw movsbl movswl movsbq movswq movslq ctlq 在较小的源值复制到较大的目的时运用字符扩展使用这些指令,注意到这里多了一个ctlq与movslq,上面说了0扩展填充0刚好与32位置零吻合所以可以提到,这里使用符号扩展扩展的是最高位,可能为1,所以movslq不能省略。ctlq 专指将%eax中的内容移动到%rax中,与movslq %eaq,%rax等效
- 上面两组指令的目的操作数只能为寄存器地址,源操作数可以来自寄存器,立即数,内存。
- 这里有一个c语言编写的函数:
long exchange(long *xp, long y)
{
long x = *xp;
*xp = y
return x
}
调用long 1 = 5; exchange(&a,8),最后返回的值为5,这是一个怎样的过程呢,该段代码经过编译器编译后变成
exchange:
movq (%rdi),%rax
movq %rsi, (%rdi)
ret
解释:%rdi 存储 xp %rsi 存储 y,首先调用xp的值对应的内存地址中的值赋值给x也就是5,然后将8赋值为xp对应的内存地址,也就是a指向的内存地址。最后返回%rax的值也就是x=5,。更有这个是有a=8,发生了数据交换。
一道练习题,通过mov指令完成类型转换:操作数地址分别存在%rdi 与%rsi中
char–>int:
movsbl (%rdi),%rax
movl %eax,(%rsi)
char–> unsigned int
movsbl (%rdi),%rax
movl %eax,(%rsi)
unsigned char –> long
movzbl (%rdi),%rax
movq %rax,(%rsi)压栈与弹栈:栈是向下增长的,较低的栈地址在下面,所以压栈就是对栈地址做减法,弹栈就是做加法。栈遵循的是后进先出的原则即 FILO,压栈使用指令pushq pushl pushw pushb,弹栈使用指令popq popl popw popb。压栈的时候首先将栈顶下移然后讲数据存储进去,如pushb $0x56 ,如果此时的栈顶位置是0x101,那么首先将栈顶向下移动1字节得到栈顶位置0x100,然后讲1字节的数据$0x56放进去,完成压栈操作。这个过程可以拆分成两步即 subn $1,%rsp movb $0x56,(%rsp),不过这样拆分将导致更大的运算量所以不被采用。弹栈的过程一样,直接将栈顶位置上移即可。即popb %rax 相当于 movb (%rsp), %rax addb $1,%rsp
加载有效地址:leaq leal leaw leab指令 只加载地址不加载值
如leaq (%rdi,%rsi,4) %rax 表示讲%rdi+4*%rsi计算出的内存地址赋值给寄存器%rax,而不是获取内存地址对应的值。
其和movq (%rdi,%rsi,4) %rax 的结果明显不同,使用mov或获取到内存地址中的值赋值而寄存器%rax。
利用这个特性可以用来进行简答的加法与乘法运算,如有这样一个C函数
long scale(long x, long y, longz){
long t = x+4y+12z;
return t
}
编译器编译后将变成:
scale:
leaq (%rdi, %rsi, 4), %rax # x+4y
leaq (%rdx, %rdx, 2), %rdx # z+2z = 3z
leaq (%rax, %rdx, 4), %rax # x+4y+4*(3z)=x+4y*12z
ret
简直不要太神奇。。。