文章系列:
用qemu模擬Intel x86平臺實驗環境 —— 概述
用qemu模擬Intel x86平臺實驗環境 —— 啓動系統
用qemu模擬Intel x86平臺實驗環境 —— 加載並運行app
本章目標
- 我們的大目標是製作一個光盤,該光盤實現兩個功能:
- 存放引導代碼,用來加載應用程序到內存,並跳轉到應用程序處執行程序
- 存放應用程序,這個引用程序是作爲一個文件放到光盤中
- 爲了實現這個目標,本章作兩個預備工作:
- 製作一個光盤並格式化成文件系統,可以存放應用程序,我們選取FAT12格式的文件系統
- 在格式化成文件系統後的光盤的第一個扇區中,寫入引導代碼,這段引導代碼將用作加載應用程序到內存,本章暫時不作,只實現點亮屏幕的功能。
實現原理
文件存放
- 要存放文件,通常做法是把存儲介質格式化成操作系統可以識別的文件系統,比如windows的
FAT12/16/32,ExFAT,VFAT,NTFS
或者linux的ext2/3/4
等。我們採用同樣的做法,首先製作一個空的磁盤介質,然後將其格式化成FAT12文件系統,如下:
dd if=/dev/zero of=floopy bs=512 count=2880
losetup /dev/loop0 floopy
mkdosfs -F 12 /dev/loop0
losetup -d /dev/loop0
- 查看其內容,前512字節是一個引導扇區(Boot Sector),前62字節是BPB的結構,BPB數據結構的字段是windows文件系統和操作系統約定好的,用於描述磁盤的物理佈局,可以適用於FAT系列文件系統和NTFS文件系統。這裏我們看到最後的文件系統類型爲FAT12
- linux下的也有自己引導扇區,叫做MBR,引導扇區裏是自己設計的數據結構,比如分區表。
引導原理
- 光盤格式化之後,掛載光盤,就可以拷貝應用程序到文件系統了,但這個光盤只有存放文件的功能,還不能引導代碼,因此我們需要把引導代碼也拷貝到引導扇區中。這裏有兩種方法,一種是保留格式化時工具留下的BPB數據結構信息,只將引導代碼dd到62自己之後,另一種就是將讓引導代碼中包含BPB數據結構,dd引導代碼的時候從偏移0開始,直接覆蓋BPB數據結構。我們選用者。
- 上圖描述了FAT12文件系統引導扇區的數據結構。
具體實現
- BPB(BIOS Parameter Block)
# BS_jmpBoot 短跳轉指令
jmp label_init
nop
# BS_OEMName 廠商名字
.ascii "ForrestY"
# BS_BytsPerSec 每扇區字節數
.word 512
# BPB_SecPerClus 每簇扇區數
.byte 1
# BPB_RsvdSecCnt 引導記錄(MBR)佔用的扇區數
.word 1
# PBP_NumFATs FAT表數目
.byte 2
# PBP_RootEntCnt 根目錄文件最大數
.word 0xe0
# PBP_TotSec16 扇區總數
.word 2880
# PBP_Media 介質描述符
.byte 0xf0
# PBP_FATSz16 每FAT扇區數
.word 9
# PBP_SecPerTrk 每磁道扇區數
.word 18
# PBP_NumHeads 磁頭數
.word 2
# PBP_HiddSec 隱藏扇區數
.long 0
# PBP_TotSec32 如果PBP_TotSec16爲0,該域記錄扇區數
.long 0
# BS_DrvNum 中斷13的驅動器號
.byte 0
# BS_Reserved1 未使用
.byte 0
# BS_BootSig 擴展引導標記
.byte 0x29
# 卷序列號
.long 0
# 卷標
.ascii "VirtualBoot"
# 文件系統類型
.ascii "FAT12 "
- 引導代碼,點亮屏幕
label_init:
movw $0x7c0, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
movw $0x180, %sp
call DispStr
loop:
jmp loop
DispStr:
movw $BootMsg, %ax
movw %ax, %bp
movw $16, %cx
movw $0x1301, %ax
movw $0xc, %bx
movb $0, %dl
int $0x10
ret
BootMsg:
.ascii "Hello, OS World!"
.org 510
.word 0xAA55
實驗結果
引導固件
引導固件包含了BPB數據結構和引導代碼兩部分,名爲boot.bin
- 寫一個run.sh的腳本,方便測試:
[root@hy b]# cat run.sh
#!/bin/bash
DEBUG="false"
PWD="$(cd `dirname $0`;pwd)"
[ $# -eq 1 ] && [ "X$1" == "X-h" ] && echo "$(basename $0) debug --for debug" && exit 0
[ $# -eq 1 ] && [ "X$1" == "Xdebug" ] && DEBUG="true"
if [ "X$DEBUG" == "Xtrue" ]; then
echo "waiting for connect gdb server..."
qemu-system-x86_64 -machine pc-i440fx-4.0 -m 2G -smp 2,sockets=2,cores=1,threads=1 \
-boot strict=on -drive file=$PWD/a.img,format=raw,if=none,id=drive-fdc0-0-0 \
-global isa-fdc.driveA=drive-fdc0-0-0 -global isa-fdc.bootindexA=1 -S -s
else
qemu-system-x86_64 -machine pc-i440fx-4.0 -m 2G -smp 2,sockets=2,cores=1,threads=1 \
-boot strict=on -drive file=$PWD/a.img,format=raw,if=none,id=drive-fdc0-0-0 \
-global isa-fdc.driveA=drive-fdc0-0-0 -global isa-fdc.bootindexA=1
fi
- make後,運行
run.sh
- 運行
run.sh debug
,qemu啓動後暫停,等待gdb連接
- 連接gdbserver,可以在0x7c00處斷住,查看寄存器信息等
工具格式化和固件格式化對比
- 拷貝之前,使用mkdofs工具已經將a.img格式化成了FAT12文件系統。可以看到前62字節的BPB數據結構。
- dd命令寫入固件後,格式化引導扇區
dd if=boot.bin ibs=512 skip=4096 of=a.img obs=512 seek=0 count=1 conv=notrunc
後,可以看到BPB結構的字段有了變化,說明BPB信息被覆蓋了,並且BPB之後多了數據,這是引導代碼。
- 拷貝測試文件test.txt,可以看到,重新被覆蓋的BPB數據結構,仍然可以被操作系統識別,作爲文件系統。擁有正常的文件保存功能。
下一步工作
-
根據根目錄表項和FAT表找到特定文件名的文件
分析test.txt文件的位置: 引導扇區中定義了根目錄條目最大數0xe0=224,每個根目錄條目大小32 byte,根目錄所佔扇區數
RootDirSectors = ((PBP_RootEntCnt * 32) + (BS_BytsPerSec - 1)) / BS_BytsPerSec = (224 * 32) + 511 / 512 = 14。
FAT12文件系統佈局如上圖,可以看到引導扇區+ FAT1 + FAT2一共佔用了1+9+9 = 19個扇區,根目錄佔了14個扇區,因此數據區從第19+14=33個扇區開始,開始於第33個扇區,偏移33 * 512 = 0x4200。注意數據區的第一個簇的號是2,不是0或者1。
從根目錄表項中看到,test.txt的文件內容從簇號3開始,對應數據區的第二個簇號,偏移0x4400。可以對上。
再看FAT1表的位圖F0 FF FF 00 F0 FF
,它表示文件的下一個簇號。
前三個字節是簇號0和簇號1的位圖,忽略entry_2 = 0x000 // 簇號2中沒有文件
entry_3 = 0xFFF // 簇號3中文件的下一個簇號是0xFFF,這是特殊簇號,表示這個簇是文件的最後一個簇。
現在,用xxd查看鏡像文件,根據FAT12文件系統規範,可以通過根目錄的表項和FAT表找到文件在鏡像中的偏移和佔用簇的數目,下一章,介紹怎麼在boot.bin的代碼中找到這個test.txt的文件。
完整代碼見 my github