汇编指令总结

Endless_daydream Lv4

referance: CSAPP第三章笔记

数据格式

C声明 汇编码后缀 大小(字节)
char b 1
short w 2
int i 4
long q 8
char* q 8
float s 4
double l 8

寄存器

63 31 15 7 0(寄存器末端位置)
%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 第5个参数
%r9 第6个参数
%r10
%r11
%r12
%r13
%r14
%r15

数据表示

下面R[$r_a$]表示取寄存器$r_a$中的值,

M_b[Addr]表示到内存中找Addr地址开始的b个字节的引用,b可省略。

格式 含义 名称
立即数 $Imm Imm 立即数寻址
寄存器 $r_a$ R[$r_a$] 寄存器寻址
存储器 Imm M[Imm] 绝对寻址
($r_a$) M[R[$r_a$]] 间接寻址
($r_b$, $r_i$) M[R[$r_b$]+R[$r_i$]]
(,$r_i$, s) M[R[$r_i$] * s] 比例变址
Imm($r_b, r_i, s$) M[Imm+R[$r_b$]+R[$r_i$]*s] 完全写法

ATT格式与Intel格式

  1. ATT格式中movq a b是将a给b,intel格式中mov a b是将b给a。操作数顺序相反
  2. intel格式操作没有后缀b/w/q等
  3. intel格式寄存器前没有%符号
  4. intel使用QWORD PTR [rbx]而不是 (%rbx)表示内存取值。

gcc和objdump使用ATT格式。

move类指令

mov

复制。

ATT中有两个操作数时,第一个是source,第二个是destination。

  • movb imm/r8, r/m8
  • movb m8, r8
  • movw imm/r16, r/m16
  • movw m16, r16
  • movl imm/r32, r/m32
  • movl m32, r32
  • movq imm32/r64, r/m64
  • movq m64, r64
  • movabsq imm64, r64

两个操作数不能同时是memory,特别地,movq不接受imm64

movz

将高位补0后复制。

  • movzbw r/m8, r16
  • movzbl r/m8, r32
  • movzwl r/m16, r32
  • movzbq r/m8, r64
  • movzwq r/m16, r64

没有 movzlq 这条指令,因为将寄存器的值修改为一个 double word 时就会将高位清零,所以使用 movl 就可以了。

虽然有 movzbqmovzwq,但一般会用 movzblmovzwl 实现相应的功能。

movs

将高位补符号位后复制。

  • movsbw r/m8, r16
  • movsbl r/m8, r32
  • movsbq r/m8, r64
  • movswl r/m16, r32
  • movswq r/m16, r64
  • movslq r/m32, r64
  • cltq: 和 movslq %eax, %rax 效果相同(但编码更短)

push/pop

  • pushq imm32/r/m64: 将 R[%rsp] 减八,然后将 operand 复制到 M[R[%rsp]]PUSH 指令不支持 imm64,会将 imm32 高位补符号位)
  • popq r/m64: 将 M[R[%rsp]] 复制到 operand,然后将 R[%rsp] 加八

%rsp向低地址增长。

原因:由于 memory layout 和 memory 曾经很小的历史原因,stack 和 heap 会往不同的方向增长,总会有一个在增长时减小地址,而 x86-64 选择了 push stack 时减小地址,这也使得访问非栈顶元素不需要给 offset 加负号。

leaq

leaq m, r64: 将 source operand 的地址复制到 destination operand(只计算 source operand 的地址,与其指向的 memory 中存储的值无关)

LEA 可以用来优化一些简单的算术,例如:

1
2
3
4
5
long scale(long x, long y, long z)
{
long t = x + 4 * y + 12 * z;
return t;
}
1
2
3
4
5
scale:
leaq (%rdi,%rsi,4), %rax
leaq (%rdx,%rdx,2), %rdx
leaq (%rax,%rdx,4), %rax
ret

这里三个 LEA 分别计算了x+4y,z+2z 和 (x+4y)+4(z+2z)。

一元运算

每种一元运算都有 b/w/l/q 四个类型,接受一个相应类型的 r/m,将这个 operand 计算后的结果存入这个 operand:

  • INC: 加一
  • DEC: 减一
  • NEG: 取反 (negate)
  • NOT: 按位取反 (complement)

二元运算

每种二元运算都有 b/w/l/q 四个类型,接受相应类型的 imm/r/m 作为 source(除了 imm64),相应类型的 r/m 作为 destination(source 和 destination 不能同时为 memory),效果为将 source“作用于”destination,将运算结果存入 destination。

  • ADD: 加
  • SUB: destination 减去 source
  • IMUL: 乘
  • XOR: 按位异或
  • OR: 按位或
  • AND: 按位与

特别地,类似 xorl %rdx, %rdx 的代码可以用来优化 movl $0, %rdx

位移

位移有 b/w/l/q 四个类型,source 只能是 imm8 或者 %cl,destination 是相应类型的 r/m。

  • SAL/SHL: 左移
  • SAR: 算术右移
  • SHR: 逻辑右移

结果是 operand 两倍长度的运算

128 位的整数叫做 oct word,需要存在两个寄存器中,在指令中一般高位放在 R[%rdx] 低位放在 R[%rax]

虽然截去高位时是否有符号对编码层面的乘法没有影响,不截去高位时就需要对 signed 和 unsigned 使用不同的指令了:

  • imulq r/m64: 计算 operand 和 R[%rax] 作为 signed integer 相乘而不截去高位的结果,存在 R[%rdx]:R[%rax] 中。(如果有两个 operand 就是上面的 二元运算 了。)
  • mulq r/m64: 计算 operand 和 R[%rax] 作为 unsigned integer 相乘而不截去高位的结果,存在 R[%rdx]:R[%rax] 中。

除法以及取模:

  • cqto/cqtd: 将 R[%rax] 高位填符号位放在 R[%rdx]:R[%rax](也就是用 R[%rax] 的符号位填满 R[%rdx])。
  • idivq: 计算 R[%rdx]:R[%rax] 有符号地除以 operand,商放在 R[%rax],余数放在 R[%rdx]
  • divq: 计算 R[%rdx]:R[%rax] 无符号地除以 operand,商放在 R[%rax],余数放在 R[%rdx]

得到的商都是向 0 取整,所以被除数为负时余数非正。

若商溢出了,则会触发 divide error 异常。所以被除数一般会是 64 位整数(在 idivq 之前用 cqto 来设置 R[%rdx],在 divq 之前将 R[%rdx] 置为全零),否则很可能溢出而触发异常。

这些运算也有 operand 为 32 位,结果为 64 位的版本:imullmullcltdidivldivl。它们以 %edx%eax 来代替 128 位运算中的 %rdx%rax

未完…

  • Title: 汇编指令总结
  • Author: Endless_daydream
  • Created at : 2024-03-07 10:14:45
  • Updated at : 2024-03-07 11:28:53
  • Link: https://endless_daydream.gitee.io/2024/03/07/CSAPP/汇编指令总结/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments