loader

org 0100h
jmp LABEL_START
;磁盤文件定義
%include "fat12hdr.inc"
%include "load.inc"
%include "pm.inc"
;GDT
LABEL_GDT:  Descriptor 0, 0, 0 ;空描述符
LABEL_DESC_FLAT_C: Descriptor 0,0fffffh, DA_CR | DA_32 | DA_LIMIT_4K
LABEL_DESC_FLAT_RW: Descriptor 0,0fffffh, DA_DRW | DA_32 | DA_LIMIT_4K
LABEL_DESC_VIDEO: Descriptor 0B8000h,0fffffh, DA_DRW | DA_DPL3 ;顯存首地址
GdtLen equ $ - LABEL_GDT
GdtPtr dw GdtLen - 1
 dd BaseOfLoaderPhyAddr + LABEL_GDT
;GDT選擇子
SelectorFlatC equ LABEL_DESC_FLAT_C - LABEL_GDT
SelectorFlatRW equ LABEL_DESC_FLAT_RW - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT

BaseOfStack equ 0100h


LABEL_START:
 mov ax,cs
 mov ds,ax
 mov es,ax
 mov gs,ax
 mov ss,ax
 mov sp,BaseOfStack
 mov dh,0
 call DispStrRealMode

        ;得到內存數
 mov ebx,0   ;ebx=後續值,開始時需爲0
 mov di,_MemChkBuf  ;es:di指向一個地址範圍描述符結構(ARDS)
.MemChkLoop:
 mov eax,0E820h
 mov ecx,20
 mov edx,0534D4150h  ;edx = 'SMAP'
 int 15h
 jc .MemChkFail  ;CF進位,表示有錯誤
 add di,20
 inc dword [_dwMCRNumber] ;dwMCRNumber = ARDS的個數
 cmp ebx,0   ;ebx置且CF未進位,則表示它是最後一個地址描述符
 jne .MemChkLoop
 jmp .MemChkOk
.MemChkFail:
 mov dword [_dwMCRNumber],0
.MemChkOk:
 ;在A盤的根目錄尋找KERNEL.BIN 根目錄32位條目記錄了文件的各項屬性
; 下面在 A 盤的根目錄尋找 KERNEL.BIN
 mov word [wSectorNo], SectorNoOfRootDirectory 
 xor ah, ah ; ┓
 xor dl, dl ; ┣ 軟驅復位
 int 13h ; ┛
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
 cmp word [wRootDirSizeForLoop], 0 ; ┓
 jz LABEL_NO_KERNELBIN  ; ┣ 判斷根目錄區是不是已經讀完, 如果讀完表示沒有找到 KERNEL.BIN
 dec word [wRootDirSizeForLoop] ; ┛
 mov ax, BaseOfKernelFile
 mov es, ax   ; es <- BaseOfKernelFile
 mov bx, OffsetOfKernelFile ; bx <- OffsetOfKernelFile 於是, es:bx = BaseOfKernelFile:OffsetOfKernelFile = BaseOfKernelFile * 10h + OffsetOfKernelFile
 mov ax, [wSectorNo]  ; ax <- Root Directory 中的某 Sector 號
 mov cl, 1
 call ReadSector

 mov si, KernelFileName ; ds:si -> "KERNEL  BIN"
 mov di, OffsetOfKernelFile ; es:di -> BaseOfKernelFile:???? = BaseOfKernelFile*10h+????
 cld
 mov dx, 10h
LABEL_SEARCH_FOR_KERNELBIN:
 cmp dx, 0     ; ┓
 jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ; ┣ 循環次數控制, 如果已經讀完了一個 Sector, 就跳到下一個 Sector
 dec dx     ; ┛
 mov cx, 11
LABEL_CMP_FILENAME:
 cmp cx, 0   ; ┓
 jz LABEL_FILENAME_FOUND ; ┣ 循環次數控制, 如果比較了 11 個字符都相等, 表示找到
 dec cx   ; ┛
 lodsb    ; ds:si -> al
 cmp al, byte [es:di] ; if al == es:di
 jz LABEL_GO_ON
 jmp LABEL_DIFFERENT
LABEL_GO_ON:
 inc di
 jmp LABEL_CMP_FILENAME ; 繼續循環

