x86/x64体系探索及编程
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第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 映像文件。