boot.asm文件注释

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;                               boot.asm
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;                                                       Forrest Yu, 2005
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;                                                       Noted by Ethan_zhang, 2011
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

;%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

;================================================================================================
;因为堆栈是从高往底走的,而程序是从0100h或07c00h的位置开始往高地址走的,所以定义堆栈的栈顶为程序的加载地址
%ifdef    _BOOT_DEBUG_
BaseOfStack        equ    0100h    ; 调试状态下堆栈基地址(栈底, 从这个位置向低地址生长)
%else
BaseOfStack        equ    07c00h    ; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长)
%endif

%include    "load.inc"
;================================================================================================

;=================================Ethan_zhang========================================
;jmp short 段内短转移,以当前ip的相对跳转地址的偏依量作为转移量,但偏移量只允许在-128 ~ +127字节之间
;nop是fat12文件头格式中的BS_jmpBoot固定格式
;=================================Ethan_zhang========================================

    jmp short LABEL_START        ; Start to boot.
    nop                ; 这个 nop 不可少

; 下面是 FAT12 磁盘的头, 之所以包含它是因为下面用到了磁盘的一些信息
%include    "fat12hdr.inc"

LABEL_START:    
    mov    ax, cs
    mov    ds, ax
    mov    es, ax
    mov    ss, ax
    mov    sp, BaseOfStack

    ; 清屏
    mov    ax, 0600h        ; AH = 6,  AL = 0h
    mov    bx, 0700h        ; 黑底白字(BL = 07h)
    mov    cx, 0            ; 左上角: (0, 0)
    mov    dx, 0184fh        ; 右下角: (80, 50)
    int    10h            ; int 10h

;=================================Ethan_zhang========================================
;在后面定义了一系列固定长度的字符串,所以这里只要用dh来表示要显示第几个固定长度的字符地址就可以显示字符了
;=================================Ethan_zhang========================================
    mov    dh, 0            ; "Booting  "
    call    DispStr            ; 显示字符串
    
    xor    ah, ah    ; ┓
    xor    dl, dl    ; ┣ 软驱复位
    int    13h    ; ┛

;=================================Ethan_zhang========================================
;寻找LOADER.BIN文件的大体思路为:
;LOADER.BIN文件存储在A盘中,我们知道A盘的格式是FAT12格式,具体格式见书本P103,其主要是有引导扇区、
;FAT1区、FAT2区、根目录区(长度不固定,需要计算)和 数据区组成。引导扇区放的就是我们写的引导代码boot.bin
;文件,我们的LOADER.BIN文件是放在数据区中的,而文件的文件名是放在根目录中,因此,如果我们想查看该盘是否
;含有我们想要的文件时,我们应该先到根目录中查看是否含有该文件名,如果有,说明该盘存在该文件,那么下面可以
;从盘中读取该文件了,文件在盘中是按照簇来进行存储的,而在FAT12盘中,一个簇中只含有一个扇区,故下面我们只以
;扇区来进行说明。所以文件在FAT12中是按照扇区(一个扇区512字节)来进行存储的,所以一个文件一般会占多个扇区,
;而每个文件占的扇区数会记录在FAT1区中,我们只要从文件根目录中获取该文件在FAT1区中的起始地址,只要该地址
;存储的不是0xFFF说明该文件还占有其他扇区,我们继续读取该地址指向的扇区,直到FAT1项中的值为0xFFF,这表示文件
;结束,读取完毕而根目录也是按照扇区进行存储的,所以我们得从根目录起始扇区开始顺序读取每个扇区,直到根目录区结束。
;而每个根目录项是32个字节,因此一个扇区可以有16个根目录项,我们挨个比较每个根目录项中的文件名和我们期望查找的文件名
;是否相同,如果相同就表示已经找到,只要取出根目录项中的DIR_FstClus,然后从FAT1项中取出相应的数据即可。
;如果没有找到,那么就到下一个根目录项中查找,如果当前扇区都没有找到,那么读取下一个扇区,知道这个根目录区结束。
;=================================Ethan_zhang========================================
; 下面在 A 盘的根目录寻找 LOADER.BIN
    mov    word [wSectorNo], SectorNoOfRootDirectory
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
    cmp    word [wRootDirSizeForLoop], 0    ; ┓
    jz    LABEL_NO_LOADERBIN                            ; ┣ 判断根目录区是不是已经读完
    dec    word [wRootDirSizeForLoop]            ; ┛ 如果读完表示没有找到 LOADER.BIN
    mov    ax, BaseOfLoader
    mov    es, ax            ; es <- BaseOfLoader
    mov    bx, OffsetOfLoader    ; bx <- OffsetOfLoader    于是, es:bx = BaseOfLoader:OffsetOfLoader
    mov    ax, [wSectorNo]    ; ax <- Root Directory 中的某 Sector 号
    mov    cl, 1                ; 要读取的扇区数目
    call    ReadSector    ;将cl个Sector中的数据读到内存es:bx处

    mov    si, LoaderFileName    ; ds:si -> "LOADER  BIN"
    mov    di, OffsetOfLoader    ; es:di -> BaseOfLoader:0100 = BaseOfLoader*10h+100
    cld             ;清除方向标志,告诉程序si,di向前移动;std指令为设置方向,告诉程序si,di向后移动
    mov    dx, 10h                        ;10h表示的是16,因为一个扇区是512字节,而每个根目录项是32字节,
                                                    ;故每个扇区含有512/32=16个根目录项
