本文最后更新于 235 天前,其中的信息可能已经有所发展或是发生改变。
内容目录
实验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