Y86-64处理器实现(1)

17 June 2025
5 mins

最近在看CSAPP的第四章,因为要记的东西比较多,所以整理一些东西帮助理解

Y86-64指令集体系结构

对于程序员可见的状态

Y86程序的每条指令都会对我们的处理器进行一些改变,我们把这个过程称之为状态的改变。这里我们需要能够知道对应的行为,使我们的状态发生了哪些变化。这些需要被观测的状态就是“对于程序员可见的状态”

image.png

程序计寄存器(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

image.png

指令细节:

指令编码

现在讨论一下程序的指令编码,我们可以在上面的图看到大致的,每个指令的编码结构略有不同但还是由以下部分组成:

指令类型 | 源 | 目的

指令类型

指令类型通常在第一个字节给出,第一个字节分为高四位和第四位。其中:

我们可以看到Y86带功能值的具体操作

image.png

源和目的

源和目的可能是寄存器或者内存地址,我们分开讨论:

image.png

现在我们可以把源和目的划分为三个部分:

|   寄存器字段   | 附加地址字段 |
|  rA   |  rB   |    Dest     |

寄存器字段占一个字节,附加地址字段占用八个字节

指令编码

通过将这几部分拼接组成就可以得到一条指令的编码,其中最重要的是每个字节编码一定要是唯一的解释。任意一个字节序列要么就是一个唯一的指令序列的编码,要么就不是一个合法的字节序列。

Y86-64异常

对于Y86,状态码包括以下情况,它描述程序执行的总体状态:

image.png

对于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的原始值,具体的原因,我们会在后面进行解释。

popq

同样的popq %rsp也是这么一个问题,可能会出现两种结果:

是将这里是将%rsp置为先前压入的值,也就是等价于mrmovq (%rsp) %rsp 。具体的原因会在之后进行解释


← 写一个Linux调试器(1)

程序性能优化(1) →