;By Marcus Xing
;boot/boot.asm,程序必须小于等于510字节
;加载LOADER.BIN,并把控制权交给LOADER
;----------------------------------------------------------------------调试的预处理
;%define _BOOT_DEBUG_ ; 做 Boot Sector 时一定将此行注释掉!
; 将此行打开后用 nasm Boot.asm -o Boot.com 做成一个.COM文件易于调试
%ifdef _BOOT_DEBUG_
org 0100h ; 调试状态, 做成 .COM 文件, 可调试
%else
org 07c00h ; Boot 状态, Bios 将把 Boot Sector 加载到 0:7C00 处并开始执行
%endif
;------------------------------------------------------------------------软盘头信息
jmp short LABEL_START ; Start to boot.
nop ; 这个 nop 不可少
; 下面是 FAT12 磁盘的头
BS_OEMName db 'MarcusX ' ; OEM String, 必须 8 个字节
BPB_BytsPerSec dw 512 ; 每扇区字节数
BPB_SecPerClus db 1 ; 每簇多少扇区
BPB_RsvdSecCnt dw 1 ; Boot 记录占用多少扇区
BPB_NumFATs db 2 ; 共有多少 FAT 表
BPB_RootEntCnt dw 224 ; 根目录文件数最大值
BPB_TotSec16 dw 2880 ; 逻辑扇区总数
BPB_Media db 0xF0 ; 媒体描述符
BPB_FATSz16 dw 9 ; 每FAT扇区数
BPB_SecPerTrk dw 18 ; 每磁道扇区数
BPB_NumHeads dw 2 ; 磁头数(面数)
BPB_HiddSec dd 0 ; 隐藏扇区数
BPB_TotSec32 dd 0 ; wTotalSectorCount为0时这个值记录扇区数
BS_DrvNum db 0 ; 中断 13 的驱动器号
BS_Reserved1 db 0 ; 未使用
BS_BootSig db 29h ; 扩展引导标记 (29h)
BS_VolID dd 0 ; 卷序列号
BS_VolLab dd 'MarcusOs0.2' ; 卷标, 必须 11 个字节
BS_FileSysType db 'FAT12 ' ; 文件系统类型, 必须 8个字节
;-------------------------------------------------------------------------宏信息
Base_Of_Loader equ 9000h ;加载LOADER的段地址
Offset_Of_Loader equ 0100h ;加载LOADER的偏移地址
Root_Dir_Begin_Sector equ 19 ;根目录区的逻辑起始逻辑扇区
;-------------------------------------------------------------------CODE_SEGMENT
LABEL_START:
mov ax,cs
mov ds,ax
mov ss,ax
mov sp,7c00h
;清屛
mov ax,0600h
mov bx,0700h
xor cx,cx
mov dx,0184fh
int 10h
;显示字符串Booting
push _sz_Booting_Message
call Disp_Str_In_Real_Mode
add sp,2
;es指向缓冲区的段地址
mov ax,Base_Of_Loader
mov es,ax
LABEL_READ_NEXT_SECTOR:
mov bx,Offset_Of_Loader ;bx指向缓冲区的偏移地址
cmp byte [_b_Root_Dir_Search_For_Loop],0 ;比较循环变量是否为0
je LABEL_NO_FOUND ;为0代表没找到,跳转到相应的标号处理
dec byte [_b_Root_Dir_Search_For_Loop] ;尚未为0,循环变量自减1
;读取当前根目录区扇区至缓冲区
push 1
mov al,byte [_b_Root_Dir_Sector_No]
xor ah,ah
push ax
call Read_Sector
add sp,4
inc byte [_b_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_Loader ;si定位到要比较的字符串偏移处
LABEL_GO_ON_CMP:
cmp cx,0 ;判断比较计数器是否为0,为0表示比较成功
je LABEL_FOUNDED ;即找到LOADER.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 ;跳转下一次比较
;没找到LOADER,跳转到这儿,显示完相应信息后死循环
LABEL_NO_FOUND:
push _sz_No_Loader_Message
call Disp_Str_In_Real_Mode
add sp,2
jmp $
;找到了LOADER,跳转到这儿
LABEL_FOUNDED:
and bx,0ffe0h ;使es:bx指向找到的LOADER的FCB的起始处
mov cx,[es:bx + 1ah] ;取得LOADER的相对于数据区的偏移扇区号
;注意:2为数据区的第一个扇区
mov ax,cx
mov bx,Offset_Of_Loader ;es:bx=9000h:0100h,准备读入一个数据扇区
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
;判断有没有下一个扇区,有则根据得到的下一个数据相对扇区号继续读
;没有则可以跳转到LOADER了
cmp ax,0fffh
je LABEL_START_LOADING
add bx,512 ;定位LOADER的加载偏移地址
mov cx,ax
jmp LABEL_GO_ON_LOADING ;跳转回去进行相应处理
;LOADER数据全部加载完毕后跳转到此
LABEL_START_LOADING:
;打印准备信息
push _sz_Ready_Message
call Disp_Str_In_Real_Mode
add sp,2
;正式跳转到LOADER!
jmp Base_Of_Loader:Offset_Of_Loader
;-------------------------------------------------------------------DATA_SECTION
LABEL_DATA:
_b_Root_Dir_Sector_No db Root_Dir_Begin_Sector ;根目录区起始逻辑扇区
_b_Root_Dir_Search_For_Loop db 14 ;找LOADER循环次数,就
;是根目录区扇区个数
_b_Is_Odd db 0 ;FAT ENTRY逻辑地址的奇或偶
_w_Disp_Pos dw 0 ;显示地址
;一些用到的串
_sz_Booting_Message db 'Booting',0
_sz_Ready_Message db 'Ready',0
_sz_No_Loader_Message db 'NoLoader',0
_sz_Dot db '.',0
_s_Name_Of_Loader db 'LOADER BIN'
;--------------------------------------------------------------------Read_Sector
Read_Sector:
;C函数原型(实模式下调用,短调用):
;void Read_Sector(byte16 first_sector_index,byte16 read_sector_number);
;注意:
;调用前es:bx必须指向缓冲区,ds指向数据区
;逻辑扇区号转化为系统调用所需参数的公式如下:
;相对扇区号/每磁道扇区号(18)的商Q,余数R
;其中:柱面号=Q>>1,磁头号=Q&1,相对起始扇区号=R+1
;对应的系统调用API如下:
;功能号ah=02
;hal=要读的扇区数
;ch=柱面(磁道)号
;cl=起始扇区号
;dh=磁头号,dl=驱动器号(0表示A盘)
;es:bx缓冲区地址
push bp
mov bp,sp
push ax
push bx
push cx
push dx
push bx ;暂存缓冲区偏移
mov ax,[bp + 6] ;取得要读取的扇区数
push ax ;暂时保存之
mov ax,[bp + 4] ;取得要读的逻辑扇区号
mov bl,[BPB_SecPerTrk]
div bl ;除以每柱面扇区数
mov ch,al
shr ch,1 ;ch<-柱面号
mov dh,al
and dh,1 ;dh<-磁头号
mov cl,ah
inc cl ;cl<-相对扇区号
mov dl,0 ;读A盘
pop ax ;al<-要读的扇区数
pop bx ;恢复缓冲区偏移
.Go_On_Reading:
mov ah,2 ;ah<-功能号
int 13h
jc .Go_On_Reading ;如果cf=1,继续读
pop dx
pop cx
pop bx
pop ax
pop bp
ret
;------------------------------------------------------------------Get_FAT_Entry
Get_FAT_Entry:
;C函数原型(实模式下调用,短调用):
;u16 Get_FAT_Entry(u16 Sector_No_In_Data_Area);
;功能:
;入口参数为数据区的相对扇区号,返回值为相应的FAT的值,以表示还有没有下一个扇区
;ds指向数据区,在此函数中,es:bx=9000h-100h:0用来作为读FAT的缓冲区
push bp
mov bp,sp
push bx
push dx
push es
mov byte [_b_Is_Odd],0 ;作用为局部变量,标记相对FAT的条目索引
;es指向9000h-100h
mov ax,Base_Of_Loader
sub ax,100h
mov es,ax
;1个FAT条目为12位,条目索引*3/2 = *1.5
;ax为相对FAT字节偏移
mov ax,[bp + 4]
mov bx,3
mul bx
mov bx,2
div bx
;判断余数是否为0,是的话为偶,跳转到
;对应标号,否的话把标记变量置1
cmp dx,0
je LABEL_EVEN
mov byte [_b_Is_Odd],1
LABEL_EVEN:
;ax=当前条目在FAT的相对逻辑偏移扇区
;dx=当前条目相对当前扇区的字节偏移
xor dx,dx
mov bx,512
div bx
add ax,1 ;ax自增1,求得相对整个软盘的逻辑扇区偏移
;读2个FAT扇区到缓冲区,一次读2个,
;因为FAT条目可能跨越2个扇区
xor bx,bx
push 2
push ax
call Read_Sector
add sp,4
mov bx,dx ;es:bx指向要求的条目首字节
mov ax,[es:bx] ;把首字节存到ax
cmp byte [_b_Is_Odd],1 ;判断奇偶,奇偶有不同的处理方式
jne LABEL_EVEN2
shr ax,4 ;奇数的处理方式
LABEL_EVEN2: ;偶数的处理方式
and ax,0fffh ;返回值放到ax中
LABEL_DONE:
pop es
pop dx
pop bx
pop bp
ret
;----------------------------------------------------------Disp_Str_In_Real_Mode
Disp_Str_In_Real_Mode:
;C函数原型(实模式下调用,短调用):
;void Disp_Str_In_Real_Mode(const char *p_sz_Str);
;注意:
;在变量_w_Disp_Pos处显示字符串,ds指向数据区,es的值在此函数中指向数据区
push bp
mov bp,sp
push ax
push bx
push cx
push dx
push di
push es
;此BIOS中断API
;功能13H
;功能描述:在Teletype模式下显示字符串
;入口参数:AH=13H
;BH=页码
;BL=属性(若AL=00H或01H)
;CX=显示字符串长度
;(DH、DL)=坐标(行、列)
;ES:BP=显示字符串的地址 AL=显示输出方式
;0——字符串中只含显示字符,其显示属性在BL中。显示后,光标位置不变
;1——字符串中只含显示字符,其显示属性在BL中。显示后,光标位置改变
;2——字符串中含显示字符和显示属性。显示后,光标位置不变
;3——字符串中含显示字符和显示属性。显示后,光标位置改变
;出口参数:无
mov ax,ds
mov es,ax
mov bp,[bp + 4] ;取得要打印的字符串指针
mov ax,[_w_Disp_Pos] ;得到显示地址
mov bl,80
div bl
mov dh,al ;dh<-行号
mov dl,ah ;dl<-列号
xor cx,cx ;字符串长度计数器
mov di,bp ;做临时指针es:di指向字符串
.1:
mov al,byte [es:di]
cmp al,0 ;遇到0计数结束
je .2
inc cx ;计数器自增1
inc di ;临时指针自增1
jmp .1
.2:
add word [_w_Disp_Pos],cx ;显示地址加上字符串长度
mov bx,0007h ;bh表示第0页,bl表示字符颜色
mov ax,1301h ;al为输出方式1
int 10h
pop es
pop di
pop dx
pop cx
pop bx
pop ax
pop bp
ret
times 510 - ($ - $$) db 0
dw 0aa55h ;引导扇区最后两个字节以0xaa55结束