操作系统真象还原实验记录之实验四:第一次进入保护模式

操作系统真象还原实验记录之实验四:保护模式

对应书中p158 第4.3.5节 让我们进入保护模式

一.相关基础知识

1.段描述符 GDT LDT GDTR LDTR 选择子

1.1 段描述符:

在这里插入图片描述
本次实验的代码中向GDT中写入了3个段描述符,分别为数据段、代码段、显存段,代码中大量篇幅都是在拼出64位段描述符

1.2 GDTR:

指令格式:lgdt 48位内存数据。 GDTR记录GDT内存起始地址
在这里插入图片描述
其中GDT界限单位是字节

1.3 LDTR:

指令格式:lldt 16位寄存器/16位内存数据
表示将某个16位寄存器数据或者16位内存数据放入LDTR寄存器。
如果GDTR是GDT的指针
那么LDTR是LDT指针的指针。
GDTR内高32位记录了GDT的内存起始地址,但是,
LDTR只有16位,并不能记录LDT的内存起始地址,所以LDTR充当选择子作用,它的高13位记录着LDT的描述符的索引值,也就是LDTR指向LDT的描述符。
LDT的描述符是GDT中的一个段描述符,将该描述符的段基址拼在一起就是LDT的内存起始地址。

1.4 选择子:TI为0表示用于索引GDT中的描述符 TI为1表示用于索引LDT中的描述符

在这里插入图片描述
选择子中的描述符索引值只是索引值,TI为0时,该索引值代表该段描述符是GDT中的第几个段描述符
TI为1时,该索引值代表该段描述符是LDT中的第几个段描述符

1.5 GDT:全局描述符表 LDT:局部描述符表
1.6上述五者之间的联系
1.6.1计算GDT中某个段的段描述符的首地址

GDT中某个段的段描述符的首地址=
该段对应的选择子的高13位 * 8+GDTR的高32位

举例:
本次实验的代码loader.s中
定义显存段的选择子的代码如下句

SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0 

GDT的首地址的标记为GDT_BASE
赋值GDTR寄存器代码如下

gdt_ptr dw GDT_LIMIT 
		dd GDT_BASE
;;;;;;源代码上下两者不是写在一起;;;;
lgdt [gdt_ptr ]

于是公式就成了
显存段段描述符的首地址=3*8+GDT_BASE

因为代码里定义的显存段在GDT中是第三个段,从选择子就可以看出来。一个段描述符有64位即8个字节,内存编号一个字节有唯一的一个编号。

1.6.2计算LDT中某个段的段描述符的首地址

LDT中某个段的段描述符的首地址=
该段对应的选择子的高13位 * 8+LDT的内存起始地址
LDT的内存起始地址位于LDT的段描述符中。获得了LDT的段描述符首地址等于获得了LDT的首地址
LDT的段描述符的起始地址=
LDTR的高13位*8+GDTR中的GDT内存起始地址

本次实验代码只涉及GDT 故无举例。

2.8086 80286 80386 实模式到保护模式的变化

  1. 首先8086只有实模式,16位通用寄存器,20位地址线,因此可以访存1MB,由于访存方式为 段寄存器 * 16 + 段偏移寄存器,故当ds为FFFF:bx为大于F,则会出现地址进位,称之为地址回绕。一个段偏移寄存器16位访存范围位64KB够用。
  2. 80286 有实模式和保护模式,16位通用、段寄存器,24位地址线,实模式下禁用第21根地址线,保留了地址回绕,为了兼容8086;保护模式下打开第21根地址线,段描述符缓冲寄存器为48位保存24位段基址,访存范围为16MB,但是由于通用寄存器16位仍然只能访问64KB,因此被淘汰。
  3. 80386 32位通用寄存器,段寄存器仍然16位,32位地址线,段基址还是段偏移范围都是4GB,有虚拟8086模式和保护模式。

3. 保护模式下寻址方式的扩展

