根据书本上的知识学习 大牛可以无视 本菜的自我总结
实模式到保护模式的总步骤
1、初始化GDT和选择子
2、16位初始化32位代码段描述符,为进入32位代码段做准备
3、加载gdtr
4、关闭中断
5、打开地址线
6、用jmp正是切换到保护模式
%include "pm.inc" ; 常量, 宏, 以及一些说明
org 0100h
jmp LABEL_BEGIN
[SECTION .gdt]
; GDT
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致代码段, 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 .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
; 初始化 32 位代码段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al
mov byte [LABEL_DESC_CODE32 + 7], ah
; 为加载 GDTR 作准备
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt 基地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
; 加载 GDTR
lgdt [GdtPtr]
; 关中断
cli
; 打开地址线A20
in al, 92h
or al, 00000010b
out 92h, al
; 准备切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
; 真正进入保护模式
jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处
; END of [SECTION .s16]
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子(目的)
mov edi, (80 * 10 + 0) * 2 ; 屏幕第 10 行, 第 0 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, 'P'
mov [gs:edi], ax
; 到此停止
jmp $
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
org 0100h
这是一个com程序,起始地址运行在0100h上
Descriptor是一个宏定义:
%macro Descriptor 3
dw %2 & 0FFFFh ; 段界限 1 (2 字节)
dw %1 & 0FFFFh ; 段基址 1 (2 字节)
db (%1 >> 16) & 0FFh ; 段基址 2 (1 节)
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性 1 + 段界限 2 + 属性 2 (2 字节)
db (%1 >> 24) & 0FFh ; 段基址 3 (1 字节)
%endmacro ; 共 8 字节
在保护模式下,虽然段值仍然由原来的16位cs ds等寄存器表示,但此时它仅仅变成了一个索引,这个索引指向一个数据结构的一个表项。这个数据结构就是GDT(LDT)。GDT的作用是用来提供段式存储机制,这种机制通过段寄存器和GDT中的描述符共同提供的。GDT中的表项称为描述符 Descriptor。
本图表示代码段和数据段描述符。描述符的种类还有系统端描述符和门描述符。
; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓
; ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃
; ┣━━╋━━╋━━╋━━╋━━┻━━┻━━┻━━╋━━╋━━┻━━╋━━╋━━┻━━┻━━┻━━┫
; ┃ G ┃ D ┃ 0 ┃ AVL┃ 段界限 2 (19..16) ┃ P ┃ DPL ┃ S ┃ TYPE ┃
; ┣━━┻━━┻━━┻━━╋━━━━━━━━━━━╋━━┻━━━━━┻━━┻━━━━━━━━━━━┫
; ┃ ③: 属性 2 ┃ ②: 段界限 2 ┃ ①: 属性1 ┃
; ┗━━━━━━━━━━━┻━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━┛
; 高地址 低地址
这个宏把段基址、段界限和段属性安排在一个GDT描述符中合适的位置。本例中的GDT共有三个描述符。这里总称为:DESC_DUMMY\DESC_CODE32\DESC_VIDEO
不过这个宏本菜是看不懂啦!233自嘲
我们看这个LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW中,9B8000h为段基址,0fffffh位段界限,DA_DRW为段属性。这个描述符指向的正是显存。
关注下面的代码:
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子(目的)
mov edi, (80 * 10 + 0) * 2 ; 屏幕第 10 行, 第 0 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, 'P'
mov [gs:edi], ax
; 到此停止
jmp $
mov ax, SelectorVideo
mov gs,ax
此时段寄存器gs变成了SelectorVideo
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
直观上看,DESC_VIDEO这个描述符相对于GDT基址的偏移。实际上,DESC_VIDEO叫做选择子Selector,不是一个偏移,它的结构如下:
; 选择子图示:
; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓
; ┃ 15 ┃ 14 ┃ 13 ┃ 12 ┃ 11 ┃ 10 ┃ 9 ┃ 8 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃
; ┣━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━╋━━╋━━┻━━┫
; ┃ 描述符索引 ┃ TI ┃ RPL ┃
; ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━┻━━━━━┛
当TI RPL位0 选择子就编程了相对于GDT基址的偏移。
mov [gs:edi],ax这行,gs指示对应显存的描述符DESC_VIDEO,这条指令把ax的值写到显存中偏移位edi中的位置。
*包含描述符的,不仅只有GDT,还有LDT。
关注[SECTION .s16]这段:
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
; 初始化 32 位代码段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al
mov byte [LABEL_DESC_CODE32 + 7], ah
; 为加载 GDTR 作准备
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt 基地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
; 加载 GDTR
lgdt [GdtPtr]
; 关中断
cli
; 打开地址线A20
in al, 92h
or al, 00000010b
out 92h, al
; 准备切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
; 真正进入保护模式
jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0 处
; END of [SECTION .s16]
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE32
LABEL_SEG_CODE32的物理地址即位[SECTION .s32]这个段的物理地址赋值给eax
也就是有效地址=段地址*16+偏移地址 mov word [LABEL_DESC_CODE32 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al
mov byte [LABEL_DESC_CODE32 + 7], ah
这三个mov分别赋给DESC_CODE32中的相应位置。由于DESC_CODE32的段界限和属性已经指定,所以DESC_CODE32的初始化全部完成。
接着执行lgdt [GdtPtr]指令
这个指令的作用是将GdtPtr指示的6字节加载到寄存器gdtr。
紧接着cli关中断,是因为保护模式下中断处理的机制是不同的,不关掉中断会出现错误。
接着是打开A20地址线,这事一个历史遗留问题。8086有20位地址总线,只能寻址到1MB。为了能访问1MB以上的内存,我们必须打开A20地址线。
in al, 92h
or al, 00000010b
out 92h, al
打开的方法并不唯一。
mov eax, cr0
or eax, 1
mov cr0, eax
cr0寄存器的第0位是PE位,此位为0时,CPU运行于实模式。为1时,CPU运行于保护模式。但是此时cs仍然是实模式下的值。我们需要吧代码段的选择子装入cs。
历史性的
jmp dword SelectorCode32:0
终于到来了。我们要跳转到DESC_CODE32对应的段的首地址,即LABEL_SEG_CODE32处。
为什么要加入DWORD呢?因为偏移地址是32位的。
描述符属性是啥?非常庞大。网上可以搜得到图片。
以DA_C+DA_32为例,DA_C=10011000b P位为1这个段在内存中存在,S位为1表明这个段是代码段或者是数据段,TYPE=8表明这个段是只执行的段。
以上本菜只是随着书本上的内容做了总结,琢磨了一个半个晚上,到现在还有点晕乎乎的,是该再研究几遍的节奏了。
欢迎大牛打脸!!!欢迎喷子!!