4. Prefixes - Part II |
I'll be back.
-- Arnold Schwarzenegger, The Movie - "Terminator"(1984)
在前一章中我们已经知道:
现在我们将要学习剩下的几个Prefixes,它们可以被划分为5个集合,分别是:
在前面我们已经学习过它了,而且也够详细的了,对吗?
改变默认的地址大小。
请注意:67与66的分别在于,66改变的是默认的操作数大小,而67则是地址的大小。两者有什么差异呢?
8A 00 MOV AL, [EAX]
现在把它的OpCode改成以67开头的:
67 8A 00 MOV AL, [BX+SI]
我们可以看到:
第2个问题我们将会在以后的{ModR/M}和{SIB}的格式讲解中回答。现在我们可以暂时认为,在16位的地址模式中无法完全使用32位中的对应的地址模式,两种模式中的寄存器有着一定的区别。(看不明白?没关系,后面的章节中会详细解释)
强调一点:Prefix 67同样也是一个“触发器”,它起的作用是“切换”,而不是“指定”。
Repeat Prefixes通常是与movs、scas、cmps等串指令搭配使用的,它们有:
F2: REPNE
F3: REP / REPE
Repeat Prefixes作为一个串操作指令的前缀,它重复执行其后的串操作指令。每一次重复都先判断(E)CX是否为0,如为0就结束重复,否则(E)CX的值减1,然后再重复其后的串操作指令。所以当(E)CX的值为0时,就不再执行其后的操作指令。
它类似于LOOP指令,但LOOP指令是先把(E)CX的值减1,后再判断是否为0。
举例:
CLD
MOV ECX, 3
REP MOVSB
运行的结果是把DS:(E)SI的3个字节(byte)移动到ES:(E)DI去。
有两点规则:
第2条规则比较难以理解,对吗?我们来举个例子:
REP LODSB
REPE LODSB
REPNE LODSB
这3条助记符的运行结果都是一样的:它会重复运行指令LODSB一共(E)CX次,而不管它的Repeat Prefixes是rep/repe[F3]还是repne[F2]。
但是请注意:第2条规则的适用范围仅仅是只使用“rep”的指令,意即无论是F2还是F3,对指令的执行结果都无影响,而这样的指令非常的少!
从OpCode的角度来看rep/repe[F3]和repne[F2]的区别:
我们知道,重复串指令时可能会改变某些标志位(例如ZF),在这种情况下,有些指令与重复前缀搭配使用时,F2和F3会把最后一位与标志位ZF进行比较,如果它们不相同,则重复串指令的操作将会结束。而有些指令不用进行这个比较的操作,因此标志位ZF对这些指令的运行结果无影响。
讲得不够清楚?呵呵,把F2和F3转换成二进制就能明白了。
1111 0010 F2
1111 0011 F3
最后我们再来看看Repeat Prefixes的结束条件:
Repeat Prefiex的结束条件 | |||||||||||
Repeat Prefix | 结束条件1 | 结束条件2 | |||||||||
REP | ECX=0 | None | |||||||||
REPE | ECX=0 | ZF=0 | |||||||||
REPNE | ECX=0 | ZF=1 |
从上表中可以看出:repe和repne的结束必须同时满足两个结束条件,而rep只管ECX等不等于0。
看到这里,再结合上面的第2条规则,我们就能更清楚了:由于rep并不与标志位ZF进行比较,所以它可以被替换成repe或者repne,对执行结果无影响!
我们先来看看这些Prefixes是什么:
Prefixes && Explanation | |||||||||||||
Prefix | Explanation | ||||||||||||
2E | CS segment override prefix | ||||||||||||
36 | SS segment override prefix | ||||||||||||
3E | DS segment override prefix | ||||||||||||
26 | ES segment override prefix | ||||||||||||
64 | FS segment override prefix | ||||||||||||
65 | GS segment override prefix |
再来看一个例子:
8B 03 MOV EAX, [DWORD DS:EBX]
65 8B 03 MOV EAX, [DWORD GS:EBX]
65就是一个Segment override prefix,用来改变默认的段,从上表中我们可以看出:65代表的是段GS。注意!这里也是用默认的概念。
读者在这里也许会存在一个疑问:默认?我怎么知道当前默认的是哪个段呢?以及为什么要用默认的概念呢?
答案是这样的:在使用内存中的数据时,处理器必须首先知道它的段地址(Segment)和偏移量(Offset),但是如果在每个地方都要显式地直接指出段地址,那么在OpCode格式中就必须增加一个新的域,这将会比现有的OpCode体系多占用大量的字节,而且处理器也必须多花费额外的时钟周期来进行解码——无论在空间还是时间上,都不值得!
因此,为了解决这个问题,一个方案诞生了:
指令由不同的定义被划分为不同的组,每个组各自有一个默认的段:
CS: for EIP pointer
ES: 目的操作数是内存单元的串指令(movs, cmps等),在这里源操作数是储存在段DS里面。
SS: 堆栈操作(push, pop等)
DS: 剩下的数据操作指令。
有了这个规则,处理器识别当前应该用哪个段将会变得非常简单而直接:
看看:
AC LODS [BYTE DS:ESI]
3E AC LODS [BYTE DS:ESI]
从上面的表中可以查出,3E是表示段DS,但是实际上在这里即使不直接指明3E,处理器也是会使用DS的,因为DS是指令LODS的默认段。
最后值得一提的是64,它表示的是段FS,也许读者会对FS不太熟悉,平时好像很少会用到。没关系,我们来简单介绍一下:FS一般是由SEH(结构化异常处理)所使用,但是由于SEH不属于OpCode格式的范畴,所以我们在这里不必深究,知道有这个概念就行了。
对于这个Prefix,Intel的文档已经解释得很清楚了,不过它的具体意义对OpCode的格式学习并无任何帮助,有兴趣的读者可以在<<Intel Architecture Software Developer's Manual Volume 2: Instruction Set Reference>>的3-387页看到关于它的详细解释。在OpCode的格式学习中,我们只需要知道F0表示的是助记符LOCK就足够了。
结束了吗?
是的!关于Prefixes的格式学习到此就告一段落了。在接下来的章节中,我们将会学习最激动人心的一部分:如何对OpCode进行手工解码。
罗聪 www.LuoCong.com |
(注:如果出现链接打不开的情况,请去掉IE浏览器的“工具->Internet选项->高级->总是以UTF-8发送URL”前面的勾。谢谢!) |