3.1 直接寻址,包括立即数寻址,寄存器直接寻址

这类操作数就是立即数或者就是寄存器内容,无需访存

3.2 内存寻址
3.2.1 格式[0x1234] 默认ds 或[gs:0x1234]

内存地址直接给出,无需寄存器

3.2.2 内存地址由寄存器给出

在这里插入图片描述

格式1 [R]

R为16位时,可以为bx、bp、si、di
R位32位时,可以为EAX、EBX、 ECX、EDX、EBP、ESI、EDI、ESP
其中[R]为bp、EBP、ESP的时候,默认为SS,其余默认DS。

格式2 [IR * F + V] 等价于[IR * F] + V 等价于 V[R * F]

IR的内容乘F加上V作为偏移地址,F可以为1、2、4、8
IR16位时,IR可以为bx、bp、si、di,F只能是1省略不写,V是不超过16位二进制补码有符号数或数值表达式、变量名、标号名,bp默认ss,其余默认ds。

IR为32位时,可以为EAX、EBX、 ECX、EDX、EBP、ESI、EDI、ESP
IR若为ESP,F只能是1,IR为EBP、ESP默认ss,其余默认ds
V是不超过32位二进制补码有符号数或数值表达式、变量名、标号名

格式3 [BR + IR * F+V] 或 V[BR][IR * F] 或· V[BR+IR * F]

在这里插入图片描述
当选用16位寄存器时,BR只能是bx、bp之一,IR只能是si、di之一,F只能是1
BR=bx时,默认ds;BR=bp时默认ss,放在前面的是BR
当选用32位寄存器时,IR不能是ESP,BR可以等于IR,BR等于EBP、ESP时,默认ss,其余默认ds,F为1时省略,放在前面的是BR

intel语法汇编指令如何使用这些寻址方式的

寻址方式的本质就是提供操作数方式,理解了获取操作数的方式,还要结合具体指令来理解具体指令是如何获取自己需要的操作数的
以mov指令举例
格式 MOV OPD, OPS
即(OPS)->OPD
在这里插入图片描述

理解:MOV指令中,OPS是源操作数,该操作数被视为内容;OPD是目的操作数,该操作数被视为地址,不能是立即数,可以是[0x1234]。从指令解释可以看出,OPD天然被加了括号。

【注】intel语法汇编将数字优先视为内容操作数,[数字]中的数字才当作内存地址;
AT&T语法则是将数字优先视为内存地址,$数字,才被视为立即数。
因为OPD只能是地址,所以不能立即数,因为intel语法会将立即数优先视为内容
但如果[0x1234]就可以充当OPD,因为加了[],0x1234就被视为内存地址。

1.如果OPS是寄存器

比如mov ax,ax;
等价于(ax)->ax (ax)表示ax中的内容,这属于寄存器直接寻址即寄存器直接提供操作数,ax表示寄存器地址。
mov [bx],[ax];
等价于((ax))->(bx) ((ax))表示ax的内容作为内存地址,访存取得操作数,(bx)表示bx中的内容,由于位于目的操作数,该内容会被视为内存地址。
((ax))就是[R]内存寻址格式,ax寄存器提供内存地址,访存获得操作数;(bx)就是寄存器直接寻址,bx寄存器直接提供操作数,只不过这个操作数被视为内存地址操作数。

这就是内存寻址、寄存器直接寻址在mov指令的具体使用,显然mov可以采用很多种寻址方式。

2.如果OPS是立即数

mov ax, 0x1234;
0x1234视为内容操作数,赋值到ax
mov ax, [0x1234];
这就是直接提供地址的内存寻址,0x1234作为内存地址,寻址取得内容作为操作数,送入ax。

二.实验记录(本次实验不涉及LDT )

2.1实验目的

