本文最后更新于 365 天前,其中的信息可能已经有所发展或是发生改变。
内容目录
实验1
代码写完之后,发现boot已经超出了512字节,怎么办呢?
代码重构:删除部分的push和pop代码,这是为了保证不会影响关键寄存器的值。
代码调整后,再次make通过;打印加载出来的文件内容。
code
org 0x7c00 ;补上三个字节 jmp short start nop ;栈的起始地址(定义栈空间) define: BaseOfStack equ 0x7c00 BaseOfLoader equ 0x9000 ;最后要把目标程序加载到这个地址处,fat表加载到这个地址的前面 RootEntryOffset equ 19 ;根目录区的逻辑扇区地址,是从逻辑第19扇区开始的 RootEntryLength equ 14 ;目录文件项占用了14个扇区 EntryItemLength equ 32 FatEntryOffset equ 1 FatEntryLength equ 9 header: BS_OEMName db "D.T.Soft" BPB_BytsPerSec dw 512 BPB_SecPerClus db 1 BPB_RsvdSecCnt dw 1 BPB_NumFATs db 2 BPB_RootEntCnt dw 224 BPB_TotSec16 dw 2880 BPB_Media db 0xF0 BPB_FATSz16 dw 9 BPB_SecPerTrk dw 18 BPB_NumHeads dw 2 BPB_HiddSec dd 0 BPB_TotSec32 dd 0 BS_DrvNum db 0 BS_Reserved1 db 0 BS_BootSig db 0x29 BS_VolID dd 0 BS_VolLab db "D.T.OS-0.01" BS_FileSysType db "FAT12 " ;函数功能:函数入口 start: mov ax, cs mov ss, ax mov ds, ax mov es, ax mov sp, BaseOfStack mov ax, RootEntryOffset mov cx, RootEntryLength mov bx, Buf call ReadSector mov si, Target mov cx, TarLen mov dx, 0 call FindEntry cmp dx, 0 jz output ;########备份第一个目录项######## mov si, bx ;对应目录项的起始地址 mov di, EntryItem ;在拷贝之后,EntryItem是目录项入口地址 mov cx, EntryItemLength call MemCpy ;########加载fat表,计算fat表占用的内存######## mov ax, FatEntryLength ;扇区长度 mov cx, [BPB_BytsPerSec];每个扇区的大小 mul cx ;ax中就保存了fat表占用的内存 mov bx, BaseOfLoader ;0x9000 -> fat表加载到0x9000地址的前面 sub bx, ax ;减去fat表在内存中占的字节数,bx中存放的是fat表起始位置 mov ax, FatEntryOffset mov cx, FatEntryLength ; ax --> logic sector number ; cx --> number of sector ; es:bx --> target address call ReadSector ;fat表就在内存当中了 ;---------------------------------------------------- ;########查表 cx对应的表项######## mov dx, [EntryItem + 0x1A] ;cx表示起始簇的值。注:0x1A是 DIR_FstClus目标项的偏移位置 mov si, BaseOfLoader loading: mov ax, dx add ax, 31 mov cx, 1 ;只读取一个扇区 push dx push bx mov bx, si call ReadSector pop bx pop cx call FatVec cmp dx, 0xFF7 jnb output ;BaseOfLoader add si, 512 jmp loading ;---------------------------------------------------- output: ;mov bp, MsgStr ;mov cx, MsgLen mov bp, BaseOfLoader mov cx, [EntryItem + 0x1C] call Print last: hlt jmp last ; cx --> index ; bx --> fat table address 在内存当中的起始位置(基址) ; ; return: ; dx --> fat[index] fat表项的值 FatVec: mov ax, cx mov cl, 2 div cl ; 判断是奇数还是偶数,进入不同的关系式处理流程,商al 余数ah push ax ;商合余数入栈 ; i = j / 2 * 3 mov ah, 0 ;index/2之后取正数,把这个余数置为0 mov cx, 3 mul cx mov cx, ax ;cx保存对应的表项在内存中的起始位置:i = index/2*3 pop ax cmp ah, 0 jz even jmp odd even: ; FatVec[j] = ( (Fat[i+1] & 0x0F) << 8 ) | Fat[i]; mov dx, cx ;Fat[i]中i add dx, 1 ;Fat[i+1]中的i+1 add dx, bx ;计算真正的 Fat[i+1] 的地址,为什么真正的地址这么计算? mov bp, dx mov dl, byte [bp] ;拿到Fat[i+1]的值 and dl, 0x0F shl dx, 8 add cx, bx ; cx是表项在内存当中的起始字节数,加上bx就是内存地址 mov bp, cx ; or dl, byte [bp] ;结果存在dl中,也就是dx的低八位 jmp return odd: ; FatVec[j+1] = (Fat[i+2] << 4) | ( (Fat[i+1] >> 4) & 0x0F ); mov dx, cx add dx, 2 add dx, bx mov bp, dx mov dl, byte [bp] mov dh, 0 ; 高8位清空,右移位可以保存值 shl dx, 4 add cx, 1 add cx, bx mov bp, cx mov cl, byte [bp] shr cl, 4 and cl, 0x0F mov ch, 0 ; 高8位清空,右移位可以保存值 or dx, cx return: ret ; ds:si --> source ; es:di --> destination ; cx --> length MemCpy: cmp si, di ;比较源内存和目标内存的大小关系 ja btoe add si, cx add di, cx dec si dec di jmp etob btoe: cmp cx, 0 jz done mov al, [si] mov byte [di], al inc si inc di dec cx jmp btoe etob: cmp cx, 0 jz done mov al, [si] mov byte [di], al dec si dec di dec cx jmp etob done: ret ; es:bx --> root entry offset address ; ds:si --> target string ; cx --> target length ; ; return: ; (dx != 0) ? exist : noexist ; exist --> bx is the target entry FindEntry: push cx mov dx, [BPB_RootEntCnt];根目录区的最大项,最大查找次数 mov bp, sp ;栈顶元素不能通过sp直接访问,这里间接访问 find: cmp dx, 0 jz noexist mov di, bx ;初始化的di指向根目录区的第0项 si di cx ; 'mov cx, [bp]' 指令的作用是将bp寄存器当前指向的栈内存地址中的值赋给cx寄存器。 ; 在这个上下文中,bp寄存器被用作一个间接的方式来访问栈顶的值,因为我们之前已经将sp(栈指针)的值赋给了bp。 ; 这里的关键操作是将cx寄存器之前压栈的值(即目标字符串的长度)重新加载到cx寄存器中。 ; 这一步是为了准备接下来的MemCmp函数调用,因为在MemCmp中,cx寄存器的值将被用作要比较的字节数。 ; 简而言之,这条指令的目的是恢复cx寄存器的原始值,以便能够正确地进行内存比较操作。 mov cx, [bp] push si call MemCmp pop si cmp cx, 0 jz exist add bx, 32 ;查找下一项,每一项占用32字节 dec dx jmp find exist: noexist: pop cx ret ; ds:si --> source ; es:di --> destination ; cx --> length ; ; return: ; (cx == 0) ? equal : noequal MemCmp: compare: cmp cx, 0 jz equal mov al, [si] cmp al, byte [di] jz goon jmp noequal goon: inc si inc di dec cx jmp compare equal: noequal: ret ; es:bp --> string address ; cx --> string length Print: mov dx, 0 ;把字符串打印都按屏幕的左上角 mov ax, 0x1301 mov bx, 0x0007 int 0x10 ret ; no parameter ; 函数功能:软驱复位 ResetFloppy: mov ah, 0x00 mov dl, [BS_DrvNum] int 0x13 ret ; ax --> logic sector number ; cx --> number of sector ; es:bx --> target address 这里是把数据读取到内存的位置 ;函数功能:读取软驱数据 ReadSector: call ResetFloppy push bx ;保护目标地址的值 push cx ;扇区长度入栈 ;逻辑扇区转换为磁盘上的具体位置(磁头号、柱面号、扇区号),于是需要做除法 mov bl, [BPB_SecPerTrk] ; BPB_SecPerTrk 每个柱面18个扇区 div bl ; =》 逻辑扇区号 / 柱面扇区数 =》ax / bl mov cl, ah add cl, 1 ; 余数+1 表示扇区号 mov ch, al shr ch, 1 ; 柱面号 mov dh, al and dh, 1 ;磁头号 mov dl, [BS_DrvNum] ; 驱动器号 pop ax ;扇区长度出栈,放到al里 pop bx mov ah, 0x02 ; 规定值 read: ;读取失败就继续读 int 0x13 jc read ret MsgStr db "No LOADER ..." MsgLen equ ($-MsgStr) Target db "LOADER " TarLen equ ($-Target) EntryItem times EntryItemLength db 0x00 Buf: times 510-($-$$) db 0x00 db 0x55, 0xaa
验证结果:
打印出了LODER里的内容
把虚拟软盘挂载到当前的Linux中去,看这个文件以及文件内容是否存在
把当前代码拷贝到这个文件当中。
当前文件内容,一个扇区无法存储,那就需要存储到多个扇区里去。
执行命令行:
delphi@delphi-vm:~/OS/06$ sudo mount -o loop data.img /mnt/hgfs/ [sudo] password for delphi: delphi@delphi-vm:~/OS/06$ sudo gedit /mnt/hgfs/loader delphi@delphi-vm:~/OS/06$ sudo umount /mnt/hgfs delphi@delphi-vm:~/OS/06$
再次运行看一下,意味着内容都被加载了。
实验2
在实验1的基础上,修改跳转地址到BaseOfLoader
;函数功能:函数入口 start: mov ax, cs mov ss, ax mov ds, ax mov es, ax mov sp, BaseOfStack mov ax, RootEntryOffset mov cx, RootEntryLength mov bx, Buf call ReadSector mov si, Target mov cx, TarLen mov dx, 0 call FindEntry cmp dx, 0 jz output ;########备份第一个目录项######## mov si, bx ;对应目录项的起始地址 mov di, EntryItem ;在拷贝之后,EntryItem是目录项入口地址 mov cx, EntryItemLength call MemCpy ;########加载fat表,计算fat表占用的内存######## mov ax, FatEntryLength ;扇区长度 mov cx, [BPB_BytsPerSec];每个扇区的大小 mul cx ;ax中就保存了fat表占用的内存 mov bx, BaseOfLoader ;0x9000 -> fat表加载到0x9000地址的前面 sub bx, ax ;减去fat表在内存中占的字节数,bx中存放的是fat表起始位置 mov ax, FatEntryOffset mov cx, FatEntryLength ; ax --> logic sector number ; cx --> number of sector ; es:bx --> target address call ReadSector ;fat表就在内存当中了 ;---------------------------------------------------- ;########查表 cx对应的表项######## mov dx, [EntryItem + 0x1A] ;cx表示起始簇的值。注:0x1A是 DIR_FstClus目标项的偏移位置 mov si, BaseOfLoader loading: mov ax, dx add ax, 31 mov cx, 1 ;只读取一个扇区 push dx push bx mov bx, si call ReadSector pop bx pop cx call FatVec cmp dx, 0xFF7 jnb BaseOfLoader add si, 512 jmp loading ;---------------------------------------------------- output: mov bp, MsgStr mov cx, MsgLen call Print last: hlt jmp last
loader.asm
新增loader.asm文件,再打印一个字符串
org 0x9000 begin: mov si, msg print: mov al, [si] add si, 1 cmp al, 0x00 je end mov ah, 0x0E mov bx, 0x0F int 0x10 jmp print end: hlt jmp end msg: db 0x0a, 0x0a db "Hello, D.T.OS!" db 0x0a, 0x0a db 0x00
loader中用的是je跳转,反编译中变成了jz,可见这两个没有区别。
把loader拷贝到软盘里去,运行从boot跳转到loader执行。命令行:
delphi@delphi-vm:~/OS/06$ sudo mount -o loop data.img /mnt/hgfs/ [sudo] password for delphi: delphi@delphi-vm:~/OS/06$ sudo cp loader /mnt/hgfs/ delphi@delphi-vm:~/OS/06$ sudo umount /mnt/hgfs/ delphi@delphi-vm:~/OS/06$ delphi@delphi-vm:~/OS/06$
再次运行,则已发生跳转执行了
把软盘data.img放到虚拟机里运行看现象, 完美。
maikefile修改
.PHONY : all clean rebuild BOOT_SRC := boot.asm BOOT_OUT := boot LOADER_SRC := loader.asm LOADER_OUT := loader IMG := data.img IMG_PATH := /mnt/hgfs RM := rm -fr all : $(IMG) $(BOOT_OUT) $(LOADER_OUT) @echo "Build Success ==> D.T.OS!" $(IMG) : bximage $@ -q -fd -size=1.44 $(BOOT_OUT) : $(BOOT_SRC) nasm $^ -o $@ dd if=$@ of=$(IMG) bs=512 count=1 conv=notrunc $(LOADER_OUT) : $(LOADER_SRC) nasm $^ -o $@ sudo mount -o loop $(IMG) $(IMG_PATH) sudo cp $@ $(IMG_PATH)/$@ sudo umount $(IMG_PATH) clean : $(RM) $(IMG) $(BOOT_OUT) $(LOADER_OUT) rebuild : @$(MAKE) clean @$(MAKE) all