- ;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"
boot/loader.asm
本文介绍了一个引导加载程序的实现细节,包括如何加载内核、设置内存分页等关键步骤。文章深入探讨了从实模式过渡到保护模式的过程,并展示了如何通过分析ELF格式将内核的不同部分映射到相应的虚拟地址。

被折叠的 条评论
为什么被折叠?