编写boot.inc和loader.s,完成在内存里写好3个段描述符(代码段、数据段、显存段)、赋值好GDTR寄存器、创建好表示3个选择子的字段后,进入保护模式(也就是那3句代码)
在保护模式下执行(也就是loader.s中最后三句代码)

mov ax, SELECTOR_VIDEO 
mov gs, ax 
 
mov byte [gs:160],’ P ’ 

在显示屏上能够打印“P”,说明在保护模式下只需要给一个显存段的选择子和一个偏移地址就能成功访问显存,实验即成功。

2.2 实验代码

2.2.1 mbr.s

将实验3中的mbr.s中的下段代码进行修改

mov cx, 1; 待读入内存的扇区数
call rd_disk_m_16;

修改后代码
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov cx, 4; 改成待读入4个扇区, loader.bin 超过了 512 字节
call rd_disk_m_16;

2.2.2 boot.inc
;一一一一一一loader和 kernel

LOADER_BASE_ADDR equ 0x900 
LOADER_START_SECTOR equ  0x2 
 
;一一一一一一- gdt 描述符属性 一一一一一一 
DESC_G_4K equ 1000_0000_0000_0000_0000_0000b   ;G位为第23位,置1代
												;表
;段界限为单位4k
DESC_D_32 equ 1_00_0000_0000_0000_0000_0000b ;D/B 宇段,第22位
							;对代码段来说是D位,置1表示指令中的有效地址及
							;操作数是32位,指令有效地址用EIP寄存器。

DESC_L equ 0_0000_0000_0000_0000_0000_0000b ; 64位代码标记,我们在32位CPU下编程,
;标记为0便可
DESC_AVL equ 0_0000_0000_0000_0000_0000b ;CPU不用此位,暂置为
DESC_LIMIT_CODE2 equ 1111_0000_0000_0000_0000b  ;段界限16~19位
				;全设为1,它在下面代码中会与段界限的0~15位拼成0xFFFF,
				;0xFFFF*4k等于4G,段基址设为0,采用平坦模型

DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2 ;
DESC_LIMIT_VIDEO2 equ 0000_0000_0000_0000_0000b ;
DESC_P equ 1_000_0000_0000_0000b   ;第15位,表示段存在
DESC_DPL_0 equ 00_0_0000_0000_0000b ;DPL在13~14位 0为最高特权级
DESC_DPL_1 equ 01_0_0000_0000_0000b 
DESC_DPL_2 equ 10_0_0000_0000_0000b 
DESC_DPL_3 equ 11_0_0000_0000_0000b 
DESC_S_CODE equ 1_0000_0000_0000b ; S为0时表示系统段, S为1时表示非系统段。
DESC_S_DATA equ DESC_S_CODE 
DESC_S_sys equ 0_0000_0000_0000b 
DESC_TYPE_CODE equ 1000_0000_0000b ;x=1,c=0, r=0,a=0 ,即代码段是可执行的,非一致
;性,不可读,己访问位a清0 配合S使用
DESC_TYPE_DATA equ 0010_0000_0000b ;
;x=0,e=0,w=1,a=0 数据段是不可执行的,向上扩展的,可写,己访问位a清0。

DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + \
DESC_P + DESC_DPL_0 + DESC_S_CODE +\
DESC_TYPE_CODE + 0x00 

DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 +\
DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + \
DESC_P + DESC_DPL_0 + DESC_S_DATA + \
DESC_TYPE_DATA + 0x00

DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 +\
DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + \
DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0B   ;注意书上这里写的是0x00,写错了
;显存起始地址应该是0xB8000

 
;一一一一一一 选择子属性一一一一一一一
RPL0 equ 00b 
RPL1 equ 01b 
RPL2 equ 10b 
RPL3 equ 11b 
TI_GDT equ 000b 
TI_LDT equ 100b

