【OS】09 – 主引导程序控制权的转移【fat表加载loader并跳转执行,boot使命结束】
本文最后更新于 235 天前,其中的信息可能已经有所发展或是发生改变。
内容目录

 

参考:【OS】09 – 主引导程序控制权的转移

 

实验1

代码写完之后,发现boot已经超出了512字节,怎么办呢?

代码重构:删除部分的push和pop代码,这是为了保证不会影响关键寄存器的值。

file

 
代码调整后,再次make通过;打印加载出来的文件内容。

file

 
 

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里的内容

file

 

把虚拟软盘挂载到当前的Linux中去,看这个文件以及文件内容是否存在

file

 

把当前代码拷贝到这个文件当中。

file

 
当前文件内容,一个扇区无法存储,那就需要存储到多个扇区里去。

file

 
执行命令行:

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$ 

 

再次运行看一下,意味着内容都被加载了。

file

 
 

实验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,可见这两个没有区别。

file

 
把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$ 

 

再次运行,则已发生跳转执行了

file

 

把软盘data.img放到虚拟机里运行看现象, 完美。

file

 
 

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

 

file

 
 
 

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