第四章boot.asm

跟上篇日志中间又隔了好几天,懒惰啊

 1 寻找LOADER.BIN文件的大体思路为:
 2 LOADER.BIN文件存储在A盘中,我们知道A盘的格式是FAT12格式,具体格式见书本P103,其主要是有引导扇区
 3 、FAT1区、FAT2区、根目录区(长度不固定,需要计算)和 数据区组成。引导扇区放的就是我们写的引导代码
 4 boot.bin文件,我们的LOADER.BIN文件是放在数据区中的,而文件的文件名是放在根目录中,因此,如果我
 5 们想查看该盘是否含有我们想要的文件时,我们应该先到根目录中查看是否含有该文件名,如果有,说明该盘存
 6 在该文件,那么下面可以从盘中读取该文件了。
 7 文件在盘中是按照簇来进行存储的,而在FAT12盘中,一个簇中只含有一个扇区,故下面我们只以扇区来进行说
 8 明。所以文件在FAT12中是按照扇区(一个扇区512字节)来进行存储的,所以一个文件一般会占多个扇区,而
 9 每个文件占的扇区数会记录在FAT1区中,我们只要从文件根目录中获取该文件在FAT1区中的起始地址,只要该
10 地址存储的不是0xFFF说明该文件还占有其他扇区,我们继续读取该地址指向的扇区,直到FAT1项中的值为
11 0xFFF,这表示文件结束,读取完毕.而根目录也是按照扇区进行存储的,所以我们得从根目录起始扇区开始顺
12 序读取每个扇区,直到根目录区结束。而每个根目录项是32个字节,因此一个扇区可以有16个根目录项,我们
13 挨个比较每个根目录项中的文件名和我们期望查找的文件名是否相同,如果相同就表示已经找到,只要取出根目录项中的DIR_FstClus,然后从FAT1项中取出相应的数据即可。
14 如果没有找到,那么就到下一个根目录项中查找,如果当前扇区都没有找到,那么读取下一个扇区,直到这个根;目录区结束。

寻找loader.bin的思路

1.首先要明白loader.bin文件的数据部分放在数据区,而关于文件大小,文件名等文件信息放在根目录区

2.所以找文件的时候先到根目录区找到文件名为loader.bin的文件信息,怎么找呢?我们知道,每个文件的信息在根目录中占一项,每项为32字节,每个扇区共有16项;同时我们

还知道根目录开始扇区位19,所以我们从该扇区开始读取数据,每次读一个扇区。在度过一个扇区以后,我们要分析这个扇区中的内容,具体是比较目录项中文件名是否为

loader.bin,若是则在根目录中文件查找完毕

3.在根目录中找到loader.bin的信息之后,我们可以得到该文件在数据区的簇号(扇区号),同时也得到在fat表中的对应的项(index),p131小例子