LABEL_SEARCH_FOR_LOADERBIN:
    cmp    dx, 0                    ; ┓循环次数控制,
    jz    LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR    ; ┣如果已经读完了一个 Sector,
    dec    dx                    ; ┛就跳到下一个 Sector
    mov    cx, 11
LABEL_CMP_FILENAME:
    cmp    cx, 0
    jz    LABEL_FILENAME_FOUND    ; 如果比较了 11 个字符都相等, 表示找到
dec    cx
    lodsb                ;将 ds:si 指向的地址内容加载到 al
    cmp    al, byte [es:di]
    jz    LABEL_GO_ON
    jmp    LABEL_DIFFERENT        ; 只要发现不一样的字符就表明本 DirectoryEntry 不是
; 我们要找的 LOADER.BIN
LABEL_GO_ON:
    inc    di
    jmp    LABEL_CMP_FILENAME    ;    继续循环

LABEL_DIFFERENT:
    and    di, 0FFE0h        ;      ┓di &= E0 为了让它指向本条目开头;因为E0的二进制为1110 0000,后面五位
                                    ;       ┃刚好是32个字节,因此di与上一个OFFE0h,可以回到本条目录的开头
    add    di, 20h            ;      ┃让di增加32个字节,即一个根目录项的大小,从而进入下一个根目录项
    mov    si, LoaderFileName    ;      ┣ 让si重新指向LoaderFileName的起始位置,因为在前面的比较过程中
                                                    ;       ┃si移动了,要重新比较的话,还得重新回到起始点
    jmp    LABEL_SEARCH_FOR_LOADERBIN;    ┛

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
    add    word [wSectorNo], 1
    jmp    LABEL_SEARCH_IN_ROOT_DIR_BEGIN

LABEL_NO_LOADERBIN:
    mov    dh, 2            ; "No LOADER."
    call    DispStr            ; 显示字符串
%ifdef    _BOOT_DEBUG_
    mov    ax, 4c00h        ; ┓
    int    21h            ; ┛没有找到 LOADER.BIN, 回到 DOS,21号中断是回到DOS中
%else
    jmp    $            ; 没有找到 LOADER.BIN, 死循环在这里
%endif

