这一章比较难,断断续续看了3天,才搞懂“实模式到保护模式的跳转”
自己的一点心得:
1.保护模式非常复杂,涉及方方面面的规则,不要想一次性、完全弄懂,每次只学该学的就好。
2.CR0(0号控制寄存器)的最后1位如果是0,CPU在实模式下运转;如果是1,在保护模式下运转。
3.一定要自己写一遍GDT、段选择符、以及实模式->保护模式跳转,不然是不可能明白的。
首先来谈谈保护模式下最基础的、最重要的——寻址方式,写寻址方式的文章很多很多,就不在赘述
(我前前后后看了10篇文章,好多教材,还是没懂,最后写了跳转代码、编译进镜像,跟着bochs虚拟机一点点调试才最后明白的)
jmp 保护模式的段选择符:目标语句在目标段的偏移量 这条语句代表:跳跃到保护模式下的目标语句。
大致的动作是这样:
CPU拿出段选择符->取段选择符前13位为段描述符在GDT中的索引-->根据GDTR基址寄存器找到GDT表位置-->取得需要的段描述符-->从中拿出32位的真正段基址-->加上一开始给出的偏移量(最一开始jmp语句中给出)-->偏移量+段基地址=获得物理地址(目前没有开启分页机制,不然要再分页一次,才能得到物理地址)
;使用NASM,如有疑问/bug,欢迎指出!
%include "pm.inc"
org 0700h
jmp LABEL_BEGIN
[section .GDT]
;GDT
; 段基址, 界限, 属性
LABEL_GDT: Descriptor 0, 0, 0
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len-1, DA_C+DA_32;这里的寻址方式似乎又出现了问题
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW
;GDT结束
GdtLen equ $ - LABEL_GDT ;GDT长度定义
GdtPtr dw GdtLen-1 ;GDT界限
dd 0 ;GDT基址
;GDT选择子
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
;
;end of [section .GDT]
[section .16] ;16位代码段
[BITS 16] ;原始指令,表示运行在16位处理器上
LABEL_BEGIN:
;将所有段寄存器和SP填好,这只是为了程序的完整性
;实际上除CS外的段寄存器,都并不会被用到
MOV AX,CS
MOV DS,AX
MOV SS,AX
MOV ES,AX
MOV SP,100H
;填写GDT中的descriptor
XOR EAX,EAX
MOV AX,CS
SHL EAX,4
ADD EAX,LABEL_SEG_CODE32
;将32位代码段的 起始物理地址的 前16位,写入段描述符的第3/4个字节内
MOV WORD [LABEL_DESC_CODE32+2],EAX;LABEL_DESC_CODE32代表着地址
SHR EAX,16
MOV BYTE [LABEL_SEG_CODE32+4],AL
MOV BYTE [LABEL_SEG_CODE32+7],AH
;将32位代码段的 起始物理地址的 前16位,写入段描述符的第5/8个字节内
;GDT中所有段描述符填写完成
;填写GDT寄存器的基地址
XOR EAX,EAX
MOV AX,CS
SHL EAX,4
ADD EAX,LABEL_GDT ;LABEL_BEGIN也是地址
MOV WORD [GdtPtr+2],EAX ;GdtPtr也是地址
;GdtPtr填写完成,低字节为段界限,高2字节为GDT的起始物理地址(32位)
;装入gdtr寄存器
LGDT [GdtPtr] ;也是地址
;装入完成
;关中断
CLI
;中断关闭完成
;开启A20地址线
IN AL,92H
OR AL,00000010B
OUT 92H,AL
;开启成功,内存不再回卷
;置位,即将打开保护模式
MOV EAX,CR0
OR AX,1
MOV CR0,EAX
;置位完成
;跳转 下面这句话编译后生成的是:jmp 0x0008H:00000000 偏移量是32位,而0x0008H不是基地址,而是段选择符的地址
JMP DWORD SelectorCode32:0;此时SelectorCode32究竟是“SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT”这句话的偏移地址还是“LABEL_DESC_CODE32 - LABEL_GDT”
;上面这句话已经是在保护模式下了,cpu接到这条指令后,将会自动在selectorcode32选择符中提取索引
;找到段描述符,提取段基地址,加上偏移量后进行寻址
;不加 dword时,如果后面偏移量不是0,可能会发生截断(0x95357454H->0x7454H)
;end of [section .16]
[SECTION 32] ;32位代码段
[BITS 32]
LABEL_SEG_CODE32:
MOV AX,SelectorVideo
MOV GS,AX
;将video选择子的地址放入gs中
;显示字
MOV EDI, (80*11+79)*2 ;11行,79列
MOV AH,0CH ;黑低红字
MOV AL,'P'
MOV [GS:EDI],AX ;覆写显存
;显示完成
JMP $
SegCode32Len equ $-LABEL_SEG_CODE32
;end of []
对实模式—>保护模式的跳转进行了更加详尽的注释。
其余的部分请大家看于渊老师的《Orange's》一书。
然后是一致/非一致代码段的call/jmp限制(保护模式下不能随意jmp执行的),做了一个思维导图:
如果我们在甲段;想访问乙段(一致段),那么只要乙>=我们,就可以访问:
接下来谈一谈描述符属性:也是很复杂的事情。
找到了一篇很好的文章:段描述符和段选择符解析,通过这文章,暂时明白了段描述符的意义。
新知识:
1.段界限值(20bit=1M),在G=0(不允许分页情况下),代表段最大长1M个字节,即段最大为1MB
在G=1(允许分页下),代表段最大长1M个页(1个页4kb),即段最大4GB
2.系统段描述符一般都在中断/异常描述符表中