代码功能总结:在这里插入图片描述
核心就是根据上图的段描述符高32位,依次定义字段最后相加,合成了代码段、数据段、显存段三个段的段描述符的高32位的字段。方便loader.s引用这些字段来构建代码段、数据段、显存段描述符。
在这里插入图片描述
然后又定义了选择子的TI和RPL字段方便在loader.s中构建选择子

2.2.3 loader.s
;------------------------
%include "boot.inc" 
section loader vstart=LOADER_BASE_ADDR 
LOADER_STACK_TOP equ LOADER_BASE_ADDR  ;相同内存地址,地址之下便是栈
jmp loader_start 

;构建 gdt 及其内部的描述符
GDT_BASE: dd 0x00000000       ;第0个段描述符不可用
		dd 0x00000000  

CODE_DESC: dd 0x0000FFFF   ;代码段描述符
		 dd DESC_CODE_HIGH4 
 
DATA_STACK_DESC: dd 0x0000FFFF ;栈段描述符 栈段和数据段共用一个描述符 均向上扩展
		 dd DESC_DATA_HIGH4  
 
VIDEO_DESC: dd 0x80000007   ; limit=(0xbffff-0xb8000)/4k=0x7  故段界限为7
			dd  DESC_VIDEO_HIGH4  ;此时dpl为0
			 
GDT_SIZE equ $ - GDT_BASE ; 先是通过地址差来获得 GDT的大小,进而用 GDT大小减1得到了段界限
GDT_LIMIT equ GDT_SIZE - 1   ;用于构建GDTR的段界限
times 60 dq 0 ;此处预留 60 个描述符的空位

;以下是构建代码段、数据段、显存段选择子
SELECTOR_CODE equ (0x0001 << 3) + TI_GDT + RPL0 
;相当于[(CODE_DESC - GDT_BASE) /8 ]<<3+ TI_GDT + RPL0 
;内存地址的编号是一个存储单元8比特,这里CODE_DESC - GDT_BASE应该等于8
;书里的备注应该写掉了"<<3"
SELECTOR_DATA equ (0x0002<< 3) + TI_GDT + RPL0 
SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0 

;以下是 gdt 的指针即GDTR,前2字节是gdt界限,后4字节是gdt起始地址 后面代码使用lgdt指令时会用上
gdt_ptr dw GDT_LIMIT 
dd GDT_BASE 

 loadermsg db '2 loader in real.' 

 loader_start: 
 
;------------------------------------------------------------
; INT 0x10 功能号: 0x13 功能描述:打印字符串
;------------------------------------------------------------
;输入:
;AH 子功能号=13H
;BH =页码
;BL =属性(若 AL=OOH OlH)
;CX =字符串长度
; (DH DL )=坐标{行、列)
;ES:BP=字符串地址
;AL=显示输出方式
; 一一字符串中只含显示字符,其显示属性在 BL
;显示后,光标位置不变
; 一一字符串中只含显示字符,其显示属性在 BL
;显示后,光标位置改变
; 一一字符事中含显示字符和显示属性。显示后,光标位置不变
; 一一字符串中含显示字符和显示属性。显示后,光标位置改变
;无返回值
mov sp, LOADER_BASE_ADDR 
mov bp, loadermsg ; ES:BP =字符串地址
mov cx, 17 ; cx =字符串长度
mov ax, 0x1301 ; AH = 13, AL = 01h 
mov bx, 0x001f ;页号为0(BH = 0)蓝底粉红字( BL = 1fh) 
mov dx, 0x1800  ;  dh=0x18 十进制为24,代表行数;dl=0x00 表示列数。使用显存的文本模式下,一共25行,所以2 loader in real 会出现在屏幕最后一行
int 0x10 ; 10h号中断 由于AH=0x13,所以该BIOS中断后会执行打印字符串的中断处理程序。
 
; 一一一一一一一一一一 准备进入保护模式 一一一一一一一一一一一一一一-
;1 打开 A20
;2 加载 gdt
;3 将cr0 的 pe 位置1 

