📄 loader.asm
字号:
;/* loader.asm :
; * 系统初始化模块代码,负责将系统内核加载至内存并进入保护模式,最终将
; * 系统控制权交由内核。
; *
; * Copyright(c) 2007, Alex P.Wonder
; * phoenixwonder@gmail.com
; *
; */
%include "bootsec.inc"
%include "pm.inc"
;%define _BOOT_DEBUG_
%ifdef _BOOT_DEBUG_
org 0100h
%else
org 0
;org 0x90000
%endif
BaseOfLdrStack equ 0x0000 ; loader的堆栈基址
BaseOfKernel equ 0x8000 ; 内核模块加载后的内存存放地址 -- 段
OffsetOfKernel equ 0x00 ; 内核模块加载后的内存存放地址 -- 偏移
BaseOfKernelPhyAddr equ BaseOfKernel*16
;KernelEntryPhyAddr equ 0x30400 ;内核运行时的内存地址
[SECTION .s16]
[BITS 16]
jmp L_START
;--变量--
wSectorNo dw 0
wRootDirSizeForLoop dw 0
;OffsetOfLoader dw 0
KernelFileName db 'KERNEL BIN' ;内核模块, ELF格式, 32位保护模式代码
loadmsg db 18,'starting kernel...', 0
errmsg db 12,'load failet!', 0
welcome db 9,'found it!', 0
bOdd db 0
dwKernelSize dw 0
;--GDT描述符初始化--
L_GDT: Descriptor 0, 0, 0 ;空描述符
; 0 ~ 4G可执行代码
L_DESC_FLAT_C: Descriptor 0, 0fffffh, DA_CR | DA_32 | DA_LIMIT_4K
; 0 ~ 4G可读写段
L_DESC_FLAT_RW: Descriptor 0, 0fffffh, DA_DRW | DA_32 | DA_LIMIT_4K
; 显存
L_DESC_VIDEO: Descriptor 0b8000h, 0ffffh, DA_DRW ;| DA_DPL3
GdtLen equ $ - L_GDT
GdtPtr dw GdtLen
dd BaseOfLoaderPhyAddr + L_GDT
SelectorFlatC equ L_DESC_FLAT_C - L_GDT
SelectorFlatRW equ L_DESC_FLAT_RW - L_GDT
SelectorVideo equ L_DESC_VIDEO - L_GDT ;+ EA_RPL3
;================================================
; BS+BPB表内容
;================================================
DECLARE_BOOTSEC_INFO
;================================================
; loader主函数
;================================================
L_START:
; mov ax, 0x4f02
; mov bx, 0x4111 ;640x480, 5:6:5
; int 0x10
mov ax, cs
mov ds, ax
mov ss, ax
mov es, ax
mov sp, BaseOfLdrStack
mov ax, 0xb800
mov gs, ax
push ebp
mov ebp, esp
sub sp, 8
mov word [bp - 2], ds
mov word [bp - 4], loadmsg + 1
mov word [bp - 6], 0 ;y
mov word [bp - 8], 0 ;x
call PrintStr
add sp, 8
pop ebp
; --在根目录中查找系统引导程序文件'kernel.bin'--
mov word [wSectorNo], SectorNoOfRootDir ;根目录扇区号
mov ax, [BPB_RootEntCnt] ; 根目录文件总数 -> wRootDirSizeForLoop(文件查找总次数)
mov word [wRootDirSizeForLoop], ax
L_SEARCH_ROOT_DIR_HERE:
mov ax, BaseOfKernel
mov es, ax ; (es) <- BaseOfKernel
mov bx, 0 ; es:bx -> BaseOfKernel: 0
mov ax, [wSectorNo] ; 读一个扇区FAT表内容到内存es:bx
mov cl, 1
call ReadSector
mov si, KernelFileName ; ds:si -> 'KERNEL.BIN'
mov di, 0
cld
mov dx, 10h ; 512 / 32(一个FAT项占用的字节数) = 16(一个扇区可以存有16个FAT项)
L_SEARCH_FOR_KERNELBIN:
cmp dx, 0
jz L_GOTO_NEXT_SECTOR ; 如果当前扇区中没找到,则查找下一个FAT扇区
dec dx
mov cx, 11 ;8.3 fat12 filename format
L_CMP_FILENAME: ;
cmp cx, 0 ;
jz L_FILENAME_FOUND ;
dec cx ;
lodsb ; ds:si -> al, si++
cmp al, byte [es:di] ;
jz L_GO_ON ; 当前字符相同则继续比较文件名的下一个字符;
jmp L_DIFFERENT ; 否则,取下一个FAT目录项比较
L_GO_ON: ;
inc di ;
jmp L_CMP_FILENAME ;
L_DIFFERENT:
and di, 0ffe0h ; di & 0xffe0 = 当前比较目录项的首字节地址
add di, 20h ; 跳过32字节,即取下一个目录项的首字节地址
mov si, KernelFileName ; KernelFileName首字节地址 -> (si)
;显示字符串
push ebp
mov ebp, esp
sub sp, 8
mov word [bp - 2], es
mov word [bp - 4], di
mov word [bp - 6], 8 ;y
mov word [bp - 8], 0 ;x
call PrintStr
add sp, 8
pop ebp
dec word [wRootDirSizeForLoop] ;wRootDirSizeForLoop--, 总比较次数-1
cmp word [wRootDirSizeForLoop], 0
jz L_NO_KERNELBIN ;总次数为0时还没有找到,放弃查找
jmp L_SEARCH_FOR_KERNELBIN
L_GOTO_NEXT_SECTOR:
inc word [wSectorNo] ; wSectorNo++, 下一个FAT扇区
jmp L_SEARCH_ROOT_DIR_HERE
L_NO_KERNELBIN:
call KillMotor ;关闭驱动器马达
;显示字符串
push ebp
mov ebp, esp
sub sp, 8
mov word [bp - 2], ds
mov word [bp - 4], errmsg + 1
mov word [bp - 6], 7 ;y
mov word [bp - 8], 0 ;x
call PrintStr
add sp, 8
pop ebp
hlt
L_FILENAME_FOUND: ;找到了KERNEL.BIN
mov ax, RootDirSectors
and di, 0xffe0 ;这时候的di还是指向根目录区中对应的目录项(KERNEL.BIN)
; 保存文件尺寸
push eax
mov eax, [es:di + 01ch] ; 文件尺寸放到在目录项偏移地址0x1ch处
mov dword [dwKernelSize], eax
pop eax
add di, 01ah ;找到首Sector, 01ah偏移位置存放了该文件在数据区中的首簇号
mov cx, word [es:di]
push cx ; 保存首簇号, 1)
add cx, ax ; (cx) <= RootDirSectors + 首簇号
add cx, DeltaSectorNo ; 数据区的第一个簇号是2,不是0或1, 所以DeltaSectorNo=1 + 9*2 - 2=17
; 执行此指令后, (cx) == kernel.bin的第一个逻辑扇区号
mov ax, BaseOfKernel
mov es, ax
mov bx, 0 ;BaseOfKernel:0中的FAT表内容已经不再需要了,直接在此内存地址放置内核文件的完整映像
mov ax, cx ;逻辑扇区号=>(ax)
L_GOON_LOADING_FILE:
mov cl, 1 ;读一个扇区到 es:bx
call ReadSector
pop ax ; 当前簇号 => (ax)
call GetFATEntry ;获得下一个簇号,并保存在ax中
cmp ax, 0ff8h
jae L_FILE_LOADED ;文件读取结束
push ax ;保存当前簇号
; 下面几行代码获得当前簇号对应的逻辑扇区号
mov dx, RootDirSectors
add ax, dx
add ax, DeltaSectorNo
add bx, [BPB_BytsPerSec] ;指向下一个存放文件内容的内存地址
jmp L_GOON_LOADING_FILE
L_FILE_LOADED:
;--文件全部加载完毕, 9000h:0000h -> kernel.bin(elf)
;显示字符串
push ebp
mov ebp, esp
sub sp, 8
mov word [bp - 2], ds
mov word [bp - 4], welcome + 1
mov word [bp - 6], 6 ;y
mov word [bp - 8], 0 ;x
call PrintStr
add sp, 8
pop ebp
call KillMotor ;关闭驱动器马达
;----进入保护模式----
lgdt [GdtPtr]
cli ;关闭所有中断
;开A20
in al, 92h
or al, 00000010b
out 92h, al
;置eflags中的保护模式标志位
mov eax, cr0
or eax, 1
mov cr0, eax
;跳到保护模式起始地址
jmp dword SelectorFlatC:(BaseOfLoaderPhyAddr + L_PM_START)
;[SECTION .data]
ALIGN 32
L_DATA:
StackSpace: times 1024 db 0
TopOfStack equ BaseOfLoaderPhyAddr + $ ;栈顶
;[SECTION .s32]
ALIGN 32
[BITS 32]
L_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
; ;显示一个调试用的字符
; mov ah, 0x0f
; mov al, 'P'
; mov [gs:((80*10+0)*2)], ax
call InitKernel
jmp SelectorFlatC:KernelEntryPhyAddr
;
; 从位于9000h:0000h中ELF格式的内核模块中抽出代码部分搬移至
; BaseOfKernelPhyAddr内存地址处.
;
InitKernel:
xor esi, esi
mov cx, word [BaseOfKernelPhyAddr + 2ch] ;获得程序头项个数(也即代码/数据段数)
movzx ecx, cx ;扩展cx->ecx,高位写0
mov esi, [BaseOfKernelPhyAddr + 1ch] ; e_phoff
add esi, BaseOfKernelPhyAddr ; [BaseOfKernelPhyAddr + e_phoff] => 程序头表在实际内存中的地址
.Begin:
mov eax, [esi + 0] ; p_type == 0 -> PT_NULL
cmp eax, 0
jz .NoAction ; 程序头项对应的内容不是可执行的代码
;memcpy, from kernel elf in memory to program vaddr
push dword [esi + 010h] ; 源数据字节长度
mov eax, [esi + 04h]
add eax, BaseOfKernelPhyAddr
push eax ; 源数据地址
push dword [esi + 08h] ; 代码加载后的逻辑内存地址(目的内存地址)
call _MemCpy
add esp, 12 ;释放上面分配的堆栈资源
.NoAction:
add esi, 020h ;取下一个程序头项内容
dec ecx
jnz .Begin ;循环搬移所有程序头项内容
ret
;
; 内存复制.
; (sp + 4) - 目的地址
; (sp + 8) - 源地址
; (sp + 12) - 复制字节数
;
_MemCpy:
mov ebp, esp
push ecx
push ebp
push esi
push edi
mov ecx, dword [ebp + 12]
mov esi, dword [ebp + 8]
mov edi, dword [ebp + 4]
rep movsb
pop edi
pop esi
pop ebp
pop ecx
ret
;================================================
; 定义一些函数(实模式)
;================================================
[SECTION .s16]
[BITS 16]
;
;读取一个扇区
; ax - 起始逻辑扇区号
; cl - 扇区数
; es:bx - 返回地址
;
ReadSector:
push bp
mov bp, sp
sub esp, 2 ;开辟两个字节存放需要读取的扇区数
mov byte [bp - 2], cl
push bx
mov bl, [BPB_SecsPerTrk]
div bl ; y在al中(商), z在ah中(余数)
inc ah ; z++(扇区号)
mov cl, ah ; cl <- 起始扇区号
mov dh, al ; dh <- y
shr al, 1 ; 柱面号
mov ch, al
and dh, 1 ; 磁头号
pop bx
mov dl, [BS_DrvNum]
.CoOnReading:
mov ah, 2
mov al, byte [bp - 2]
int 13h
jc .CoOnReading ; 如果读取错误,CF=1, 这里就不停地读,直到正确为止
add esp, 2
pop bp
ret
;
; 功 能:
; 根据文件的簇号获得对应FAT表项内容(文件下一个簇号).
; 输 入:
; ax - 文件的当前簇号
; 输 出:
; ax - 下一个簇号
; 返回值:
;
GetFATEntry:
push es
push bx
push ax
mov ax, BaseOfLoader
sub ax, 0100h
mov es, ax ;BaseOfLoader地址前空出4k给es段(用来临时保存读取的FAT扇区)
pop ax
mov byte [bOdd], 0
mov bx, 3
mul bx ; ax*3 => (dx:ax)
mov bx, 2
div bx ; (dx:ax) / 2 => (ax):商, (dx):余数
cmp dx, 0
jz L_EVEN
mov byte [bOdd], 1
L_EVEN:
xor dx, dx
mov bx, [BPB_BytsPerSec]
div bx ; 求逻辑扇区号对应的FAT项在第几个FAT内容扇区中, ax/BPB_BytsPerSec => dx(余数), ax(商)
push dx ; dx保存FAT项在FAT扇区中的偏移
mov bx, 0
; 连续读取2个逻辑扇区号对应的FAT表扇区到内存地址es:bx
add ax, SectorNoOfFAT1
mov cl, 2
call ReadSector
pop dx
add bx, dx ;(es:bx + dx) 保存文件的FAT12项
mov ax, [es:bx] ;读2个字节到ax
cmp byte [bOdd], 1
jnz L_EVEN_2
shr ax, 4
L_EVEN_2:
and ax, 0fffh
L_GET_FAT_ENTRY_OK:
pop bx
pop es
ret
;
; 功 能:
; 短字符串显示,短字符串中第一个字节保存的是字符串的长度,因此字符串
; 的长度范围为0~255.
; i.e. shrtMsg db 8, '12345678'
;
; 输 入:
; (es:bp) - 短字符串地址
; (dh), (dl) - Y, X
; (bl) - 字符属性(b7: 闪烁, b6~b4: 背景色, b3: 字符高亮, b2~b0: 字符颜色)
; 输 出:
; (无)
;
ShowShortMsg:
push ax
push bx
push cx
xor cx, cx
mov cl, [bp] ;获得字符串字符个数
inc bp ;指向字符串中第一个字符
mov ah, 19
mov bh, 0 ;页号
mov al, 0 ;光标不动
int 10h
dec bp ;恢复bp的值
pop cx
pop bx
pop ax
ret
;==================================================================
; C格 式: void PrintInt(int x, int y, unsigned char val)
; 说 明:
;
; 参 数:
;
; 返回值:
;
;==================================================================
PrintInt:
push ebp
mov ebp, esp
push eax
push ebx
push ecx
mov ax, 0xb800
mov gs, ax
mov ebx, [ebp + 8] ;x
mov ecx, [ebp + 12] ;y
mov al, cl
mov cl, 80
mul cl
add eax, ebx
shl eax, 1 ; (y*80 + x)*2
xor ebx, ebx
mov bl, [ebp + 16]
mov bh, 0x0f ;黑底白字
mov [gs:eax], bx
pop ecx
pop ebx
pop eax
leave
ret
;==================================================================
; C格 式: void PrintStr(int x, int y, char *str) (实地址模式)
; 说 明:
;
; 参 数:
;
; 返回值:
; = 0 -- fail
; > 0 -- successful
;==================================================================
PrintStr:
push bp
mov bp, sp
push ax
push bx
push cx
push es
push si
push di
mov si, [bp + 8] ; off of str
mov ax, [bp + 10] ; seg of str
mov es, ax
;xor di, di
mov ax, [bp + 6]
mov bl, 80
mul bl
add ax, [bp + 4]
shl ax, 1 ; (y*80 + x)*2
mov di, ax
.L0:
mov al, [es:si]
cmp al, 0
jz .quitit
;显示一个字符
mov ah, 0x0f
mov [gs:di], ax
;指向下一个字符地址
inc si
add di, 2
jmp .L0
.quitit:
pop di
pop si
pop es
pop cx
pop bx
pop ax
mov sp, bp
pop bp
ret
KillMotor:
push dx
mov dx, 03f2h
mov al, 0
out dx, al
pop dx
ret
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -