第9课 - 主引导程序控制权的转移

BootLoader内存布局

    
 

通过FAT表加载文件内容

    

实验步骤

    在虚拟软盘中创建体积较大的文本文件(Loader)
    将Loader的内容加载到BaseOfLoader地址处
    打印Loader中的文本(判断加载是否完全)

第一个Loader程序

    起始地址0x9000(org 0x9000)
    通过int 0x10在屏幕上打印字符串
    

汇编小提示:标志寄存器

    
    jxx代表了一个指令族,功能是根据标志位进行调整
        jo当OF为1则跳转
        jc当CF为1则跳转
        jns当SF不为1则跳转
        jz当ZF为1则跳转
        je比较结果相等则跳转(即:jz)

小结

    Boot需要进行重构保证在512字节内完成功能
    在汇编程序中 尽量确保函数调用前后通用寄存器的状态不变
    Boot成功加载Loader后将控制权转移
    Loader程序没有代码体积上的限制

代码

 1 // makefile
 2 
 3 .PHONY : all clean rebuild
 4 
 5 BOOT_SRC := boot.asm
 6 BOOT_OUT := boot.bin
 7 
 8 LOADER_SRC := loader.asm
 9 LOADER_OUT := loader
10 
11 IMG := data.img
12 IMG_PATH := /mnt/hgfs
13 
14 RM := rm -rf
15 
16 all : $(IMG) $(BOOT_OUT) $(LOADER_OUT)
17     @echo "Build Success ==> D.T.OS!"
18 
19 $(IMG) :
20     # bximage $@ -q -fd -size=1.44
21     bximage -mode=create -q -fd=1.44M $@
22 
23 $(BOOT_OUT) : $(BOOT_SRC)
24     nasm $^ -o $@
25     dd if=$@ of=$(IMG) bs=512 count=1 conv=notrunc
26 
27 $(LOADER_OUT) : $(LOADER_SRC)
28     nasm $^ -o $@
29     sudo mount -o loop $(IMG) $(IMG_PATH)
30     sudo cp $@ $(IMG_PATH)/$@
31     sudo umount $(IMG_PATH)
32 
33 clean :
34     $(RM) $(IMG) $(BOOT_OUT) $(LOADER_OUT)
35 
36 rebuild :
37     @$(MAKE) clean
38     @$(MAKE) all
  1 // boot.asm
  2 org 0x7c00                                ; 从0x7c00处开始存储代码
  3 
  4 jmp short start                            ; BS_JmpBoot,短跳指令(3字节);jmp指令占用1字节,地址占用1字节;下面的nop空指令占用1字节;共3字节
  5 nop
  6 
  7 define:
  8     BaseOfStack     equ 0x7c00            ; 定义栈的起始地址,注意栈是从高到低增长的,先加后入栈,先出后减
  9     BaseOfLoader    equ 0x9000            ; 内存加载地址
 10     RootEntryOffset equ 19                ; 目录文件项从19逻辑扇区开始
 11     RootEntryLength equ 14                ; 目录文件项共14扇区
 12     EntryItemLength    equ 32                ; 根目录区每个目录项大小
 13     FatEntryOffset    equ 1                ; FAT表起始扇区(FAT表记录了文件簇对应的实际地址)
 14     FatEntryLength    equ 9                ; FAT表长度(扇区)
 15 
 16 
 17 ; ##############################################################################
 18 header:                                    ; MBR
 19     BS_OEMName        db "D.T.Soft"        ; OEM字符,8个,不足以空格填充
 20     BPB_BytsPerSec    dw 512                ; 每扇区字节数
 21     BPB_SecPerClus    db 1                ; 每簇占用扇区数
 22     BPB_RsvdSecCnt    dw 1                ; Boot占用的扇区数
 23     BPB_NumFATs        db 2                ; FAT表的记录数
 24     BPB_RootEntCnt    dw 224                ; 最大根目录文件数
 25     BPB_TotSec16    dw 2880                ; 逻辑扇区总数
 26     BPB_Media        db 0xF0                ; 媒体描述符
 27     BPB_FATSz16        dw 9                ; 每个FAT占用扇区数
 28     BPB_SecPerTrk    dw 18                ; 每个磁道扇区数
 29     BPB_NumHeads    dw 2                ; 磁头数
 30     BPB_HiddSec        dd 0                ; 隐藏扇区数
 31     BPB_TotSec32    dd 0                ; 如果BPB_TotSec16是0,则在这里记录扇区总数
 32     BS_DrvNum        db 0                ; 中断13的驱动器号
 33     BS_Reserved1    db 0                ; 未使用
 34     BS_BootSig        db 0x29                ; 扩展引导标志
 35     BS_VolID        dd 0                ; 卷序列号
 36     BS_VolLab        db "D.T.OS-0.01"    ; 卷标,必须11个字符,不足以空格填充
 37     BS_FileSysType    db "FAT12   "        ; 文件系统类型,必须使8个字符,不足填充空格
 38 
 39 
 40 ; ##############################################################################
 41 start:                                    ; 汇编起始标号,类似于main()函数,之前用jmp跳转到此
 42     mov ax, cs                            ; 设置相关的段寄存器
 43     mov ss, ax
 44     mov ds, ax
 45     mov es, ax
 46     mov sp, BaseOfStack                    ; 设置函数调用栈地址
 47 
 48     mov ax, RootEntryOffset                ; 记录目录文件项起始扇区
 49     mov cx, RootEntryLength                ; 记录目录文件项占用扇区数
 50     mov bx, Buf                            ; 读取的目录文件项存放处
 51 
 52     call ReadSector                        ;     读取指定扇区数据到Buf缓存
 53     mov si, Target                        ; 要查找的文件名
 54     mov cx, TarLen                        ; 文件名长度
 55     mov dx, 0                            ; 设置默认返回值为查找失败
 56 
 57     call FindEntry                        ;     查找文件,dx作为返回值
 58 
 59     cmp dx, 0                            ; 如果没有找到文件就输出提示,否則继续
 60     jz output
 61 
 62     mov si, bx                            ; 将读取的数据bx放到si,这里是将目录文件项在Buf的地址作为源
 63     mov di, EntryItem                    ; 存储根目录区的内存空间di
 64     mov cx, EntryItemLength                ; 根目录区的大小
 65 
 66     call MemCpy                            ;     调用内存拷贝,把目录文件项的第一个目录项从Buf拷贝到EntryItem
 67 
 68     mov ax, FatEntryLength                ; Fat表占用扇区数
 69     mov cx, [BPB_BytsPerSec]            ; 每扇区字节数
 70     mul cx                                ; mul乘法,al*cx,结果放到AX系列寄存器,此时ax等于Fat表占用字节数
 71     mov bx, BaseOfLoader                ; Fat表内存暂存区地址
 72     sub bx, ax                            ; 减法,将读取的数据放到Fat暂存区的前面,注意上面的乘法结果不能溢出,这里的AX没有溢出(9*512=4608,ax=16bit=65535)
 73 
 74     mov ax, FatEntryOffset                ; Fat1起始扇区
 75     mov cx, FatEntryLength                ; Fat表的长度
 76 
 77     call ReadSector                        ;     读取Fat表,放到bx指向的地址
 78 
 79     mov dx, [EntryItem + 0x1A]            ; FAT表内存地址加0x1A写入dx寄存器,即文件开始的簇号(DIR_FstClus)
 80 
 81     ;call FatVec                            ;    获取簇对应的地址
 82 
 83     ;jmp last
 84     mov si, BaseOfLoader                ; 数据加载区起始地址
 85 
 86 loading:
 87     mov ax, dx                            ; 将文件簇号放入ax,注意一个簇占用1扇区,否则需要转换成扇区数
 88     add ax, 31                            ; 31=33-2;将簇号加上数据区域的地址;数据起始于33扇区,且从2开始,前2个表项不使用
 89     mov cx, 1                            ; 读一个扇区
 90     push dx
 91     push bx
 92     mov bx, si                            ; 将数据存放地址放入bx
 93     call ReadSector                        ; 从ax读取cx大小到bx,读一个扇区的文件区数据到BaseOfLoader的前面
 94     pop bx
 95     pop cx                                ; pop cx ==> (pop dx;mov cx, dx;)
 96     call FatVec                            ; 返回下一个簇的编号;bx=FAT1表的内存地址,cx=文件开始簇号,dx=下一个簇号
 97     cmp dx, 0xFF7                        ; 是否为坏簇
 98     jnb BaseOfLoader                    ; 簇号如果不小于坏簇编号就跳转;cmp的第一个操作数不小于第二个操作数就跳转
 99     ;jnb output
