官术网_书友最值得收藏!

第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大?。?/p>

查詢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 映像文件。
主站蜘蛛池模板: 客服| 喜德县| 松溪县| 苏州市| 凌海市| 绥中县| 吉水县| 平顺县| 阿坝县| 岳阳市| 垣曲县| 梁平县| 宁波市| 梁河县| 株洲县| 四子王旗| 赫章县| 朝阳市| 巢湖市| 达州市| 新晃| 松原市| 中牟县| 会理县| 库车县| 宣化县| 武义县| 漳浦县| 栖霞市| 铜山县| 开化县| 牡丹江市| 通江县| 嘉禾县| 蒙山县| 三穗县| 潍坊市| 津南区| 乳源| 正宁县| 神木县|