LABEL_FILENAME_FOUND:            ; 找到 LOADER.BIN 后便来到这里继续
    mov    ax, RootDirSectors
    and    di, 0FFE0h        ; 同上面一样,回到当前根目录项的起始位置
    add    di, 01Ah        ; 01Ah是DIR_FstClus 在根目录项中的偏移地址,它表示该文件在FAT1区中的起始序号
    mov    cx, word [es:di]
    push    cx            ; 保存此 Sector 在 FAT 中的序号
    add    cx, ax
    add    cx, DeltaSectorNo    ; 这句完成时 cl 里面变成 LOADER.BIN 的起始扇区号 (从 0 开始数的序号)
    mov    ax, BaseOfLoader
    mov    es, ax            ; es <- BaseOfLoader
    mov    bx, OffsetOfLoader    ; bx <- OffsetOfLoader    于是, es:bx = BaseOfLoader:OffsetOfLoader = BaseOfLoader * 10h + OffsetOfLoader
    mov    ax, cx            ; ax <- Sector 号

LABEL_GOON_LOADING_FILE:
    push    ax            ; ┓
    push    bx            ; ┃
    mov    ah, 0Eh    ; ┃ 每读一个扇区就在 "Booting  " 后面打一个点, 形成这样的效果:
    mov    al, '.'        ; ┃
    mov    bl, 0Fh    ; ┃ Booting ......
    int    10h                ; ┃ 10号中断是专门用来显示字符用的,其中AH表示要显示字符的格式
    pop    bx                ; ┃ AL表示要显示的字符,所以显示一个字符占2个字节
    pop    ax                ; ┛

    mov    cl, 1
    call    ReadSector
    pop    ax            ; 取出此 Sector 在 FAT 中的序号
    call    GetFATEntry
    cmp    ax, 0FFFh
    jz    LABEL_FILE_LOADED
    push    ax            ; 保存 Sector 在 FAT 中的序号
    mov    dx, RootDirSectors
    add    ax, dx
    add    ax, DeltaSectorNo
    add    bx, [BPB_BytsPerSec]    ;BPB_BytsPerSec表示每个扇区的字节数,即512字节
                                                    ;将其赋给bx,说明将bx后移512个字节,Sector->es:bx
    jmp    LABEL_GOON_LOADING_FILE
LABEL_FILE_LOADED:

    mov    dh, 1            ; "Ready."
    call    DispStr            ; 显示字符串

; *****************************************************************************************************
    jmp    BaseOfLoader:OffsetOfLoader    ; 这一句正式跳转到已加载到内存中的 LOADER.BIN 的开始处
                        ; 开始执行 LOADER.BIN 的代码
                        ; Boot Sector 的使命到此结束
; *****************************************************************************************************



;============================================================================
;变量
;----------------------------------------------------------------------------
wRootDirSizeForLoop    dw    RootDirSectors    ; Root Directory 占用的扇区数, 在循环中会递减至零.
wSectorNo        dw    0        ; 要读取的扇区号
bOdd            db    0        ; 奇数还是偶数

;============================================================================
;字符串
;----------------------------------------------------------------------------
LoaderFileName        db    "LOADER  BIN", 0    ; LOADER.BIN 之文件名
; 为简化代码, 下面每个字符串的长度均为 MessageLength
MessageLength        equ    9
BootMessage:        db    "Booting  "; 9字节, 不够则用空格补齐. 序号 0
Message1        db    "Ready.   "; 9字节, 不够则用空格补齐. 序号 1
Message2        db    "No LOADER"; 9字节, 不够则用空格补齐. 序号 2
;============================================================================


;----------------------------------------------------------------------------
; 函数名: DispStr
;----------------------------------------------------------------------------
; 作用:
;    显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based)
DispStr:
    mov    ax, MessageLength
    mul    dh
    add    ax, BootMessage
    mov    bp, ax            ; ┓
    mov    ax, ds            ; ┣ ES:BP = 串地址
    mov    es, ax            ; ┛
    mov    cx, MessageLength    ; CX = 串长度
    mov    ax, 01301h        ; AH = 13,  AL = 01h
    mov    bx, 0007h        ; 页号为0(BH = 0) 黑底白字(BL = 07h)
    mov    dl, 0
    int    10h            ; 调用10号中断显示字符
    ret


