罗聪 OpCode教程 笔记二

来源:互联网 发布:百度春运迁徙大数据图 编辑:程序博客网 时间:2024/06/18 10:55

http://www.luocong.com/learningopcode/doc/2._从哪里开始,到哪里结束.htm

 

计算机是如何知道哪里是某一条指令的开始,到哪里才是它的结束呢?

看下面例子:
EB:imm8  对应mnemonic就是  jmp,imm8 
这是一条近跳转指令,imm8表示一个8位的立即数,意思是jmp到imm8的偏移后的地址去。

这条OpCode的域格式是这样的:{code}{Immediate}
一共是2个字节。

问题来了,我们用肉眼一看就知道,这代表2个字节,我们也知道这条指令应该是从EB开始,总长度是2个字节,到imm8为结束,
可是计算机是怎么知道这一点的呢?假如有一串OpCode发送给处理器,例如“90EB0090”,让它从中找到这个jmp指令,它会不会认不出来呢?
又或者,传送一串OpCode给处理器,例如“EB1234”,它会不会把后面的34也算进了jmp的跳转范围呢?

答案是,不会的。

CPU有个寄存器叫做EIP,它储存了内存中的某个地址,这个地址会告诉CPU,哪里是当前指令的开始;但是,在CPU没有对OpCode进行解码之前,它并不会知道哪里才是指令的结束。

让我们来举个例子:
00401000    90    NOP

第一列表示的是内存中的地址,在这里是00401000,它同时也是EIP的值,此时EIP = 00401000
第二列表示的是OpCode,第三列表示的是mnemonic,相信不必多说,读者也能明白它们的意思:90对应NOP

由于EIP = 00401000,所以CPU会知道当前的指令应该是从内存单元中的00401000开始,在这里,储存了一个OpCode:90,接下来CPU会对90进行解码:

OpCode:90
域格式:{code}

只有1个字节。所以CPU就会知道,OpCode“90”是从内存地址“00401000”开始,到“00401001”结束。

不过还有一种特殊情况:如果CPU遇到了无效的指令,它就会无法解析,例如OpCode“FFFF”,在运行的时候,会产生一个异常。

又例如:EB:imm8

EB是域{code},当EIP遇到内存中的EB的地址时,CPU就会知道第1个字节后面会跟着一个imm8立即数,总长度是2个字节。

至此,我们可以给出:

 初步的结论

1. 开始:处理器认为当前EIP指向的内存单元中的第一个字节就是指令的开始。
2. 结束:处理器通过对OpCode进行解码(大多数情况下是根据{code}域),从而知道哪里是结束。 

不过,不得不提的一点是:

在运行完一条指令后,EIP并不总是指向下一条指令的开始!
举个例子:

00401000    EB 00    JMP 00000002
00401002    90       NOP

此时EIP = 00401000,EB00翻译成mnemonic就是JMP 00000002。为什么呢?因为EB:imm8是2个字节的OpCode,在这里imm8的值是00,所以00(imm8) + 02(本条OpCode的长度) = 02(应该跳转的地址),也就是跳转到相对偏移为02的地方去。

因此,EB00运行完后,EIP的值应该是00401002,也就是指向90的地址,下一步处理器将会执行指令“NOP”。

好,再看:

00401000    EB FE    JMP 00000000
00401002    90       NOP

此时EIP = 00401000,但是为什么EBFE会是JMP 00000000呢?想想看?

答案:
FE + 02 = 100

由于imm8的关系(8位只表示一个字节),100其实只取00(100其实是2个字节了——其高位为0,即0100)
所以这条指令运行后,EIP应该还是00401000,没有改变!原因是这条指令的跳转地址是它本身!后面00401002处的“90”永远都不会执行!
真正的底层程序员应该会理解指令的本质,而不仅仅是从指令的字面上去理解它的意思。

让我们回到正题。再来看一些应用:
OpCode:04 AC
00401000    04 AC    ADD AL, 0AC

我们已经知道,AC是助记符lodsb的OpCode,00401000是OpCode 04AC的开始地址,而00401002将会是它的结束(这个指令只有2个字节的关系)。但是,我们一直以来都没讨论的是:如果把这条OpCode从中间截断!即从00401001地址处开始的指令会是什么呢?

如果我们把寄存器EIP的内容设置成00401001,我们就会发现:
处理器会把AC看作lodsb,而不是:
ADD AL, 0AC
04:imm8(AL+imm8)中的imm8

应用这个原理,我们来看一个小例子,假设要实现下面的算法:
IF zf = 0
 lodsb
ELSE
 add al, 0AC
试试写成助记符?不知道读者朋友们会怎么写——我会写成这样:

jnz $+1
add al, 0AC

解释如下:

如果标志位zf等于0,则EIP会指向add al, 0AC的第2个字节,也就是AC——我们知道AC表示助记符lodsb
明白了吗?使人惊奇的是,整个算法的实现只用了区区4个字节!

这个算法的OpCode:

00401000    75 01    JNZ SHORT 3
00401002    04 AC    ADD AL, 0AC

让我们来看看每个字节表示什么意思:

75:imm8 是 7501 的域格式
75是JNZ的OpCode,imm8在这里是01,会加到EIP里面去,整个7501表示如果这条指令被执行了,则EIP会指向下一条指令的第2个字节的地址。

04AC的域格式:
04:imm8 其中:
04 - {code}
AC - {Immediate}

整个算法实现的思路如下:

如果zf=0,7501这条指令就会把下一条指令的起始地址+1(75后面的操作数就是需要跳的字节数:0不跳,1跳一个,n就跳n个……但是字节是有符号的,负的就往后跳……所以jnz short xxx是有最大的跳跃限制的),然后把跳跃后的地址赋值给EIP——也就是00401003,从而迫使处理器认为AC所在的地址才是下一条指令的开始(跳过了OpCode 04),这时,AC会被当成{code}。

否则,EIP会指向04AC所在的地址00401002,所以下一条指令的开始就会从04开始算起,处理器会认出域格式:
04:imm8(add al, imm8)
这时,AC会被当成{Immediate},而不是{code}。

呵呵,是不是有点儿迷糊了?

为了加深理解,最后再给大家看一个算法及其实现:

IF zf = 0
 inc eax
ELSE
 mov al, 40

答案:

00401000    75 01    JNZ SHORT 3
00401002    B0 40    MOV AL, 40

嗯……提示一下:40表示的是inc eax……聪明的你,明白了吗?

结束
本章到这里已经结束了,但是……OpCode的学习只是刚刚开始而已,请大家打好精神,为后面的旅程作好准备!

 

原创粉丝点击