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