LABEL_DIFFERENT:
 and di, 0FFE0h  ; else┓ 這時di的值不知道是什麼, di &= e0 爲了讓它是 20h 的倍數
 add di, 20h   ;     ┃
 mov si, KernelFileName ;     ┣ di += 20h  下一個目錄條目
 jmp LABEL_SEARCH_FOR_KERNELBIN;   ┛

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
 add word [wSectorNo], 1
 jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN

LABEL_NO_KERNELBIN:
 mov dh, 2   ; "No KERNEL."
 call DispStrRealMode  ; 顯示字符串
%ifdef _LOADER_DEBUG_
 mov ax, 4c00h  ; ┓
 int 21h   ; ┛沒有找到 KERNEL.BIN, 回到 DOS
%else
 jmp $   ; 沒有找到 KERNEL.BIN, 死循環在這裏
%endif

LABEL_FILENAME_FOUND:   ; 找到 KERNEL.BIN 後便來到這裏繼續
 mov ax, RootDirSectors
 and di, 0FFF0h  ; di -> 當前條目的開始

 push eax
 mov eax, [es : di + 01Ch]  ; ┓
 mov dword [dwKernelSize], eax ; ┛保存 KERNEL.BIN 文件大小
 pop eax

 add di, 01Ah  ; di -> 首 Sector
 mov cx, word [es:di]
 push cx   ; 保存此 Sector 在 FAT 中的序號
 add cx, ax
 add cx, DeltaSectorNo ; 這時 cl 裏面是 LOADER.BIN 的起始扇區號 (從 0 開始數的序號)
 mov ax, BaseOfKernelFile
 mov es, ax   ; es <- BaseOfKernelFile
 mov bx, OffsetOfKernelFile ; bx <- OffsetOfKernelFile 於是, es:bx = BaseOfKernelFile:OffsetOfKernelFile = BaseOfKernelFile * 10h + OffsetOfKernelFile
 mov ax, cx   ; ax <- Sector 號

LABEL_GOON_LOADING_FILE:
 push ax   ; ┓
 push bx   ; ┃
 mov ah, 0Eh   ; ┃ 每讀一個扇區就在 "Loading  " 後面打一個點, 形成這樣的效果:
 mov al, '.'   ; ┃
 mov bl, 0Fh   ; ┃ Loading ......
 int 10h   ; ┃
 pop bx   ; ┃
 pop ax   ; ┛

 mov cl, 1
 call ReadSector
 pop ax   ; 取出此 Sector 在 FAT 中的序號
 call GetFATEntry
 cmp ax, 0FFFh
 jz LABEL_FILE_LOADED
 push ax   ; 保存 Sector 在 FAT 中的序號
 mov dx, RootDirSectors
 add ax, dx
 add ax, DeltaSectorNo
 add bx, [BPB_BytsPerSec]
 jmp LABEL_GOON_LOADING_FILE
LABEL_FILE_LOADED:

 call KillMotor  ; 關閉軟驅馬達

 mov dh, 1   ; "Ready."
 call DispStrRealMode  ; 顯示字符串
 ;下面準備跳入保護模式
 lgdt [GdtPtr]
 ;關中斷
 cli
 ;打開地址線A20
 in al,92h
 or al,00000010b
 out 92h,al
 ;準備切換到保護模式
 mov eax,cr0
 or eax,1
 mov cr0,eax
 ;真正進入保護模式
 jmp dword SelectorFlatC:(BaseOfLoaderPhyAddr + LABEL_PM_START)
 jmp $
;============================================================================
;變量
;----------------------------------------------------------------------------
wRootDirSizeForLoop dw RootDirSectors ; Root Directory 佔用的扇區數
wSectorNo  dw 0  ; 要讀取的扇區號
bOdd   db 0  ; 奇數還是偶數
dwKernelSize  dd 0  ; KERNEL.BIN 文件大小 
;=======================================================================
;字符串部分
KernelFileName db "KERNEL  BIN",0
MessageLength equ 9
LoadMessage db "Loading  "
Message1 db "Ready.   "
Message2 db "No KERNEL"

