boot/loader.asm

;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"
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值