;一一一一一一一-打开 A20 一一一一一
in al,0x92
or al, 0000_0010B 
out 0x92,al 
 
;一一一一一一一一加载 GDT (也就是设置好gdtr,gdtr记录着gdt的起始地址)一一一一一一一-
lgdt [gdt_ptr ] 

;一一一一一一一一 cr0位置1 一一一一一一一-
mov eax, cr0 
or eax, 0x00000001 
mov cr0, eax 

jmp dword SELECTOR_CODE:p_mode_start  ;刷新流水线

[bits 32] 
p_mode_start: 
;;;;用选择子初始化段寄存器
	mov ax, SELECTOR_DATA 
	mov ds, ax 
	mov es, ax 
	mov ss, ax 
	mov esp, LOADER_STACK_TOP 
	mov ax, SELECTOR_VIDEO 
	mov gs, ax 
	
	
	mov byte [gs:0xA0], 'P' 
 
	jmp $

lodaer.s代码功能总结:

  1. 通过给出低4字节加上引用boot.inc定义好的高4字节字段来向内存写入无用的第0段和3个有用段的段描述符(代码段、数据段、显存段)。并将他们的内存地址依次标记为GDT_BASE、CODE_DESC、DATA_STACK_DESC、VIDEO_DESC

  2. GDT_BASE因此为GDT的内存起始地址,可以用来创建GDTR。根据上述标号可以计算出3个段描述符各自的索引,从而可以创建各个段的选择子。

  3. 内存里写好3个段描述符、赋值好GDTR寄存器、创建好表示3个选择子的字段后,在实模式下利用BIOS中断13号中断打印字符串“2 loader in real”
    再执行“3步走”代码打开保护模式,然后用定义好的选择子字段初始化段寄存器,
    通过指令:
    mov byte [gs:160],’ P ’
    在保护模式下访问内存的显存段,并写入“P”字符。所以当程序执行完保护模式下的代码后,显示屏上会打印出“P”字段。

  4. 数据段和代码段段描述符段基址均为0,段界限均为4GB,即平坦模式:段偏移寄存器32位访问范围4GB,不可能换段。ss和ds寄存器都为数据段选择子。

  5. 显存段段基址为0xb8000,用于访问显存,gs为显存段选择子。

2.3实验记录

1.编译loader.s

nasm -o loader.bin loader.s

2.编译mbr.s

nasm -o mbr.bin mbr.s 

3.将mbr.bin刻入第0扇区

dd if=/home/Seven/bochs2.68/bin/mbr.bin of=/home/Seven/bochs2.68/bin/Seven.img bs=512 count=1 seek=0 conv=notrunc

4.将loader.bin刻入第2扇区(注意count=2书上为1,因为loader.bin618字节超过了512字节,其实是刻入第2和第3扇区)

dd if=/home/Seven/bochs2.68/bin/loader.bin of=/home/Seven/bochs2.68/bin/Seven.img bs=512 count=2 seek=2 conv=notrunc

5.模拟bochs

./bochs -f bochsrc.disk

效果图
在这里插入图片描述

2.4实验结果

在这里插入图片描述

2.5 实验总结

主要就是总结执行下面的指令时CPU偷偷做了哪些事,涉及到保护模式下的内存段保护措施与寻址方式方面的知识

	mov ax, SELECTOR_VIDEO 
	mov gs, ax 
	mov byte [gs:0xA0], 'P'
2.5.1保护模式下内存段的保护措施(书P173)
2.5.1.1向段寄存器加载选择子时的保护

指令执行到mov gs,ax时,不会直接将ax赋值给gs。
在这里插入图片描述
(1)处理器会先判断ax的描述符索引值是否有效、是否越界。
处理器先检查ax的TI位,本实验会发现是1,表示ax是GDT某个段描述符的指针。
这时如果描述符索引值为0,则无效,此条指令抛异常,因为GDT第0个描述符是无效描述符。
如果不是0,便从GDTR中获得低16位即段界限和高32位GDT基址。段界限单位是字节。所以直接通过公式:
索引值*8+7+GDT基址<=段界限-1+GDT基址
判断是否越界。如果越界则抛异常

