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

程序计寄存器(RF)
Y86有15个程序寄存器,每个寄存器存储着一个64位的字,分别是:
1 | %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),寄存器(r)。指令的第一个字母指定了源,第二个字母指定了目的 - 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状态
指令编码
现在讨论一下程序的指令编码,我们可以在上面的图看到大致的,每个指令的编码结构略有不同但还是由以下部分组成:
1 | 指令类型 | 源 | 目的 |
指令类型
指令类型通常在第一个字节给出,第一个字节分为高四位和第四位。其中:
- 高四位是代码(code)部分,用来决定操作类型
- 第四位是功能(function)部分,用决定操作所使用的功能。不过功能值只有在i相关指令共用一个操作的时候才有用
我们可以看到Y86带功能值的具体操作

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

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

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