第13章 操作数和有效地址的尺寸

第13章 操作数和有效地址的尺寸

16位操作尺寸的指令和32位操作尺寸的指令可能具有相同的机器码,为了区分这两种不同操作尺寸的指令,32位处理器引入了默认操作尺寸的概念。

代码清单13-1

该章的代码实现的功能和上章一样,都是在页面打印:Protect Mode OK.

image

只是在进入保护模式后,采用jmp隐式的更新代码段寄存器CS,然后采用32位的操作数尺寸编译后续要执行的源码。

INTEL 80286处理器的16位保护模式

80286也是16位处理器,有24根地址线,可以访问16MB的内存。为了访问1MB以上的内存,同时为了支持多用户和多任务的系统,并加强对内存和程序的保护,80286引入了保护模式。

80286的段寄存器做了扩展,包括一个段选择器,以及一个描述符高速缓存器。在80286的保护模式下,传送到段寄存器里的值段选择子,用来在描述符表中选择一个段描述符。段描述符格式如下:

image

指令的操作尺寸

指令的操作尺寸(Operation Size):是指令的数据长度及指令在访问内存时的有效地址长度。

16位操作尺寸

指令的操作尺寸是16位的,指令操作数长度是8位或者16位的,有效地址长度是16位的。

例如:

mov al,cl            ;8位
mov bx,[0x2e]        ;16位
add dx,[bx]          ;16位
push dx              ;16位
sub dx,[bx+si+0x03]  ;16位

32位操作尺寸

指令的操作尺寸是32位的,指令操作数长度是8位或者32位的,可以兼容16位操作尺寸的指令。

例如:

mov al,[0x2e]           ;al是8位的,内存有效地址0x0000002e
mov edx,eax             ;32位    
add al,[eax]            ;al是8位的,内存地址32位的,由eax提供
and edx,[eax+esi*8+3]   ;32位 
push edx                ;32位

默认操作尺寸

简化指令的编码:一种可行的方法就是让16位操作尺寸的指令和32位操作尺寸的指令使用相同的编码。

例如:

;以下两条指令的机器码:89 c8
mov ax,cx   ;16位指令
mov eax,ecx ;32位指令

指令格式:机器指令分成5大部分:前缀、操作码、寻址方式和操作数类型、立即数、位移。

image

  • 前缀:重复前缀(REP/REPE/REPNE);段超越前缀(ES、DS、CS);总线封锁前缀(LOCK)等。
  • 操作码:mov、add、inc等。
  • 寻址方式和操作数类型:可选的,1~2字节。例如:eax,[bx+si+0x02]等。
  • 立即数:mov eax,0x02 中的0x02就是立即数。
  • 位移:8位或32位的位移,例如 mov ecx,[eax+ebx*8+0x02] 中的0x02。

例如:汇编语言指令

mov dx,[bx+si+0x02]

;编码后机器码:8B 50 02
;操作码0x8B;
;之后1字节的寻址方式和操作数类型部分;
;  位7和位6的值是01,表示使用了基地址变址的寻址方式,而且带有8位偏移量;
;  位5~位3的值是010,指示目的操作数为寄存器DX;
;  位2~位0的值是000,表示寻址方式为“BX+SI+8位位移”。
;之后是1字节的位移0x02。

画图表示如下:

image

再看汇编语言指令:

mov edx,[eax+0x02]

;编码后机器码也是:8B 50 02
;除了机器码第2个字节:位5~位3的值是010,指示目的操作数为寄存器EDX;
;其余都是一样的。

CS描述符高速缓存器中的D位:机器指令8B 50 02到底是什么?16位机器上没有问题,32位机器上取决于CS描述符高速缓存器中的D位,它用来指定处理器当前默认的操作尺寸(Default Operation Size)。

  • CS描述符高速缓存器中的D位 = 0,16位操作尺寸。
  • CS描述符高速缓存器中的D位 = 1,32位操作尺寸。

CS描述符高速缓存器中的D位变化情况:

  • 处理器刚加电时为0;
  • 刚进入保护模式未刷新CS时也是0,此时处理器工作在16位保护模式下。
  • 后续可以刷新CS,可以设置为1,也可以为0。1就是32位保护模式,0就是16位保护模式。

操作尺寸反转前缀

如果当前默认的操作尺寸是16位的,想执行32位的,或者,如果当前默认的操作尺寸是32位的,想执行16位的。只需要添加反转操作数尺寸的前缀0x66即可

我把说中说明的整理了一个表格,更容易看一些:

image

简单理解就是加了 0x66 反转前缀的指令,在16位下会被看成32位的执行,在32位下会被当做16位执行。

反转有效地址尺寸:前缀0x67则用来反转有效地址尺寸。