(2)如果有效且未越界,cpu会进行段类型检查
处理器会计算ax对应的段描述符的首地址即:
显存段段描述符首地址=
ax的高13位*8+GDTR的高32位
然后找到显存段段描述符的TYPE字段,进行段类型检查

在这里插入图片描述
不符合规则抛异常,
(3)如果符合,再检查P位来确认段是否存在,如果为0,抛缺页异常,执行缺页中断处理程序。

如果P为1,便将A位置1,表示已访问。

此时加载选择子时的所有保护措施才算通过,段寄存器gs才能被赋值为ax,
同时还会把显存段段描述符放入段描述符缓冲寄存器。
下次要用段描述符中的字段就直接从缓冲寄存器里取,无需再次访存。

2.5.1.2 访问代码段和数据段的保护

对于代码段和数据段来说,CPU每访问一个地址,都要确认该地址不能越界其内存段的范围。
当CPU执行到指令:mov byte [gs:0xA0], 'P’时,就是在访问数据段中的显存段,会先进行保护检查,查看是否越界。

(1)先在段描述符缓冲寄存器找到G位
显存段段描述符的G位本次实验已置为1,表示段界限单位为4K

DESC_G_4K equ 1000_0000_0000_0000_0000_0000b   ;G位为第23位,置1代表段界限为单位4k

(2)然后再找到段界限值
本次实验显存段段描述符的段界限19~16位已全置为0,后16位的十进制为7,段界限值为7,注意,段界限值只是个偏移量,并且是从0算起的偏移量

DESC_LIMIT_VIDEO2 equ 0000_0000_0000_0000_0000b ;
;;;;;;;;;;;;;;;;;;;;;;;;;源代码上下不在一起;;;;;;;;;;;;;;;;;;;;;;;
VIDEO_DESC: dd 0x80000007   ; limit=(0xbffff-0xb8000)/4k=0x7  故段界限为7
			dd  DESC_VIDEO_HIGH4  ;此时dpl为0

(3)最后在段描述符缓冲寄存器中拼出32位显存段段基址为0x000B8000
根据书上的公式:
段基址+偏移地址 <= (段界限值+1)*4k-1+段基址

但是按照本次书上实验代码的批注,这里的段界限值代表的显存段可以容纳4K个字节的个数,所以并不是从0算起。4k段界限就是本次显存段可以容纳的字节数。
所以计算公式为
显存段段基址+偏移地址 <= 段界限值
4k-1+段基址
判断是否越界,越界则抛异常

本句代码偏移地址160,160<7*4k-1
显然不越界。

我觉得对于段界限从0还是从1算起,不是本次实验重点,无需深究。

2.5.2保护模式下内存段的寻址方式

保护模式下
[gs:160]的寻址方式上面已经涵盖了。
回忆实模式下
[gs:160]的寻址方式为:gs * 16+160
即 (段寄存器<<4)+段内偏移地址。
对比学习。

3.补充

关于loader.s中,进入保护模式后第一条指令的理解
指令如下

jmp dword SELECTOR_CODE:p_mode_start  ;刷新流水线

这句代码是第84行,看似无用,但不写程序会报错。

按照书上的原话:这条指令的功能是
既要改变段描述符缓冲寄存器的值,又要清空流水线。
依此句话便可理解这条指令。

3.1 为什么要刷新cs对应的段描述符缓冲寄存器的值

3.1.1.段描述符缓冲寄存器作用(书P139)

先直接说结论,它的作用在不同模式下不同。
实模式下段描述符缓冲寄存器的作用是省去了段基址左移4位的重复计算
保护模式下段描述符缓冲寄存器的作用是减少访存次数。
在这里插入图片描述

