OpCode的6个域:
- Prefixes
- code
- ModR/M
- SIB
- Displacement
- Immediate
记住:
- 在实际的使用中,并不是这所有的6个域都会被用到的,但是有一项却是一定会有的,那就是第2项:code,有些指令甚至只会用到code这一项。
- 这6个域的排列顺序绝对不能乱,必须严格按照上面的顺序进行。有些域也许不会出现,但是只要出现了,编号小的域就绝对不允许出现在编号大的域的后面,反之亦然。
Prefixes合集
在前一章中我们已经知道:
- 所有Prefixes的长度都是1个字节。
- 一个OpCode可能会有几个Prefixes。
- 如果有多个Prefixes,那么它们的顺序可以打乱。
- 如果Prefixes不能对随它之后的OpCode起作用,那么它就会被忽略。
现在我们将要学习剩下的几个Prefixes,它们可以被划分为5个集合,分别是:
- Change DEFAULT operand size. (66)
- Change DEFAULT address size. (67)
- Repeat prefixes. (F2, F3)
- Segment override prefixes(change DEFAULT segment). (2E, 36, 3E, 26, 64, 65)
- LOCK prefix. (F0)
读者在这里也许会存在一个疑问:默认?我怎么知道当前默认的是哪个段呢?以及为什么要用默认的概念呢?
答案是这样的:在使用内存中的数据时,处理器必须首先知道它的段地址(Segment)和偏移量(Offset),但是如果在每个地方都要显式地直接指出段地址,那么在OpCode格式中就必须增加一个新的域,这将会比现有的OpCode体系多占用大量的字节,而且处理器也必须多花费额外的时钟周期来进行解码——无论在空间还是时间上,都不值得!
因此,为了解决这个问题,一个方案诞生了:
指令由不同的定义被划分为不同的组,每个组各自有一个默认的段:
CS: for EIP pointer ES: 目的操作数是内存单元的串指令(movs, cmps等),在这里源操作数是储存在段DS里面。 SS: 堆栈操作(push, pop等) DS: 剩下的数据操作指令。
有了这个规则,处理器识别当前应该用哪个段将会变得非常简单而直接:
- 如果有“Segment override prefix”,那么就使用这个prefix所指定的段。
- 否则就使用默认的段。
看看:
AC LODS [BYTE DS:ESI] 3E AC LODS [BYTE DS:ESI]
从上面的表中可以查出,3E是表示段DS,但是实际上在这里即使不直接指明3E,处理器也是会使用DS的,因为DS是指令LODS的默认段。
<