进入保护模式;初始化全局描述符表,通过GDT进入代码段、数据段、堆栈段
; FILE: c11_mbr.asm
; DATE: 20191229
; TITLE: 硬盘主引导扇区代码
; 设置堆栈段和栈指针
; 0x07c00以此为界限,mbr代码段cs:ip向上,mbr堆栈段ss:sp向下
mov ax, cs
mov ss, ax
mov sp, 0x7c00
; 计算gdt所在的逻辑段地址
; 32位忽略高位0,折算为20位,即ds:bx形式(ds*16 + bx)
mov ax, [cs:gdt_base + 0x7c00] ; 低16位
mov dx, [cs:gdt_base + 0x7c00 + 2] ; 高16位
mov bx, 16
div bx
mov ds, ax ; 商为逻辑段地址
mov bx, dx ; 余数为偏移地址
; 创建gdt第#0号描述符
; 处理器规定,gdt中第一个描述符必须是空描述符
mov dword [bx], 0x00000000
mov dword [bx+0x04], 0
; 创建gdt第#1号描述符,保护模式下的代码段描述符
; 该段:线性基地址为0x0000 7c00,段界限为0x0 01FF(即段长512字节),粒度为字节(G=0)
; 属于存储器的段(S=1),是32位的段(D=1),目前位于内存中(P=1),段的特权级为0(DPL=00),
; 是可执行的代码段(TYPE=1000)
mov dword [bx+0x08],0x7c0001ff
mov dword [bx+0x0c],0x00409800
; 创建gdt第#2号描述符,保护模式下的数据段描述符(文本模式下的显示缓冲区)
; 该段:线性基地址为0x0000 b800,段界限为0x0 FFFF(即段长64KB),粒度为字节(G=0)
; 属于存储器的段(S=1),是32位的段(D=1),目前位于内存中(P=1),段的特权级为0(DPL=00),
; 是可读可写、向上扩展的数据段(TYPE=0010)
mov dword [bx+0x10],0x8000ffff
mov dword [bx+0x14],0x0040920b
; 创建gdt第#3号描述符,保护模式下的堆栈段描述符
; 该段:线性基地址为0x0000 0000,段界限为0x0 7A00,粒度为字节(G=0)
; 属于存储器的段(S=1),是32位的段(D=1),目前位于内存中(P=1),段的特权级为0(DPL=00),
; 是可读可写、向下扩展的数据段(TYPE=0010),在这里是栈段
mov dword [bx+0x18],0x00007a00
mov dword [bx+0x1c],0x00409600
; 初始化描述符表寄存器GDTR
; 描述符表的界限(总字节数减1)。这里共有4个描述符,每个描述符8字节,共31字节
mov word [cs:gdt_size+0x7c00], 31
; lgdt指令的操作数是一个48位(6字节)的内存区域,低16位是gdt的界限值,高32位是gdt的基地址
lgdt [cs:gdt_size+0x7c00]
; 打开地址线A20
; 芯片ICH的处理器接口部分,有一个用于兼容老式设备的端口0x92,端口0x92的位1用于控制A20
in al, 0x92
or al, 0000_0010B
out 0x92, al
; 禁止中断
; 保护模式和实模式下的中断机制不同,在重新设置保护模式下的中断环境之前,必须关中断
cli
; 开启保护模式
; CR0的第1位(位0)是保护模式允许位(Protection Enabel, PE)
mov eax, cr0
or eax, 1
mov cr0, eax
; 清空流水线、重新加载段选择器
; 遇到jmp或call指令,处理器一般会清空流水线,另一方面,还会重新加载段选择器,并刷新描述符高速缓存器中的内容
; 建议:在设置了控制寄存器CR0的PE位之后,立即用jmp或call指令
jmp dword 0x0008:flush ; 不管是16位还是32位远转移,现在已经处于保护模式下,
; 处理器将把第一个操作数0x0008视为段选择子,而不是是模式下的逻辑段地址
; 段选择子0x0008,即 0000_0000_00001_0_00(PRL为00,TI为0,索引号为1)
; 当指令执行时,处理器加载CS,从GDT中取出相应的描述符加载到CS描述符高速缓存
; jmp dword, 32位的远转移指令。
; 16位的绝对远转移指令只有5字节,使用16位的偏移量,它会使标号flush的汇编地址相应地变小
[bits 32] ; 从进入保护模式开始,之后的指令都应当按32位操作数方式编译
; 当处理器执行到这里时,它会按32位模式进行译码
flush:
; 段选择子:15~3位,描述符索引;2, TI(0为GDT,1为LDT); 1~0位,RPL(特权级)
mov cx, 0000_0000_00010_0_00B ; 已初始化的GDT中,数据段为第2号描述符
mov ds, cx ; 当处理器执行任何改变段选择器的指令时,就将指令中提供的索引号乘以8作为偏移地址,同GDTR中提供的线性地址相加,
; 以访问GDT,将找到的描述符加载到不可见的描述符高速缓存部分
; 以下在屏幕上显示"Protect mode OK."
mov byte [0x00],'P'
mov byte [0x02],'r'
mov byte [0x04],'o'
mov byte [0x06],'t'
mov byte [0x08],'e'
mov byte [0x0a],'c'
mov byte [0x0c],'t'
mov byte [0x0e],' '
mov byte [0x10],'m'
mov byte [0x12],'o'
mov byte [0x14],'d'
mov byte [0x16],'e'
mov byte [0x18],' '
mov byte [0x1a],'O'
mov byte [0x1c],'K'
; 简单验证:32位下的栈操作,其默认的操作数大小是32位的
mov cx, 0000_0000_00011_0_00B ; 已初始化的GDT中,数据段为第3号描述符
mov ss, cx
mov esp, 0x7c00
mov ebp, esp
push byte '.' ; 栈中压入一个立即数(字节)
sub ebp, 4
cmp ebp, esp ; 验证压入立即数时,esp是否减4;若是,则显示该立即数'.'
jnz halt
pop eax
mov byte [0x1e], al ; 显示刚压入的立即数'.'
halt:
hlt ; 已禁止中断,将不会被唤醒
; lgdt指令的操作数是一个48位(6字节)的内存区域,低16位是gdt的界限值,高32位是gdt的基地址
gdt_size dw 0
gdt_base dd 0x00007e00
times 510-($-$$) db 0
db 0x55, 0xaa
来源:CSDN
作者:trb331617
链接:https://blog.csdn.net/trb331617/article/details/103795208