首页 > 操作系统 > x86 保护模式浅析(三)
2019
05-28

x86 保护模式浅析(三)

工具准备

  • NASM 汇编器,以及一个文本编辑器
  • VirtualBox 虚拟机(或者 Bochs 也可以)

开始实战

进入保护模式的汇编代码

gdt_base equ 7e00h  ; gdt_base 表示 GDT 头部的地址

; 这一段代码主要是将 GDT 的头部地址,转换为 “段:偏移” 的形式,在实模式下的寻址
mov ax, gdt_base
xor dx, dx
mov bx, 10h
div bx
mov ds, ax
mov bx, dx

; 构造 GDT(系统约定,第一个选择子要用 0 填充)
mov dword [ds:bx], 0         ; 填为 0
mov dword [ds:bx + 04h], 0   ; 填为 0

; 代码段选择子(设置属性:可执行,可读,ring0 特权级执行)
mov dword [ds:bx + 08h], 7c0001ffh
mov dword [ds:bx + 0Ch], 00409A00h

; 数据段选择子(设置属性:不可执行,可写,地址向上增长)
mov dword [ds:bx + 10h], 8000ffffh
mov dword [ds:bx + 14h], 0040920Bh

; 堆栈段选择子(设置属性:不可执行,可写,地址向下增长)
mov dword [ds:bx + 18h], 00007A00h
mov dword [ds:bx + 1Ch], 00409600h

; 对向量的低字,写入 GDT 界限(GDT 总字节数 - 1)
mov word [cs:gdt_vector], 20h - 1

; 对向量的高双字,写入 GDT 的线性地址
mov dword [cs:gdt_vector + 2], gdt_base

; 启用 A20 地址线
in al, 92h
or al, 2
out 92h, al

; 关闭中断
cli

; 加载 GDTR 寄存器
lgdt [cs:gdt_vector]

; 设置 PE 位,使 CPU 进入保护模式
mov eax, cr0
or al, 1
mov cr0, eax

; 命运的跳转,清空流水线,并串行化地执行跳转后的机器代码
jmp 8h:flush

[bits 32]
flush:	
	mov ax, 10h  ; 设置段选择子(这里设置的数据段即是显存区域)
	mov ds, ax

    ; 通过段选择子,直接访问内存,向屏幕第一行第一列打印红色的 'P'
	mov byte [ds:0], 'P'
	mov byte [ds:1], 0Ch
	
    ; 死循环(进入保护模式就大功告成了,现在什么也不做,让 CPU 空转)
	jmp $

; GDT 向量,低字表示 GDT 界限,高双字表示 GDT 线性地址
gdt_vector dw 0
           dd 0

times 510 - ($ - $$) db 0  ; 填充剩余扇区空间
db 55h, 0aah  ; 可引导扇区标识符

使用汇编器进行汇编

NASM 汇编器的目录位置如果被添加到 PATH 环境变量中去了,就可以直接在命令提示符中使用。笔者这里源文件叫做 mbr.asm,运行如下命令将在当前目录下生成一个 mbr.bin 文件,这就是生成的机器码文件

x86 保护模式浅析(三) - 第1张  | 阿龙咖啡
nasm -o mbr.bin mbr.asm

写 VHD 虚拟磁盘

如果你需要将引导程序写入虚拟磁盘中,最好使用 VHD 格式的固定大小的磁盘,因为笔者写了一个命令行工具 —— Fixed VHD Writer 放在了 Github 上。它可以使用 LBA 的方式对固定大小的 VHD 磁盘进行写扇区的操作,如果要使用笔者的工具,在创建虚拟机时,使用固定大小的 VHD 格式虚拟磁盘是必要的

Fixed VHD Writer
[-h] 显示帮助
[-r] 指定读取的文件
[-w] 指定写入的固定大小的 VHD 磁盘
[-a] 指定 LBA 扇区编号
x86 保护模式浅析(三) - 第2张  | 阿龙咖啡
Fixed VHD Writer 使用示范

使用笔者的工具,将 mbr.bin 写入固定大小的 VHD 磁盘的 0 扇区,写入工具输出了一些信息,写入字节数、写入扇区数等

启动虚拟机

现在可以启动虚拟机了,如果一切顺利,那么你将看到虚拟机屏幕开头会显示出一个红色的 ‘P’ 字符,这表示 CPU 成功进入保护模式,大功告成!

x86 保护模式浅析(三) - 第3张  | 阿龙咖啡
进入保护模式成功,红色的 ‘P’ 字符

结语

基本上,进入保护模式可以简单地划分为 5 个步骤:

  • 设置 A20 地址线(本篇采用 Fast A20 Gate 方式)
  • 关闭中断
  • 加载 GDT 到 GDTR 寄存器
  • 设置 CR0 控制寄存器的 PE 位
  • 命运的跳转(清空流水线)

至于 GDT 的数据结构、GDT 向量,其实都只是起到描述作用。读者可以先不看笔者的代码自己实现一下,这样可以加深印象,真正地理解并且吃透

参考资料

[1] 李忠 , 王晓波 . x86汇编语言:从实模式到保护模式 [M] . 北京 : 电子工业出版社 , 2013.


打赏 赞(3)
微信
支付宝
微信二维码图片

微信扫描二维码打赏

支付宝二维码图片

支付宝扫描二维码打赏

最后编辑:
作者:Yenyu
Yenyu
编程爱好者

留下一个回复

你的email不会被公开。