此篇为《深入理解计算机系统》第四章内容。
定义一套指令集架构,包括定义不同的状态组件、指令集的编码、一系列编程规范、以及对异常的处理。
(图1 Y86-64指令集)
如图1所示,Y86-64架构只拥有由8字节整数组成的操作数,并有少数寻址模式,以及少量的操作集。图1左边的halt,nop, rrmovq rA, rB是右边编码的汇编表示。
几个细节需要清楚:
在x86-64中(Y86-64是x86-64的子集),movq(移动)被拆成了4个不同的指令:irmovq,rrmovq,mrmovq,rmmovq,清晰地表示了movq操作的来源和目的。指令的第一个字母表示来源,i代表immediate即立即数,r表示register寄存器,m代表memory内存;第二个字母表示目的,和来源不同,只有r和m能表示目的。分清这四种不同类型的数据传输,对如何实现它们是非常有帮助的。
有4个整数操作指令,即图1中的OPq。它们是addq,subq,andq,xorq。它们只对寄存器数据进行操作。
7个跳转指令,图1中的jXX,jmp,jle,jl,je,jne,jge,jg。
6个条件传送指令,cmovXX:cmovele,cmovl,cmove,cmovne,cmovge,cmovg。
call指令向栈中推入返回地址(return address),然后跳转到目的地址。ret指令表示从返回地址返回。
pushq和popq,推栈、弹栈。
halt指令表示停止执行指令。
由图1可知,每个指令需要1到10字节不等,由首字节区分不同的指令。首字节分为2部分,每部分4位(bit):高位表示指令代码(code),低位表示功能(function)。如图1所示,代码的值从0到0xB。功能的值只在同一组指令共享代码时起作用。
(图2 Y86-64指令集功能的代码)
图2展示了整数操作、分支跳转、条件传送指令的编码。注意rrmovq和条件传送指令(如cmovle)的指令代码相同。你可以把rrmovq当作非条件传送;同样,jmp为非条件跳转;可以看出,rrmovq和jmp的功能码相同都为0(小格:这里的0应该就是非条件的意思)。
(图3 Y86-64程序寄存器标识符)
如图3所示,15个寄存器都有其标识符(register identifier, ID),从0到0xE。0xF用来表示不需要寄存器。
从图1可以看出,一些指令的长度只有有个字节。其它需要操作数的指令则占用更长字节。首先,可能有附加的字节表示是否需要一个或两个寄存器,即图1中的rA和rB。根据图1左边的汇编代码我们可以看出rA、rB谁表示源,谁表示目的。一些指令如分支跳转、call、则不需要寄存器字节位。而有些只需要一寄存器操作数的指令如irmoq、pushq、popq将另一个寄存器指示符设为0xF。这种约定在处理器的实现中非常有用。
还有一些指令需要额外的8字节常数字(constant word)。这个字可以作为irmovq的立即数、rmmovq和mrmovq的地址偏移符、以及分支跳转和call指令的目的。
和x86-64一样,所有的整数都用小端法编码,在反汇编中字节顺序又返过来。例如,让我们尝试生成下述汇编指令的字节码。
rmmovq %rsp, 0x123456789abcd(%rdx)
从图1中我们可以知道,rmmovq头一个字节为40,%rsp编码为rA字段, %rdx编码为rB字段。从图3我们可以得知rArB为42(%rsp 4,%rdx 2)。接着,让我们看看如何处理8字节常数字。我们先用0填满8字节,得到00 01 23 45 67 89 ab cd,将其反转为cd ab 89 67 45 23 01 00。最后拼凑起来得到指令编码为4042cdab896745230100。
指令集一个很重要的特性是,字节编码必须拥有唯一的解释性。什么意思呢?就是说,任意一段字节编码,要么有意义,要么无意义。在Y86-64指令中,每一个指令的首字节位都有唯一的指令代码+功能的组合,拿到这个字节位,我们就可以知道任意附加字节的长度和意义。这个特性保证了处理器能够无二义地执行目标代码程序。即便这段代码嵌入到了程序的其它字节中,只要我们从这个字节编码的第一个字节开始处理,我们还是能弄清这段编码是什么意思。
还是拿上面的指令举例,对于4042cdab896745230100来说,只要我们确定了40,我们就知道这表示rmmovq指令,进而确定还有多少字节需要拿来解释完这个指令。如果我们找到的是89674523010,那对处理器来说,这就是无意义的编码。