比如,
保护模式下的访存:
要获得段内地址的前提是要先获得该段基址,而段基址位于段描述符,所以要获得段基址必须先获得段描述符基址。
所以在没有段描述符缓冲寄存器之前,每次访问段内某个地址都需要两次访存。
在拥有缓冲寄存器器之后,第一次访问该段需要两次访存,同时该段段描述符就被赋值给了段描述符缓冲寄存器。第二次以后的每次访问该段段内地址访存都是1次。

实模式下:
虽然段描述符缓冲寄存器是用来保存段描述符的,段描述符是保护模式下的产物,实模式并没有。
但是,实模式下段寄存器都是16位,为了只通过寄存器寻址就能访问1MB内存地址,实模式下的寻址方式为段基址4+段内偏移地址。
因此实模式下每次访存避免不了左移四位的重复计算。
这时段描述符缓冲寄存器不用白不用,用来保存段基址
4后的值,从而每次访存可以避免重复计算。

3.1.2为什么要改变段描述符缓冲寄存器(书P172)

在这里插入图片描述
实模式下,段描述符缓冲寄存器保存的是16位段基址左移4位的,只有20位有效,其余均无效,为0,这也包括D/B位。段描述符缓冲寄存器的值在重新引用对应段寄存器后才会更新。
因此当进入保护模式后,段描述符缓冲寄存器的属性值很多都是错的,其中D/B为0,一定是错的。
在这里插入图片描述
首先D/B位对于代码段是D,对于栈段是B。保护模式有32位保护模式和16位保护模式(用于兼容的实模式),cs对应段描述符缓冲区寄存器的D位为1且开启了保护模式,那么代码位于32位段下。

其次0x66用于操作数数据类型反转,0x67用于有效地址数据类型反转,
或者说寄存器寻址使用0x66,寄存器间接寻址使用0x67。

因为x86指令编码规则下,eax与ax编号相同,无法区分。
程序员16位段下若使用mov eax, 0x1234;,表示程序员想使用eax内容作为目的操作数
但16位段cpu默认使用ax,所以编译需要增加0x66反转指明eax。
或者32位段下mov dword [ax], 0x1234;
cpu默认使用eax,需要增加0x67反转指明有效偏移地址存于ax。

若32位段([bits 32]伪指令下)使用push ax;
NASM会编译成两条mov指令,(esp) - 2 -> esp; (ax)-> (esp);第二句mov加上0x66反转
倘若执行此句代码时,cs对应的段描述符缓冲寄存器的D位为1,ss对应的B位也为1,那么这句代码就真的位于32位代码段,32位栈段,默认使用eax,esp。0x66反转eax成ax,esp是寄存器间接寻址,需要0x67反转。故32位下成功使用ax,esp。

假设没有84行代码,接下来分析89行代码报错原因:
首先分析NASM将89行代码转换成的机器码:
mov ax,立即数;
这句汇编代码数据类型是16位,所以机器码首先是
MOV的操作码 + ax编号与寻址方式(寄存器寻址)+ 16位立即数
由于伪指令[bits 32]的原因,源、目的操作数都应该会被cpu当成32位送入ALU,所以需要加操作数反转符号0x66。
再来分析89行机器码上cpu过程:
89行代码译码时,82行代码正在执行,所以89行代码仍处于实模式,按16位操作数译码,16位接收到0x66反转信号变成32位,源、目的操作数被当成32位ALU,然后ALU会把32位立即数送入eax。这样就造成了严重错误,本来只读16位立即数,现在你多读了16位,明显你读的是下一条指令的机器码的内容!!!

如果加上84行代码
首先84行代码会被编译成16位指令,然后执行前也是按16位译码,所以正确执行。
cs被重新赋值,D位为1,同时指令流水线也被刷新。89行代码上流水线时,cpu也会按32位译码,所以执行正确。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值