Things have changed in the past two decades.
什么是 OpCode?
不管计算机技术的发展如何日新月异,其最基本的东西是不会突然改变的。OpCode 就是这其中的一样东西——因此,Bill Gates 的这句话用在这里并不太合适。
在开始回答什么是 OpCode 之前,请让我先来提几个小问题。
- 计算机只认识 0 和 1 吗?
- 如果上面的回答是 “是”,那么我们平时写的程序源代码是 0 和 1 吗?
- 如果上面的回答是 “不是”,那么计算机是怎么“知道” 我们的程序的意思的?
按顺序作答,依次是:
- 是
- 不是
- ???
最后一个问题的答案是……?我们来举个例子,在汇编语言中:NOP
,这条指令很简单,是吧?
在编译的时候,Assembler 会扫描整个源代码。
在前面我们已经知道了,由于计算机只认识 0 和 1,所以,源代码 “NOP” 是无法直接运行的。当 Assembler 遇到 “NOP” 的时候,为了生成让计算机能运行的 “东西”(暂且这样称呼吧),就会以十六进制数“0x90” 来代替它。
在这里,“0x90”就是 “OpCode”,而“NOP” 则是“助记符(mnemonic)”。
OpCode 的全称:OpCode 就是 Operation Code,意即操作码的意思。
一个 OpCode 只对应一个助记符吗?
示例:OpCode && mnemonic
OpCode操作码 | mnemonic助记符 |
---|---|
0x90 | NOP |
0x90 | XCHG AX, AX |
0x90 | XCHG EAX, EAX |
从上表中可以看出,同一个 OpCode 可以对应 N 个 mnemonic。为什么会这样呢?原因现在不必深究,以后自然会明白的。
一个助记符只对应一个 OpCode 吗?
示例:OpCode && mnemonic
mnemonic助记符 | OpCode操作码 |
---|---|
ADD EAX, 1 | 0x83C001 |
ADD EAX, 1 | 0x0501000000 |
ADD EAX, 1 | 0x81C001000000 |
从上表中也可以看出,同一个 mnemonic 可以对应多个 OpCode。原因同样留待以后再说。
OpCode 管中窥豹
有 6 个域是 OpCode 可能会用到的,或者说 OpCode 是由这 6 个域组成的——不过请注意:它们的名字是什么,这并不重要——重要的是它们的排列顺序。
它们是:
- Prefixes
- code
- ModR/M
- SIB
- Displacement
- Immediate
OpCode 的这 6 个域的详细介绍留待以后再说,现在首先要知道:
在实际的使用中,并不是这所有的 6 个域都会被用到的,但是有一项却是一定会有的,那就是第 2 项:code,有些指令甚至只会用到 code 这一项。
例如:
OpCode && mnemonic
OpCode | mnemonic |
---|---|
0xC3 | RETN |
0x2F | DAS |
0x90 | NOP |
0xAC | LODSB |
上表中的几个 OpCode 都只用到了 code 这一项。其中的最后一项:0xAC,让我们来看看能不能给它加上一些额外的 “东西”:0xF3AC REP LODSB
可以看到:rep lodsb
为什么会多了个 “rep” 呢?是不是由额外的 “F3” 造成的呢?
Yes,猜对了,我们来看看它的 OpCode 格式描述,如下:(注:用 {} 包围起来的是域的名称)
AC -- {code}
F3 AC -- {Prefix}{code}
因此,F3 就是域 Prefix
在稍后的章节中我们会知道,F3 表示的是 Rep Prefix,它也能与 movsb,stosb 等指令联用,但是,具体细节在这里暂不深究。
让我再来强调一次:OpCode 中的 6 个域是可选的(除了域 code 之外),不必都用上,但是 code 是一定会有的。
知道了这一点,我们再来看一些例子:
OpCode && mnemonic
OpCode | mnemonic |
---|---|
27 | DAA |
2F | DAS |
3F | AAS |
37 | AAA |
D40A | AAM |
D50A | AAD |
在 Intel 的文档中,上表中的所有指令都是 1 字节的,但是,我们能够看到 AAM 和 AAD 是 2 字节的,到底有什么不同呢?
先不要看下面的答案,试着自己想一想……
.
.
.
.
.
.
.
.
.
We can see:
- AAM 和 AAD 都是 2 字节的,然而其余的 4 个指令都是 1 字节的。
- AAM 和 AAD 的 OpCode 的第 2 个字节都是0Ah。
如果你还没把大学里的汇编知识彻底还给老师的话,应该还记得 AAM 和 AAD 的描述:
AAM : divide al by 10
商 放在AH里
余数 放在AL里
AAD : AL = AH * 10 + AL
注意到了吗?两者的操作都与 10 有关。而且两者的 OpCode 的第二个字节都是 10(0Ah)。
人类与动物的其中一个区别是具有思维的联想性。聪明的你是不是猜到了什么?
嗯……0Ah会不会是偶然的呢?它会不会是操作数的一部分?进一步地,AAM 与 AAD 的指令格式会不会不是:
D40A for AAM
D50A for AAD
而是:
D4:imm8 for AAM
D5:imm8 for AAD
以及,imm8 可以是任何别的数字呢?(注:imm8 表示 8 位的立即数)
答案是肯定的!
事实上,我们可以通过反汇编器得知,D407表示的是 AAM 7,D508表示的是 AAD 8,以此类推。
现在,我们又知道了一种新的指令格式:
{code}{Immediate}(域2和域6)
还有别的,以后再说。
There’s Something We Should Know…
最后再强调一点:
- 虽然并不是 6 个域都是必要的,但是,它们的排列顺序绝对不能乱,必须严格按照上面的顺序进行。有些域也许不会出现,但是只要出现了,编号小的域就绝对不允许出现在编号大的域的后面,反之亦然。
例如,{Prefix}{code} 的顺序绝对不允许变成 {code}{Prefix}。
不相信?举个例子:4004和0440(假设在 32 位条件下)
OpCode && mnemonic
OpCode | mnemonic |
---|---|
4004 | INC EAX |
0440 | ADD AL, 40h |
明白了吗?
理解了 OpCode 的规则,将有助于底层程序员明白一些鲜为人知的事情。在接下来的章节中,我们将学习 OpCode 的 6 个域的详细信息。