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 )后的输出(输出文件内容,由于文件我写了中文注释所以有乱码)