核心目的:实现由实模式到保护模式的转换
核心步骤:
1)程序定义了GDT数据结构
2)16位代码进行了一些与GDT有关的操作
3)程序最后跳到32位代码中做了一点操作显存的工作
进入保护模式的主要步骤:
1)准备GDT
2)用lgdt加载gdtr
3)关中断、打开A20
4)置cr0的PE位
5)跳转,进入保护模式
逻辑地址到线性地址的转换:
1)先从GDTR寄存器中获得GDT基址。
2)然后再GDT中以段选择器高13位位置索引值得到段描述符。
3)段描述符符包含段的基址、限长、优先级等各种属性,这就得到了段的起始地址(基址),再以基址加上偏移地址yyyyyyyy才得到最后的线性地址。
怎样查看线性地址的内容:
sudo ndisasm -o 0x7c00 pmtest1.bin >> wz.asm 同
x /80bx 0x00007c80 命令对比,可以看到线性地址中的内容一一对应
; ==========================================
; pmtest1.asm
; 编译方法:nasm pmtest1.asm -o pmtest1.bin
; ==========================================
%include "pm.inc" ; 常量, 宏, 以及一些说明
org 07c00h
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 .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 //eax为 cs:LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + 2], ax //将LABEL_SEG_CODE32放到第二个字(也即3 4字节)
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al //将al放到低字节
mov byte [LABEL_DESC_CODE32 + 7], ah //将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 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, 'P'
mov [gs:edi], ax
; 到此停止
jmp $
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
1.实模式
Intel 8086 16位CPU,有16位的寄存器、16位的数据总线以及20位的地址总线(1MB的寻址能力)。
物理地址 = 段地址 x 16 + 偏移
此时段地址可以看做是地址的一部分。
2.保护模式
Intel 80386进入32位时代,有32位地址总线(寻址空间可达4GB)。
保护模式下段的概念发生了变化,段地址仍虽然由原来16位cs、ds寄存器表示,但此时它变成了一个索引,这个索引指向一个数据结构的一个表项,表项中详细定义了段的起始地址、界限、属性等。这个数据结构,就是GDT(LDT),GDT中的表项也有一个专门的名字,叫做描述符。
GDT的作用是提供段式存储机制,这种机制是通过段寄存器和GDT中描述符共同提供的。
; 宏 ------------------------------------------------------------------------------------------------------
;
; 描述符
; usage: Descriptor Base, Limit, Attr
; Base: dd
; Limit: dd (low 20 bits available)
; Attr: dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
dw %2 & 0FFFFh ; 段界限1 //将limit的低16位放在最低0 1两个字节
dw %1 & 0FFFFh ; 段基址1 //将base的低16位放在2 3字节
db (%1 >> 16) & 0FFh ; 段基址2 //将base的第三位字节放在4字节
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性1 + 段界限2 + 属性2 //按字节位置进行拼凑成 5 6字节
db (%1 >> 24) & 0FFh ; 段基址3 //将base的第四字节(从低位开始计数)放在7字节
%endmacro ; 共 8 字节
3.保护模式——段选择子(段选择符)
段选择子包括三部分:描述符索引(index)、TI、请求特权级(RPL)。他的index(描述符索引)部分表示所需要的段的描述符在描述符表的位置,由这个位置再根据在GDTR中存储的描述符表基址就可以找到相应的描述符。然后用描述符表中的段基址加上逻辑地址(SEL:OFFSET)的OFFSET就可以转换成线性地址,段选择子中的TI值只有一位0或1,0代表选择子是在GDT选择,1代表选择子是在LDT选择。请求特权级(RPL)则代表选择子的特权级,共有4个特权级(0级、1级、2级、3级)。
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT // 所需要的段的描述符在描述符表的位置
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子(目的) //段选择子(Selector)由GDTR访问全局描述符表是通过“段选择子”(实模式下的段寄存器)来完成的。段选择子是一个16位的寄存器(同实模式下的段寄存器相同),这里是寄存器gs
mov [gs:edi], ax //gs的之为SelectorVideo,它对应显存的描述符DESC_VIDEO,这条指令将ax的值写入段DESC_VIDEO显存中偏移位edi的位置。
4.实模式到保护模式
step1.将描述符LABEL_DESC_CODE32初始化完成:将LABEL_SEG_CODE32的物理地址赋值给eax,并将它分成三部分赋值给描述符LABEL_DESC_CODE32中的相应位置。
step2.将GdtPtr指示的6字节加载到寄存器gdtr
GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限
dd 0 ; GDT基地址
lgdt [GdtPtr]
step3.关中断,保护模式下中断处理机制是不同的
step4.打开A20地址线。A20地址不打开,只能寻址1MB
step5.将寄存器cr0的第0位置为1
寄存器cr0的第0位是PE位,0-实模式,1-保护模式
step6.此时cs的值仍是实模式下的值,需要把代码段的选择子装入cs
jmp dword SelectorCode32:0
jmp dword ptr内存地址:以内存地址单元处的双字来修改指令,高地址内容修改CS,低地址内容修改IP,内存地址可以以任何合法的方式给出
sudo ndisasm -o 0x7c00 pmtest1.bin >> wz.asm查看值
00007C75 66EA000000000800 jmp dword 0x8:0x0 这里的SelectorCode32为8,赋值给cs
bochs调试模式中,sreg可以看到cs的内容:
cs:0x0008, dh=0x00409900, dl=0x7c800014, valid=1
Code segment, base=0x00007c80, limit=0x00000014, Execute-Only, Non-Conforming, Accessed, 32-bit
5.描述符属性
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32; 非一致代码段
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址
DA_C EQU 98h ; 存在的只执行代码段属性值 1001 1000
DA_32 EQU 4000h ; 32 位段 100 0000 0000 0000
DA_DRW EQU 92h ; 存在的可读写数据段属性值 1001 0010
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性1 + 段界限2 + 属性2 //按字节位置进行拼凑成 5 6字节