;=======================================================================
;函數部分
;=======================================================================
;DispStrRealMode dh爲序號
DispStrRealMode:
 mov ax,MessageLength
 mul dh
 add ax,LoadMessage
 mov bp,ax  ;
 mov ax,ds  ;es:bp = 串地址
 mov es,ax  ;
 mov cx,MessageLength ;cx=串長度
 mov ax,01301h
 mov bx,0007h
 mov dl,0
 add dh,3  ;從第3行往下顯示
 int 10h  ;int 10h
 ret
;----------------------------------------------------------------------------
; 函數名: ReadSector
;----------------------------------------------------------------------------
; 作用:
; 從序號(Directory Entry 中的 Sector 號)爲 ax 的的 Sector 開始, 將 cl 個 Sector 讀入 es:bx 中
ReadSector:
 ; -----------------------------------------------------------------------
 ; 怎樣由扇區號求扇區在磁盤中的位置 (扇區號 -> 柱面號, 起始扇區, 磁頭號)
 ; -----------------------------------------------------------------------
 ; 設扇區號爲 x
 ;                           ┌ 柱面號 = y >> 1
 ;       x           ┌ 商 y ┤
 ; -------------- => ┤      └ 磁頭號 = y & 1
 ;  每磁道扇區數     │
 ;                   └ 餘 z => 起始扇區號 = z + 1
 push bp
 mov bp, sp
 sub esp, 2   ; 闢出兩個字節的堆棧區域保存要讀的扇區數: byte [bp-2]

 mov byte [bp-2], cl
 push bx   ; 保存 bx
 mov bl, [BPB_SecPerTrk] ; bl: 除數
 div bl   ; y 在 al 中, z 在 ah 中
 inc ah   ; z ++
 mov cl, ah   ; cl <- 起始扇區號
 mov dh, al   ; dh <- y
 shr al, 1   ; y >> 1 (其實是 y/BPB_NumHeads, 這裏BPB_NumHeads=2)
 mov ch, al   ; ch <- 柱面號
 and dh, 1   ; dh & 1 = 磁頭號
 pop bx   ; 恢復 bx
 ; 至此, "柱面號, 起始扇區, 磁頭號" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^
 mov dl, [BS_DrvNum]  ; 驅動器號 (0 表示 A 盤)
.GoOnReading:
 mov ah, 2   ; 讀
 mov al, byte [bp-2]  ; 讀 al 個扇區
 int 13h
 jc .GoOnReading  ; 如果讀取錯誤 CF 會被置爲 1, 這時就不停地讀, 直到正確爲止

 add esp, 2
 pop bp

 ret
;----------------------------------------------------------------------------
; 函數名: GetFATEntry
;----------------------------------------------------------------------------
; 作用:
; 找到序號爲 ax 的 Sector 在 FAT 中的條目, 結果放在 ax 中
; 需要注意的是, 中間需要讀 FAT 的扇區到 es:bx 處, 所以函數一開始保存了 es 和 bx
GetFATEntry:
 push es
 push bx
 push ax
 mov ax, BaseOfKernelFile ; ┓
 sub ax, 0100h  ; ┣ 在 BaseOfKernelFile 後面留出 4K 空間用於存放 FAT
 mov es, ax   ; ┛
 pop ax
 mov byte [bOdd], 0
 mov bx, 3
 mul bx   ; dx:ax = ax * 3
 mov bx, 2
 div bx   ; dx:ax / 2  ==>  ax <- 商, dx <- 餘數
 cmp dx, 0
 jz LABEL_EVEN
 mov byte [bOdd], 1
LABEL_EVEN:;偶數
 xor dx, dx   ; 現在 ax 中是 FATEntry 在 FAT 中的偏移量. 下面來計算 FATEntry 在哪個扇區中(FAT佔用不止一個扇區)
 mov bx, [BPB_BytsPerSec]
 div bx   ; dx:ax / BPB_BytsPerSec  ==> ax <- 商   (FATEntry 所在的扇區相對於 FAT 來說的扇區號)
     ;    dx <- 餘數 (FATEntry 在扇區內的偏移)。
 push dx
 mov bx, 0   ; bx <- 0 於是, es:bx = (BaseOfKernelFile - 100):00 = (BaseOfKernelFile - 100) * 10h
 add ax, SectorNoOfFAT1 ; 此句執行之後的 ax 就是 FATEntry 所在的扇區號
 mov cl, 2
 call ReadSector  ; 讀取 FATEntry 所在的扇區, 一次讀兩個, 避免在邊界發生錯誤, 因爲一個 FATEntry 可能跨越兩個扇區
 pop dx
 add bx, dx
 mov ax, [es:bx]
 cmp byte [bOdd], 1
 jnz LABEL_EVEN_2
 shr ax, 4
