loader.asm

来源:互联网 发布:ubuntu 选择性能模式 编辑:程序博客网 时间:2024/04/29 04:20

org 0100h
 jmp LABEL_BEGIN
 
 %include "include/pm.inc"
 %include "include/FAT12hdc.inc"  

 Base_Of_Loader   equ 9000h        ;LOADER缓冲区的段地址(实模式)
 Offset_Of_Loader  equ 0100h        ;LOADER缓冲区的偏移地址(实模式)
 Base_Of_Kernel   equ 8000h        ;内核缓冲区的段地址(实模式)
 Offset_Of_Kernel  equ 0000h        ;内核缓冲区的偏移地址(实模式)
 
 Page_Dir_Base    equ 100000h       ;页目录物理地址
 Page_Tbl_Base    equ 101000h       ;页表物理地址
 
 Loader_Phy_Address equ Base_Of_Loader * 16 ;LOADER的物理段地址
 Kernel_Phy_Address equ Base_Of_Kernel * 16 ;KERNEL的物理段地址
 Kernel_Entry_Point equ 30400h             ;KERNEL的入口物理地址   
 
;gdt------------------------------------------------------------------
[section .gdt]
LABEL_DESC_GDT: Descriptor 0,0,0
LABEL_DESC_FLAT_RW: Descriptor 0,0fffffh,DA_DRW + DA_LIMIT_4K
LABEL_DESC_FLAT_C: Descriptor 0,0fffffh,DA_CR + DA_32 + DA_LIMIT_4K
LABEL_DESC_DATA:  Descriptor 0,Data_Len - 1,DA_DRW
LABEL_DESC_STACK32: Descriptor 0,Stack32_Len,DA_DRWA + DA_32
LABEL_DESC_VIDEO:  Descriptor 0b8000h,0ffffh,DA_DRW

Selector_Flat_RW equ LABEL_DESC_FLAT_RW - LABEL_DESC_GDT
Selector_Flat_C  equ LABEL_DESC_FLAT_C  - LABEL_DESC_GDT
Selector_Data   equ LABEL_DESC_DATA   - LABEL_DESC_GDT
Selector_Stack32 equ LABEL_DESC_STACK32 - LABEL_DESC_GDT
Selector_Video  equ LABEL_DESC_VIDEO  - LABEL_DESC_GDT

Gdt_Len equ $ - LABEL_DESC_GDT
Gdt_Ptr: dw Gdt_Len - 1
     dd (Loader_Phy_Address + LABEL_DESC_GDT)
;end of gdt-----------------------------------------------------------

[section .stack32]
[bits 32]
LABEL_STACK32:
 times 512 db 0
Stack32_Len equ $ - LABEL_STACK32 - 1
;end of stack32-------------------------------------------------------

;data section---------------------------------------------------------
[section .data]
LABEL_DATA:
;显示的字符串
String_Len equ 9
_sz_Loading: db 'Loading  '
Loading_Message_Index equ 0
_sz_Ready:  db 'Ready    '
Ready_Message_Index equ 1
_sz_No_Kernel: db 'No Kernel'
No_Kernel_Message_Index equ 2
;加载Kernel有关的数据
LOADER_NAME equ 11          ;LOADER文件名的长度
;一堆变量 
_w_Current_Root_Sector: dw Root_Dir_Sector_Index     ;控制根目录当前读取的扇区号
_w_Root_Sector_For_Loop: dw Root_Dir_Sector_Number   ;控制读取根目录的扇区数
_b_Odd: db 0     ;控制加载的文件在FAT中的相对偏移字节的奇偶
_sz_Kernel_Name: db "KERNEL  BIN"   ;LOADER文件名,用于比较
_w_Kernel_Offset: dw 0         ;控制131行的LOADER缓冲区的偏移

;实模式下使用这些符号
;字符串
_Memory_Info_Title: db 'BaseAddrL BaseAddrH LengthL   LengthH   Type',0ah,0
_Ram_Size: db 'Ram Size:',0
_Return: db 0ah,0
_Disp_Pos: dd (80 * 4) * 2
_Some_Int: dd 098ff22eeh
_ARD_Buffer: times 256 db 0
_ARD_Number: dd 0
_dwRam_Size dd 0
 
_ARD_Temp:
_Base_Low: dd 0
_Base_High: dd 0
_Len_Low: dd 0
_Len_High: dd 0
_Type: dd 0