image

0x67就意味着将 [eax+0x02] 和 [bx+si+0x02] 进行反转。

编译时的操作尺寸

Bits指令:Bits是编译器提供的伪指令,用于通知编译器编译后面的指令时,默认操作尺寸,它的用法是在关键字 bits 的后面跟数字 16、32、64。
例如:

bits 16          ;16位模式,加方刮号也可以: [bits 16]
mov cx,dx        ;89 D1
mov eax,ebx      ;66 89 D8

bits 32          ;32位模式
mov cx,dx        ;66 89 D1
mov eax,ebx      ;89 D8 

最后,如果没有指定指令编译时的处理器默认操作尺寸,则默认是“bits 16”的。

清空流水线并串行化处理器

安装代码段描述符:将代码段描述符指向主引导程序所在的区域。

;创建#2描述符,保护模式下的代码段描述符
mov dword [bx+0x10],0x7c0001ff
mov dword [bx+0x14],0x00409800
  • 线性基地址为0x00007C00;
  • 段界限为0x001EE,粒度为字节(G=0),该段的长度为512字节;
  • 属于存储器的段(s=1);
  • 默认的操作尺寸是32位的(D=1);
  • 该段目前位于内存中(P=1);
  • 段的特权级为0(DPL=00);
  • 这是一个只能执行的代码段(TYPE=1000)

加载全局描述符表寄存器GDTR:长度较上章加8,因为多了一个代码段描述符。

;初始化描述符表寄存器GDTR
mov word [cs: gdt_size+0x7c00],23  ;描述符表的界限(总字节数减一)
lgdt [cs: gdt_size+0x7c00]

进行远转移:进入保护模式,进行一个远转移。

;以下进入保护模式... ...
jmp 0000000000010_0_00B:flush    ;相当于jmp 0x0010:flush
  • 描述符索引:2,用来选择2号描述符。
  • 表指示器位TI:0,指向GDT。
  • 请求特权级RPL:0,最高特权级。
    这条远转移指令执行时,处理器加载段选择器CS,从GDT中取出相应的描述符加载到CS描述符高速缓存器,此时描述符高速缓存里的内容:
  • 基地址为0x00007C00;
  • 段界限为0x1FF;
  • 长度为0x200;
  • 默认操作尺寸为32位。

flush标号表示在相对于段起始处的一段距离,用作段内偏移量(有效地址)。

image

使用伪指令bits 32:使用jmp后,CS描述符高速缓存器的D位为1,默认操作尺寸为32位,所以后面的指令也需要按照32位操作尺寸编译。

;jmp后增加微指令
bits 32

清空流水线:执行jmp指令时,处理器会清空流水线,重新按指令的自然顺序执行。

显示字符:把数据段选择子0x0008加载到数据段DS选择器,而后通过默认的段选择器可以更加方便显示字符。

CS不能用mov改变:在保护模式下,不允许使用mov指令改变段寄存器CS的内容,比如:mov cs,ax。

有效地址尺寸和内存访问

段界限:每当一条指令访问内存时,处理器会用指令中的有效地址和这个界限值进行比较,如果超出这个界限值,就会被处理器阻止。

例如:

mov ax,0x2000
mov ds,ax            ; 数据段寄存器的内容是0x2000
; 在执行了段寄存器DS的传送指令后,
; 用调试命令sreg显示各个段寄存器的内容,可以发现,
; 在实模式下,段寄存器DS的内容是0x2000,
; 描述符高速缓存器里的基地址是0x20000,段界限是默认值0x0000ffff。

mov eax,0xffff       ; 
mov dl,[eax]         ; 只访问1个字节,正常
mov edx,[eax]        ; 访问4个字节,越界报错。

cli
hlt

times 510-($-$$) db 0
db 0x55,0xaa
  • 在实模式下,处理器将段界限预置成0xFFFF;
  • 在保护模式下,段描述符高速缓存器里的段界限不是处理器预置的,而是来自段描述符。

通过Bochs查看ds信息:

image

实模式下访问全部4GB内存的方法:先从实模式进入保护模式,为段寄存器(比如DS)选择一个基地址部分高于1MB的段描述符,然后从保护模式退回到实模式,就可以利用段寄存器中的残留内容来访问超过1MB以上的内存。

一般指令在32位操作尺寸下的扩展

由于32位的处理器都拥有32位的寄存器和算术逻辑部件,而且同内存芯片之间的数据通路至少是32位的,因此,所有以寄存器或者内存单元为操作数的指令都被扩充,以适应32位的算术逻辑操作

加法指令add:在32位处理器上,除了允许8位或者16位的操作数,32位的操作数现在也是可用的:

