CSAPP6——程序机器级表示2
汇编入门(二)
x86-64 架构中的整型寄存器如下图所示(暂时不考虑浮点数的部分)
仔细看看寄存器的分布,我们可以发现有不同的颜色以及不同的寄存器名称,黄色部分是 16 位寄存器,也就是 16 位处理器 8086 的设计,然后绿色部分是 32 位寄存器(这里我是按照比例画的),给 32 位处理器使用,而蓝色部分是为 64 位处理器设计的。这样的设计保证了令人震惊的向下兼容性,几十年前的 x86 代码现在仍然可以运行!
前六个寄存器(%rax, %rbx, %rcx, %rdx, %rsi, %rdi)称为通用寄存器,有其『特定』的用途:
- %rax(%eax) 用于做累加
- %rcx(%ecx) 用于计数
- %rdx(%edx) 用于保存数据
- %rbx(%ebx) 用于做内存查找的基础地址
- %rsi(%esi) 用于保存源索引值
- %rdi(%edi) 用于保存目标索引值
而 %rsp(%esp) 和 %rbp(%ebp) 则是作为栈指针和基指针来使用的。下面我们通过 movq
这个指令来了解操作数的三种基本类型:立即数(Imm)、寄存器值(Reg)和内存值(Mem)。
对于 movq
指令来说,需要源操作数和目标操作数,源操作数可以是立即数、寄存器值或内存值的任意一种,但目标操作数只能是寄存器值或内存值。指令的具体格式可以这样写 movq [Imm|Reg|Mem], [Reg|Mem]
,第一个是源操作数,第二个是目标操作数,例如:
movq Imm, Reg
->mov $0x5, %rax
->temp = 0x5;
movq Imm, Mem
->mov $0x5, (%rax)
->*p = 0x5;
movq Reg, Reg
->mov %rax, %rdx
->temp2 = temp1;
movq Reg, Mem
->mov %rax, (%rdx)
->*p = temp;
movq Mem, Reg
->mov (%rax), %rdx
->temp = *p;
这里有一种情况是不存在的,没有 movq Mem, Mem
这个方式,也就是说,我们没有办法用一条指令完成内存间的数据交换。
上面的例子中有些操作数是带括号的,括号的意思就是寻址,这也分两种情况:
- 普通模式,®,相当于
Mem[Reg[R]]
,也就是说寄存器 R 指定内存地址,类似于 C 语言中的指针,语法为:movq (%rcx), %rax
也就是说以 %rcx 寄存器中存储的地址去内存里找对应的数据,存到寄存器 %rax 中 - 移位模式,D®,相当于
Mem[Reg[R]+D]
,寄存器 R 给出起始的内存地址,然后 D 是偏移量,语法为:movq 8(%rbp),%rdx
也就是说以 %rbp 寄存器中存储的地址再加上 8 个偏移量去内存里找对应的数据,存到寄存器 %rdx 中
因为寻址这个内容比较重要,所以多说两句,不然之后接触指针会比较吃力。对于寻址来说,比较通用的格式是 D(Rb, Ri, S)
-> Mem[Reg[Rb]+S*Reg[Ri]+D]
,其中:
D
- 常数偏移量Rb
- 基寄存器Ri
- 索引寄存器,不能是 %rspS
- 系数
除此之外,还有如下三种特殊情况
(Rb, Ri)
->Mem[Reg[Rb]+Reg[Ri]]
D(Rb, Ri)
->Mem[Reg[Rb]+Reg[Ri]+D]
(Rb, Ri, S)
->Mem[Reg[Rb]+S*Reg[Ri]]
我们通过具体的例子来巩固一下,这里假设 %rdx 中的存着 0xf000
,%rcx 中存着 0x0100
,那么
0x8(%rdx)
=0xf000
+0x8
=0xf008
(%rdx, %rcx)
=0xf000
+0x100
=0xf100
(%rdx, %rcx, 4)
=0xf000
+4*0x100
=0xf400
0x80(, %rdx, 2)
=2*0xf000
+0x80
=0x1e080
了解了寻址之后,我们来看看运算指令,这里以 leaq
指令为例子。具体格式为 leaq Src, Dst
,其中 Src
是地址的表达式,然后把计算的值存入 Dst
指定的寄存器,也就是说,无须内存引用就可以计算,类似于 p = &x[i];
。我们来看一个具体的例子,假设一个 C 函数是:
1 | long m12(long x) |
复制
对应的汇编代码为:
1 | leaq (%rdi, %rdi, 2), %rax # t <- x+x*2 |
复制
可以看到是直接对 %rdi 寄存器中存的数据(地址)进行运算,然后赋值给 %rax。最后给出一些常见的算术运算指令,注意参数的顺序,而且对于有符号和无符号数都是一样的,更多的信息可以参考 Intel 官方文档[3]。
需要两个操作数的指令
addq Src, Dest
->Dest = Dest + Src
subq Src, Dest
->Dest = Dest - Src
imulq Src, Dest
->Dest = Dest * Src
salq Src, Dest
->Dest = Dest << Src
sarq Src, Dest
->Dest = Dest >> Src
shrq Src, Dest
->Dest = Dest >> Src
xorq Src, Dest
->Dest = Dest ^ Src
andq Src, Dest
->Dest = Dest & Src
orq Src, Dest
->Dest = Dest | Src
需要一个操作数的指令
incq Dest
->Dest = Dest + 1
decq Dest
->Dest = Dest - 1
negq Dest
->Dest = -Dest
notq Dest
->Dest = ~Dest