Memory_Info_Title equ _Memory_Info_Title - $$
Ram_Size equ _Ram_Size - $$
Return equ _Return - $$
Disp_Pos equ _Disp_Pos - $$
Some_Int equ _Some_Int - $$
ARD_Buffer equ _ARD_Buffer - $$
ARD_Number equ _ARD_Number - $$
dw_Ram_Size equ _dwRam_Size - $$
 
ARD_Temp equ _ARD_Temp - $$
Base_Low equ _Base_Low - $$
Base_High equ _Base_High - $$
Len_Low equ _Len_Low - $$
Len_High equ _Len_High - $$
Type equ _Type - $$

Data_Len equ $ - LABEL_DATA

;end of data section--------------------------------------------------
[section .code16]
[bits 16]
LABEL_BEGIN:
 mov ax,cs
 mov ds,ax
 mov ax,0
 mov ss,ax               ;Boot中所占的区域已经作废,当来作实模式下的栈
 mov sp,7c00h
 
 mov dh,Loading_Message_Index
 call Disp_Str_In_Real_Mode
 
 ;编程思路:找到根目录下的KERNEL.BIN
 ;根目录的范围是总扇区编号19到32,共占14个扇区
 ;分别读取每个根目录扇区,共14次
 ;每个扇区读16次
 ;每个扇区可以容纳512/32=16个文件信息
LABEL_SEARCH_KERNEL_BEGIN:
 cmp word [_w_Root_Sector_For_Loop],0 ;比较要读取的根目录扇区数
 je LABEL_KERNEL_NOT_FOUND      ;如果为0表示没有找到
 dec word [_w_Root_Sector_For_Loop]
 
 mov ax,1               ;读取当前扇区至es:bx处
 push ax
 mov ax,[_w_Current_Root_Sector]
 push ax
 mov ax,Offset_Of_Kernel
 push ax
 mov ax,Base_Of_Kernel
 push ax
 call Read_Sector
 add sp,8
 
 mov dx,File_Number_Per_Sector     ;初始化dx控制一个扇区循环次数为16次
 mov si,_sz_Kernel_Name        ;ds:si指向KERNEL的名字字符串
 mov ax,Base_Of_Kernel         ;es:di指向缓冲区
 mov es,ax
 mov di,Offset_Of_Kernel 
 
LABEL_NEXT_FILE: 
 cmp dx,0
 je LABEL_READ_NEXT_SECTOR      ;如果为0表示当前扇区比较结束,读下一个扇区
 dec dx
 
LABEL_CMP_BEGIN:
 mov cx,11               ;初始化cx控制比较次数为最多为11次          

LABEL_CMP_GO_ON:
 cmp cx,0               ;如果cx为0,表示找到KERNEL
 je LABEL_KERNEL_FOUND
 dec cx
 
 lodsb                 ;读取一个KERNEL名字字符串到al
 cmp al,byte [es:di]          ;比较之
 jne LABEL_DIFF            ;有不同的直接进行下一个文件的比较 
 jmp LABEL_NEXT_CMP          ;相同则进行下一个字符的比较   
 
LABEL_DIFF:
 mov si,_sz_Kernel_Name        ;恢复si,使ds:si重新指向字符串
 and di,0ffe0h 
 add di,File_Item_Byte_Number     ;恢复di,使es:di指向下个根目录
 jmp LABEL_NEXT_FILE
 
LABEL_NEXT_CMP:
 inc di
 jmp LABEL_CMP_GO_ON
 
LABEL_READ_NEXT_SECTOR:         ;做读下一个扇区的准备工作
 inc word [_w_Current_Root_Sector]
 jmp LABEL_SEARCH_KERNEL_BEGIN
 
LABEL_KERNEL_NOT_FOUND:
 ;显示一些信息
 mov dh,No_Kernel_Message_Index
 call Disp_Str_In_Real_Mode
 jmp $
 
LABEL_KERNEL_FOUND:
 ; 显示一些信息
 mov dh,Ready_Message_Index
 call Disp_Str_In_Real_Mode
 
 and di,0ffe0h             ;此时找到KERNEL,恢复di指向名字字段的首字节
 add di,FAT_Offset_In_File_Item    ;此时di指向此文件在FAT的序号,记录序号的段占两字节
 mov ax,[es:di]            ;取出准备调用Get_FAT_Entry   
 push ax               ;临时保存之,等待日后调用函数时使用
 
 add ax,Root_Dir_Sector_Index   
 add ax,Root_Dir_Sector_Number
 sub ax,2               ;此时ax为KERNEL的相应扇区在DATA区的总偏移
 
