;By Marcus Xing
;boot/loader.asm
;加载原始KERNEL.BIN,在保护模式下分析ELF把各
;段转移到对应的虚拟地址上,并把控制权交给KERNEL
org 0100h
%include "pm.inc"
;-------------------------------------------------------------------------------宏信息
Base_Of_Loader equ 9000h ;加载LOADER的段地址
Offset_Of_Loader equ 0100h ;加载LOADER的偏移地址
Base_Of_Kernel equ 8000h ;加载KERNEL的段地址
Offset_Of_Kernel equ 0h ;加载KERNEL的偏移地址
Base_Of_Page_Dir equ 200000h ;页目录表的首址
Base_Of_Page_Tbl equ 201000h ;页表的首址
Base_Of_Loader_Phy_Addr equ Base_Of_Loader * 10h ;加载LOADER的物理基地址
Base_Of_Kernel_Phy_Addr equ Base_Of_Kernel * 10h ;加载原始KERNEL的物理基地址
Root_Dir_Begin_Sector equ 19 ;根目录区的逻辑起始逻辑扇区
Kernel_Entry_Point_Phy_Addr equ 30400h ;内核的入口地址
;-------------------------------------------------------------------CODE_SEGMENT
[section .code16]
[bits 16]
LABEL_START:
mov ax,cs
mov ds,ax
mov ax,3000h
mov ss,ax
mov sp,0100h
;取得内存信息存入缓冲区
;进入保护模式后显示出来
;BIOS int 15h
xor ebx,ebx ;后续值,不用程序员关注,初始为0
mov di,_Memory_Info_Buffer ;es:di指向缓冲区
.l:
mov ecx,20 ;每个ARDStruct为20字节
mov edx,0534d4150h ;edx='SMAP'
mov eax,0e820h ;功能号
int 15h
jc .fail ;如果CF为1,则出错,置ARDStruct变量为0
add di,20 ;es:di指向下一段缓冲区
inc dword [_d_ARDStruct_Num] ;ARDStruct变量自增1
cmp ebx,0 ;判断ebx是否为0,为0则读取结束,否则继续读取
jne .l
jmp .done
.fail:
mov dword [_d_ARDStruct_Num],0
.done:
;回车
push _sz_Return
call Disp_Str_In_Real_Mode
add sp,2
;显示字符串Loading
push _sz_Loading_Message
call Disp_Str_In_Real_Mode
add sp,2
;es指向缓冲区的段地址
mov ax,Base_Of_Kernel
mov es,ax
LABEL_READ_NEXT_SECTOR:
mov bx,Offset_Of_Kernel ;bx指向缓冲区的偏移地址
cmp word [_w_Root_Dir_Search_For_Loop],0 ;比较循环变量是否为0
je LABEL_NO_FOUND ;为0代表没找到,跳转到相应的标号处理
dec word [_w_Root_Dir_Search_For_Loop] ;尚未为0,循环变量自减1
;读取当前根目录区扇区至缓冲区
push 1
push word [_w_Root_Dir_Sector_No]
call Read_Sector
add sp,4
inc word [_w_Root_Dir_Sector_No] ;定位到下一个根目录扇区,为下一次读做准备
mov dx,16 ;一个扇区有16个FCB,要循环16次
LABEL_GO_ON_NEXT_DIR_ITEM:
cmp dx,0 ;判断是否为0
je LABEL_READ_NEXT_SECTOR ;为0就读下一个根目录扇区
dec dx ;dx自减1
mov cx,11 ;FCB中的文件名字段有11位,故循环变量为11
mov si,_s_Name_Of_Kernel ;si定位到要比较的字符串偏移处
LABEL_GO_ON_CMP:
cmp cx,0 ;判断比较计数器是否为0,为0表示比较成功
je LABEL_FOUNDED ;即找到KERNEL.BIN,跳转到相应标号处理
dec cx ;cx自减1
mov al,[si] ;ds:si指向比较字符串,赋给al
cmp al,[es:bx] ;es:bx指向当前FCB的文件名字段,比较两者
je LABEL_CMP_OK ;比较成功则进行下一次比较
and bx,0ffe0h ;不成功则把bx的低5位清零,因为一个FCB为32
add bx,32 ;字节,再加32则定位到下一个FCB的文件名处
jmp LABEL_GO_ON_NEXT_DIR_ITEM ;跳转,比较下一个FCB
LABEL_CMP_OK:
;两个串的定位器都自增1
inc si
inc bx
jmp LABEL_GO_ON_CMP ;跳转下一次比较
;没找到KERNEL,跳转到这儿,显示完相应信息后死循环
LABEL_NO_FOUND:
push _sz_No_Kernel_Message
call Disp_Str_In_Real_Mode
add sp,2
jmp $
;找到了KERNEL,跳转到这儿
LABEL_FOUNDED:
and bx,0ffe0h ;使es:bx指向找到的KERNEL的FCB的起始处
mov cx,[es:bx + 1ah] ;取得KERNEL的相对于数据区的偏移扇区号
;注意:2为数据区的第一个扇区
mov ax,cx
mov bx,Offset_Of_Kernel ;es:bx=8000h:0000h,准备读入一个数据扇区
LABEL_GO_ON_LOADING:
;每从数据区读一个扇区则显示一个点
push _sz_Dot
call Disp_Str_In_Real_Mode
add sp,2
add ax,31 ;得到要读取的数据扇区的逻辑地址
;读一个数据扇区
push 1
push ax
call Read_Sector
add sp,4
;得到当前数据扇区在FAT中的值
push cx
call Get_FAT_Entry
add sp,2
;判断有没有下一个扇区,有则根据得到的下一个数据相对扇区号继续读
;没有则可以分析KERNEL.BIN的ELF格式了
cmp ax,0fffh
je LABEL_START_LOADING
add bx,512 ;定位KERNEL的加载偏移地址
mov cx,ax
jmp LABEL_GO_ON_LOADING ;跳转回去进行相应处理
;KERNEL数据全部加载完毕后跳转到此
LABEL_START_LOADING:
;打印准备信息
push _sz_Ready_Message
call Disp_Str_In_Real_Mode
add sp,2
;回车
push _sz_Return
call Disp_Str_In_Real_Mode
add sp,2
;下一步准备跳入保护模式
call Kill_Motor ;关闭软驱马达
lgdt [GDT_Ptr] ;加载GDT信息到GDTR
cli ;关中断
;打开A20,可以寻址到1M开外
in al,92h
or al,00000010b
out 92h,al
;CR0最低位置1,使CPU处于保护模式
mov eax,cr0
or al,1
mov cr0,eax
;跳转到32位代码段中
jmp dword Selector_Flat_C:(Base_Of_Loader_Phy_Addr + LABEL_SEG_CODE32)
;--------------------------------------------------------------PM_CODE32_SECTION
[section .code32]
[bits 32]
LABEL_SEG_CODE32:
;ds,es,ss,fs都指向4G读写平坦段
mov ax,Selector_Flat_RW
mov ds,ax
mov es,ax
mov fs,ax
;设置好堆栈,1K空间
mov ss,ax
mov esp,Stack32_Len
;gs指向显存段
mov ax,Selector_Video
mov gs,ax
;----------------------------------------------------------------------------显示内存信息
;显示内存头信息
push sz_Memory_Info_Title
call Disp_Str
add esp,4
;回车
push sz_Return
call Disp_Str
add esp,4
mov ecx,[d_ARDStruct_Num] ;ecx<-ADRStruct个数,控制外层循环
mov esi,Memory_Info_Buffer ;ds:esi指向内存信息缓冲区
.1:
mov edx,5 ;控制内层循环,因为1个ARDStruct有5个字段
mov edi,d_Base_Addr_Low ;es:edi指向ARDStruct缓冲区
.2:
cmp edx,0 ;判断edx是否为0
je .3 ;是的话跳转到.3
dec edx
lodsd ;eax<-ds:esi,add esi,4
;显示出来
push eax
call Disp_Int
pop eax
stosd ;es:edi<-eax,add edi,4
;打印2个空格
call Disp_Space
call Disp_Space
jmp .2 ;继续内层循环
.3:
cmp dword [d_Type],1 ;判断类型字段是否为1,即是否可被我们的OS使用
jne .4 ;不是就跳转到4,显示下一个ARDStruct
;判断当前ARDStruct地址范围
;跟当前内存范围大小,当前内存
;范围小则更新之
mov eax,[d_Base_Addr_Low]
add eax,[d_Length_Low]
cmp [d_Memory_Size],eax
ja .3
mov [d_Memory_Size],eax
.4:
;回车
push sz_Return
call Disp_Str
add esp,4
loop .1 ;执行外层循环
;显示内存范围字符串
push sz_Memory_Size
call Disp_Str
add esp,4
;显示内存范围数值
push dword [d_Memory_Size]
call Disp_Int
add esp,4
;回车
push sz_Return
call Disp_Str
add esp,4
;--------------------------------------------------------------分页设置(一一对应)
mov eax,[d_Memory_Size]
mov ebx,1024 * 1024 * 4 ;一个页目录项对应4M物理内存
div ebx ;eax<-页目录表个数
cmp edx,0
je .11
inc eax ;余数不为0页目录加1
.11:
push eax ;暂存页目录个数
mov ecx,eax ;循环次数
mov eax,Base_Of_Page_Tbl | 1b | 10b | 100b ;指向页表,每个页目录可读可写,用户级,存在
mov edi,Base_Of_Page_Dir ;es:edi指向页目录表地址
.22:
stosd
add eax,4096
loop .22
pop eax ;恢复页目录个数
mov ebx,1024 ;每个页目录项对应1024个页表项
mul ebx
mov ecx,eax ;ecx<-页目录项个数
mov eax,0 | 1b | 10b | 100b ;一一映射,每个页表项可读可写,用户级,存在
mov edi,Base_Of_Page_Tbl ;es:edi指向页表地址
.33:
stosd
add eax,4096
loop .33
;cr3<-页目录地址
mov eax,Base_Of_Page_Dir
mov cr3,eax
;置cr0最高位为1,打开分页
mov eax,cr0
or eax,80000000h
mov cr0,eax
;------------------------------------------------------把ELF格式的KERNEL读入内存
;KERNEL.BIN原始文件位于8000h:0h处
mov cx,[Base_Of_Kernel_Phy_Addr + 44] ;取得PROGRAM HEADER的个数
and ecx,0ffffh ;ecx高16位清零
mov esi,[Base_Of_Kernel_Phy_Addr + 28] ;取得PROGRAM HEADER TABLE相对文件偏移
add esi,Base_Of_Kernel_Phy_Addr ;ds:esi指向PROGRAM HEADER TABLE的第一项
.s:
cmp dword [esi + 0],0 ;判断当前PROGRAM HEADER的TYPE是否为0
je .next ;为0直接跳过处理下一项
push dword [esi + 16] ;当前PROGRAM HEADER的长度入栈
push dword [esi + 8] ;当前PROGRAM HEADER的虚拟地址入栈
mov eax,[esi + 4]
add eax,Base_Of_Kernel_Phy_Addr ;eax<-当前PROGRAM HEADER的内容地址
push eax
call Memory_Copy ;复制之
add esp,12
.next:
add esi,32 ;ds:esi指向下一个项,一个项32字节
loop .s
push dword [d_Disp_Pos_In_PM] ;进入内核前把显示位置入栈供内核使用
;********************************************************************
jmp Selector_Flat_C:Kernel_Entry_Point_Phy_Addr ;****正式进入内核****
;********************************************************************
;-------------------------------------------------------------PM_STACK32_SECTION
[section .stack32]
[bits 32]
times 1024 db 0
Stack32_Len equ Base_Of_Loader_Phy_Addr + $
;-------------------------------------------------------------------DATA_SECTION
LABEL_DATA:
_w_Root_Dir_Sector_No dw Root_Dir_Begin_Sector ;根目录区起始逻辑扇区
_w_Root_Dir_Search_For_Loop dw 14 ;找LOADER循环次数,就
;是根目录区扇区个数
_b_Is_Odd db 0 ;FAT ENTRY逻辑地址的奇或偶
_w_Disp_Pos_In_RM dw 0 ;实模式显示地址
_d_Disp_Pos_In_PM dd 320 ;保护模式显示地址,从第3行开始
;一些用到的串
_sz_Loading_Message db 'Loading',0
_sz_Ready_Message db 'Ready',0
_sz_No_Kernel_Message db 'No Kernel',0
_sz_Dot db '.',0
_sz_Return db 0ah,0
_sz_Space db 20h,0
_sz_PM_Message db 'HELLO!',0
_s_Name_Of_Kernel db 'KERNEL BIN'
_sz_Memory_Info_Title db 'BaseAddrL BaseAddrH LengthLow LengthHigh Type',0
_sz_Memory_Size db 'Memory Size:',0
_Memory_Info_Buffer: ;内存信息缓冲区,能存放12个ARDStruct,
times 256 db 0 ;如果内存太大可能不够用
_d_ARDStruct_Num dd 0 ;ARDStruct的个数
;一个ARDStructd的缓冲区
_d_Base_Addr_Low dd 0 ;基址低32位
_d_Base_Addr_High dd 0 ;基址高32位
_d_Length_Low dd 0 ;长度低32位
_d_Length_High dd 0 ;长度高32位
_d_Type dd 0 ;类型
_d_Memory_Size dd 0 ;可用内存长度
;保护模式下用到得标号(偏移)
d_Disp_Pos_In_PM equ Base_Of_Loader_Phy_Addr + _d_Disp_Pos_In_PM
sz_PM_Message equ Base_Of_Loader_Phy_Addr + _sz_PM_Message
sz_Return equ Base_Of_Loader_Phy_Addr + _sz_Return
sz_Space equ Base_Of_Loader_Phy_Addr + _sz_Space
sz_Memory_Info_Title equ Base_Of_Loader_Phy_Addr + _sz_Memory_Info_Title
sz_Memory_Size equ Base_Of_Loader_Phy_Addr + _sz_Memory_Size
Memory_Info_Buffer equ Base_Of_Loader_Phy_Addr + _Memory_Info_Buffer
d_ARDStruct_Num equ Base_Of_Loader_Phy_Addr + _d_ARDStruct_Num
d_Base_Addr_Low equ Base_Of_Loader_Phy_Addr + _d_Base_Addr_Low
d_Base_Addr_High equ Base_Of_Loader_Phy_Addr + _d_Base_Addr_High
d_Length_Low equ Base_Of_Loader_Phy_Addr + _d_Length_Low
d_Length_High equ Base_Of_Loader_Phy_Addr + _d_Length_High
d_Type equ Base_Of_Loader_Phy_Addr + _d_Type
d_Memory_Size equ Base_Of_Loader_Phy_Addr + _d_Memory_Size
;----------------------------------------------------------------------------GDT
[section .gdt]
;GDT开始
LABEL_GDT:
LABEL_DESC_DUMMY:
Descriptor 0,0,0
LABEL_DESC_FLAT_RW:
Descriptor 0,0fffffh,DA_DRW + DA_32 + DA_LIMIT_4K ;读写4G平坦段
LABEL_DESC_FLAT_C:
Descriptor 0,0fffffh,DA_CR + DA_32 + DA_LIMIT_4K ;4G可执行段
LABEL_DESC_VIDEO:
Descriptor 0b8000h,0ffffh,DA_DRW + DA_DPL3 ;指向显存首址段,DPL为3
;为之后的进程准备
;选择子
Selector_Flat_RW equ LABEL_DESC_FLAT_RW - LABEL_GDT
Selector_Flat_C equ LABEL_DESC_FLAT_C - LABEL_GDT
Selector_Video equ LABEL_DESC_VIDEO - LABEL_GDT + SA_RPL3
;RPL为3,为之后的进程准备
GDT_Len equ $ - LABEL_GDT ;GDT长度宏
GDT_Ptr: ;准备加载进GDTR的6字节数据结构
dw GDT_Len - 1
dd Base_Of_Loader_Phy_Addr + LABEL_GDT
;------------------------------------------------------------lib_in_protect_mode
[section .lib_in_protect_mode]
[bits 32]
%include "lib_in_protect_mode.inc"
;---------------------------------------------------------------lib_in_real_mode
[section .lib_in_real_mode]
[bits 16]
%include "lib_in_real_mode.inc"