先说16位和32位,你指的应该是代码段的属性吧:
在实模式下,代码段默认是16位,可以用0x66和0x67前缀执行部分32位操作。
在保护模式下,代码段的属性由CS选择子指向的段描述符中的D/B域定义,该位为1时为32位段,为0则为16位段。
至于16位和32位指令长度,有不同的情况。
有些指令,同一个机器指令码,当段长度属性不同时仅仅是寄存器操作数的位数不同。例如,机器指令0x55,在32位段中,执行push ebp的功能,而在16位段中,是push bp。类似的还有pop ecx/pop cx,movsw/movsd,ADD AX,CX/ADD EAX,ECX,等等。
还有很少的指令,在16位和32位意义是不同的,比如双字节指令0x88,0x07,在16位是mov [bx],al,在32位是mov [edi],al,因为0x07中的寄存器编码在16位和32位不一致。
还有一些指令,是我调试软件时发现的,使用0x66前缀后它的执行结果与最初的预料不一样,比如0x0FC8,bswap eax,如果加上0x66,OD将它反汇编成bswap ax。但是,它并不是交换al和ah,而是将ax清零。意外???其实不是。原因在于,bswap指令是纯32位的,在8086时代并没有这样一条指令。也就是说,实际上bswap ax不是有效的16位指令!至于INTEL为什么不让它产生一个异常而是将AX清零,我也不知道,反正情况是这样的。
那为16位代码不需要bswap操作吗?答案是:在16位代码中,用xchg ah,al指令就可以实现了!不过这里又有一个情况,32位也可以xchg ah,al,而实验结果是,16位和32位的xchg ah,al的机器码是不一样的!我也不知道是不是汇编器的原因,但我的实验是xchg ah,al和xchg al,ah的16位和32位机器指令刚好相反。
再说说变长指令。
INTEL的处理器虽然一次又一次地升级,但指令编码却越来越复杂,这都是出于“兼容”的考虑。
因为它是变长指令,指令的长度取决于具体的指令,只有在对指令动态地解码才能决定,不像很多RISC机器,比如ARM,指令的边界就是4字节对齐的地址(ARM状态)或偶地址对齐的地址(THUMB状态)。
虽然CPU的实际译码过程是非常复杂的,不过为了便于理解,可以简化地进行想象,类似反汇编引擎的解码过程:
比如,pop ecx的机器码是0x59,push ebp是0x55,是单字节指令。CPU的指令译码逻辑发现它是单字节指令,于是直接进入下一轮的译码周期。
而mov eax, edi的机器码是0x8b 0xc7,译码先看到0x8b,根据译码表,知道它还有ModRM域,没有IMM域,于是把下一个字节当作ModRM域来译码,根据ModRM译码结果,后面没有SIB和地址偏移,于是该条指令解码结果,进入下一个周期。
依此类推,译码器通过对指令字节的解析来确定是否到达指令的边界。
其实我只是凭自己的感觉说的,不是搞专业的,不保证正确。更专业的分析,可以看一下mik写机器指令的分析文章:
http://bbs.pediy.com/search.php?searchid=1212752
以及egogg写的相关文章:
http://bbs.pediy.com/search.php?searchid=1212760