LABEL_READ_AGAIN:
 mov bx,1               ;把KERNEL的相应一个扇区读到指定的位置
 push bx
 push ax
 mov ax,Offset_Of_Kernel
 add ax,[_w_Kernel_Offset]
 push ax
 mov ax,Base_Of_Kernel
 push ax
 call Read_Sector
 add sp,8 
 
 pop ax
 
 push ax
 call Get_FAT_Entry
 add sp,2
 
 cmp ax,0fffh             ;不等于0FFFH的话说明已是最后一个扇区了
 je LABEL_EXIT
 push ax
 
 add ax,Root_Dir_Sector_Index   
 add ax,Root_Dir_Sector_Number
 sub ax,2   
 add word [_w_Kernel_Offset],Byte_Number_Per_Sector
 
 jmp LABEL_READ_AGAIN

LABEL_EXIT:

 mov ax,Base_Of_Loader
 mov ds,ax
 mov es,ax
 ;获得内存信息,存放在缓冲区中
 mov ebx,0
 mov di,_ARD_Buffer
LABEL_CHK_MEMORY:
 mov eax,0e820h
 mov ecx,20
 mov edx,0534D4150h
 int 15h
 jc LABEL_CHK_ERROR
 add di,20
 inc dword [_ARD_Number]
 cmp ebx,0
 je LABEL_EXIT_CHK_MEMORY
 jmp LABEL_CHK_MEMORY
LABEL_CHK_ERROR:
 mov dword [_ARD_Number],0
LABEL_EXIT_CHK_MEMORY:
 
 ;填充数据段描述符
 xor eax,eax
 mov eax,Loader_Phy_Address
 add eax,LABEL_DATA
 mov word [LABEL_DESC_DATA + 2],ax
 shr eax,16
 mov byte [LABEL_DESC_DATA + 4],al
 mov byte [LABEL_DESC_DATA + 7],ah
 
 ;填充栈段描述符
 xor eax,eax
 mov eax,Loader_Phy_Address
 add eax,LABEL_STACK32
 mov word [LABEL_DESC_STACK32 + 2],ax
 shr eax,16
 mov byte [LABEL_DESC_STACK32 + 4],al
 mov byte [LABEL_DESC_STACK32 + 7],ah

 lgdt [Gdt_Ptr]
 
 cli
 
 in al,92h
 or al,00000010h
 out 92h,al
 
 mov eax,cr0
 or al,1
 mov cr0,eax
 
 jmp dword Selector_Flat_C:(Loader_Phy_Address + LABEL_CODE32)
 
 ;Read_Sector----------------------------------------------------------
;压栈顺序 1:欲读取的扇区个数
;     2:欲读取的扇区总号
;     3:读取的扇区的缓冲区的偏移地址
;     4:读取的扇区的缓冲区的段地址
;---------------------------------------------------------------------
;入口参数 
;     ah=02h
;     al:扇区数
;     ch:柱面
;     cl:扇区
;     dh:磁头
;     dl:驱动器,00H~7FH:软盘;80H~0FFH:硬盘
;     es:bx=缓冲区的地址
;出口参数
;     cf=0——操作成功,ah=00H,al=传输的扇区数,否则,ah=状态代码,参见功能号01H中的说明     
;----------------------------------------------------------------------
;求解磁头号,柱面号,相对扇区数的算法如下
; 设扇区号为 x
;                          ┌ 柱面号 = y >> 1
;       x           ┌ 商 y ┤
; -------------- => ┤      └ 磁头号 = y & 1
;  每磁道扇区数     │
;                   └ 余 z => 起始扇区号 = z + 1
Read_Sector:
 push bp
 mov bp,sp
 
 push ax
 push bx
 push cx
 push dx
 push es
 
 mov ax,[bp + 8]            ;扇区总数送ax
 mov bl,18               ;除数是18,一个柱面有18个扇区
 div bl                ;商送al,余数送ah
 mov ch,al    
 shr ch,1               ;求得柱面
 mov dh,al    
 and dh,1               ;求得磁头
 mov cl,ah
 inc cl                ;求得相对扇区偏移
 mov dl,0               ;读取A盘
 mov ax,[bp + 4]
 mov es,ax               ;设置缓冲区段地址
 mov bx,[bp + 6]            ;设置缓冲区偏移地址
 
