Loader原理
Loader主要负责检测硬件信息,处理器模式切换和向内核传递数据三项工作
检测硬件信息
Loader主要通过BIOS的中断服务程序来获取硬件信息,这些硬件信息大部分都只能在实模式下获取,而系统工作于非实模式下,那么就需要在进入内核程序前将这些信息检测出来,作为参数传递给内核程序
在这些信息中有物理地址空间信息,显示器信息等等。
处理器模式切换
从BIOS运行的实模式,到32位的保护模式,再到64位操作系统使用的IA-32e模式,在各模式的切换中,Loader都必须手动创建各模式要使用的临时数据,并按照标准进行模式之间的跳转。
向内核传递数据
Loader向内核主要转递两种数据,一种是控制信息,另一种则是之前说的硬件信息
控制信息一般用于控制内核执行流程或限制内核的某些功能,如启动模式(图形还是字符)、启动方式(网络还是本地)、终端重定向(串口或显示器)
代码部分
由于这部分涉及到很多我没学过的知识,所以我也不确定能不能讲清楚,只能试着解释一下,没懂的地方就暂且存疑留到后面解释
设置GDT数据结构
org 0x1000
jmp Label_Start
%include "fat16.inc"
[SECTION gdt] ; SECTION在这里的作用就是起一个修饰的作用,并不会在编译时产生什么作用
LABEL_GDT: dd 0, 0 ; GDT第一个描述符定义为null
; BASE: 0
; limit: 0xfffff
; G: 1
; D: 1
; P: 1
; DPL: 0
; type: 1010 , 代码段,表示权限可执行,可读
LABEL_DESC_CODE32: dd 0x0000ffff, 0x00cf9a00 ; 代码段描述符
; type: 0010,数据段,表示权限可读可写
LABEL_DESC_DATA32: dd 0x0000FFFF,0x00CF9200 ; 数据段描述符
GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT段界限
dd LABEL_GDT ; GDT基地址
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT ; 代码段选择子,一个16位数据,用来指向代码段描述符的起始地址
SelectorData32 equ LABEL_DESC_DATA32 - LABEL_GDT
[SECTION gdt64] ; IA-32e的GDP和保护模式是一样的位数,但是其内存是强制平坦型的,我没有查到强制平坦的含义,理解应该是不需要划分段了,所以取消了段基址和段限长两个参数的意义
LABEL_GDT64: dq 0x0000000000000000
LABEL_DESC_CODE64: dq 0x00209800 00000000
LABEL_DESC_DATA64: dq 0x0000920000000000
GdtLen64 equ $ - LABEL_GDT64
GdtPtr64 dw GdtLen64 - 1
dd LABEL_GDT64
SelectorCode64 equ LABEL_DESC_CODE64 - LABEL_GDT64
SelectorData64 equ LABEL_DESC_DATA64 - LABEL_GDT64
寻址方式
GDT(globel description table)全局描述符表,再次之前的实模式下,通过[段基址:段偏移]可以访问所有的内存空间,在变换到保护模式后,访问内存空间的过程变成:
- 访问GDTR寄存器(里面存放的是GDT的首地址)
- 通过一个段选择子找到要访问的那个数据的段描述符
- 表项中存放的就是要访问的那个地址的真实地址
还有一点要注意的时,在实模式下,段寄存器是16位的,但是,随着地址线的变多,段寄存器位数没有变,所以为了访问更大的地址空间,段偏移量就要够大,如何存储每个段的段偏移量就产生了问题,这也就产生了使用GDT来解决这个问题的办法
GDT结构
base:32位段基地址,之所以这么分散,一句话就是历史原因
limit:20位段限长,用来鉴定访问的地址是否超出界限,也就是“保护”的地址
G:=0,最大段限长为
2
20
=
1
M
B
2^{20}=1MB
220=1MB,段限长单位为1字节
=1,最大段限长为
2
12
×
2
20
=
4
G
B
2^{12} \times 2^{20}=4GB
212×220=4GB,段限长的单位是4KB
D/B:如果描述符指向代码段,这就是D位,D=1使用32位地址和32/8操作数
D=0使用16位地址和16/8操作数
如果描述符指向数据段,这就是B位,B=1段上限为4GB
B=0段上限为64KB
如果描述符指向堆栈段,这就是B位,B=1时使用32位操作数,堆栈指针为ESP
B=0时使用16位操作数,堆栈指针为SP
AVL:保留位
P:=1段在内存中存在,=0不存在
DPL:特权级,0是最高,3是最低
type:<8,数据段
>=8,代码段
段寄存器数据结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m4K7NuuV-1686823496586)(https://zuos-tec-1311460166.cos.ap-chengdu.myqcloud.com/test/20230613220539.png)]
突破1MB寻址能力
在此前的实模式中,一直使用的是20位地址线寻址,最大也超不过1MB的寻址能力,为了实现这个功能,需要先开启地址A20功能,不用去管A20的开启怎么就实现访问1MB以上空间,只需要知道,它就是1MB以上和以下之前的一个开关,打开后就可以进入以上的空间。
关于如何打开A20有很多办法,这里用的是将0x92端口的第一位置1,就可以开启A20
[SECTION .s16]
[BITS 16] ; [BITS 16/32]是告诉编译器将会在16位还是32位的cpu上运行,其编译的方式是按16位还是32位进行编译
Label_Start:
mov ax, cs
mov ds, ax
mov es, ax
mov ax, 0x00
mov ss, ax
mov sp, 0x7c00
mov ax, 1301h
mov bx, 000fh
mov dx, 0201h ;row 2
mov cx, 12
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, StartLoaderMessage
int 10h ; 显示开始loader字符串
; 开启A20信号线,将92h端口第1位置1
push ax
in al, 92h
or al, 00000010b
out 92h, al
pop ax
第一次进入和退出保护模式
CR0
cr开头的控制寄存器,32位,下面是每一位的意义
PG:Paging(分页),=1时表示启用分页机制;=0时表示禁用分页机制,这时线性地址就是物理地址。【什么是线性地址?】。如果PE标志位未设置,PG位无意义;在PE被清除时设置PG会导致通用保护异常
CD:Cache Disable,缓存禁用。当CD和NW都被清除时,处理器的内部或外部缓存可以对整个物理内存的存储位置进行缓存。当CD被设定时,缓存将受到限制【不懂为什么限制缓存,暂且就不设置】
NW:Not Write-through:好像是和CD搭配使用的,不懂
AM:Alignment Mask(对齐掩码)。设置AM位,启用自动对齐检查;清除AM位,禁用对齐检查。对齐检查的进行需要满足以下几个条件,1. AM标志被设置;2. EFLAGS寄存器中的AC为被设置;3. CPL(当前特权级)等于3;4. CPU处于保护模式或虚拟8086模式下。
WP:Write Protect写保护。当WP被设置时,阻止特权程序对只读页面进行写操作;当被清除时允许。WP和cr4.CET有关,设置CET之前必须设置WP,当CET是1时,不能清除WP。
NE:Numeric Error(数值错误)是CR0寄存器的第5位(bit 5)。当设置了NE标志时,启用内部的机制来报告x87 FPU错误;当清除NE标志时,启用PC风格的x87 FPU错误报告机制。当NE标志被清除并且IGNNE#输入被触发时,x87 FPU错误会被忽略。当NE标志被清除并且IGNNE#输入未触发时,未屏蔽的x87 FPU错误会导致处理器断言FERR#引脚以生成外部中断,并在执行下一个等待的浮点指令或WAIT/FWAIT指令之前立即停止指令执行。
FERR#引脚用于驱动外部中断控制器的输入(FERR#引脚模拟Intel 287和Intel 387 DX数学协处理器的ERROR#引脚)。NE标志、IGNNE#引脚和FERR#引脚与外部逻辑一起用于实现PC风格的错误报告。使用FERR#和IGNNE#来处理浮点异常在现代操作系统中已不推荐使用;这种非本机的方法还限制了新型处理器仅能在一个逻辑处理器处于活动状态。
ET:Extension Type扩展类型,对于Pentium 4、Intel Xeon、P6系列和Pentium处理器,该标志位被保留且固定为1。而对于Intel386和Intel486处理器,当设置了该标志位时,表示支持Intel 387 DX数学协处理器指令。
MP:monitor coprocessor监视协处理器,它控制WAIT(或FWAIT)指令与TS标志(CR0的第3位)的交互作用。如果设置了MP标志,并且TS标志也被设置,那么WAIT指令将引发设备不可用异常(#NM)。如果清除了MP标志,则WAIT指令将忽略TS标志的设置。
PE:Protection Enable保护使能,这也是在这里要用到的最重要的一个标志位,设置PE,启用保护模式;清除PE,启用实模式。如要启动分页机制,要同时启动PE和PG标志。
CR0有用的也差不多只有PE位,在这里列出来,是因为即使知道它没用,也想在看的时候能看到全貌
; 开启和关闭保护模式
cli ; 关中断
db 0x66 ; 在16位编址的段中使用32位的命令时前面要加一个0x66
lgdt [GdtPtr] ; 使用该命令可以将GDT的基地址和长度加载到GDTR寄存器中
mov eax, cr0
or eax, 1
mov cr0, eax ; 将PE位置1
mov ax, SelectorData32
mov fs, ax ; 使用fs存储保护模式下设置的临时数据段选择子
mov eax, cr0
and al, 11111110b ; 将PE位置0关闭保护模式
mov cr0, eax
sti ; 开中断
搜索kernel.bin文件
没有什么特别的,和之前搜索loader.bin是一样的过程
; ------------- search kernel.bin ---------------
mov word [SectorNo], SectorNumOfRootDirStart
; 准备对某一个扇区的第一个目录项进行对比的一些前期工作,包括读入该扇区的全部目录项,和赋一些初值
; 输入参数 [SectorNo]
Label_Search_In_Root_Dir_Begin:
cmp word [RootDirSizeForLoop], 0
jz Label_No_Kernel_File ; 循环找到最后一个扇区还是没有,当RootDirSizeForLoop为0时表示没有扇区可以比较了
dec word [RootDirSizeForLoop]
xor esi,esi
mov si, [SectorNo]
mov ax, 0x00
mov es, ax ; 由于后面的lodsb要使用es:di指向目录项中文件名,所以这里也将其归一下0
mov ax, 0x8000
mov di, ax
call Func_Read_One_Sector ; 读入根目录扇区,缓存地址是0x8000
mov si, KernelFileName
mov ax, RootDirBuffer
mov di, ax
cld
mov bx, DirEntryPerSector ; bx存储每个扇区存储的目录项数目
; 判断当前扇区的目录项是否都已对比完
Label_Search_For_Kernel_File:
cmp bx, 0 ; 如果bx变为0,就是在当前扇区目录项中没有该文件
jz Label_Goto_Next_Sector_In_Root_Dir
dec bx
mov cx, FileNameLen ; cx存储文件名的长度
; 对比文件名其中的一个字符
Label_Cmp_FileName:
cmp cx, 0 ; 当cx为0,表示对比完了文件名的每一个字符,和目录项中文件名是相同的
jz Label_FileName_Found
dec cx
lodsb ; 将esi指向的地址处的数据取出来赋给AL寄存器,esi=esi+1,这里的esi指向的是kernelfilename的那个位置
cmp al, byte [es:di]
jz Label_Go_On ; 如果当前字符相同,就继续对比下一个字符
jmp Label_Different ; 如果字符不相同,就调到字符不同的下一个函数
; 为对比文件名下一个字符做准备
Label_Go_On:
inc di ; 由于lodsb中esi已经自动加一,所以对比下一个字符只需要对di加一就可以跳转
jmp Label_Cmp_FileName
; 文件名不同时的处理
Label_Different:
and di, 0xffe0 ; 将前5位归0,di变成了当前目录项的起始位置
add di, 20h ; di加32,变成了下一个目录项的起始位置
mov si, KernelFileName
jmp Label_Search_For_Kernel_File
; 对读取下一个扇区做准备
Label_Goto_Next_Sector_In_Root_Dir:
add word [SectorNo], 1 ; 对比的第几个扇区加1
jmp Label_Search_In_Root_Dir_Begin
; 没有kernel.bbin文件的显示
Label_No_Kernel_File:
mov ax, 1301h
mov bx, 008Ch
mov dx, 0301h ;row 3
mov cx, 21
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, NoKernelMessage
int 10h
jmp $
发现kernel文件并转存
; 发现了kernel.bin文件的处理
Label_FileName_Found:
mov ax, RootDirSectors
and di, 0xffe0
add di, DirFstClus ; 找到对应目录项中存放首簇簇号的地址
mov cx, word [es:di] ; 将首簇簇号赋给cx
push cx
add cx, SectorAndClusterBalance ; cx等于簇号对应的扇区号
mov eax, BaseOfTmpKernelAddr
mov es, eax
mov bx, OffsetOfTmpKernelFile
mov ax, cx
; 加载kernel文件到临时地址
Label_Go_On_Loading_Kernel_File:
push ax
push bx ; 现在栈中依次存放的是,kernel临时文件偏移量,文件扇区号,文件簇号
mov ah, 0x0e
mov al, '.'
mov bl, 0x0f
int 10h ; 显示....
pop bx
xor eax, eax
pop ax
mov esi, eax
mov di, bx
call Func_Read_One_Sector
; 保护环境
pop ax
push cx ;
push eax ; 文件簇号
push fs ;
push edi ;
push ds
push esi
; mov eax, 0x11111111
; mov fs, ax
; mov es, ax
; jmp $ ; 加上这几句代码,进过测试 ,在关闭保护模式后fs的表示范围又只有16位了
mov cx, 0x200
mov ax, BaseOfKernelFile
mov fs, ax
mov edi, dword [OffsetOfKernelFileCount]
mov ax, BaseOfTmpKernelAddr
mov ds, ax
mov esi, OffsetOfTmpKernelFile
mov es, eax
; 将kernel文件从临时地址移到最后的1MB开始地址
Label_Mov_Kernel:
mov al, byte [ds:esi] ; al=临时地址文件一字节
mov byte [fs:edi], al ; 0x100000地址开始处,
inc edi
inc esi
loop Label_Mov_Kernel
mov eax, 0x1000
mov ds, eax
mov dword [OffsetOfKernelFileCount], edi ; 一个扇区转移结束后,又将该地址存入下一个扇区起始处
pop esi
pop ds
pop edi
pop fs
pop eax ; 文件簇号
pop cx
call Func_Get_FAT_Entry ; 调用获取FAT表的函数
cmp ax, 0xfff8
jl Label_Kernel_File_Loaded ; 如果大于0xfff8,表示文件加载完成
push ax ; 压入簇号
add ax, SectorAndClusterBalance
jmp Label_Go_On_Loading_Kernel_File
Label_Kernel_File_Loaded:
mov ax, 1301h
mov bx, 000fh
mov dx, 0301h ;row 2
mov cx, 18
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, Kernel_File_Loaded
int 10h
; jmp $ ; 在这里停顿后查看0x100000处信息,kernel的确是move过去了,但是[fs:edi]这时的fs是16位的,难道这样也能组合成20位以上的地址吗
剩余代码就是从保护模式进入IA-32e 模式,但是这段代码还没弄懂,就不做注释了,loader.bin的全部代码放在最下面
org 0x1000
jmp Label_Start
%include "fat16.inc"
[SECTION gdt] ; SECTION在这里的作用就是起一个修饰的作用,并不会在编译时产生什么作用
LABEL_GDT: dd 0, 0 ; GDT第一个描述符定义为null
; BASE: 0
; limit: 0xfffff
; G: 1
; D: 1
; P: 1
; DPL: 0
; type: 1010 , 代码段,表示权限可执行,可读
LABEL_DESC_CODE32: dd 0x0000ffff, 0x00cf9a00 ; 代码段描述符
; type: 0010,数据段,表示权限可读可写
LABEL_DESC_DATA32: dd 0x0000FFFF,0x00CF9200 ; 数据段描述符
GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT段界限
dd LABEL_GDT ; GDT基地址
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT ; 代码段选择子,一个16位数据,用来指向代码段描述符的起始地址
SelectorData32 equ LABEL_DESC_DATA32 - LABEL_GDT
[SECTION gdt64] ; IA-32e的GDP和保护模式是一样的位数,但是其内存是强制平坦型的,我没有查到强制平坦的含义,理解应该是不需要划分段了,所以取消了段基址和段限长两个参数的意义
LABEL_GDT64: dq 0x0000000000000000
LABEL_DESC_CODE64: dq 0x0020980000000000
LABEL_DESC_DATA64: dq 0x0000920000000000
GdtLen64 equ $ - LABEL_GDT64
GdtPtr64 dw GdtLen64 - 1
dd LABEL_GDT64
SelectorCode64 equ LABEL_DESC_CODE64 - LABEL_GDT64
SelectorData64 equ LABEL_DESC_DATA64 - LABEL_GDT64
[SECTION .s16]
[BITS 16] ; [BITS 16/32]是告诉编译器将会在16位还是32位的cpu上运行,其编译的方式是按16位还是32位进行编译
Label_Start:
mov ax, cs
mov ds, ax
mov es, ax
mov ax, 0x00
mov ss, ax
mov sp, 0x7c00
mov ax, 1301h
mov bx, 000fh
mov dx, 0201h ;row 2
mov cx, 12
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, StartLoaderMessage
int 10h ; 显示开始loader字符串
; 开启A20信号线,将92h端口第1位置1
push ax
in al, 92h
or al, 00000010b
out 92h, al
pop ax
; 开启和关闭保护模式
cli ; 关中断
db 0x66 ; 在16位编址的段中使用32位的命令时前面要加一个0x66
lgdt [GdtPtr] ; 使用该命令可以将GDT的基地址和长度加载到GDTR寄存器中
mov eax, cr0
or eax, 1
mov cr0, eax ; 将PE位置1
mov ax, SelectorData32
mov fs, ax ; 使用fs存储保护模式下设置的临时数据段选择子
mov eax, cr0
and al, 11111110b ; 将PE位置0关闭保护模式
mov cr0, eax
sti ; 开中断
; ------------- search kernel.bin ---------------
mov word [SectorNo], SectorNumOfRootDirStart
; 准备对某一个扇区的第一个目录项进行对比的一些前期工作,包括读入该扇区的全部目录项,和赋一些初值
; 输入参数 [SectorNo]
Label_Search_In_Root_Dir_Begin:
cmp word [RootDirSizeForLoop], 0
jz Label_No_Kernel_File ; 循环找到最后一个扇区还是没有,当RootDirSizeForLoop为0时表示没有扇区可以比较了
dec word [RootDirSizeForLoop]
xor esi,esi
mov si, [SectorNo]
mov ax, 0x00
mov es, ax ; 由于后面的lodsb要使用es:di指向目录项中文件名,所以这里也将其归一下0
mov ax, 0x8000
mov di, ax
call Func_Read_One_Sector ; 读入根目录扇区,缓存地址是0x8000
mov si, KernelFileName
mov ax, RootDirBuffer
mov di, ax
cld
mov bx, DirEntryPerSector ; bx存储每个扇区存储的目录项数目
; 判断当前扇区的目录项是否都已对比完
Label_Search_For_Kernel_File:
cmp bx, 0 ; 如果bx变为0,就是在当前扇区目录项中没有该文件
jz Label_Goto_Next_Sector_In_Root_Dir
dec bx
mov cx, FileNameLen ; cx存储文件名的长度
; 对比文件名其中的一个字符
Label_Cmp_FileName:
cmp cx, 0 ; 当cx为0,表示对比完了文件名的每一个字符,和目录项中文件名是相同的
jz Label_FileName_Found
dec cx
lodsb ; 将esi指向的地址处的数据取出来赋给AL寄存器,esi=esi+1,这里的esi指向的是kernelfilename的那个位置
cmp al, byte [es:di]
jz Label_Go_On ; 如果当前字符相同,就继续对比下一个字符
jmp Label_Different ; 如果字符不相同,就调到字符不同的下一个函数
; 为对比文件名下一个字符做准备
Label_Go_On:
inc di ; 由于lodsb中esi已经自动加一,所以对比下一个字符只需要对di加一就可以跳转
jmp Label_Cmp_FileName
; 文件名不同时的处理
Label_Different:
and di, 0xffe0 ; 将前5位归0,di变成了当前目录项的起始位置
add di, 20h ; di加32,变成了下一个目录项的起始位置
mov si, KernelFileName
jmp Label_Search_For_Kernel_File
; 对读取下一个扇区做准备
Label_Goto_Next_Sector_In_Root_Dir:
add word [SectorNo], 1 ; 对比的第几个扇区加1
jmp Label_Search_In_Root_Dir_Begin
; 没有kernel.bbin文件的显示
Label_No_Kernel_File:
mov ax, 1301h
mov bx, 008Ch
mov dx, 0301h ;row 3
mov cx, 21
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, NoKernelMessage
int 10h
jmp $
; 发现了kernel.bin文件的处理
Label_FileName_Found:
mov ax, RootDirSectors
and di, 0xffe0
add di, DirFstClus ; 找到对应目录项中存放首簇簇号的地址
mov cx, word [es:di] ; 将首簇簇号赋给cx
push cx
add cx, SectorAndClusterBalance ; cx等于簇号对应的扇区号
mov eax, BaseOfTmpKernelAddr
mov es, eax
mov bx, OffsetOfTmpKernelFile
mov ax, cx
; 加载kernel文件到临时地址
Label_Go_On_Loading_Kernel_File:
push ax
push bx ; 现在栈中依次存放的是,kernel临时文件偏移量,文件扇区号,文件簇号
mov ah, 0x0e
mov al, '.'
mov bl, 0x0f
int 10h ; 显示....
pop bx
xor eax, eax
pop ax
mov esi, eax
mov di, bx
call Func_Read_One_Sector
; 保护环境
pop ax
push cx ;
push eax ; 文件簇号
push fs ;
push edi ;
push ds
push esi
; mov eax, 0x11111111
; mov fs, ax
; mov es, ax
; jmp $ ; 加上这几句代码,进过测试 ,在关闭保护模式后fs的表示范围又只有16位了
mov cx, 0x200
mov ax, BaseOfKernelFile
mov fs, ax
mov edi, dword [OffsetOfKernelFileCount]
mov ax, BaseOfTmpKernelAddr
mov ds, ax
mov esi, OffsetOfTmpKernelFile
mov es, eax
; 将kernel文件从临时地址移到最后的1MB开始地址
Label_Mov_Kernel:
mov al, byte [ds:esi] ; al=临时地址文件一字节
mov byte [fs:edi], al ; 0x100000地址开始处,
inc edi
inc esi
loop Label_Mov_Kernel
mov eax, 0x1000
mov ds, eax
mov dword [OffsetOfKernelFileCount], edi ; 一个扇区转移结束后,又将该地址存入下一个扇区起始处
pop esi
pop ds
pop edi
pop fs
pop eax ; 文件簇号
pop cx
call Func_Get_FAT_Entry ; 调用获取FAT表的函数
cmp ax, 0xfff8
jl Label_Kernel_File_Loaded ; 如果大于0xfff8,表示文件加载完成
push ax ; 压入簇号
add ax, SectorAndClusterBalance
jmp Label_Go_On_Loading_Kernel_File
Label_Kernel_File_Loaded:
mov ax, 1301h
mov bx, 000fh
mov dx, 0301h ;row 2
mov cx, 18
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, Kernel_File_Loaded
int 10h
; jmp $ ; 在这里停顿后查看0x100000处信息,kernel的确是move过去了,但是[fs:edi]这时的fs是16位的,难道这样也能组合成20位以上的地址吗
; ------------------- 获取内存地址信息 ---------------------
mov ax, 1301h
mov bx, 000Fh
mov dx, 0401h ;row 4
mov cx, 24
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, StartGetMemStructMessage
int 10h
mov ebx, 0
mov ax, 0
mov es, ax
mov di, MemoryStructBufferAddr
Label_Get_Mem_Struct:
mov eax, 0xe820
mov ecx, 20
mov edx, 0x534d4150
int 15h
jc Label_Get_Mem_Struct_Fail
add di, 20
cmp ebx, 0
jne Label_Get_Mem_Struct
jmp Label_Get_Mem_Struct_Ok
Label_Get_Mem_Struct_Fail:
mov ax, 1301h
mov bx, 008Ch
mov dx, 0501h ;row 5
mov cx, 23
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, GetMemStructErrMessage
int 10h
jmp $
Label_Get_Mem_Struct_Ok:
mov ax, 1301h
mov bx, 000Fh
mov dx, 0501h ;row 6
mov cx, 29
push ax
mov ax, ds
mov es, ax
pop ax
mov bp, GetMemStructOKMessage
int 10h
; ; 获取SVGA信息
; mov ax, 1301h
; mov bx, 000Fh
; mov dx, 0601h ;row 8
; mov cx, 23
; push ax
; mov ax, ds
; mov es, ax
; pop ax
; mov bp, StartGetSVGAVBEInfoMessage
; int 10h
; 开启保护模式
cli
db 0x66
lgdt [GdtPtr] ; 初始化GDT
db 0x66
lidt [IDT_POINTER] ; 初始化IDT
mov eax, cr0
or eax, 1 ; 将最后一位置1
mov cr0, eax
jmp dword SelectorCode32:GO_TO_TMP_Protect
[SECTION .s32]
[BITS 32]
GO_TO_TMP_Protect:
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov ss, ax
mov esp, 0x7e00
call support_long_mode
test eax, eax
jz no_support
; 在地址0x90000处初始化临时页表
mov dword [0x90000], 0x91007
mov dword [0x90800], 0x91007
mov dword [0x91000], 0x92007
mov dword [0x92000], 0x000083
mov dword [0x92008], 0x200083
mov dword [0x92010], 0x400083
mov dword [0x92018], 0x600083
mov dword [0x92020], 0x800083
mov dword [0x92028], 0xa00083
; 加载GDTR
db 0x66
lgdt [GdtPtr64]
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, 7E00h
; 打开PAE(物理地址扩展功能)
mov eax, cr4
bts eax, 5
mov cr4, eax
; 修改cr3
mov eax, 0x90000
mov cr3, eax
; enable long-mode
mov ecx, 0C0000080h ;IA32_EFER
rdmsr
bts eax, 8
wrmsr
;======= open PE and paging
mov eax, cr0
bts eax, 0
bts eax, 31
mov cr0, eax
jmp SelectorCode64:OffsetOfKernelFile
;======= test support long mode or not
support_long_mode:
mov eax, 0x80000000
cpuid
cmp eax, 0x80000001
setnb al
jb support_long_mode_done
mov eax, 0x80000001
cpuid
bt edx, 29
setc al
support_long_mode_done:
movzx eax, al
ret
;======= no support
no_support:
jmp $
[SECTION .s16lib]
[BITS 16]
Func_Get_FAT_Entry:
push es
push esi
push di
push dx
push ax ; 文件簇号
shr ax, 7 ; 每个FAT扇区存放256个FAT表项,除以256就是对应的扇区号
add ax, SectorNumOfFAT1Start
mov esi, eax
mov di, FATBuffer
mov dx, BaseOfTmpKernelAddr
mov es, dx
call Func_Read_One_Sector
pop ax
and ax, 0x007F
shl ax, 2
mov di, ax
mov ax, word [es:di] ; 获取到了下一个簇号
pop dx
pop di
pop esi
pop es
ret
; 读取一个扇区函数(主控制器主盘)
; 输入参数:esi,LBA ; ds:di,数据存放地址
; 输出参数:无
Func_Read_One_Sector:
; 读取状态,看是否第7位是0,第6位是否是1,也就是表示硬盘空闲且就绪的状态
mov dx,0x1f7
.not_ready1:
nop ; 一个短暂的停顿
in al,dx ; 将端口数据读入al中
and al,0xc0 ; 将al和1100 0000做与运算,其结果是将al的第6,7位保留下来,其他都是0
cmp al,0x40 ; 将al和0100 0000做比较,如果相等,证明硬盘空闲且就绪,如果不是就循环再检测
jne .not_ready1
; 设置要读取扇区的数量
mov dx,0x1f2
mov al,1
out dx,al
; 将LBA的前14位设置到0x1f3~0x1f5端口上
mov eax,esi
mov dx,0x1f3
out dx,al
shr eax,8 ; 右移8位,这样al的值就是第9-16位的LBA值
mov dx,0x1f4
out dx,al
shr eax,8
mov dx,0x1f5
out dx,al
; 设置0x1f6端口,前四位是LBA的最后四位,后四位,依据通过LBA从主设备读取数据的要求,应该设置为1110
shr eax,8
mov dx,0x1f6
and al,0x0f ; 将al的前四位进行保留,也就是LBA的后四位
or al,0xe0 ; 和1110进行或的结果,就是将al的后四位设置为1110
out dx,al
; 写入读硬盘命令
mov dx,0x1f7
mov al,0x20 ; 0x20是读命令
out dx,al
; 再次检查硬盘状态是否为空闲,且可以从硬盘中读数据的状态,也就是0x1f7应该为10001000
.not_ready2:
nop
in al,dx
and al,0x88 ; 将al的第7和第3位保留
cmp al,0x08 ; 判断第7位是否为0,第三位是否为1
jne .not_ready2
mov cx,256 ; 由于每次只能接受两个字节,所以一个扇区需要读取256次
mov dx,0x1f0
.read_data:
in ax,dx
mov [es:di],ax ; 将读出的数据存入es:di开始的地址
add di,2
loop .read_data
ret
;======= tmp IDT
IDT:
times 0x50 dq 0
IDT_END:
IDT_POINTER:
dw IDT_END - IDT - 1
dd IDT
; ------------- 变量 -----------------------
SectorNo dw 0 ; [SectorNo]表示SectorNo地址下的数,即初值为0
RootDirSizeForLoop dw RootDirSectors
KernelFileName: db "KERNEL BIN",0
OffsetOfKernelFileCount dd OffsetOfKernelFile ;4B,存放kernel偏移量
Kernel_File_Loaded: db "Kernel File Loaded"
StartLoaderMessage: db "Start Loader"
NoKernelMessage: db "ERROR:No KERNEL Found"
KernelFoundMessage: db "Kernel found"
StartGetMemStructMessage: db "Start Get Memory Struct."
GetMemStructErrMessage: db "Get Memory Struct ERROR"
GetMemStructOKMessage: db "Get Memory Struct SUCCESSFUL!"
; SVGA info 没获取
StartGetSVGAVBEInfoMessage: db "Start Get SVGA VBE Info"
GetSVGAVBEInfoErrMessage: db "Get SVGA VBE Info ERROR"
GetSVGAVBEInfoOKMessage: db "Get SVGA VBE Info SUCCESSFUL!"
StartGetSVGAModeInfoMessage: db "Start Get SVGA Mode Info"
GetSVGAModeInfoErrMessage: db "Get SVGA Mode Info ERROR"
GetSVGAModeInfoOKMessage: db "Get SVGA Mode Info SUCCESSFUL!"