LABEL_EVEN_2:
 and ax, 0FFFh

LABEL_GET_FAT_ENRY_OK:

 pop bx
 pop es
 ret
;----------------------------------------------------------------------------


;----------------------------------------------------------------------------
; 函數名: KillMotor
;----------------------------------------------------------------------------
; 作用:
; 關閉軟驅馬達
KillMotor:
 push dx
 mov dx, 03F2h
 mov al, 0
 out dx, al
 pop dx
 ret
;----------------------------------------------------------------------------
;從此以後的代碼在保護模式下執行
;32位代碼段你,由實模式跳入
[SECTION .s32]
ALIGN 32
[BITS 32]
LABEL_PM_START:
 mov ax,SelectorVideo
 mov gs,ax
 mov ax,SelectorFlatRW
 mov ds,ax
 mov es,ax
 mov fs,ax
 mov ss,ax
 mov esp,TopOfStack
 push szMemChkTitle
 call DispStr
 add esp,4
 call DispMemInfo
 call SetupPaging
 mov ah,0Fh
 mov al,'P'
 mov [gs:((80*0 +39) * 2)],ax
 call InitKernel
 ;jmp $
 jmp SelectorFlatC:KernelEntryPointPhyAddr ;進入內核!!!!!!
%include "lib.inc"
; 顯示內存信息 --------------------------------------------------------------
DispMemInfo:
 push esi
 push edi
 push ecx

 mov esi, MemChkBuf
 mov ecx, [dwMCRNumber];for(int i=0;i<[MCRNumber];i++)//每次得到一個ARDS
.loop:      ;{
 mov edx, 5    ;  for(int j=0;j<5;j++)//每次得到一個ARDS中的成員
 mov edi, ARDStruct   ;  {//依次顯示:BaseAddrLow,BaseAddrHigh,LengthLow
.1:      ;               LengthHigh,Type
 push dword [esi]   ;
 call DispInt    ;    DispInt(MemChkBuf[j*4]); // 顯示一個成員
 pop eax    ;
 stosd     ;    ARDStruct[j*4] = MemChkBuf[j*4];
 add esi, 4    ;
 dec edx    ;
 cmp edx, 0    ;
 jnz .1    ;  }
 call DispReturn   ;  printf("\n");
 cmp dword [dwType], 1 ;  if(Type == AddressRangeMemory)
 jne .2    ;  {
 mov eax, [dwBaseAddrLow];
 add eax, [dwLengthLow];
 cmp eax, [dwMemSize]  ;    if(BaseAddrLow + LengthLow > MemSize)
 jb .2    ;
 mov [dwMemSize], eax  ;    MemSize = BaseAddrLow + LengthLow;
.2:      ;  }
 loop .loop    ;}
      ;
 call DispReturn   ;printf("\n");
 push szRAMSize   ;
 call DispStr    ;printf("RAM size:");
 add esp, 4    ;
      ;
 push dword [dwMemSize] ;
 call DispInt    ;DispInt(MemSize);
 add esp, 4    ;

 pop ecx
 pop edi
 pop esi
 ret
; ---------------------------------------------------------------------------

; 啓動分頁機制 --------------------------------------------------------------
SetupPaging:
 ; 根據內存大小計算應初始化多少PDE以及多少頁表
 xor edx, edx
 mov eax, [dwMemSize]
 mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 一個頁表對應的內存大小
 div ebx
 mov ecx, eax ; 此時 ecx 爲頁表的個數,也即 PDE 應該的個數
 test edx, edx
 jz .no_remainder
 inc ecx  ; 如果餘數不爲 0 就需增加一個頁表
.no_remainder:
 push ecx  ; 暫存頁表個數

 ; 爲簡化處理, 所有線性地址對應相等的物理地址. 並且不考慮內存空洞.

 ; 首先初始化頁目錄
 mov ax, SelectorFlatRW
 mov es, ax
 mov edi, PageDirBase ; 此段首地址爲 PageDirBase
 xor eax, eax
 mov eax, PageTblBase | PG_P  | PG_USU | PG_RWW