LABEL_RETRY:
 mov ax,[bp + 10]           ;设置读取扇区的个数
 mov ah,2               ;设置功能号
 int 13h                ;中断调用
 jc LABEL_RETRY            ;如果读取不成功,再继续读取
 
 pop es
 pop dx
 pop cx
 pop bx
 pop ax
 
 pop bp

 ret 
;end of Read_Sector---------------------------------------------------

;Get_FAT_Entry--------------------------------------------------------
;压栈顺序 1:一个文件在FAT中的序号
;返回值:在此FAT中序号的值,保留在ax中
Get_FAT_Entry:
 push bp
 mov bp,sp
 
 push bx
 push dx
 push di
 push es
 
 mov ax,[bp + 4]            ;求出在FAT中偏移的字节数,乘以1.5倍
 mov bx,3
 mul bx
 
 mov bx,2
 div bx
 
 mov byte [_b_Odd],0
 
 cmp dx,0
 je LABEL_EVEN             ;余数等于0的话,就为偶字节
 mov byte [_b_Odd],1          ;余数等于1的话,就为奇字节

LABEL_EVEN:
 xor dx,dx
 mov bx,Byte_Number_Per_Sector     ;字节数除以512,得到在FAT中的偏移扇区
 div bx
 inc ax                ;得到总的扇区偏移,用来调用Read_Sector
                    ;dx中保存相对扇区偏移的字节数
 mov bx,2               ;读两个扇区,以免出现跨扇区的情况
 push bx
 push ax
 mov ax,Offset_Of_Loader
 push ax
 mov ax,Base_Of_Loader - 100h     ;换个地儿存放缓冲区
 push ax
 call Read_Sector
 add sp,8
 
 mov ax,Base_Of_Loader - 100h     ;es:di指向要读取的FAT的首字节
 mov es,ax
 mov di,Offset_Of_Loader
 add di,dx
 mov ax,[es:di]
 
 cmp byte [_b_Odd],0 
 je LABEL_EVEN2
 shr ax,4               ;奇数的策略
LABEL_EVEN2:
 and ax,0fffh             ;偶数的策略
 
 pop es
 pop di
 pop dx
 pop bx
 
 pop bp

 ret 
;end of Get_FAT_Entry-------------------------------------------------
 
;Disp_Str_In_Real_Mode------------------------------------------------
;入口参数:dh代表在实模式下字符串的偏移
;功能13h
;功能描述:在Teletype模式下显示字符串
;入口参数:ah=13h
;bh=页码
;bl=属性(若AL=00H或01H)
;cx=显示字符串长度
;(dh、dl)=坐标(行、列)
;es:bp=显示字符串的地址 al=显示输出方式
;0——字符串中只含显示字符,其显示属性在bl中。显示后,光标位置不变
;1——字符串中只含显示字符,其显示属性在bl中。显示后,光标位置改变
;2——字符串中含显示字符和显示属性。显示后,光标位置不变
;3——字符串中含显示字符和显示属性。显示后,光标位置改变
;出口参数:无

Disp_Str_In_Real_Mode:
 push es
 push ax
 push bx
 push cx
 push dx
 push bp
 
 mov ax,String_Len
 mul dh
 add dh,2
 add ax,_sz_Loading
 mov bp,ax
 mov ax,ds
 mov es,ax
 mov cx,String_Len
 mov ax, 01301h  ; AH = 13,  AL = 01h
 mov bx, 0007h   ; 页号为0(BH = 0) 黑底白字(BL = 07h)
 mov dl, 0
 int 10h      ; int 10h
 
 pop bp
 pop dx
 pop cx
 pop bx
 pop ax
 pop es
 
 ret
;end of Disp_Str_In_Real_Mode-----------------------------------------

;end of loader_entry--------------------------------------------------

