Things have changed in the past two decades.

— Bill Gates(1995)

什么是 OpCode?

不管计算机技术的发展如何日新月异,其最基本的东西是不会突然改变的。OpCode 就是这其中的一样东西——因此,Bill Gates 的这句话用在这里并不太合适。

在开始回答什么是 OpCode 之前,请让我先来提几个小问题。

  1. 计算机只认识 0 和 1 吗?
  2. 如果上面的回答是 “是”,那么我们平时写的程序源代码是 0 和 1 吗?
  3. 如果上面的回答是 “不是”,那么计算机是怎么“知道” 我们的程序的意思的?

按顺序作答,依次是:

  1. 不是
  2. ???

最后一个问题的答案是……?我们来举个例子,在汇编语言中: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 个域组成的——不过请注意:它们的名字是什么,这并不重要——重要的是它们的排列顺序。

它们是:

  1. Prefixes
  2. code
  3. ModR/M
  4. SIB
  5. Displacement
  6. 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 格式描述,如下:(注:用 {} 包围起来的是域的名称)

  1. AC -- {code}
  2. F3 AC -- {Prefix}{code}
  3. 因此,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:

  1. AAM 和 AAD 都是 2 字节的,然而其余的 4 个指令都是 1 字节的。
  2. AAM 和 AAD 的 OpCode 的第 2 个字节都是0Ah

如果你还没把大学里的汇编知识彻底还给老师的话,应该还记得 AAM 和 AAD 的描述:

  1. AAM : divide al by 10
  2. 放在AH
  3. 余数 放在AL

AAD : AL = AH * 10 + AL

注意到了吗?两者的操作都与 10 有关。而且两者的 OpCode 的第二个字节都是 10(0Ah)。

人类与动物的其中一个区别是具有思维的联想性。聪明的你是不是猜到了什么?

嗯……0Ah会不会是偶然的呢?它会不会是操作数的一部分?进一步地,AAM 与 AAD 的指令格式会不会不是

  1. D40A for AAM
  2. D50A for AAD

而是:

  1. D4:imm8 for AAM
  2. D5:imm8 for AAD

以及,imm8 可以是任何别的数字呢?(注:imm8 表示 8 位的立即数)

答案是肯定的!

事实上,我们可以通过反汇编器得知,D407表示的是 AAM 7D508表示的是 AAD 8,以此类推。

现在,我们又知道了一种新的指令格式:

  1. {code}{Immediate}(域2和域6

还有别的,以后再说。

There’s Something We Should Know…

最后再强调一点:

  • 虽然并不是 6 个域都是必要的,但是,它们的排列顺序绝对不能乱,必须严格按照上面的顺序进行。有些域也许不会出现,但是只要出现了,编号小的域就绝对不允许出现在编号大的域的后面,反之亦然。

例如,{Prefix}{code} 的顺序绝对不允许变成 {code}{Prefix}。

不相信?举个例子:40040440(假设在 32 位条件下)

OpCode && mnemonic

OpCode mnemonic
4004 INC EAX
0440 ADD AL, 40h

明白了吗?

理解了 OpCode 的规则,将有助于底层程序员明白一些鲜为人知的事情。在接下来的章节中,我们将学习 OpCode 的 6 个域的详细信息。


https://blog.csdn.net/ArtX/article/details/1548433?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-3-1548433-blog-79609715.pc_relevant_multi_platform_whitelistv3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-3-1548433-blog-79609715.pc_relevant_multi_platform_whitelistv3&utm_relevant_index=6