;----------------------------------------------------------------------------
; 函数名: ReadSector
;----------------------------------------------------------------------------
; 作用:
;    从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中
ReadSector:
    ; -----------------------------------------------------------------------
    ; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号)
    ; -----------------------------------------------------------------------
    ; 设扇区号为 x
    ;                              ┌ 柱面号 = y >> 1
    ;       x                       ┌ 商 y ┤
    ; -------------- => ┤      └ 磁头号 = y & 1
    ;  每磁道扇区数     │
    ;                              └ 余 z => 起始扇区号 = z + 1
    push    bp
    mov    bp, sp
    sub    esp, 2            ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2]

    mov    byte [bp-2], cl
    push    bx            ; 保存 bx
    mov    bl, [BPB_SecPerTrk]    ; bl: 除数
    div    bl            ; y 在 al 中, z 在 ah 中
    inc    ah            ; z ++
    mov    cl, ah            ; cl <- 起始扇区号
    mov    dh, al            ; dh <- y
    shr    al, 1            ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2)
    mov    ch, al            ; ch <- 柱面号
    and    dh, 1            ; dh & 1 = 磁头号
    pop    bx            ; 恢复 bx
    ; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^
    mov    dl, [BS_DrvNum]        ; 驱动器号 (0 表示 A 盘)
.GoOnReading:
    mov    ah, 2            ; 读
    mov    al, byte [bp-2]        ; 读 al 个扇区
    int    13h
    jc    .GoOnReading        ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止

    add    esp, 2
    pop    bp

    ret

;----------------------------------------------------------------------------
; 函数名: GetFATEntry
;----------------------------------------------------------------------------
; 作用:
;    找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中
;    需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bx
GetFATEntry:
    push    es
    push    bx
    push    ax
    mov    ax, BaseOfLoader    ; ┓
    sub    ax, 0100h                    ; ┣ 在 BaseOfLoader 后面留出 4K 空间用于存放 FAT
    mov    es, ax                        ; ┛
    pop    ax
    mov    byte [bOdd], 0
    mov    bx, 3
    mul    bx            ; dx:ax = ax * 3
    mov    bx, 2
    div    bx            ; dx:ax / 2  ==>  ax <- 商, dx <- 余数
    cmp    dx, 0
    jz    LABEL_EVEN
    mov    byte [bOdd], 1
LABEL_EVEN:;偶数
    xor    dx, dx            ; 现在 ax 中是 FATEntry 在 FAT 中的偏移量. 下面来计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区)
    mov    bx, [BPB_BytsPerSec]
    div    bx            ; dx:ax / BPB_BytsPerSec  ==>    ax <- 商   (FATEntry 所在的扇区相对于 FAT 来说的扇区号)
                    ;                dx <- 余数 (FATEntry 在扇区内的偏移)。
    push    dx
    mov    bx, 0            ; bx <- 0    于是, es:bx = (BaseOfLoader - 100):00 = (BaseOfLoader - 100) * 10h
    add    ax, SectorNoOfFAT1    ; 此句执行之后的 ax 就是 FATEntry 所在的扇区号
    mov    cl, 2
    call    ReadSector        ; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界发生错误, 因为一个 FATEntry 可能跨越两个扇区
    pop    dx
    add    bx, dx
    mov    ax, [es:bx]
    cmp    byte [bOdd], 1
    jnz    LABEL_EVEN_2
    shr    ax, 4
LABEL_EVEN_2:
    and    ax, 0FFFh

LABEL_GET_FAT_ENRY_OK:

    pop    bx
    pop    es
    ret
;----------------------------------------------------------------------------

times     510-($-$$)    db    0    ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw     0xaa55                ; 结束标志

posted @ 2011-11-07 15:54  MagiCube  阅读(3320)  评论(0编辑  收藏  举报