[section .code32]            ;跳入保护模式
[bits 32]
LABEL_CODE32:
 mov ax,Selector_Data
 mov ds,ax
 mov es,ax
 mov ax,Selector_Video
 mov gs,ax
 
 mov ax,Selector_Stack32
 mov ss,ax
 mov esp,Stack32_Len
 
 push Memory_Info_Title        ;显示内存头信息
 call Disp_Str

 call Disp_Mem_Size          ;显示内存信息
 
 mov ax,Selector_Flat_RW
 mov es,ax
 call Setup_Paging          ;启动分页,但线性地址还是等价于物理地址
 
 mov ah,0ch
 mov al,'P'
 mov [gs:(80 * 20) * 2],ax
 
 mov ax,Selector_Flat_RW
 mov ds,ax
 mov ax,es
 
 call Init_Kernel
 
 jmp Selector_Flat_C:Kernel_Entry_Point

;Disp_Mem_Size--------------------------------------------------------
Disp_Mem_Size:
 push eax
 push ecx
 push esi
 push  edi
 
 mov esi,ARD_Buffer
 mov ecx,[ARD_Number]
.w:
 push ecx
 mov ecx,5
 
 mov edi,ARD_Temp
 
.n:
 lodsd
 stosd
 mov [Some_Int],eax
 push Some_Int
 call Disp_32Bit_Int
 push edi
 mov edi,[Disp_Pos]
 xor ax,ax
 mov ah,0ch
 mov al,'h'
 mov [gs:edi],ax
 add edi,2
 mov al,' '
 mov [gs:edi],ax
 add edi,2
 mov [Disp_Pos],edi
 pop edi

 loop .n
 
 cmp dword [Type],1
 jne .skip
 mov eax,[Base_Low]
 add eax,[Len_Low]
 cmp eax,[dw_Ram_Size]
 jb .skip
 mov [dw_Ram_Size],eax
.skip: 
 
 pop ecx
 call Disp_Return
 loop .w
 
 push Ram_Size
 call Disp_Str
 push dw_Ram_Size
 call Disp_32Bit_Int
 
 push edi
 mov edi,[Disp_Pos]
 xor ax,ax
 mov ah,0ch
 mov al,'h'
 mov [gs:edi],ax
 add edi,2
 mov [Disp_Pos],edi
 pop edi
 
 pop edi
 pop esi
 pop ecx
 pop eax
 ret
;end of Disp_Mem_Size-------------------------------------------------

;Setup_Paging---------------------------------------------------------
Setup_Paging:
 push eax
 push ebx
 push ecx
 push edx
 push edi

 xor edx,edx
 mov eax,[dw_Ram_Size]
 mov ebx,400000h
 div ebx
 cmp edx,0
 jne .1
 inc eax 
.1:
 push eax
 mov ecx,eax
 
 mov eax,Page_Tbl_Base | PG_P | PG_RWW | PG_USU
 mov edi,Page_Dir_Base

.2:
 stosd
 add eax,4096
 loop .2
 
 pop eax
 mov ebx,1024
 mul ebx
 mov ecx,eax
 
 mov eax,PG_P | PG_RWW | PG_USU
 mov edi,Page_Tbl_Base

.3:
 stosd
 add eax,4096
 loop .3
 
 mov eax,Page_Dir_Base
 mov cr3,eax
 
 mov eax,cr0
 or eax,80000000h
 mov cr0,eax
 
 pop edi
 pop edx
 pop ecx
 pop ebx
 pop eax

 ret
;end of Setup_Paging--------------------------------------------------

;Init_Kernel----------------------------------------------------------
Init_Kernel:
 push eax
 push ecx
 push edx
 push esi
 
 mov cx,[Kernel_Phy_Address + 2ch]   ;取得Program Section Table的个数
 movzx ecx,cx
 
 mov esi,[Kernel_Phy_Address + 1ch]  ;取得Program Section Table的在文件的偏移字节
 mov dx,[Kernel_Phy_Address + 2ah]   ;取得一个程序描述符的长度
 movzx edx,dx
 add esi,Kernel_Phy_Address      ;指向表中的第一个程序描述符的起始位置

.begin:
 cmp dword [esi],0              ;类型如果为0,跳到下一项
 je .next
 
 mov eax,[esi + 10h]          ;压入程序的长度
 push eax
 mov eax,[esi + 8]           ;压入目的指针
 push eax
 mov eax,[esi + 4]           
 add eax,Kernel_Phy_Address      ;压入源指针
 push eax
 call Memory_Copy
 
.next:
 add esi,edx
 dec ecx
 jnz .begin
 
 pop esi
 pop edx
 pop ecx
 pop eax

 ret
;end of Init_Kernel---------------------------------------------------
 
 %include "diy.inc"

;end of code32--------------------------------------------------------

原创粉丝点击