書名: x86/x64體系探索及編程作者名: 鄧志著本章字數: 1050字更新時間: 2019-03-01 11:49:35
第3章 編寫本書的實驗例子
在前一章里對x86/x64平臺的匯編語言作了簡要的介紹,在這一章里,我們將為本書的實驗例子建立一個可運行的實驗平臺。
本書的所有例子都是基于祼機(無任何操作系統)運行,因此我們有兩種選擇。
① 編寫自己的MBR代碼放在0扇區里,由BIOS讀取MBR加載到0x7c00,在后面由這個MBR讀取磁盤中的扇區。
② 借助其他的文件格式,例如:使用FAT32文件格式,生成一個FAT32格式的可啟動盤。我們需要編寫自己的活動分區引導記錄。
本書的所有例子中的硬盤映像文件和U盤都使用FAT32文件格式,有比較通用的好處,在絕大多數機器上都可以讀取運行。
3.1 實驗的運行環境
本書的實驗例子可以運行在三種環境里。
① Bochs模擬器:任何支持x64的版本都可以,筆者使用的是目前最新的2.5.1版本。
② VMware虛擬機:使用較新的7版本或8版本。
③ 在真實機器上運行。
筆者的機器是Intel Westmere微架構的Core i5移動處理器筆記本,是SandyBridge架構的上一代i5處理器。本書很多實驗例子只能在真實機器上運行,Bochs和VMware并不支持那些功能。
問題1:使用軟盤還是U盤?甚至是硬盤?
在Bochs里可以使用軟盤或硬盤的映像(image)文件,在真實機器上我們可以使用U盤作為介質啟動計算機運行實驗程序。本書例子的運行使用以下三種介質。
① 軟盤映像:統一使用文件名為demo.img,可以在Bochs或VMware里運行。
② 硬盤映像:統一使用文件名為c.img,可以在Bochs里運行。
③ U盤:將生成的硬盤映像直接寫入U盤,在真實機器上運行。
因此,本書的例子中最終生成兩個映像文件:demo.img文件用于在Bochs或VMware里運行;c.img文件用于在Bochs或真實機器上運行。
使用FAT32文件格式
在使用軟盤啟動時,我們直接將boot代碼寫在MBR里。而當使用U盤啟動真實機器時會遇到一些麻煩,我們的代碼寫在MBR里,某些機器將啟動不了。
因此,當使用U盤在真實機器上測試時,將使用FAT32文件格式,也就是硬盤映像c.img使用FAT32文件格式。
如下所示,對于軟盤映像(文件名為demo.img)和硬盤映像(文件名為c.img),有兩種文件組織方式。
① 軟盤映像文件(demo.img):將boot模塊直接寫入軟盤的0扇區,那么boot代碼就是我們的MBR程序,BIOS將讀軟盤的0扇區(我們的boot代碼)到內存的7c00h位置上。
② 硬盤映像文件(c.img)和U盤:將boot模塊寫入映像文件和U盤的63號扇區,BIOS讀取硬盤或U盤的0扇區(這是FAT32文件格式啟動盤生成的MBR代碼)代碼到內存7c00h,然后再由這個MBR代碼來從63號扇區讀我們的boot程序。
無論如何,我們的boot程序最終是運行在7c00h區域里,然后我們的boot程序的職責是負責加載后續的模式(包括lib16、lib32、lib64、protected和long模式)。
3.2 生成空白的映像文件
現在我們需要生成兩個空白的映像文件:demo.img和c.img(硬盤),以便編譯后文件寫入映像文件中。在以后實驗里,我們既可以使用demo.img也可以使用c.img來運行實驗例子。
demo.img是軟盤映像文件,它的大小為1.44MB;c.img是硬盤映像文件,它的大小為1MB(對于本書的代碼來說足夠了)。
當然,如果您不需要使用c.img或U盤進行測試,只生成demo.img也足夠了(只是不能在真實機器上運行,除非使用軟盤來啟動機器)。
生成空白映像文件的方法有很多,可以使用任何一個十六進制編輯軟件生成,也可以使用類似WinISO軟件來生成。本書介紹使用nasm及Bochs自帶的bximage工具來生成。
3.2.1 使用nasm編譯器生成
不用覺得奇怪,確實可以用nasm來生成一個空白的映像文件。
實驗3-1:在真實機器上測試boot代碼
下面是示例源代碼。
代碼清單3-1(topic03\ex3-1\demo.asm):
;demo.asm ; Copyright (c) 2009-2012 mik ; All rights reserved. ; 建立一個 1.44MB 的 floppy 映像文件,名為:demo.img ; ; 生成命令:nasm demo.asm -o demo.img ; ; 用 0 填滿 1.44MB floppy 的空間 times 0x168000-($-$$) db 0
這個代碼中讓nasm生成0來填滿floppy的1.44MB空間,命令如下。
nasm demo.asm –o demo.img
經過nasm編譯后生成一個bin格式的文件,命名為demo.img,那么這個demo.img就可以作為我們所需要的空白軟盤映像文件。
3.2.2 使用bximage工具
使用Bochs自帶的disk建立工具bximage是最簡單的方法,基本上敲幾個回車鍵就
可以了,既可以用來生成軟盤映像,也可以用來生成硬盤映像文件。
E:\x86\source\topic03\ex3-1>bximage =================================================================== bximage Disk Image Creation Tool for Bochs $Id:bximage.c,v 1.34 2009/04/14 09:45:22 sshwarts Exp $ =================================================================== Do you want to create a floppy disk image or a hard disk image? Please type hd or fd. [hd] fd
在這一步里選擇生成軟盤還是硬盤映像,輸入fd生成軟盤映像文件。
Choose the size of floppy disk image to create,in megabytes. Please type 0.16,0.18,0.32,0.36,0.72,1.2,1.44,1.68,1.72,or [1.44] I will create a floppy image with cyl=80 heads=2 sectors per track=18 total sectors=2880 total bytes=1474560
這里顯示了軟盤的大小,默認為1.44MB,輸入回車鍵。
What should I name the image? [a.img] Writing:[] Done. I wrote 1474560 bytes to a.img. The following line should appear in your Bochsrc: floppya:image="a.img",status=inserted (The line is stored in your windows clipboard,use CTRL-V to paste) Press any key to continue
接著輸入文件名,默認為a.img,這里我們輸入demo.img。
然后我們使用bximage工具,用同樣的步驟來生成一個1MB大小的c.img文件(硬盤映像文件)。那么當前的目錄下就同時有了demo.img和c.img兩個文件。我們的硬盤映像文件只需要1MB就足夠了。
使用bximage的好處是可以生成一條Bochs配置行,直接粘貼插入到Bochs配置文件中,保存后就Bochs可以使用軟盤映像或硬盤映像了。
3.3 設置Bochs配置文件
我們可以在Bochs自帶示例配置文件bochsrc-sample.txt上進行修改(不建議這樣做),也可以復制為新的文件(文件名為bs),再進行更改,下面是關鍵的地方。
floppya:1_44=demo.img,status=inserted ata0-master:type=disk,path="c.img",mode=flat,cylinders=2,heads=16,spt=63
上一行是軟盤的配置,使用demo.img作為軟盤映像文件,下一行是硬盤的配置,使用c.img作為硬盤映像文件。
#boot:floppy boot:disk
配置啟動項,可以選擇floppy或disk(用于硬盤映像)。在這里,筆者選擇使用硬盤映像作為啟動介質,那么在以后的實驗里都統一在Bochs里使用硬盤映像。
而在VMware里統一使用floppy映像,這樣做的好處是,既測試了floppy,也測試了硬盤(為使用U盤作測試)。
在稍后,我們將看到如何將c.img文件寫入U盤在真實機器上測試。
3.4 源代碼的基本結構
本書的源代碼按照章來組織,每章的所有例子在統一的目錄下,例如:topic18\目錄下存放有第18章的所有例子。
每個topic目錄下有數個實驗例子,每個例子組織為一個子目錄,如:topic18\ex18-1\目錄下存放著第18章的實驗例子1的源代碼。
整個x86\source\目錄下的文件組織如下所示。
x86\source\目錄是所有源碼的根目錄,下面有以下內容。
① inc\目錄:定義所有源碼的支持頭文件,典型的有lib.inc、support.inc、protected.inc等文件。它們定義了一些源碼使用的常量和宏。
② lib\目錄:下面是所有庫函數的實現代碼,典型的有lib16.asm、lib32.asm、lib64.asm、apic.asm、debug.asm等文件,所有的例子都要使用這些庫文件。
③ common\目錄:下面有所有實驗例子的共用代碼,典型的有boot.asm、setup.asm、long.asm、handler32.asm等文件。
④ topic01\到topicXX\目錄:每個目錄代表一章。每個topic目錄下有數個子目錄,各代表一個實驗例子。
它們的下一層目錄里,例如:ex1-1\是第1章里的實驗1的源代碼,這些目錄下面典型的有boot.asm,setup.asm、protected.asm、long.asm等文件。
注意:在這些子目錄下(與實驗源碼在同一目錄)還有其他重要的文件。
① demo.img:軟盤映像文件。
② c.img:硬盤映像文件。
③ bs:Bochs配置文件。
④ config.txt:merge工具的配置文件(后面將會了解到)。
3.5 編譯源代碼
所有的源代碼都是使用nasm編譯生成的,如果整個x86\source\根目錄在e:盤下,則編譯topic03\ex3-2\setup.asm源文件所使用的nasm命令行如下所示。
當前工作目錄進入到x86\source\topic03\里,需要為nasm提供一個include目錄(也就是源碼的根目錄,使用-I<XXX>參數),最后目標文件需要指明在哪個目錄下。
默認情況下,生成一個setup二進制文件放在topic03\ex3-2\目錄下。你可以為它指定一個輸出文件名。
e:\x86\source\topic03>nasm –I..\ ex3-2\setup.asm –o setup.bin
使用-o參數,提供一個輸出文件名。這個輸出文件也可以指定輸出目錄。
3.6 映像文件內的組織
在將目錄下的源文件編譯生成bin文件后,需要將這些bin文件寫入demo.img(軟盤映像文件)或者c.img(硬盤映像文件)中,這些bin文件在映像文件中如何組織呢?
如下所示,軟盤映像文件(demo.img)分為五大部分:boot模塊,setup模塊,protected模塊,long模塊和庫代碼模塊。
① boot模塊:放在0號扇區。
② setup模塊:從1號扇區開始。
③ lib16模塊:從20號扇區開始。
④ protected模塊:從64號扇區開始。
⑤ long模塊:從128號扇區開始。
⑥ lib32模塊:從192號扇區開始。
硬盤映像文件(c.img)與軟盤映像文件的不同是:boot模塊放在63號扇區里,0號扇區是FAT32文件格式的MBR代碼。
3.7 使用merge工具
將nasm編譯后生成的所有bin格式文件按前面所述結構分別寫入軟盤映像文件(demo.img)和硬盤映像文件(c.img)中,方法有很多。
① 最原始和麻煩的方法是:使用hex編輯軟件逐個將bin文件寫入demo.img或c.img中。
② 使用dd工具:這個免費的工具可以進行磁盤/文件的復制/寫入/合并等操作。
dd工具的使用示例如下。
dd if=boot of=demo.img count=1
if提供輸入文件,of提供輸出文件,count提供數量,以block為單位(512字節),作用是將boot寫入demo.img的0開始處,即軟盤磁盤映像的0扇區處。
dd if=boot of=demo.img count=1 seek=1
上面這個命令是將boot模塊寫入到demo.img的1扇區,使用seek參數跳過1個block。
編寫merge工具
然而,這些都不是好方法,另一個方法是使用筆者為本書的實驗例子編寫的merge工具。merge是一個合并寫入映像工具,類似于dd操作,可批量寫入文件。merge工具將讀取當前目錄下的配置文件config.txt來做相應的寫入工作。
merge工具和dd工具可以在x86\tools\目錄找到,筆者使用的是Windows 7平臺。如果系統平臺上提示缺少某些DLL文件,請使用筆者提供的merge工具源碼,自行使用VC來編譯生成merge工具。
3.7.1 merge的配置文件
merge工具需要在當前目錄提供一個配置文件,文件名必須為config.txt
e:\x86\source>merge Error:cannot open the config file:config.txt (系統找不到指定的文件)
當無config.txt文件時,執行merge命令會出現上面的錯誤提示。
下面是config.txt文件的配置示例。
# 輸入文件,輸入文件 offset,輸出文件,輸出文件 offset,寫入 block 數( 1 block=512 bytes) # **** 每一項用逗號分隔 **** # # example: # #模塊名 offset 輸出文件名 offset count(1 count=512 bytes) #------------------------------------------------- # boot, 0,demo.img,0,1 # setup, 0,demo.img,1,2 # init, 0,demo.img,3,5 # # 意思是: # boot 模塊從 block 0 開始寫入 demo.img 寫入位置為 block 0,寫入 1 個 block # setup 模塊從 block 0 開始寫入 demo.img 寫入位置為 block 1,寫入 2 個 block # init 模塊從 block 0 開始寫入 demo.img 寫入位置為 block 3,寫入 5 個 block # 下面是第2章中使用到的配置實例: boot,0,demo.img,0,1
#是注釋,注意要單獨在一行里注釋,每一行對應一條記錄(共可容納20條記錄),每條記錄指示merge怎樣寫入到目標文件里。每條記錄有5個域。
① 需要寫入的模塊名(源文件)。
② 源文件的偏移扇區。
③ 寫入的目標文件名。
④ 寫入的目標起始扇區位置。
⑤ 寫入扇區的數量。
3.7.2 執行merge命令
我們執行merge命令將輸出下面的信息。
e:\x86\source\topic02\ex2-2>merge entry #0: uboot ---> c.img: success entry #1: setup ---> c.img: success entry #2: e:\x86\source\lib\lib16 ---> c.img: success entry #3: boot ---> demo.img: success entry #4: setup ---> demo.img: success entry #5: e:\x86\source\lib\lib16 ---> demo.img: success
上面的信息表明:有6條信息已成功寫入到目標文件,merge工具已經同時寫入到c.img和demo.img文件里。若輸出下面的信息
e:\x86\source\topic02\ex2-2>merge entry #0: uboot ---> c.img: success entry #1: setup ---> c.img: success entry #2: e:\x86\source\lib\lib16 ---> c.img: success fatal:open the file for read (系統找不到指定的文件)
上面的出錯信息表明:在寫入第3條記錄時,其中一個文件(源文件或目標文件)不存在。但并不影響前面已經寫入的記錄
3.8 使用U盤啟動真實機器
把所有源碼生成的bin格式文件寫入到硬盤映像文件c.img中后,我們就可以直接把c.img文件寫入到U盤。
3.8.1 使用merge工具寫U盤
merge工具也可以寫入U盤,只要在config.txt配置文件里增加一條寫U盤的記錄即可。
######## 下面是寫入 u 盤 ####### c.img,0,\\.\g:,0,200
這條記錄的源文件是c.img,目標文件是\\.\g:(這個文件是U盤的符號鏈接),寫入200個扇區(這里的扇區數只要滿足所有的源碼大小就可以了,并不需要寫入1MB大小)。
查詢U盤的掛接點
要寫入U盤,我們必須先知道U盤的文件名(注意:在你的機器上可能會是不同的文件名)。
記錄下來:可以使用U盤對應的卷名、設備名或者掛接名作為寫入對象。
使用dd工具可以查找磁盤設備的卷名、設備名以及符號鏈接名,如下所示。
E:\x86\source\topic02>dd --list rawwrite dd for windows version 0.6beta3. Written by John Newbigin <jn@it.swin.edu.au> This program is covered by terms of the GPL Version 2. Win32 Available Volume Information \\.\Volume{99bbfcb8-fc50-11e0-ae9d-88ae1d4bdccc}\ link to \\?\Device\HarddiskVolume20 removeable media Mounted on \\.\g:
上面執行了dd–list命令,我們看到U盤掛接在\\.\g:上,我們可以把這個掛接點作為U盤文件名。
當向config.txt增加一條寫\\.\g:文件的記錄,執行merge命令將輸出下面的信息。
e:\x86\source\topic02\ex2-2>merge entry #0: uboot ---> c.img: success entry #1: setup ---> c.img: success entry #2: e:\x86\source\lib\lib16 ---> c.img: success entry #3: boot ---> demo.img: success entry #4: setup ---> demo.img: success entry #5: e:\x86\source\lib\lib16 ---> demo.img: success entry #6: c.img ---> \\.\g:: success
留意最后一條記錄#6,c.img文件已經成功寫入\\.\g:(U盤)文件里。
3.8.2 使用hex編輯軟件寫U盤
在config.txt配置文件無誤時,寫入一般都會成功(除了文件不存在外,我沒失敗過)。如果merge工具寫U盤失敗,你可以使用傳統的方法,使用任何一個hex軟件(十六進制編輯軟件)來寫U盤。
這里推薦一個比較好用輕量級的免費hex編輯軟件HxD,HxD運行需要使用管理員權限,在HxD里打開c.img源文件和U盤,將c.img文件使用覆蓋寫入到U盤。使用十六進制編輯軟件總能獲得成功,即使用系統不能識別磁盤的情況下。
但是請小心使用這項功能,當用寫模式打開disk時,務必看清楚是不是自己要寫的U盤,以防誤寫了硬盤。
實驗3-2:在真實機器上測試boot代碼
現在在真實的機器上測試我們的代碼,用上述的方法將boot模塊和setup模塊寫入U盤,使用U盤啟動機器運行。boot和setup模塊將在稍后講述。
為了支持讀取U盤,編譯boot.asm的時候請使用以下命令。
nasm –Ie:\x86\source ex3-2\boot.asm –d UBOOT –o ex3-2\uboot
編譯時使用–d參數定義一個符號UBOOT(這是很重要的),這個符號用來編譯生成讀0x80的drive號。
是的,您無須在意是U盤還是硬盤,使用硬盤形式讀取U盤總能成功。大多數情況下U盤以USB-HDD形式工作。這是U盤模擬硬盤形式。如果不是,請留意BIOS檢測出來的是USB-FDD還是USB-ZIP,然后在BIOS啟動選項中做相應的設置。
在真實機器上的運行結果如下所示。
上面顯示的信息“the message from setup module at sector 20…”是來自setup模塊,寫在U盤的第20號扇區,用來演示讀取U盤扇區功能。
3.9 編寫boot代碼
完整的boot.asm代碼和setup.asm代碼在topic03\ex3-2\目錄下,下面是boot的主體代碼。
代碼清單3-2(topic03\ex3-2\boot.asm):
; boot.asm ; Copyright (c) 2009-2012 mik ; All rights reserved. ; ; 編譯命令是:nasm boot.asm -o boot (用軟盤啟動) ; nasm boot.asm –o boot –d UBOOT (用U盤啟動) ; 生成 boot 模塊后,寫入 demo.img(磁盤映像)的第 0 扇區(MBR) %include "..\inc\support.inc" %include "..\inc\ports.inc" bits 16 ;-------------------------------------- ; now,the processor is real mode ;-------------------------------------- ; Int 19h 加載 sector 0 (MBR) 進入 BOOT_SEG 段,BOOT_SEG 定義為 0x7c00 org BOOT_SEG start: cli ; enable a20 line FAST_A20_ENABLE sti ; set BOOT_SEG environment mov ax,cs mov ds,ax mov ss,ax mov es,ax mov sp,BOOT_SEG ; 設 stack 底為 BOOT_SEG call clear_screen mov si,hello call print_message mov si,20 ; setup 模塊在第20號扇區里 mov di,SETUP_SEG - 2 call load_module ; 使用 load_module() 讀多個扇區 mov si,SETUP_SEG call print_message mov si,word [load_message_table + eax * 2] call print_message next: jmp $
boot代碼的主要功能是顯示信息和從磁盤中讀取扇區到內存中。我們主要關注如何讀取磁盤。
問題:使用CHS方式還是LBA方式?使用int 13h/ah=02h還是int 13h/ah=42h擴展功能形式讀取磁盤?
在本書中,所有例子使用的都是LBA方式,LBA形式方便較直觀地反映出模塊在映像或磁盤的位置,在前面寫入U盤的實驗中就是以LBA方式寫入。
代碼清單3-3(topic03\ex3-2\boot.asm):
;---------------------------------------------------------------------- ; read_sector(int sector,char *buf):read one floppy sector(LBA mode) ; input: ; esi - sector ; di - buf ;---------------------------------------------------------------------- read_sector: pusha push es push ds pop es ; 測試是否支持 int 13h 擴展功能 call check_int13h_extension test ax,ax jz do_read_sector_extension ; 支持 mov bx,di ; data buffer mov ax,si ; disk sector number ; now:LBA mode --> CHS mode call LBA_to_CHS ; now:read sector %ifdef UBOOT mov dl,0x80 ; for U 盤或者硬盤 %else mov dl,0 ; for floppy %endif mov ax,0x201 int 0x13 setc al ; 0:success 1:failure jmp do_read_sector_done ; 使用擴展功能讀扇區 do_read_sector_extension: call read_sector_extension mov al,0 do_read_sector_done: pop es popa movzx ax,al ret
上面的read_sector()函數負責讀入1個扇區,輸入參數接受的是LBA方式,同時應該使用int 13h的擴展功能號來進行讀/寫,不但是因為它能支持更寬的讀/寫范圍,而且還因為為它使用更簡單。它的工作邏輯如下所示。
測試BIOS是否支持擴展的int 13h功能,不支持的話使用原來的int 13h功能擴展。
3.9.1 LBA轉換為CHS
盡管代碼清單3-3中的read_sector()輸入的是讀取的LBA扇區號,但仍然可以使用舊的int 13h/ah=02來讀磁盤。這就需要將LBA尋址轉換為CHS尋址。轉換的工作交由LBA_to_CHS()完成。
代碼清單3-4(topic03\ex3-2\boot.asm):
;------------------------------------------------------- ; LBA_to_CHS():LBA mode converting CHS mode for floppy ; input: ; ax - LBA sector ; output: ; ch - cylinder ; cl - sector (1-63) ; dh - head ;------------------------------------------------------- LBA_to_CHS: mov cl,SPT div cl ; al=LBA / SPT,ah=LBA % SPT ; cylinder=LBA / SPT / HPC mov ch,al shr ch,(HPC / 2) ; ch=cylinder ; head=(LBA / SPT ) % HPC mov dh,al and dh,1 ; dh=head ; sector=LBA % SPT + 1 mov cl,ah inc cl ; cl=sector ret
LBA_to_CHS()的結果是ch存放cylinder,cl存放sector,dh存放head。SPT和HPC定義在頭文件support.inc中,下面是support.inc中的部分代碼。
代碼清單3-5(inc\support.inc):
; SPT(sector per track):每磁道上的 sector 數 ; HPC(head per cylinder):每個 cylinder 的 head 數 ; 下面是 floppy 的參數 %define SPT 18 %define HPC 2
代碼清單3-4中的LBA_to_CHS()對于軟盤來說能很好并且高效地處理,可是它有一些漏洞。
cylinder的值應該包括cl的高2位和ch的8位,組合成共10位的cylinder值,最大是0x3FF,int 13h/ah=02h能訪問的最大cylinder數是1024個(0~1023)。
int 13h/ax=02h功能號中能最大處理的head是256個(0~255),最大處理的sector是64個(0~63)。
8G的限制:int 13h/ah=02h最大能讀取的范圍是1024×64×256×512=8G。
對于floppy來說不可能達到超過256個cylinder,所以代碼清單3-5中的LBA_to_CHS()可以很好地工作,現在的BIOS都支持int 13h的擴展功能,代碼清單3-3中的read_sector()實際讀磁盤是交由另一個函數read_sector_extension()去做(注:這里沿用了C函數的稱呼,在匯編中應該稱為過程)。
3.9.2 測試是否支持int 13h擴展功能
在使用int 13h擴展前應該先測試BIOS是否支持int 13h的擴展讀/寫功能,執行測試的是check_int13h_extension(),代碼如下。
代碼清單3-6(topic03\ex3-2\boot.asm):
;-------------------------------------------------------- ; check_int13h_extension():測試是否支持 int13h 擴展功能 ; ouput: ; 0 - support,1 - not support ;-------------------------------------------------------- check_int13h_extension: push bx mov bx,0x55aa mov ah,0x41 %ifdef UBOOT mov dl,0x80 ; for hard disk %endif int 0x13 setc al ; 失敗 jc do_check_int13h_extension_done cmp bx,0xaa55 setnz al ; 不支持 jnz do_check_int13h_extension_done test cx,1 setz al ; 不支持擴展功能號:AH=42h-44h,47h,48h do_check_int13h_extension_done: pop bx movzx ax,al ret
代碼清單3-6中的check_int13h_extension()使用int 13h/ah=41h測試是否支持int 13h擴展讀/寫,支持則返回0,不支持則返回1。
3.9.3 使用int 13h擴展讀磁盤
代碼清單3-7(topic03\ex3-2\boot.asm):
;-------------------------------------------------------------- ; read_sector_extension():使用擴展功能讀扇區 ; input: ; esi - sector ; di - buf (es:di) ;-------------------------------------------------------------- read_sector_extension: xor eax,eax push eax push esi ; 要讀的扇區號 (LBA) - 64 位值 push es push di ; buf 緩沖區 es:di - 32 位值 push word 0x01 ; 扇區數,word push word 0x10 ; 結構體 size,16 bytes mov ah,0x42 ; 擴展功能號 %ifdef UBOOT mov dl,0x80 %else mov dl,0 %endif mov si,sp ; 輸入結構體地址 int 0x13 add sp,0x10 ret
int 13h/ah=42h擴展功能需要一個disk address packet結構體地址作為參數,在讀之前應該要構造這樣一個結構體數據,這個結構體大致如下。
typedef struct Disk_Address_Packet { short packet_size; /* struct's size */ short sectors; /* 讀多少個 sector */ char *buffer; /* buffer address(segment:offset 形式)*/ long long start_sector; /* 從哪個 sector 開始讀 */ } DAP;
在read_sector_extension()代碼中分別壓入start_sector、buffer、sector,以及packet_size來構建一個結構體數據,結構體的大小是0x10(16個字節)。
代碼清單3-7中定義的drive號根據所定義的符號進行選擇,如果命令行如下。
nasm boot.asm –o boot –d UBOOT
編譯命令預定義了一個符號UBOOT,用來開啟為U盤或硬盤進行讀盤。
3.9.4 最后看看load_module()
最后需要介紹的是load_module(),它用來加載一個模塊。
代碼清單3-8(topic03\ex3-2\boot.asm):
;------------------------------------------------------------------- ; load_module(int module_sector,char *buf): 加載模塊到 buf 緩沖區 ; input: ; esi:module_sector 模塊的扇區 ; di:buf 緩沖區 ; example: ; load_module(SETUP_SEG,SETUP_SECTOR); ;------------------------------------------------------------------- load_module: call read_sector ; read_sector(sector,buf) test ax,ax jnz do_load_module_done mov cx,[di] ; 讀取模塊 size test cx,cx setz al jz do_load_module_done add cx,512 - 1 shr cx,9 ; 計算 block(sectors) do_load_module_loop: call dot dec cx jz do_load_module_done inc esi add di,0x200 call read_sector test ax,ax jz do_load_module_loop do_load_module_done: ret
load_module()用來加載多個扇區數據,在模塊頭部是2個字節的模塊size值。代碼中先讀出的size值,然后計算模塊占用多少sectors,再由read_sector()去讀取扇區數據。
實驗3-3:測試分別從floppy和hard disk啟動
分別為硬盤和軟盤讀取編譯兩個版本。
編譯硬盤啟動版本
使用下面的編譯參數(加上–dUBOOT)。
nasm –I..\ ex3-2\boot.asm –o ex3-2\uboot –d UBOOT
輸出文件名為uboot,放在ex3-2\目錄下。
編譯軟盤啟動版本
nasm –I..\ ex3-2\boot.asm
接著使用merge工具寫入demo.img和c.img映像文件中,demo.img是軟盤映像,而c.img是硬盤映像。當然,需要在config.txt配置文件中寫入相應的配置,然后執行merge命令。
entry #0: boot ---> demo.img: success entry #1: setup ---> demo.img: success entry #2: uboot ---> c.img: success entry #3: setup ---> c.img: success
我們在Bochs的配置文件里分別用floppy和hard disk映像進行啟動,然后測試它們的運行結果,以下是使用floppy映像啟動的結果。
而以下是使用硬盤映像啟動的結果,注意它們顯示的最后一行信息是不同的。
對比一下這里的運行結果和實驗3-2在真實機器上測試的結果是否一致。
是的,它們是一致的,U盤在使用USB-HDD模式時和硬盤的效果是一樣的。
3.10 總結
這一章里的所有源碼都在topic03\目錄下,有完全的實驗環境,包括以下內容。
- boot.asm源碼和編譯后的boot模塊。
- setup.asm源碼和編譯后的setup模塊。
- Bochs配置文件bs,Bochs的版本是2.5.1。
- merge工具的配置文件config.txt。
- demo.img映像文件(1.44MB軟盤)。
- c.img 映像文件。