文章目录
一、相关基础
1、如何定位一个内存地址:
- 实模式下:段基址:偏移地址,其中段寄存器中直接存储的是段基址
- 保护模式下:段基址:偏移地址,其中段寄存器中存储的并不是段基址,而是存储的选择子,其中选择子的作用是为了在段描述符表GDT中定位一个段描述符,其中段描述符里面存储了段基址。
2、什么是段描述符表和段描述符以及选择子呢
- 段描述符表是一个连续的内存空间,里面存储了段描述符,每个段描述符由8个字节组成也就是64位,段描述符中存储了相关的信息,其中包含了段基址。
- 选择子里面存储了段描述符在段描述符表中的位置,用来索引段描述符。
3、如何用选择子来索引段描述符呢
- GDTR寄存器的结构:
- GDTR寄存器是用来存储GDT的首地址的,而选择子中高13位对应了GDT中第几个段描述符,GDT中的索引是从0开始的,第0个描述符又不可用,每个描述符又占用8个字节。
- GDT中某个段描述符的首地址:GDTR高32位+选择子中高13位*8;
二、实验记录
1、实验目的
本次的实验目的主要是在内存中构建3个段描述符(代码段,数据段,显存段)以及创建相应的选择子,最后开启保护模式。
2、实验步骤
1、构建段描述符
- 为了更方便了构建段描述符,我们把每个字段以及每个字段的不同情形分别宏定义出来放到boot.inc头文件当中
;一一一一一一- gdt 描述符属性 一一一一一一
DESC_G_4K equ 1000_0000_0000_0000_0000_0000b ;G位为第23位,置1代
;表
;段界限为单位4k
DESC_D_32 equ 1_00_0000_0000_0000_0000_0000b ;D/B 宇段,第22位
;对代码段来说是D位,置1表示指令中的有效地址及
;操作数是32位,指令有效地址用EIP寄存器。
DESC_L equ 0_0000_0000_0000_0000_0000_0000b ; 64位代码标记,我们在32位CPU下编程,
;标记为0便可
DESC_AVL equ 0_0000_0000_0000_0000_0000b ;CPU不用此位,暂置为
DESC_LIMIT_CODE2 equ 1111_0000_0000_0000_0000b ;段界限16~19位
;全设为1,它在下面代码中会与段界限的0~15位拼成0xFFFF,
;0xFFFF*4k等于4G,段基址设为0,采用平坦模型
DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2 ;
DESC_LIMIT_VIDEO2 equ 0000_0000_0000_0000_0000b ;
DESC_P equ 1_000_0000_0000_0000b ;第15位,表示段存在
DESC_DPL_0 equ 00_0_0000_0000_0000b ;DPL在13~14位 0为最高特权级
DESC_DPL_1 equ 01_0_0000_0000_0000b
DESC_DPL_2 equ 10_0_0000_0000_0000b
DESC_DPL_3 equ 11_0_0000_0000_0000b
DESC_S_CODE equ 1_0000_0000_0000b ; S为0时表示系统段, S为1时表示非系统段。
DESC_S_DATA equ DESC_S_CODE
DESC_S_sys equ 0_0000_0000_0000b
DESC_TYPE_CODE equ 1000_0000_0000b ;x=1,c=0, r=0,a=0 ,即代码段是可执行的,非一致
;性,不可读,己访问位a清0 配合S使用
DESC_TYPE_DATA equ 0010_0000_0000b ;
;x=0,e=0,w=1,a=0 数据段是不可执行的,向上扩展的,可写,己访问位a清0。
- 根据段描述符的格式和每个字段的作用构建三个段描述符的高32位
DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + \
DESC_P + DESC_DPL_0 + DESC_S_CODE +\
DESC_TYPE_CODE + 0x00
DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 +\
DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + \
DESC_P + DESC_DPL_0 + DESC_S_DATA + \
DESC_TYPE_DATA + 0x00
DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 +\
DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + \
DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0B ;注意书上这里写的是0x00,写错了
;显存起始地址应该是0xB8000
- 创建3个段描述符
GDT中第0个段描述符不可用,把位置留出来即可
;构建 gdt 及其内部的描述符
GDT_BASE: dd 0x00000000 ;第0个段描述符不可用
dd 0x00000000
CODE_DESC: dd 0x0000FFFF ;代码段描述符
dd DESC_CODE_HIGH4
DATA_STACK_DESC: dd 0x0000FFFF ;栈段描述符 栈段和数据段共用一个描述符 均向上扩展
dd DESC_DATA_HIGH4
VIDEO_DESC: dd 0x80000007 ; limit=(0xbffff-0xb8000)/4k=0x7 故段界限为7
dd DESC_VIDEO_HIGH4 ;此时dpl为0
GDT_SIZE equ $ - GDT_BASE ; 先是通过地址差来获得 GDT的大小,进而用 GDT大小减1得到了段界限
GDT_LIMIT equ GDT_SIZE - 1 ;用于构建GDTR的段界限
times 60 dq 0 ;此处预留 60 个描述符的空位
- 设置GDTR寄存器字段,后续加载GDTR直接加载该地址即可。
;以下是 gdt 的指针即GDTR,前2字节是gdt界限,后4字节是gdt起始地址 后面代码使用lgdt指令时会用上
gdt_ptr dw GDT_LIMIT
dd GDT_BASE
2、创建选择子
- 创建选择子每个字段处于不同情形的宏定义,方便创建选择子
;一一一一一一 选择子属性一一一一一一一
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b
TI_LDT equ 100b
- 创建3个段描述符的选择子
GDT中第0个段描述符不可用,但是要把位置留出来,因此代码段为第1个段描述符索引为1,数据段为第2个为2,显存段为第3个为3。
;以下是构建代码段、数据段、显存段选择子
SELECTOR_CODE equ (0x0001 << 3) + TI_GDT + RPL0
;相当于[(CODE_DESC - GDT_BASE) /8 ]<<3+ TI_GDT + RPL0
;内存地址的编号是一个存储单元8比特,这里CODE_DESC - GDT_BASE应该等于8
;书里的备注应该写掉了"<<3"
SELECTOR_DATA equ (0x0002<< 3) + TI_GDT + RPL0
SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
3、进入保护模式
- 打开A20
in al,0x92
or al, 0000_0010B
out 0x92,al
- 加载GDT(也就是设置好gdtr,gdtr记录着gdt的起始地址)
lgdt [gdt_ptr ]
- cr0位置置1
mov eax, cr0
or eax, 0x00000001
mov cr0, eax
3、实验代码
1、修改mbr.s中的代码
loader.bin 超过了 512 字节,读入磁盘扇区的数量不在是1啦。
mov cx, 1; 待读入内存的扇区数
call rd_disk_m_16;
修改后代码
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov cx, 4; 改成待读入4个扇区, loader.bin 超过了 512 字节
call rd_disk_m_16;
2、loader.s
构建段描述符表GDT以及选择子,开启保护模式(打开A20,设置cr0为1,加载GDT),通过选择子和段描述符的方式访问显存,打印字符’P’,悬停在此处。
;------------------------
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR ;相同内存地址,地址之下便是栈
jmp loader_start
;构建 gdt 及其内部的描述符
GDT_BASE: dd 0x00000000 ;第0个段描述符不可用
dd 0x00000000
CODE_DESC: dd 0x0000FFFF ;代码段描述符
dd DESC_CODE_HIGH4
DATA_STACK_DESC: dd 0x0000FFFF ;栈段描述符 栈段和数据段共用一个描述符 均向上扩展
dd DESC_DATA_HIGH4
VIDEO_DESC: dd 0x80000007 ; limit=(0xbffff-0xb8000)/4k=0x7 故段界限为7
dd DESC_VIDEO_HIGH4 ;此时dpl为0
GDT_SIZE equ $ - GDT_BASE ; 先是通过地址差来获得 GDT的大小,进而用 GDT大小减1得到了段界限
GDT_LIMIT equ GDT_SIZE - 1 ;用于构建GDTR的段界限
times 60 dq 0 ;此处预留 60 个描述符的空位
;以下是构建代码段、数据段、显存段选择子
SELECTOR_CODE equ (0x0001 << 3) + TI_GDT + RPL0
;相当于[(CODE_DESC - GDT_BASE) /8 ]<<3+ TI_GDT + RPL0
;内存地址的编号是一个存储单元8比特,这里CODE_DESC - GDT_BASE应该等于8
;书里的备注应该写掉了"<<3"
SELECTOR_DATA equ (0x0002<< 3) + TI_GDT + RPL0
SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
;以下是 gdt 的指针即GDTR,前2字节是gdt界限,后4字节是gdt起始地址 后面代码使用lgdt指令时会用上
gdt_ptr dw GDT_LIMIT
dd GDT_BASE
loadermsg db '2 loader in real.'
loader_start:
;------------------------------------------------------------
; INT 0x10 功能号: 0x13 功能描述:打印字符串
;------------------------------------------------------------
;输入:
;AH 子功能号=13H
;BH =页码
;BL =属性(若 AL=OOH OlH)
;CX =字符串长度
; (DH DL )=坐标{行、列)
;ES:BP=字符串地址
;AL=显示输出方式
; 一一字符串中只含显示字符,其显示属性在 BL
;显示后,光标位置不变
; 一一字符串中只含显示字符,其显示属性在 BL
;显示后,光标位置改变
; 一一字符事中含显示字符和显示属性。显示后,光标位置不变
; 一一字符串中含显示字符和显示属性。显示后,光标位置改变
;无返回值
mov sp, LOADER_BASE_ADDR
mov bp, loadermsg ; ES:BP =字符串地址
mov cx, 17 ; cx =字符串长度
mov ax, 0x1301 ; AH = 13, AL = 01h
mov bx, 0x001f ;页号为0(BH = 0)蓝底粉红字( BL = 1fh)
mov dx, 0x1800 ; dh=0x18 十进制为24,代表行数;dl=0x00 表示列数。使用显存的文本模式下,一共25行,所以2 loader in real 会出现在屏幕最后一行
int 0x10 ; 10h号中断 由于AH=0x13,所以该BIOS中断后会执行打印字符串的中断处理程序。
; 一一一一一一一一一一 准备进入保护模式 一一一一一一一一一一一一一一-
;1 打开 A20
;2 加载 gdt
;3 将cr0 的 pe 位置1
;一一一一一一一-打开 A20 一一一一一
in al,0x92
or al, 0000_0010B
out 0x92,al
;一一一一一一一一加载 GDT (也就是设置好gdtr,gdtr记录着gdt的起始地址)一一一一一一一-
lgdt [gdt_ptr ]
;一一一一一一一一 cr0位置1 一一一一一一一-
mov eax, cr0
or eax, 0x00000001
mov cr0, eax
jmp dword SELECTOR_CODE:p_mode_start ;刷新流水线
[bits 32]
p_mode_start:
;;;;用选择子初始化段寄存器
mov ax, SELECTOR_DATA
mov ds, ax
mov es, ax
mov ss, ax
mov esp, LOADER_STACK_TOP
mov ax, SELECTOR_VIDEO
mov gs, ax
mov byte [gs:0xA0], 'P' ;mov byte [gs:160],’ P ’ ;一行80个字符,每个字符的显示要占俩个字节也就是160
jmp $
3、boot.inc
一些宏定义主要在这里面,更方面、灵活的写代码。
;一一一一一一loader和 kernel
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
;一一一一一一- gdt 描述符属性 一一一一一一
DESC_G_4K equ 1000_0000_0000_0000_0000_0000b ;G位为第23位,置1代
;表
;段界限为单位4k
DESC_D_32 equ 1_00_0000_0000_0000_0000_0000b ;D/B 宇段,第22位
;对代码段来说是D位,置1表示指令中的有效地址及
;操作数是32位,指令有效地址用EIP寄存器。
DESC_L equ 0_0000_0000_0000_0000_0000_0000b ; 64位代码标记,我们在32位CPU下编程,
;标记为0便可
DESC_AVL equ 0_0000_0000_0000_0000_0000b ;CPU不用此位,暂置为
DESC_LIMIT_CODE2 equ 1111_0000_0000_0000_0000b ;段界限16~19位
;全设为1,它在下面代码中会与段界限的0~15位拼成0xFFFF,
;0xFFFF*4k等于4G,段基址设为0,采用平坦模型
DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2 ;
DESC_LIMIT_VIDEO2 equ 0000_0000_0000_0000_0000b ;
DESC_P equ 1_000_0000_0000_0000b ;第15位,表示段存在
DESC_DPL_0 equ 00_0_0000_0000_0000b ;DPL在13~14位 0为最高特权级
DESC_DPL_1 equ 01_0_0000_0000_0000b
DESC_DPL_2 equ 10_0_0000_0000_0000b
DESC_DPL_3 equ 11_0_0000_0000_0000b
DESC_S_CODE equ 1_0000_0000_0000b ; S为0时表示系统段, S为1时表示非系统段。
DESC_S_DATA equ DESC_S_CODE
DESC_S_sys equ 0_0000_0000_0000b
DESC_TYPE_CODE equ 1000_0000_0000b ;x=1,c=0, r=0,a=0 ,即代码段是可执行的,非一致
;性,不可读,己访问位a清0 配合S使用
DESC_TYPE_DATA equ 0010_0000_0000b ;
;x=0,e=0,w=1,a=0 数据段是不可执行的,向上扩展的,可写,己访问位a清0。
DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + \
DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + \
DESC_P + DESC_DPL_0 + DESC_S_CODE +\
DESC_TYPE_CODE + 0x00
DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 +\
DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + \
DESC_P + DESC_DPL_0 + DESC_S_DATA + \
DESC_TYPE_DATA + 0x00
DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 +\
DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + \
DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0B ;注意书上这里写的是0x00,写错了
;显存起始地址应该是0xB8000
;一一一一一一 选择子属性一一一一一一一
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b
TI_LDT equ 100b
4、实验结果
1、编译loader.s
nasm -o loader.bin loader.s
2、编译mbr.s
nasm -o mbr.bin mbr.s
3、将mbr.bin刻入到第0扇区
dd if=/home/llh/bochs/mbr.bin of=/home/llh/bochs/hd60M.img bs=512 count=1 seek=0 conv=notrunc
4、将loader.bin刻入到第2和3扇区
dd if=/home/llh/bochs/loader.bin of=/home/llh/bochs/hd60M.img bs=512 count=2 seek=2 conv=notrunc
5、模拟bochs
bin/bochs -f bochsrc.disk