最近在看CSAPP的第四章,因为要记的东西比较多,所以整理一些东西帮助理解
Y86-64指令集体系结构
对于程序员可见的状态
Y86程序的每条指令都会对我们的处理器进行一些改变,我们把这个过程称之为状态的改变。这里我们需要能够知道对应的行为,使我们的状态发生了哪些变化。这些需要被观测的状态就是“对于程序员可见的状态”

程序计寄存器(RF)
Y86有15个程序寄存器,每个寄存器存储着一个64位的字,分别是:
%rax %rcx %rdx %rbx %rsp %rbp %rsi %rdi %r8~%r14
不同的程序寄存器做不同的用处,之后再细说
程序寄存器(PC)
程序寄存器用于存放当前正在执行的指令的地址,通过修改PC值,可以控制处理器执行指令
条件码(CC)
由三个1位的条件码组成:ZF SF OF,它们保存着最近的算数或逻辑指令造成影响的有关信息
程序状态(Stat)
它表明程序执行的总体状态,用于指示程序是在正常运行还是出现了某种异常状态
内存(DMEM)
内存实际上可以理解为一个很大的字节数组,保存着程序和数据。这里我们的Y86程序只考虑用虚拟地址来引用内存位置。我们只认为虚拟内存系统想Y86提供了一个单一的字节数组映像
Y86-64指令
这里的汇编代码格式采用ATT

指令细节:
- x86的movq在这里被拆分成了:
rrmovq irmovq rmmovq mrmovq,显式的指明了指令的源和目的。其中对应立即数(i),内存(m),寄存器®。指令的第一个字母指定了源,第二个字母指定了目的 - OPq对应着四个整数指令:
addq andq subq xorq,它们只对寄存器进行操作。这些操作会设置三个条件码ZF(零),SF(符号),OF(溢出) - jXX对应着七个跳转指令:jmp(无条件跳转),jle(小于等于跳转),jl(小于跳转),je(等于跳转),jne(不等于跳转),jge(大于等于跳转),jg(大于跳转)。跳转指令会根据条件码进行分支判断跳转
- cmovXX对应了六个条件传送指令:cmovle(小于等于传送),cmovl(小于传送),cmove(等于传送),cmovne(不等于传送),cmovge(大于等于传送),cmovg(大于传送)。条件传送只能用于满足条件时的传送,且源和目的只能是寄存器。
- call将返回地址入栈,然后跳转到目标地址。ret从这样的调用中返回
- pushq和popq实现入栈与出栈
- halt指令用于停止指令的执行,并将状态码设置成HLT状态
指令编码
现在讨论一下程序的指令编码,我们可以在上面的图看到大致的,每个指令的编码结构略有不同但还是由以下部分组成:
指令类型 | 源 | 目的
指令类型
指令类型通常在第一个字节给出,第一个字节分为高四位和第四位。其中:
- 高四位是代码(code)部分,用来决定操作类型
- 第四位是功能(function)部分,用决定操作所使用的功能。不过功能值只有在i相关指令共用一个操作的时候才有用
我们可以看到Y86带功能值的具体操作

源和目的
源和目的可能是寄存器或者内存地址,我们分开讨论:
- 寄存器:15个程序寄存器每个都有一个相对应的寄存器标识符(register ID),这些程序寄存器存在CPU的一个寄存器文件中,这样我们可以把寄存器文件视作一个小的,以寄存器ID为地址的随机访问存储器。如果ID值为
0xF意味着不访问任何寄存器。ID值如下:

- 内存地址:这里需要分情况讨论,可能存在三种用法:其一是将内存地址作为一个目的地址;其二是将内存地址作为rmmovq和mrmovq的地址指示符的偏移地址;其三是将其作为irmovq的立即数。内存地址在指令中是一个8字节的长数字,使用小端序编码。
现在我们可以把源和目的划分为三个部分:
| 寄存器字段 | 附加地址字段 |
| rA | rB | Dest |
寄存器字段占一个字节,附加地址字段占用八个字节
指令编码
通过将这几部分拼接组成就可以得到一条指令的编码,其中最重要的是每个字节编码一定要是唯一的解释。任意一个字节序列要么就是一个唯一的指令序列的编码,要么就不是一个合法的字节序列。
Y86-64异常
对于Y86,状态码包括以下情况,它描述程序执行的总体状态:

对于Y86,当遇到这些异常的时候我们就让处理器停止执行指令。不过更完整的设计中,处理器会调用一个异常处理程序,这个过程用来处理在遇到的某种类型的异常。
Y86-64程序
我们尝试将这个递归求和的程序翻译成Y86的汇编形式:
int rsun(int *start,int count){
if(count <= 0)
return 0;
return *start + rsum(start+1,count-1);
}
Y86:
# int rsun(int *start,int count)
# start in %rdi count in %rsi
rsum:
xorq %rax %rax # sum = 0
andq %rsi %rsi # set CC -> if %rsi != 0 , ZF = 0
je return # if count == 0 , return 0 -> if ZF == 1 , jmp
pushq %rbx
mrmovq (%rdi) %rbx
irmovq $-1 %r10
addq %r10 %rsi
irmovq $8 %r10
addq %r10 %rdi
call rsum
addq %rbx %rax
popq %rbx
return:
ret
Y86-64指令详情
大多数的Y86指令是容易理解且稳定的,不过我们需要注意两个特别的指令的组合。
pushq
pushq将栈指针rsp-8,并且将一个寄存器的值写入内存中。因此,当执行pushq %rsp时,指令的结果是不确定的,我们可能遇到两种情况:
- 压入%rsp的原始值
- 压入减去8的%rsp的值
实际上这里会压入%rsp的原始值,具体的原因,我们会在后面进行解释。
popq
同样的popq %rsp也是这么一个问题,可能会出现两种结果:
- %rsp置为先前压入的值
- %rsp置为+8后的%rsp的值
是将这里是将%rsp置为先前压入的值,也就是等价于mrmovq (%rsp) %rsp 。具体的原因会在之后进行解释