.1:
 stosd
 add eax, 4096  ; 爲了簡化, 所有頁表在內存中是連續的.
 loop .1

 ; 再初始化所有頁表
 pop eax   ; 頁表個數
 mov ebx, 1024  ; 每個頁表 1024 個 PTE
 mul ebx
 mov ecx, eax  ; PTE個數 = 頁表個數 * 1024
 mov edi, PageTblBase ; 此段首地址爲 PageTblBase
 xor eax, eax
 mov eax, PG_P  | PG_USU | PG_RWW
.2:
 stosd
 add eax, 4096  ; 每一頁指向 4K 的空間
 loop .2

 mov eax, PageDirBase
 mov cr3, eax
 mov eax, cr0
 or eax, 80000000h
 mov cr0, eax
 jmp short .3
.3:
 nop

 ret


; 分頁機制啓動完畢 ----------------------------------------------------------

; InitKernel ---------------------------------------------------------------------------------
; 將 KERNEL.BIN 的內容經過整理對齊後放到新的位置
; 遍歷每一個 Program Header,根據 Program Header 中的信息來確定把什麼放進內存,放到什麼位置,以及放多少。
; --------------------------------------------------------------------------------------------
InitKernel:
 xor esi,esi
 mov cx,word [BaseOfKernelFilePhyAddr + 2CH] ;ecx<-pElfHdr->e_phnum
 movzx ecx,cx
 mov esi,[BaseOfKernelFilePhyAddr + 1Ch]
 add esi,BaseOfKernelFilePhyAddr
.Begin:
 mov eax,[esi + 0]
 cmp eax,0
 jz .NoAction
 push dword [esi + 010h]
 mov eax,[esi + 04h]
 add eax,BaseOfKernelFilePhyAddr
 push eax
 push dword [esi + 08h]
 call MemCpy
 add esp,12
.NoAction:
 add esi,020h
 dec ecx
 jnz .Begin

 ret
;32位data段
[SECTION .data1]

ALIGN 32

LABEL_DATA:
; 實模式下使用這些符號
; 字符串
_szMemChkTitle: db "BaseAddrL BaseAddrH LengthLow LengthHigh   Type", 0Ah, 0
_szRAMSize: db "RAM size:", 0
_szReturn: db 0Ah, 0
;; 變量
_dwMCRNumber: dd 0 ; Memory Check Result
_dwDispPos: dd (80 * 6 + 0) * 2 ; 屏幕第 6 行, 第 0 列。
_dwMemSize: dd 0
_ARDStruct: ; Address Range Descriptor Structure
  _dwBaseAddrLow:  dd 0
  _dwBaseAddrHigh:  dd 0
  _dwLengthLow:   dd 0
  _dwLengthHigh:  dd 0
  _dwType:   dd 0
_MemChkBuf: times 256 db 0
;
;; 保護模式下使用這些符號
szMemChkTitle  equ BaseOfLoaderPhyAddr + _szMemChkTitle
szRAMSize  equ BaseOfLoaderPhyAddr + _szRAMSize
szReturn  equ BaseOfLoaderPhyAddr + _szReturn
dwDispPos  equ BaseOfLoaderPhyAddr + _dwDispPos
dwMemSize  equ BaseOfLoaderPhyAddr + _dwMemSize
dwMCRNumber  equ BaseOfLoaderPhyAddr + _dwMCRNumber
ARDStruct  equ BaseOfLoaderPhyAddr + _ARDStruct
 dwBaseAddrLow equ BaseOfLoaderPhyAddr + _dwBaseAddrLow
 dwBaseAddrHigh equ BaseOfLoaderPhyAddr + _dwBaseAddrHigh
 dwLengthLow equ BaseOfLoaderPhyAddr + _dwLengthLow
 dwLengthHigh equ BaseOfLoaderPhyAddr + _dwLengthHigh
 dwType  equ BaseOfLoaderPhyAddr + _dwType
MemChkBuf  equ BaseOfLoaderPhyAddr + _MemChkBuf


; 堆棧就在數據段的末尾
StackSpace: times 1024 db 0
TopOfStack equ BaseOfLoaderPhyAddr + $ ; 棧頂
; SECTION .data1 之結束 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章