4.反复根据步骤三得到信息读完整个文件

  1 ;%define    _BOOT_DEBUG_    ; 做 Boot Sector 时一定将此行注释掉!将此行打开后用 nasm Boot.asm -o Boot.com 做成一个.COM文件易于调试
  2 
  3 %ifdef    _BOOT_DEBUG_
  4     org  0100h            ; 调试状态, 做成 .COM 文件, 可调试
  5 %else
  6     org  07c00h            ; Boot 状态, Bios 将把 Boot Sector 加载到 0:7C00 处并开始执行
  7 %endif
  8 
  9 ;================================================================================================
 10 %ifdef    _BOOT_DEBUG_
 11 BaseOfStack        equ    0100h    ; 调试状态下堆栈基地址(栈底, 从这个位置向低地址生长)
 12 %else
 13 BaseOfStack        equ    07c00h    ; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长)
 14 %endif
 15 
 16 BaseOfLoader        equ    09000h    ; LOADER.BIN 被加载到的位置 ----  段地址
 17 OffsetOfLoader        equ    0100h    ; LOADER.BIN 被加载到的位置 ---- 偏移地址
 18 
 19 RootDirSectors        equ    14    ; 根目录占用空间
 20 SectorNoOfRootDirectory    equ    19    ; Root Directory 的第一个扇区号
 21 SectorNoOfFAT1        equ    1    ; FAT1 的第一个扇区号    = BPB_RsvdSecCnt
 22 DeltaSectorNo        equ    17    ; DeltaSectorNo = BPB_RsvdSecCnt + (BPB_NumFATs * FATSz) - 2
 23                     ; 文件的开始Sector号 = DirEntry中的开始Sector号 + 根目录占用Sector数目 + DeltaSectorNo
 24 ;================================================================================================
 25 
 26     jmp short LABEL_START        ; Start to boot.
 27     nop                ; 这个 nop 不可少
 28 
 29     ; 下面是 FAT12 磁盘的头
 30     BS_OEMName    DB 'ForrestY'    ; OEM String, 必须 8 个字节
 31     BPB_BytsPerSec    DW 512        ; 每扇区字节数
 32     BPB_SecPerClus    DB 1        ; 每簇多少扇区
 33     BPB_RsvdSecCnt    DW 1        ; Boot 记录占用多少扇区
 34     BPB_NumFATs    DB 2        ; 共有多少 FAT 表
 35     BPB_RootEntCnt    DW 224        ; 根目录文件数最大值
 36     BPB_TotSec16    DW 2880        ; 逻辑扇区总数
 37     BPB_Media    DB 0xF0        ; 媒体描述符
 38     BPB_FATSz16    DW 9        ; 每FAT扇区数
 39     BPB_SecPerTrk    DW 18        ; 每磁道扇区数
 40     BPB_NumHeads    DW 2        ; 磁头数(面数)
 41     BPB_HiddSec    DD 0        ; 隐藏扇区数
 42     BPB_TotSec32    DD 0        ; 如果 wTotalSectorCount 是 0 由这个值记录扇区数
 43     BS_DrvNum    DB 0        ; 中断 13 的驱动器号
 44     BS_Reserved1    DB 0        ; 未使用
 45     BS_BootSig    DB 29h        ; 扩展引导标记 (29h)
 46     BS_VolID    DD 0        ; 卷序列号
 47     BS_VolLab    DB 'Tinix0.01  '; 卷标, 必须 11 个字节
 48     BS_FileSysType    DB 'FAT12   '    ; 文件系统类型, 必须 8个字节  
 49 
 50 LABEL_START:    
 51     mov    ax, cs
 52     mov    ds, ax
 53     mov    es, ax
 54     mov    ss, ax
 55     mov    sp, BaseOfStack
 56 
 57     ; 清屏
 58     mov    ax, 0600h        ; AH = 6,  AL = 0h
 59     mov    bx, 0700h        ; 黑底白字(BL = 07h)
 60     mov    cx, 0            ; 左上角: (0, 0)
 61     mov    dx, 0184fh        ; 右下角: (80, 50)
 62     int    10h            ; int 10h
 63 
 64     mov    dh, 0            ; "Booting  "
 65     call    DispStr            ; 显示字符串
 66     
 67     xor    ah, ah    ;
 68     xor    dl, dl    ; ┣ 软驱复位
 69     int    13h    ;
 70     
 71 ; 下面在 A 盘的根目录寻找 LOADER.BIN
 72     mov    word [wSectorNo], SectorNoOfRootDirectory
 73 LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
 74     cmp    word [wRootDirSizeForLoop], 0    ;
 75     jz    LABEL_NO_LOADERBIN        ; ┣ 判断根目录区是不是已经读完
 76     dec    word [wRootDirSizeForLoop]    ; ┛ 如果读完表示没有找到 LOADER.BIN
 77     mov    ax, BaseOfLoader
 78     mov    es, ax            ; es <- BaseOfLoader
 79     mov    bx, OffsetOfLoader    ; bx <- OffsetOfLoader    于是, es:bx = BaseOfLoader:OffsetOfLoader
 80     mov    ax, [wSectorNo]    ; ax <- Root Directory 中的某 Sector 号
 81     mov    cl, 1
 82     call    ReadSector
 83 
 84     mov    si, LoaderFileName    ; ds:si -> "LOADER  BIN"
 85     mov    di, OffsetOfLoader    ; es:di -> BaseOfLoader:0100 = BaseOfLoader*10h+100
 86     cld
 87     mov    dx, 10h ;这里为什么是16,因为目录项中每项为32字节,每个扇区为32字节,所以共有16项
 88 LABEL_SEARCH_FOR_LOADERBIN:
 89     cmp    dx, 0                                        ; ┓循环次数控制,
 90     jz    LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR    ; ┣如果已经读完了一个 Sector,
 91     dec    dx                                            ; ┛就跳到下一个 Sector
 92     mov    cx, 11                                      ;11个字符,有一个不一样则不是所要找的文件
 93 LABEL_CMP_FILENAME:
 94     cmp    cx, 0
 95     jz    LABEL_FILENAME_FOUND    ; 如果比较了 11 个字符都相等, 表示找到
 96 dec    cx
 97     lodsb                ; ds:si -> al
 98     cmp    al, byte [es:di]
 99     jz    LABEL_GO_ON
