3. Prefixes - Part I | ![]() |
转自老罗
To be, or not to be: that is the question.
-- William Shakespeare, "HAMLET".
Hello, Prefixes!
就像经典的“Hello World!”程序一样,让我们也从最简单的一个实例看起:
OpCode && mnemonic | |||||
OpCode | mnemonic | ||||
40 | INC EAX | ||||
66 40 | INC AX |
假设默认的操作数是32位,我们就可以得到上表的结果。(为什么默认是32位?看到后面就会明白的)
我们可以看到,40表示的是INC EAX,66 40表示的是INC AX,两者的分别在于:前者的操作数是32位的(EAX),而后者是16位的(AX)。
从OpCode的角度来看,后者比前者多了一个66,就导致了不同的结果,唔……Intel x86规定:
66是一个Prefix,我们把Prefix翻译为前缀,所谓前缀,就是与code进行组合用以产生出某些变化形式的“东西”。唔……好拗口啊,真不好解释,请看晕了的朋友不要抛砖头,继续往下阅读吧。
认识 Prefixes
回忆一下第一章中介绍的OpCode的6个域:
- Prefixes
- code
- ModR/M
- SIB
- Displacement
- Immediate
记住:
- 在实际的使用中,并不是这所有的6个域都会被用到的,但是有一项却是一定会有的,那就是第2项:code,有些指令甚至只会用到code这一项。
- 这6个域的排列顺序绝对不能乱,必须严格按照上面的顺序进行。有些域也许不会出现,但是只要出现了,编号小的域就绝对不允许出现在编号大的域的后面,反之亦然。
Prefixes是所有的域中最容易理解的一个,请先明了它的一些特性:
![]() 1. 它是唯一的一个可能出现在code之前的域。 2. 所有的Prefixes都只有1个字节。 3. 在一个OpCode中可能会有多个Prefixes。 |
看看刚才提到过的prefix 66,这个prefix的意思是“切换默认的操作数的大小”。例如在有的系统中有2种默认的操作数大小:16位和32位。操作数有可能会被写成16位或者32位,唯一的区分方法是看它有没有prefix 66。
唔……是不是讲得不够清楚呢?我们来看看:
OpCode && mnemonic | |||||
OpCode | mnemonic | ||||
66 AD | LODSW | ||||
AD | LODSD |
依然假设默认的操作数是32位的,有没有发现什么不寻常的地方?
LODSW和LODSD的code域是一样的——都是AD!其实,LODSW和LODSD这两个指令是同一个指令,只不过它们的操作数大小不一样——LODSW使用了2个字节(16位)的WORD作为操作数,而LODSD则使用了4个字节(32位)的DWORD作为操作数。
看到这里,读者应该能够明白了:prefix 66的作用是切换默认的操作数大小。请注意我们并没有说“指定”,而是“切换”!反映到这个例子中,就是“切换默认的32位操作数到16位”,而不是“指定操作数的大小为16位”。
这点非常重要!!!绝对不是在玩文字游戏!!
如果默认的操作数大小是WORD(16位),那么切换后就是DWORD(32位);反之,如果默认的操作数大小是DWORD(32位),那么切换后就是WORD(16位)。
切记!Prefixes 66就像一个触发器一样,起的作用就是进行切换。
让我们再来看一个特例:
B0 FF MOV AL, 0FF
8A C1 MOV AL, CL
看清楚了吗?现在的操作数是AL和CL,加上prefix 66后会如何?
66 B0 FF MOV AL, 0FF
66 8A C1 MOV AL, CL
Faint!没有任何变化!
为什么呢?我们可以猜测一下:也许并不是所有情况下的操作数大小都可以随意改变的。假如这个改变是不允许的,那么它就会被忽略。
为了证实这个猜想,让我们来看看下一个更有趣的例子:
prefix F3(rep)的作用是让CPU对随后的指令循环执行ecx(cx)次,指令INC EAX的OpCode是40,好,如果我们想连续执行3次INC EAX的话,应该怎么样呢?
也许你会想当然地认为应该这样写:
xor eax, eax
mov ecx, 3
rep inc eax
实际上!并不是这样!这样的程序的运行结果是:
- 没有任何异常(exception)产生。
- 最后eax = 1,这意味着prefix F3并没有起作用——它被忽略了。
现在我们可以证实之前的想法:
如果Prefixes不能对随它之后的OpCode起作用,那么它就会被忽略。
再回忆一下之前提到的三个特性:
- Prefixes是唯一的一个可能出现在code之前的域。
- 所有的Prefixes都只有1个字节。
- 在一个OpCode中可能会有多个Prefixes。
前面两点应该比较容易理解,让我们来看看第3点是什么意思。
如果想得到下面的指令:
REP LODSW
它的OpCode将会是:
66 F3 AD
解释如下:
66 AD:LODSW
F3: REP
都是前面讲过的内容,不难吧?只是组合起来使用罢了。
不过……细心的读者可能会问:为什么要把66放在第一位,把F3放在第二位呢?把它们的位置调换一下行不行?答案是:行!事实上它也可以写成:
F3 66 AD
效果是一样的!
![]() ![]() ![]() |
最后,我们还可以得出一个推论:
由于每个Prefixes会多占用1个字节,所以也必定会导致处理器多使用一个指令周期进行解码——无论在时间还是空间上都会造成浪费。因此,我们应该权衡在哪些场合才使用Prefixes,如非必要,应该减少对它的使用。
Is it ALL?
Of course not! 由于章节的篇幅问题,Prefixes的进一步讲解会放在后面的章节中继续进行,我们会看到更多的有关Prefixes的信息。
罗聪 www.LuoCong.com |
(注:如果出现链接打不开的情况,请去掉IE浏览器的“工具->Internet选项->高级->总是以UTF-8发送URL”前面的勾。谢谢!) |