[书]x86汇编语言:从实模式到保护模式 -- 第11章 进入保护模式,初识全局描述符表GDT

為{幸葍}努か 提交于 2020-01-01 22:26:23

进入保护模式;初始化全局描述符表,通过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

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!