100     add si, 512                            ; 指向下一个簇(当前一个簇占一个扇区,一个扇区512字节)
101     jmp loading                            ; 循环加载
102 
103 output:
104     mov bp, MsgStr
105     mov cx, MsgLen
106     ;mov bp, BaseOfLoader
107     ;mov cx, [EntryItem + 0x1C]            ; 输出长度为当前文件的大小,EntryItem=匹配的文件的文件项,0x1C=Dir_FileSize
108 
109     call Print                            ; 调用字符串打印函数
110 
111 last:                                    ; 死循环
112     hlt
113     jmp last
114 
115 
116 ; ##############################################################################
117 ; cx --> index
118 ; bx --> fat table address
119 ;
120 ; return:
121 ;    dx --> fat[index]
122 FatVec:                                    ; 获取簇对应的地址,bx=fat表的地址,cx文件起始簇号
123     mov ax, cx                            ; 下面是乘以1.5的相关操作(/2*3),因为2个簇占3字节,通过簇号的奇偶校验决定该簇在FAT表的偏移
124     mov cl, 2
125     div cl                                ; 文件簇编号除以2,商放al,余数放ah;余数为0时簇号为偶数,相对FAT表的偏移量采用低12bit(低1.5字节),否则采用高12bit(高1.5字节)
126 
127     push ax                                ; ah用于决定截取方式,al保留了除以2的整数值
128 
129     mov ah, 0                            ; ax的高位置0,相当于舍去余数保留商
130     mov cx, 3
131     mul cx                                ; ax乘以3
132     mov cx, ax                            ; cx=文件簇在FAT表的位置
133 
134     pop ax
135 
136     cmp ah, 0                            ; 对簇地址判断奇偶决定比特组合方式(前12比特还是后12比特)
137     jz even                                ; 如果余数是0就跳转到even截取前1.5字节
138     jmp odd                                ; 否则跳转到odd截取后1.5字节
139 
140 even:                                    ; 偶数:截取前1.5字节;fatVec[j] = ( (Fat[i+1] & 0x0f) << 8 ) | Fat[i];
141     mov dx, cx                            ; 文件簇偏移放入dx
142     add dx, 1                            ; 偏移到第二个字节,需要截取第二个字节
143     add dx, bx                            ; 文件簇索引+Fat表的地址(得到第二个字节的实际地址)
144     mov bp, dx                            ; 借助bp寄存器读取当前簇的第二个字节
145     mov dl, byte [bp]
146     and dl, 0x0F                        ; 保留低4位
147     shl dx, 8                            ; 左移8位使第二字节的低4bit放到dx的高位
148     add cx, bx                            ; 指向第一个字节
149     mov bp, cx
150     or  dl, byte [bp]                    ; 读取当前簇的第一个字节放到dx的低位,将2个字节组合起来
151     jmp return
152 
153 odd:                                    ; 奇数:截取后1.5字节;FatVec[j+1] = (Fat[i+2] << 4) | ( (Fat[i+1] >> 4) & 0x0F );
154     mov dx, cx
155     add dx, 2                            ; 偏移到第三个字节(最后一个字节)
156     add dx, bx                            ; 文件簇偏移量+Fat表的地址(得到第三个字节的实际地址)
157     mov bp, dx
158     mov dl, byte [bp]                    ; dl保存第3个字节的数据
159     mov dh, 0                            ; 将dx的高位置0
160     shl dx, 4                            ; 将读取的最后一个字节左移4位,留出低4位给第二个字节的高4位(注意dx是16bit的,左移4位后dl的高4位移到dh的低4位)
161     add cx, 1                            ; 偏移指向第二个字节
162     add cx, bx                            ; 文件簇偏移量+Fat表的地址(得到第二个字节的实际地址)
163     mov bp, cx                            ; 读取第二个字节的值
164     mov cl, byte [bp]
165     shr cl, 4                            ; 将读取的第二个字节右移4位,高位移向低位
166     and cl, 0x0F                        ; 保留右移过后的低位4bit
167     mov ch, 0                            ; 将cx的高位置0
168     or  dx, cx                            ; 将2个字节组合起来,最后一字节(存储在2字节的寄存器)左移4位 | 第二字节右移4bit
169 
170 return:
171     ret
172 
173 
174 ; ##############################################################################
175 ; ds:si --> source
176 ; es:di --> destination
177 ; cx    --> length
178 MemCpy:                                    ; 内存拷贝
179     ;push si
180     ;push di
181     ;push cx
182     ;push ax
183 
184     cmp si, di                            ; 比较内存地址大小,因为两个内存地址空间如果有重复拷贝方式将不同
185 
186     ja btoe                                ; jump if above,如果源地址大于目的地址就从前向后拷贝,否则从后向前拷贝;避免两个内存空间有重复地址时覆盖未拷贝的源数据。
187     
188     add si, cx                            ; 从后向前拷贝需要源和目标地址都挪到最后,
189     add di, cx
190     dec si                                ; 上面的add使指针指向了最后一个字节的后面,所以需要回退一个字节
191     dec di
192 
193     jmp etob
194 
195 btoe:
196     cmp cx, 0                            ; 如果长度为0就停止
197     jz done
198     mov al, [si]                        ; 借助al寄存器将源地址的一个字节(al=8bit)拷贝到目的地址
199     mov byte [di], al
200     inc si                                ; inc递增指令,拷贝完一个字节后源和目标地址指向下一个
201     inc di
202     dec cx                                ; 长度减1
203     jmp btoe                            ; 循环拷贝
204 
205 etob:                                    ; end to begin
206     cmp cx, 0                            ; 如果拷贝完毕就结束
207     jz done
208     mov al, [si]
209     mov byte [di], al
210     dec si                                ; 源和目标地址递减,注意是从后向前拷贝的
211     dec di
212     dec cx
213     jmp etob                            ; 循环拷贝
214 
215 done:                                    ; 内存拷贝完毕还原寄存器数据
216     ;pop ax
217     ;pop cx
218     ;pop di
219     ;pop si
220 
221     ret
222 
223 
224 ; ##############################################################################
225 ; es:bx --> root entry offset address
226 ; ds:si --> target string
227 ; cx    --> target length
228 ;
229 ; return:
230 ;        (dx != 0) ? exist : noexist
231 ;            exist --> bx is the target entry
232 FindEntry:                                ; 查找根目录文件
233     ;push di
234     ;push bp
235     push cx
236 
237     mov dx, [BPB_RootEntCnt]            ; 最大根目录文件数
238     mov bp, sp                            ; 栈地址,不能直接将sp栈顶指针赋值给通用寄存器
239 
240 find:
241     cmp dx, 0                            ; 如果没有文件就跳转到noexist结束
242     jz noexist
243     mov di, bx                            ; bx==Buf缓存;ReadSector已经读取数据到Buf缓存
244     mov cx, [bp]                        ; 借助中间寄存器获取栈顶指针,此时的bp栈顶指针指向最后入栈的cx寄存器
245     push si
246     call MemCmp                            ; 内存匹配查找(文件查找),返回cx表示匹配的字符数(为0表示没有文件或匹配成功)
247     pop si
248     cmp cx, 0                            ; 返回0表示匹配成功或根目录没有文件,否则继续查找
249     jz exist
250     add bx, EntryItemLength                ; Buf缓存地址加32,每个目录项占用32字节
251     dec dx                                ; dx-1,文件数减1
252     jmp find
253 
254 exist:
255 noexist:
256     pop cx
257     ;pop bp
258     ;pop di
259 
260     ret
261 
262 
263 ; ##############################################################################
264 ; ds:si --> source
265 ; es:di --> destination
266 ; cx    --> length
267 ;
268 ; return:
269 ;        (cx == 0) ? equal : noequeal
270 MemCmp:                                    ; 内存数据对比
271     ;push si
272     ;push di
273     ;push ax
274 
275 compare:
276     cmp cx, 0                            ; 到末尾(文件名结束符)就跳转到equal,目录项的第一段是文件名
277     jz equal
278     mov al, [si]                        ; si源(要查找的文件名)
279     cmp al, byte [di]                    ; di目标(根目录区的文件名)
280     jz goon                                ; 匹配一个字节就跳转到goon继续循环判断
281     jmp noequal                            ; 不匹配就跳转到noequal,函数返回以便查找下一个文件
282 goon:
283     inc si                                ; 源和目标+1递增
284     inc di
285     dec cx                                ; 剩余字符串长度递减
286     jmp compare                            ; 跳转到compare继续循环判断
287 
288 equal:
289 noequal:
290     ;pop ax
291     ;pop di
292     ;pop si
293 
294     ret
295 
296 
297 ; ##############################################################################
298 ; es:bp --> string address
299 ; cx    --> string length
300 Print:                                    ; 字符串打印函数
301     mov dx, 0                            ; dx打印的起始行列
302     mov ax, 0x1301                        ; ah=0x13,在Teletype电传打字机模式下输出;al=0x01,字符串只含字符,启用BL属性,光标跟随移动
303     mov bx, 0x0007                        ; bh页码,bl前景色;bl=07,黑底白字
304     int 0x10                            ; 打印中断
305     ret                                    ; 函数返回
306 
307 
308 ; ##############################################################################
309 ; no parameter
310 ResetFloppy:                            ; 重置软盘
311     ;push ax
312     ;push dx
313 
314     mov ah, 0x00                        ; 磁盘系统复位
315     mov dl, [BS_DrvNum]                    ; 驱动器号(0x00~0x7F软盘,0x80~0x0FF硬盘)
316     int 0x13                            ; 读取磁盘的中断
317     
318     ;pop dx
319     ;pop ax
320 
321     ret
322 
323 ; ax    --> logic sector number
324 ; cx    --> number of sector
325 ; es:bx    --> target address
326 ReadSector:                                ; 读扇区(函数)
327     ;push bx
328     ;push cx
329     ;push dx
330     ;push ax
331     
332     call ResetFloppy                    ; 重置软盘
333     
334     push bx
335     push cx
336 
337     mov bl, [BPB_SecPerTrk]                ; 每柱面(磁道)扇区数;本段代码用于计算柱面、磁头和扇区号以及设置驱动器号
338     div bl                                ; 除法,用于计算要读取的数据的起始柱面和扇区偏移;被除数在AX(或者DX高位+AX地位),商在AL,余数在AH
339     mov cl, ah                            ; FAT的扇区从0开始,软盘的扇区从1开始
340     add cl, 1                            ; cl记录要读取的数据相对柱面的扇区偏移,注意FAT扇区和软盘扇区的起始编号不同
341     mov ch, al                            ; 读取的数据起始柱面号
342     shr ch, 1                            ; 计算数据所在柱面偏移量;右移一位表示除以2(当前软盘只有上下2个磁头);shr移位大于1时需要借助cl寄存器;
343     mov dh, al                            ; 商,dh记录起始磁头
344     and dh, 1                            ; 如果逻辑柱面(磁道)号是偶数就在0磁头,否则就是1磁头(目前只有2个磁头)
345     mov dl, [BS_DrvNum]                    ; 设置驱动器号
346 
347     pop ax                                ; 还原要读取的扇区数,相当于原来的cx,因为cx是最后入栈的,这里是最先出栈
348     pop bx                                ; bx已设置为指向Buf
349 
350     mov ah, 0x02                        ; 0x02读扇区,磁头、驱动器号、柱面(磁道)、扇区-->dh、dl、ch、cl;长度、Buf-->ax、bx
351 
352 read:
353     int 0x13                            ; 读取磁盘的中断
354     jc read                                ; 若进位位(CF)被置位,表示调用失败,需要重新读取
355 
356     ;pop ax
357     ;pop dx
358     ;pop cx
359     ;pop bx
360 
361     ret
362 
363 
364 ; ##############################################################################
365 MsgStr db "No LOADER ..."                ; 定义字符串
366 MsgLen equ ($-MsgStr)                    ; 定义上面字符串长度标号
367 
368 Target db "LOADER     "                    ; 要查栈的文件名
369 TarLen equ ($-Target)                    ; 要查找的文件名长度
370 
371 EntryItem times EntryItemLength db 0x00    ; 目录项空间,且用0填充根目录区,32字节
372 
373 Buf:                                    ; Buf空间,数据的读取和写入空间;下面两行代码是为mbr准备的
374     times 510-($-$$) db 0x00            ; 用0填充mbr中的剩余部分(注意留出0x55aa两字节空间)
375     db 0x55, 0xaa                        ; mbr的最后两个字节用0x55AA结尾
 1 // loader.asm
 2 org 0x9000
 3 
 4 begin:
 5     mov si, msg
 6 
 7 print:
 8     mov al, [si]
 9     add si, 1
10     cmp al, 0x00
11     je end
12     mov ah, 0x0E
13     mov bx, 0x0F
14     int 0x10
15     jmp print
16 
17 end:
18     hlt
19     jmp end
20 
21 msg:
22     db 0x0a, 0x0a
23     db "Hello, D.T.OS!"
24     db 0x0a, 0x0a
25     db 0x00 

输出

    找到文件(loader)正常输出(Hello,D.T.OS!)
    
 
    找不到文件(LOADER    A)的错误输出(No LOADER ...)
    
    找到文件(LOADER     )后的输出(输出文件内容,由于文件我写了中文注释所以有乱码)
    

猜你喜欢

转载自www.cnblogs.com/Dua677/p/9160689.html