add al,bl    
add ax,bx
add eax,ebx                  ;32位操作数
add dword [ecx],0x0000005f   ;32位立即数

单操作数指令:除了双操作数指令,单操作数指令也同样允许32位操作数,比如:

inc al
inc dword [0x2000]
dec dword [eax*2]    ;32位操作数

逻辑移动指令

shl eax,1    ;32位操作数
shl eax,9    ;32位操作数

; 逻辑移动指令的源操作数如果是
寄存器的话,则依然必须使用CL。
; 32位处理器在实际执行时,要先将源操作数(在寄存器CL内)同0x1F做逻辑与。
; 也就是说,仅保留源操作数的低5位,因此,实际移动的次数最大为31。
shl dword [eax*2+0x08],cl 

为什么最大的移动次数是31位?

因为 0x1F 二进制表示为 0001_1111B,
cl & 0001_1111B 只会保留最后5位。
最大就只能是:0010_0000B - 1B = 2^5-1 = 31 

循环次数

  • 默认操作尺寸是16位的,loop指令的循环次数在寄存器CX中;
  • 默认操作尺寸是32位的,则使用的是寄存器ECX;

乘法指令mul

;16位处理器上
mul r/m8        ;AX <- AL*r/m8,
mul r/m16       ;DX:AX <- AX*r/m16,高位存储在DX,低位存储在AX
;32位处理器上
mul r/m32       ;EDX:EAX <- EAX*r/m32,高位存储在EDX,低位存储在EAX

有符号数乘法指令imul与此相同。

除法指令div:无符号数和有符号数除法也做了32位扩展。

div r/m32 
idiv r/m32
  • 被除数是64位的,高32位在寄存器EDX;低32位在寄存器EAX。
  • 除数是32位的,位于32位的寄存器,或者存放有32位实际操作数的内存地址。
  • 指令执行后,32位的商在寄存器EAX,32位的余数在寄存器EDX。

push和pop:允许压入双字(32位)操作数。

push imm8    ;立即压入8位操作数,操作码是6A
push imm16   ;立即压入16位操作数,操作码是68
push imm32   ;立即压入32位操作数,操作码是68

压入字节:用byte修饰。

push byte 0x55 ;16位和32位机器码都是:6A 55
               ;  16位:压入0x0055,(SP)-2
               ;  32位:压入0x00000055,(ESP)-4

压入字:用word修饰。

push word 0xfffb ;被看成有符号数。
                 ; 16位:压入0xfffb
                 ; 32位:压入0xfffffffb

压入双字:用dword修饰。

push dword 0xfb ;16位和32位均压入0x000000FB
                ;栈指针(SP或ESP)都先减去4

操作数位于通用寄存器或内存单元:对于实际操作数位于通用寄存器,或者位于内存单元的情况,只能压入字或者双字。

push r/m16    ;16位寄存器或者内存地址
push r/m32    ;32位寄存器或者内存地址

例如:

;寄存器,则可以使用16位或者32位的通用寄存器。
push ax    ;16位寄存器,字
push edx   ;32位寄存器,双字

;内存,关键词word或者dword修饰。
push word [0x2000]            ;字
push dword [ecx+esi*2+0x02]   ;双字

无论是在内存还是寄存器中,

  • 在16位模式下,如果压入的是字操作数,那么先将SP的内容减去2;如果压入的是双字,应当先将SP的内容减去4。
  • 在32位模式下,如果压入的是字操作数,那么先将ESP的内容减去2;如果压入的是双字,应当先将ESP的内容减去4。

压入段寄存器:压入段寄存器的操作比较特殊,以下是压入段寄存器的push指令格式。

push cs    ;机器指令位0E
push ds    ;机器指令位1E
push es    ;机器指令位06
push fs    ;机器指令位0F A0
push gs    ;机器指令位0F A8
push ss    ;机器指令位16
  • 在16位模式下,先将SP的内容减去2,然后直接压入段寄存器的内容;
  • 在32位模式下,要先将段寄存器的内容用零扩展到32位,即高16位为全零,然后,将ESP的内容减去4,再压入扩展后的32位值。

本章习题

第1题:

image

第2题:

image

如果是按照32位操作数的写法,按照32位编译,那么 mov ebx,16 会多出16位高位部分,按照32位执行就没有问题。

现在是16位操作数尺寸的写法,按照16位进行编译,按照32位执行,所以当执行到 BB 这条指令的时候会把指令当成是32位的,会把BB指令后面32位的数字当做立即数,即1000F7E3,就是将后面F7E3指令当成是立即数的部分了。

书上没有给出答案,我尝试在文心一言问了一下,意思应该是一样的,关键句:隐式的扩展到32位操作

image

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晴空闲雲

感谢家人们的投喂

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值