好了,我们现在已经知道,计算机的BIOS已经把引导扇区的512字节的内容读入到了0:0x7c00处,然后就跳转到0:0x7C00处去执行,也就是执行引导扇区代码,引导扇区代码boot0执行代码dump如下(它很有用,以后我们还不时回头来看):
上图中的4x16个字节是保留的4个分区信息下面,我们对FreeBSD启动扇区代码boot0.s进行逐步分析。boot0.s代码如下:
#
# Copyright (c) 1998 Robert Nordier
# All rights reserved.
#
# Redistribution and use in source and binary forms are freely
# permitted provided that the above copyright notice and this
# paragraph and the following disclaimer are duplicated in all
# such forms.
#
# This software is provided "AS IS" and without any express or
# implied warranties, including, without limitation, the implied
# warranties of merchantability and fitness for a particular
# purpose.
#
以上的Coyright就不用翻译了。 # $FreeBSD: src/sys/boot/i386/boot0/boot0.s,v 1.27 2003/11/20 20:28:18 jhb Exp $ 以上供版本管理软件使用
# A 512-byte boot manager.
.set NHRDRV,0x475 # Number of hard drives
.set ORIGIN,0x600 # Execution address
.set FAKE,0x800 # Partition entry
.set LOAD,0x7c00 # Load address
.set PRT_OFF,0x1be # Partition table
.set TBL0SZ,0x3 # Table 0 size
.set TBL1SZ,0xb # Table 1 size
.set MAGIC,0xaa55 # Magic: bootable
.set B0MAGIC,0xbb66 # Identification
.set KEY_ENTER,0x1c # Enter key scan code
.set KEY_F1,0x3b # F1 key scan code
.set KEY_1,0x02 # #1 key scan code
#
# Addresses in the sector of embedded data values.
# Accessed with negative offsets from the end of the relocated sector (%ebp).
#
.set _NXTDRV,-0x48 # Next drive
.set _OPT,-0x47 # Default option
.set _SETDRV,-0x46 # Drive to force
.set _FLAGS,-0x45 # Flags
.set _TICKS,-0x44 # Timeout ticks
.set _FAKE,0x0 # Fake partition entry
.set _MNUOPT,0xc # Menu options
以上是定义相关的参数值,例如“.set NHRDRV,0x475”类似于C语言中的“#define NHRDRV 0x475”
.globl start # Entry point
.code16 # This runs in real mode
#
# Initialise segments and registers to known values.
# segments start at 0.
# The stack is immediately below the address we were loaded to.
#
start:
cld # String ops inc
xorw %ax,%ax # Zero
movw %ax,%es # Address
movw %ax,%ds # data
movw %ax,%ss # Set up
movw $LOAD,%sp # stack
以上代码: 1)首先使用“cld”指令清除方向标志,使得以下的进行“rep”操作时SI和DI的值递增。 2)使ax清零,并使除代码段cs外的另外两个数据段寄存器es、ds和堆栈段ss清零。当然,此时cs由于reset或初始上电已经为零了。 3)BIOS已经把引导扇区的512字节的内容读入到了0:0x7c00处,movw $LOAD,%sp 使得堆栈指针指向扇区代码(或曰本段代码 0:0x7c00)的顶部。虽然堆栈向下生长可能会影响代码的内容,但下面我们马上就把位于0:7c00处代码移到其他地方去执行。
#
# Copy this code to the address it was linked for
#
movw %sp,%si # Source
movw $start,%di # Destination
movw {GetProperty(Content)}x100,%cx # Word count
rep # Relocate
movsw # code
把位于0:7c00处的代码搬移到0:0x600处。注意,此时由于代码连接的重定向,$start=0x600。
#
# Set address for variable space beyond code, and clear it.
# Notice that this is also used to point to the values embedded in the block,
# by using negative offsets.
movw %di,%bp # Address variables
movb {GetProperty(Content)}x8,%cl # Words to clear
rep # Zero
stosw # them
通过以上一段代码的执行,本代码已被搬移到0:0x600处,此时si=di=0x600+0x100,以上代码把di的值保存到bp,bp此时指向本程序搬移后的未用的空间的首部,且把此bp所指的16字节空间清零。以上过程如下图所示: ┏>0:0x600 ┏━━━━━┓ ┃ ┃ ┃ ┃ ┃ 搬 ┃ ┃ ┃ 移 ┃ ┃ ┃ 之 ┃ ┃ ┃ 后 ┃ ┃ ┃ 的 ┃ ┃ ┃ 代 ┃ ┃ ┃ 码 ┃ ┃ ┃ ┃ ┃ 0:0x7ff ┣━━━━━┫ ┃ ┃ 0 ┃<-bp指向这里(0:0x800),以此开始的16字节被清零。 ┃ ┣━━━━━┫以下所称的fake partition entry就是指这里。 ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┣━━━━━┫ ┃ ┃ 0 ┃ ┃ ┗━━━━━┛ 0:0x7c00 ┏━━━━━┓ ┛ ┃ ┃ ┃ 搬 ┃ ┃ 移 ┃ ┃ 之 ┃ ┃ 前 ┃ ┃ 的 ┃ ┃ 代 ┃ ┃ 码 ┃ ┃ ┃ 0:0x7dff ┗━━━━━┛ 图(二)
#
# Relocate to the new copy of the code.
#
incb -0xe(%di) # Sector number
jmp main-LOAD+ORIGIN # To relocated code
把以上清零的16字节的第二个字节置为1,表示我们已经读取了一个分区。然后跳转到搬移之后的新代码的main处执行。
#
# Check what flags were loaded with us, specifically, Use a predefined Drive.
# If what the bios gives us is bad, use the '0' in the block instead, as well.
#
main:
testb {GetProperty(Content)}x20,_FLAGS(%bp) # Set number drive?
jnz main.1 # Yes
testb %dl,%dl # Drive number valid?
js main.2 # Possibly (0x80 set)
main.1:
movb _SETDRV(%bp),%dl # Drive number to use
上面说过,BIOS把磁盘的引导扇区读入到内存之后,其dl的内容表示启动设备,但我们安装好FreeBSD之后,我们可以改变此引导扇区的内容,其中的一个改变就是可以使我们可以“手动指定”我们实际安装FreeBSD的分区,如果我们希望指定FreeBSD所在的boot分区,那么我们在bp-0x45处的位置(即_FLAGS(%bp)处)的bit 0x20置1,那么上面的“movb _SETDRV(%bp),%dl”一句中movb_SETDRV(%bp),%dl(即bp-0x46)即指向我们“手动指定”FreeBSD所在分区代码,例如,IDE的C、D盘(严格来说是第一个物理磁盘的第一个和第二个分区)的代码分别为 0x80和0x81。如果没有“手动指定”启动分区,那么,我们缺省使用机器当前启动的分区,上面说过,机器当前启动的分区代码放在dl中。因为FreeBSD Boot Manager 不可能安装到软盘(如果从软盘启动则dl为0),所以,使用testb %dl,%dl来判断驱动器代码是否合法(volid)。有关_FLAGS(%bp)中其他bit位表示的意义,在随后的代码分析中慢慢给你道来。
#
# Whatever we decided to use, now store it into the fake
# partition entry that lives in the data space above us.
#
main.2:
movb %dl,_FAKE(%bp) # Save drive number
callw putn # To new line
pushw %dx # Save drive number
以上第一句把FreeBSD启动分区的代码保存到_FAKE(%bp)(bp-0)处,也就是说,上图(二)的bp处保存的是FreeBSD启动分区的代码(_FAKE=0)。 “callw putn”一句在屏幕上打印“回车”和“换行”,“pushw %dx”一句把启动分区的值压入堆栈。
#
# Start out with a pointer to the 4th byte of the first table entry
# so that after 4 iterations it's beyond the end of the sector.
# and beyond a 256 byte boundary and has overflowed 8 bits (see next comment).
# (remember that the table starts 2 bytes earlier than you would expect
# as the bootable flag is after it in the block)
#
movw $(partbl+0x4),%bx # Partition table (+4)
xorw %dx,%dx # Item number
以上代码首先把%bx指向分区表partbl的的第四个字节,这里存放的是分区类型,如82表示Linux Native分区83表示Linux Swap 分区,有关分区表的细节请详见本文的尾部。然后dx清零,此后,dx将作为遍历磁盘分区的列举代号使用。启动分区代码dl的原来的值在上面已经压入了堆栈保存。
#
# Loop around on the partition table, printing values until we
# pass a 256 byte boundary. The end of loop test is at main.5.
#
main.3:
movb %ch,-0x4(%bx) # Zero active flag (ch == 0)
btw %dx,_FLAGS(%bp) # Entry enabled?
jnc main.5 # No
上面首先使得第一个分区的活动标志为0,标志它不为活动标志,因为ch的值为0。至于第二句“btw %dx,_FLAGS(%bp)”中的_FLAGS(%bp)是上面我们说到的“手动指定我们实际安装FreeBSD的分区代码”。其中的bit 0x20我们在上面已经提到过。_FLAGS(%bp)中的其他位表示是否我们需要检查相应的磁盘分区。缺省情况下,我们需要检查所有的磁盘分区。检查磁盘分区看是否有可以启动的磁盘分区,例如,可能磁盘上的某个分区为WindowsXP或者是Linux等。如果我们没有改变在磁盘上该处的值,则相应的bit位的值为0,表示所有的分区都要检查(因为此时_FLAGS(%bp)中的值为0),否则,只针对FLAGS(%bp)中相应的bit位未被设置为1的分区进行检查。大家知道,FreeBSD Manager启动时可能出现以下的提示:
F1 FreeBSD
F2 ??
F3 BSD
F4 ??
Default F1
其中,上面的提示中出现了令人讨厌的“??”,为了避免出现“??”的提示,我们可以设置相应的第一分区和第四分区不检查,就需要正确设置_FLAGS(%bp)中的bit位。设置好后,屏幕可能出现以下的提示:
F1 FreeBSD
F2 BSD
Default F1
#
# If any of the entries in the table are
# the same as the 'type' in the slice table entry,
# then this is an empty or non bootable partition. Skip it.
#
movb (%bx),%al # Load type
movw $tables,%di # Lookup tables
movb $TBL0SZ,%cl # Number of entries
repne # Exclude
scasb # partition?
je main.5 # Yes
我们从上面已经知道起始(%bx)指向的是MBR中分区信息1(16字节)的位置(见图(三)),以上代码在“忽略的分区类型$tables”中查找看是否本分区是不可启动的或者不合法的分区。不可启动的或者不合法的分区类型有3($TBL0SZ=3)个,它们是“0x0, 0x5, 0xf”,见下面的$tables处。如果是不可启动的或者不合法的分区类型则跳转到main.5,进行下一轮循环。
#
# Now scan the table of known types
#
movb $TBL1SZ,%cl # Number of entries
repne # Known
scasb # type?
jne main.4 # No
#
# If it matches get the matching element in the
# next array. if it doesn't, we are already
# pointing at its first element which points to a "?".
#
addw $TBL1SZ,%di # Adjust
main.4:
movb (%di),%cl # Partition
addw %cx,%di # description
callw putx # Display it
上面检查看所检查的分区类型是否为我们知道的分区类型,知道的分区类型有11($TBL1SZ=0xb)个,它们是:“0x1, 0x4, 0x6, 0xb, 0xc, 0xe, 0x83,0x9f, 0xa5, 0xa6, 0xa9”,见下面的$tables处。如果不是以上的类型,则跳转到main.4。那么,(%di)所指的字串是“??”,如果分区类型是“0x1, 0x4, 0x6, 0xb, 0xc, 0xe, 0x83,0x9f, 0xa5, 0xa6, 0xa9”之一,则(%di)所指的字串是“DOS”、“Linux”、“FreeBSD”或“BSD”等。见下面的“os_misc”、“os_dos”、“os_linux”、“os_freebsd”、“os_bsd”等标记。 callw putx调用putx函数,在屏幕上打印:“Fx XXX”。其中XXX为DOS”、“Linux”、“FreeBSD”或“BSD”等。
main.5:
incw %dx # Next item
addb {GetProperty(Content)}x10,%bl # Next entry
jnc main.3 # Till done
遍历磁盘分区的举代号dx加1,重复下一轮循环查找。bl加上0x10(0x10=16)表示寻址到下一个分区信息(加16字节)入口。循环直到255字节边界。
#
# Passed a 256 byte boundary..
# table is finished.
# Add one to the drive number and check it is valid,
#
popw %ax # Drive number
subb {GetProperty(Content)}x80-0x1,%al # Does next
cmpb NHRDRV,%al # drive exist? (from BIOS?)
jb main.6 # Yes
“popw %ax”把上面压入堆栈的bx(当前的启动扇区)值弹出到ax中。例如,如果计算机是从软盘启动的则dl=0,若是从IDE的C、D盘启动的则dl分别为 0x80和0x81。然而,FreeBSD的Boot Manerger不能够安装到软盘上,所以,dl只能为0x80、0x81,0x82...等。在计算机的BIOS地址0:0x475处存放的是计算机的硬盘的数目,“subb {GetProperty(Content)}x80-0x1,%al”一句等于“sub{GetProperty(Content)}x79,%al”,例如,即当前驱动器如果是C盘,则al的值是ox80-0x79=1,然后再与计算机的硬盘的数目比较,如果当前所在硬盘不是最后一个硬盘,则直接跳转到main.6。如果当前所在硬盘是最后一个硬盘,则继续执行。
# If not then if there is only one drive,
# Don't display drive as an option.
#
decw %ax # Already drive 0?
jz main.7 # Yes
如果只有一个硬盘,则直接跳转到main.7,这样,本计算机只有一个硬盘,所以不用显示其他磁盘相关的提示。
# If it was illegal or we cycled through them,
# then go back to drive 0.
#
xorb %al,%al # Drive 0
下面的内容表示多于一个磁盘的情况。此时“al”清0,与磁盘列举相关。
#
# Whatever drive we selected, make it an ascii digit and save it back
# to the "next drive" location in the loaded block in case we
# want to save it for next time.
# This also is part of the printed drive string so add 0x80 to indicate
# end of string.
#
main.6:
addb