boot/boot.asm

来源:互联网 发布:美画人生淘宝店 编辑:程序博客网 时间:2024/05/16 11:58

;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结束