100     jmp    LABEL_DIFFERENT        ; 只要发现不一样的字符就表明本 DirectoryEntry 不是
101 ; 我们要找的 LOADER.BIN
102 LABEL_GO_ON:
103     inc    di
104     jmp    LABEL_CMP_FILENAME    ;    继续循环
105 
106 LABEL_DIFFERENT:
107     and    di, 0FFE0h                        ; else ┓    di &= E0 为了让它指向本条目开头
108     add    di, 20h                            ;
109     mov    si, LoaderFileName                    ;     ┣ di += 20h  下一个目录条目
110     jmp    LABEL_SEARCH_FOR_LOADERBIN;
111 
112 LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
113     add    word [wSectorNo], 1
114     jmp    LABEL_SEARCH_IN_ROOT_DIR_BEGIN
115 
116 LABEL_NO_LOADERBIN:
117     mov    dh, 2            ; "No LOADER."
118     call    DispStr            ; 显示字符串
119 %ifdef    _BOOT_DEBUG_
120     mov    ax, 4c00h        ;
121     int    21h            ; ┛没有找到 LOADER.BIN, 回到 DOS
122 %else
123     jmp    $            ; 没有找到 LOADER.BIN, 死循环在这里
124 %endif
125 
126 LABEL_FILENAME_FOUND:            ; 找到 LOADER.BIN 后便来到这里继续
127     mov    ax, RootDirSectors
128     and    di, 0FFE0h        ; di -> 当前条目的开始
129     add    di, 01Ah        ; di -> 首 Sector,该条目对应的开始簇号(扇区号),看根目录条目格式表
130     mov    cx, word [es:di]
131     push    cx            ; 保存此 Sector 在 FAT 中的序号
132     add    cx, ax
133     add    cx, DeltaSectorNo    ; 这句完成时 cl 里面变成 LOADER.BIN 的起始扇区号 (从 0 开始数的序号)
134     mov    ax, BaseOfLoader
135     mov    es, ax            ; es <- BaseOfLoader
136     mov    bx, OffsetOfLoader    ; bx <- OffsetOfLoader    于是, es:bx = BaseOfLoader:OffsetOfLoader = BaseOfLoader * 10h + OffsetOfLoader
137     mov    ax, cx            ; ax <- Sector 号
138 
139 LABEL_GOON_LOADING_FILE:
140     push    ax            ;
141     push    bx            ;
142     mov    ah, 0Eh            ; ┃ 每读一个扇区就在 "Booting  " 后面打一个点, 形成这样的效果:
143     mov    al, '.'            ;
144     mov    bl, 0Fh            ; ┃ Booting ......
145     int    10h            ; ┃AL=字符,BH=页码,BL=颜色(只适用于图形模式)
146     pop    bx            ;
147     pop    ax            ;
148 
149     mov    cl, 1
150     call    ReadSector
151     pop    ax            ; 取出此 Sector 在 FAT 中的序号,见132行
152     call    GetFATEntry
153     cmp    ax, 0FFFh
154     jz    LABEL_FILE_LOADED
155     push    ax            ; 保存 Sector 在 FAT 中的序号
156     mov    dx, RootDirSectors
157     add    ax, dx
158     add    ax, DeltaSectorNo
159     add    bx, [BPB_BytsPerSec]
160     jmp    LABEL_GOON_LOADING_FILE
161 LABEL_FILE_LOADED:
162 
163     mov    dh, 1            ; "Ready."
164     call    DispStr            ; 显示字符串
165 
166 ; *****************************************************************************************************
167     jmp    BaseOfLoader:OffsetOfLoader    ; 这一句正式跳转到已加载到内存中的 LOADER.BIN 的开始处
168                         ; 开始执行 LOADER.BIN 的代码
169                         ; Boot Sector 的使命到此结束
170 ; *****************************************************************************************************
171 
172 
173 
174 ;============================================================================
175 ;变量
176 ;----------------------------------------------------------------------------
177 wRootDirSizeForLoop    dw    RootDirSectors    ; Root Directory 占用的扇区数, 在循环中会递减至零.
178 wSectorNo        dw    0        ; 要读取的扇区号
179 bOdd            db    0        ; 奇数还是偶数
180 
181 ;============================================================================
182 ;字符串
183 ;----------------------------------------------------------------------------
184 LoaderFileName        db    "LOADER  BIN", 0    ; LOADER.BIN 之文件名
185 ; 为简化代码, 下面每个字符串的长度均为 MessageLength
186 MessageLength        equ    9
187 BootMessage:        db    "Booting  "; 9字节, 不够则用空格补齐. 序号 0
188 Message1        db    "Ready.   "; 9字节, 不够则用空格补齐. 序号 1
189 Message2        db    "No LOADER"; 9字节, 不够则用空格补齐. 序号 2
190 ;============================================================================
191 
192 
193 ;----------------------------------------------------------------------------
194 ; 函数名: DispStr
195 ;----------------------------------------------------------------------------
196 ; 作用:
197 ;    显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based)
198 DispStr:
199     mov    ax, MessageLength
200     mul    dh
201     add    ax, BootMessage
202     mov    bp, ax            ;
203     mov    ax, ds            ; ┣ ES:BP = 串地址
204     mov    es, ax            ;
205     mov    cx, MessageLength    ; CX = 串长度
206     mov    ax, 01301h        ; AH = 13,  AL = 01h
207     mov    bx, 0007h        ; 页号为0(BH = 0) 黑底白字(BL = 07h)
208     mov    dl, 0
209     int    10h            ; int 10h
210     ret
211 
212 
213 ;----------------------------------------------------------------------------
214 ; 函数名: ReadSector
215 ;----------------------------------------------------------------------------
216 ; 作用:
217 ;    从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中
218 ReadSector:
219     ; -----------------------------------------------------------------------
220     ; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号)
221     ; -----------------------------------------------------------------------
222     ; 设扇区号为 x
223     ;                           ┌ 柱面号 = y >> 1
224     ;       x           ┌ 商 y ┤
225     ; -------------- => ┤      └ 磁头号 = y & 1
226     ;  每磁道扇区数     │
227     ;                   └ 余 z => 起始扇区号 = z + 1
228     push    bp
229     mov    bp, sp
230     sub    esp, 2            ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2]
231 
232     mov    byte [bp-2], cl
233     push    bx            ; 保存 bx
234     mov    bl, [BPB_SecPerTrk]    ; bl: 除数
235     div    bl            ; y 在 al 中, z 在 ah 中
236     inc    ah            ; z ++
237     mov    cl, ah            ; cl <- 起始扇区号
238     mov    dh, al            ; dh <- y
239     shr    al, 1            ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2)
240     mov    ch, al            ; ch <- 柱面号
241     and    dh, 1            ; dh & 1 = 磁头号
242     pop    bx            ; 恢复 bx
243     ; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^
244     mov    dl, [BS_DrvNum]        ; 驱动器号 (0 表示 A 盘)
245 .GoOnReading:
246     mov    ah, 2            ;
247     mov    al, byte [bp-2]        ; 读 al 个扇区
248     int    13h
249     jc    .GoOnReading        ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止
250 
251     add    esp, 2
252     pop    bp
253 
254     ret
255 
256 ;----------------------------------------------------------------------------
257 ; 函数名: GetFATEntry
258 ;----------------------------------------------------------------------------
259 ; 作用:
260 ;    找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中
261 ;    需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bx
262 GetFATEntry:
263     push    es
264     push    bx
265     push    ax
266     mov    ax, BaseOfLoader    ;
267     sub    ax, 0100h        ; ┣ 在 BaseOfLoader 后面留出 4K 空间用于存放 FAT,注意这里是0100,而不是01000
268     mov    es, ax            ;
269     pop    ax
270     mov    byte [bOdd], 0
271     mov    bx, 3
272     mul    bx            ; dx:ax = ax * 3
273     mov    bx, 2
274     div    bx            ; dx:ax / 2  ==>  ax <- 商, dx <- 余数
275     cmp    dx, 0
276     jz    LABEL_EVEN
277     mov    byte [bOdd], 1
278 LABEL_EVEN:;偶数
279     xor    dx, dx            ; 现在 ax 中是 FATEntry 在 FAT 中的偏移量. 下面来计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区)
280     mov    bx, [BPB_BytsPerSec]
281     div    bx            ; dx:ax / BPB_BytsPerSec  ==>    ax <- 商   (FATEntry 所在的扇区相对于 FAT 来说的扇区号)
282                     ;                dx <- 余数 (FATEntry 在扇区内的偏移)。
283     push    dx
284     mov    bx, 0            ; bx <- 0    于是, es:bx = (BaseOfLoader - 100):00 = (BaseOfLoader - 100) * 10h
285     add    ax, SectorNoOfFAT1    ; 此句执行之后的 ax 就是 FATEntry 所在的扇区号
286     mov    cl, 2
287     call    ReadSector        ; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界发生错误, 因为一个 FATEntry 可能跨越两个扇区
288     pop    dx
289     add    bx, dx
290     mov    ax, [es:bx]
291     cmp    byte [bOdd], 1
292     jnz    LABEL_EVEN_2
293     shr    ax, 4
294 LABEL_EVEN_2:
295     and    ax, 0FFFh
296 
297 LABEL_GET_FAT_ENRY_OK:
298 
299     pop    bx
300     pop    es
301     ret
302 ;----------------------------------------------------------------------------
303 
304 times     510-($-$$)    db    0    ; 填充剩下的空间,使生成的二进制代码恰好为512字节
305 dw     0xaa55                ; 结束标志

参考:

http://blog.chinaunix.net/uid-27024249-id-3449728.html

http://blog.csdn.net/robbie1314/article/details/5765117

http://blog.csdn.net/asd8182651/article/details/7388541

原文地址:https://www.cnblogs.com/cdwodm/p/2912831.html