|
本帖最后由 不点 于 2014-11-18 18:14 编辑
- if [ "$1" = "" ]; then
- # write error message to /dev/stderr
- echo "Please use \`make' to build images. exit!" 1>&2
- exit 1
- elif [ "$1" = "mbr_8G.img" ]; then
- # 0x3F00=16128
- fatsize=16128
- elif [ "$1" = "mbr_16G.img" ]; then
- # 0x7E00=32256
- fatsize=32256
- elif [ "$1" = "mbr_32G.img" ]; then
- # 0xFC00=64512
- fatsize=64512
- elif [ "$1" = "mbr_64G.img" ]; then
- # 0x1F800=129024
- fatsize=129024
- elif [ "$1" = "mbr_128G.img" ]; then
- # 0x3F000=258048
- fatsize=258048
- else
- # write error message to /dev/stderr
- echo "Invalid image file name: $1. exit!" 1>&2
- exit 1
- fi
复制代码
Makefile 调用 buildimg.sh 的语法是这样的:
sh buildimg.sh mbr_8G.img
sh buildimg.sh mbr_16G.img
sh buildimg.sh mbr_32G.img
sh buildimg.sh mbr_64G.img
sh buildimg.sh mbr_128G.img
可以手动执行这几条命令中的任意一条。这个脚本目前没有支持 256G 或更大的 U 盘,但你的软件可以很容易照葫芦画瓢添加支持直到 2048G 的 U 盘。
- if [ ! -f multimbr.mbr ]; then
- # write error message to /dev/stderr
- echo "Cannot find multimbr.mbr, exit!" 1>&2
- exit 1
- fi
复制代码
需要用 multimbr.mbr 中的代码来构建 mbr_?G.img 的前 191 个扇区(扇区 0 至扇区 190)。multimbr.mbr 总共是由两个扇区构成的。
multimbr.mbr 的第一个扇区的代码用来构建 mbr_?G.img 的前 190 个扇区(扇区 0 至扇区 189)。
multimbr.mbr 的第二个扇区的代码用来构建 mbr_?G.img 的扇区 190。
扇区 0 - 扇区 189,共 190 个扇区,填充的是第一阶段的扇区。目的是为了能够捕捉到 BIOS 传递过来的控制。只要屏幕有显示信息,就是捕捉到了控制。第一阶段在完成必要的探测之后,会同时加载第二阶段(扇区 190)和第三阶段(它是 GRLDR,位于扇区 191 至扇区 1341)的代码到内存 0000:7E00。
首扇区的扇区序列号为 0x3F60,往后逐步递降,递降到最小的扇区序列号 0x0020 为止(它就是扇区 16192 的扇区序列号)。再往后则是 FAT32 的数据结构,因此,那是不可以随便填入扇区序列号的。
multimbr.mbr 是由源代码文件 multimbr.S 编译生成的。它是这个软件的核心,是保证启动成功率的关键。测试稳定之后,它们一般就不会随便改动了。所以,你可以直接把 multimbr.mbr 嵌入到你的软件里面。multimbr.S 是 public domain 的,你可以用于任何目的,请看授权协议。但 GRLDR 是 GPL 软件,大概你只能把它单独放在你的软件包里面,而且你还得提供源代码,或者告知源代码如何能够让别人获得。
- if [ ! -f grldr ]; then
- # write error message to /dev/stderr
- echo "Cannot find grldr, exit!" 1>&2
- exit 1
- fi
复制代码
从扇区 191 开始,至扇区 1341,共 1151 个扇区,要放置 GRLDR 了,这当然是把原始的 grldr 嵌入扇区序号以后再放置到这里的。所以,没有 GRLDR 是不行的,要报错退出。
但是启动代码并不检查 GRLDR 的结构,你可以用任何东西来冒充 GRLDR,让 multimbr 的核心代码去加载执行。
第一阶段的 multimbr 代码(即 multimbr.mbr 的第一扇区代码,也即 mbr_?G.img 的开头 190 个扇区上的代码)会把控制权交给第二阶段的 multimbr 代码(即 multimbr.mbr 的第二扇区代码,也即位于 mbr_?G.img 的扇区号 190 上的代码),内存地址是 0000:7E00。然后第二阶段代码再把控制权交给第三阶段的 GRLDR,内存地址是 0000:8000。
扇区 190,共 1 个扇区,是第二阶段的扇区(stage2),它的物理序列号是 0x3EA2。第二阶段接管控制时, CS:IP=0000:7E00。第二阶段的代码首先检查 CPU 寄存器以及内存的数据是否正常,如果有错误,则停机并打印失败信息,让用户了解失败的原因。如果一切正常,则把内存中的第三阶段的 GRLDR 进行整理(即去掉其内嵌的物理序列号);然后打印一条信息,让用户有机会按 P 键暂停。用户松开 P 键,2 秒之后就启动 GRLDR 了。用户每秒按一次 P 键,可保持程序处于暂停状态。GRLDR 取得控制时, CS:IP=0000:8000,DH=0xFF,DL=启动盘的盘号(0 是软盘,0x80 是硬盘);同时,堆栈上也压入了 DX 的值,虽然这对于 GRLDR 来说,没什么用处。
扇区 191 - 扇区 1341,共 1151 个扇区,是第三阶段的扇区(grldr)。注意,它的每个扇区都带有正确的物理序列号。它的物理序列号是从 0x3EA1 (十进制 16033)开始的,逐步递降。grldr 的最后一个扇区的物理序列号是 0x3A23(十进制 14883)。
扇区 1342 - 扇区 16192,共 14851 个扇区(大约 7M),是空白扇区,但每个扇区都带有物理序列号。其最开头的扇区的物理序列号是 0x3A22(十进制 14882),其结尾的扇区的物理序列号是 0x0020(十进制 32)。空白扇区是用户可以使用的。当然了,用户不可以破坏扇区的物理序列号,要保持正确的物理序列号。用户可能需要在自己的 menu.lst 菜单中添加自己的特殊工具,才能使用空白扇区上的数据。
从扇区 16193 开始,是 FAT32 的文件系统结构扇区。其开头的 31 个扇区(0x1F 个扇区)是保留扇区。紧接着是两份 FAT 表。在两份 FAT 表之后,紧接着是根目录,占用 8 个扇区(也就是 4K,这也就是我们所使用的 cluster “簇” 的长度)。因为根目录是空的,里面没有东西,所以,这 8 个扇区都填满了 00 字节。
- if [ ! -f preset_menu.lst ]; then
- # write error message to /dev/stderr
- echo "Cannot find preset_menu.lst, exit!" 1>&2
- exit 1
- fi
复制代码
原始 grldr 的内置菜单会被修改为 preset_menu.lst 的内容。所以,preset_menu.lst 必须存在。
- # locate bootlace signature
- i=$((0x2000+$(od -A n -j 0x206C -N 4 -t u4 grldr)-0x8200))
- j=$((0xce1a02b0-$(od -A n -j $i -N 4 -t u4 grldr)))
- if [ "$j" != 0 ]; then
- # write error message to /dev/stderr
- echo "Unrecognized grldr. exit!" 1>&2
- exit 1
- fi
复制代码
检查是不是 GRLDR 文件,如果不是的,那就没法修改内置菜单了。
- if [ ! -f grldr.new ] || [ grldr.new -ot grldr ]; then
- # write message to /dev/stderr
- echo Updating grldr.new ... 1>&2
- {
- # duplicate the head
- dd bs=8197 skip=0 count=1 2> /dev/null
- # discard the control byte
- dd bs=1 count=1 2> /dev/null 1>&2
- # write new control byte and disable PXE
- printf "\x01"
- # duplicate the body and locate the preset menu
- dd bs=$((i+16-8198)) count=1 2> /dev/null
- # write our new preset menu.
- cat preset_menu.lst
- # end the menu with a null
- printf "\x00"
- } < grldr > grldr.new
- fi
复制代码
修改了 grldr 的控制字节,屏蔽掉 PXE 功能,启动更安全可靠。同时也修改了内置菜单。生成了新的 grldr.new,这才是真正要安装的第三阶段启动文件。
- # Redirect stdout to destination image file
- exec > $1
复制代码
这条命令的意思是,接下来所有的输出都重定向到 mbr_?G.img。
- # two FATs, data area
- # data area=fatsize * clusters_per_FAT32_sector * sectors_per_cluster
- # clusters_per_FAT32_sector = 512 / 4 = 128
- # sectors_per_cluster = 8
- # 128 * 8 = 1024
- # data area = fatsize * 1024
- # two FATs = fatsize * 2
- # two FATs + data area = fatsize * 1026
- t=$((fatsize*1026))
- i=16224; #; i=$((33+126+255*63))
复制代码
t 是从 FAT 表开始的 FAT32 卷所占用的全部扇区数。
16224 就是 0x3F60,它是首扇区的序列号。序列号是逐步递降的。从首扇区(扇区 0)至扇区 16192,每个扇区都带有序列号。从 16193 开始,所有的扇区都不再带有序列号。
16224+t 就是整个 U 盘的总扇区数。
- j=0
- a=0
- b=0
- cat grldr.new /dev/zero | while [ $i -gt 31 ]; do
复制代码
扇区序列号大于 31,意思是从首扇区一直延续到扇区 16192。扇区 16192 的扇区序列号是 32(=0x20)。
- a=$((i&255))
- b=$(((i>>8)&255))
复制代码
扇区序列号的低字节放在变量 a 中,高字节放在变量 b 中。
第一个 FAT 表位于扇区 16224,所以,这里的变量 k 就是从当前扇区开始直到 FAT32 分区结尾处的扇区数。
变量 L 比 k 小 63。表示当前扇区的分区表上的 FAT32 卷的长度。
- c=$((k&255))
- d=$(((k>>8)&255))
- p=$(((k>>16)&255))
- q=$(((k>>24)&255))
- y=$((l&255))
- z=$(((l>>8)&255))
- r=$(((l>>16)&255))
- s=$(((l>>24)&255))
复制代码
c,d,p,q 是 k 从低到高的四个字节。y,z,r,s 是 L 从低到高的四个字节。
- m=$((i-32+1))
- n=$((i-32+6))
- e=$((m&255))
- f=$(((m>>8)&255))
- g=$((n&255))
- h=$(((n>>8)&255))
复制代码
m, n 是保存 FAT32 文件系统信息的扇区的指针。e,f 是 m 的两个字节。g,h 是 n 的两个字节。
下面这一段是生成第一阶段的扇区,即,扇区号小于 190 的那些扇区。
- #; 190 MBR sectors (stage 1)
- printf "\xEB\x5E\x90\x4D\x53\x57\x49\x4E\x34\x2E\x31\x00\x02\x08\x$(printf %x $a)\x$(printf %x $b)\x02\x00\x00\x00\x00\xF8\x00\x00\x3F\x00\xFF\x00\x3F\x00\x00\x00\x$(printf %x $c)\x$(printf %x $d)\x$(printf %x $p)\x$(printf %x $q)\x$(printf %x $((fatsize&255)))\x$(printf %x $(((fatsize>>8)&255)))\x$(printf %x $(((fatsize>>16)&255)))\x$(printf %x $(((fatsize>>24)&255)))\x00\x00\x00\x00\x02\x00\x00\x00\x$(printf %x $e)\x$(printf %x $f)\x$(printf %x $g)\x$(printf %x $h)"
复制代码
先写入扇区开头的 52 个字节,属于 BPB 的范畴。固定的那些域,不再解释。可变的那些数据有:扇区序列号,总扇区数,每个FAT表所占用的扇区数,以及保存 FAT32 文件系统信息的两个扇区的指针。
- dd if=multimbr.mbr bs=1 skip=52 count=406 2> /dev/null
复制代码
从 multimbr.mbr 取 406 字节,当然,先跳过 52 个字节,因为刚才已经写好了 52 字节。只需从 multimbr.mbr 中取出 406 字节。因此,总共已经生成的字节数是 406 + 52 = 458(=0x1CA)。那么,下一个字节所处的偏移就是 0x1CA,也就是分区表上的分区长度域。
- printf "\x$(printf %x $y)\x$(printf %x $z)\x$(printf %x $r)\x$(printf %x $s)"
复制代码
是的,它就是分区长度的四个字节。
下面这段是要执行的,即,生成空的分区表项,不再创建第二个主分区(或扩展分区)
- # this will not create the second partition
- printf "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
- else
复制代码
下面这段目前被屏蔽,不会执行。假如执行它,则会生成第二主分区,其长度延伸到 U 盘的结尾。
- # this will create the second partition
- # 0xFFFFFFFF=4294967295
- len=$((4294967295-k))
- printf "\x00\xFE\xFF\xFF\x0C\xFE\xFF\xFF\x$(printf %x $c)\x$(printf %x $d)\x$(printf %x $p)\x$(printf %x $q)\x$(printf %x $((len&255)))\x$(printf %x $(((len>>8)&255)))\x$(printf %x $(((len>>16)&255)))\x$(printf %x $(((len>>24)&255)))"
- fi
复制代码
下面这句,又生成两个空的主分区表项,并用 55 AA 结束这个扇区。
- printf "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x55\xAA"
- true
- elif [ $j -eq 190 ]; then
复制代码
下面这段,是生成扇区 190,它是第二阶段的扇区。第一阶段的扇区在接管控制以后,会把控制权交给第二阶段的这个扇区。
- #; 1 sector of stage 2
- dd if=multimbr.mbr skip=1 count=1 2> /dev/null
复制代码
第二阶段的扇区生成完毕,但要稍微多做一点工作,就是把 grldr.new 开头的 14 个字节打印出来(记住,所有的打印结果都是重定向到 mbr_?G.img 了)。
- #; 1151 sectors for GRLDR(stage 3). It begins at sector 191.
- dd bs=14 count=1 2> /dev/null
- true
- elif [ $j -lt 1341 ]; then
复制代码
下面这段,插入扇区序列号,打印 grldr.new 的 510 个字节;再插入下一个扇区序列号,再打印 grldr.new 的 510 字节;如此反复,直到扇区 1340 为止。
- printf "\x$(printf %x $a)\x$(printf %x $b)"
- dd bs=510 count=1 2> /dev/null
- true
- elif [ $j -eq 1341 ]; then
复制代码
下面这段,是处理第三阶段的最后一个扇区(也就是 grldr.new 的尾巴),扇区号是 1341。先打印出扇区序列号,然后又打印出 grldr.new 的 496 个字节。总共打印了 498 个字节,但加上先前在扇区 190 时多打印的 14 个字节,正好凑够 512 字节。所以,第三阶段的扇区生成完毕。
- printf "\x$(printf %x $a)\x$(printf %x $b)"
- dd bs=496 count=1 2> /dev/null
- #; end of GRLDR at sector 1341
- true
复制代码
如果存在 userdata 文件,则下一段会执行 break 命令退出循环。
- elif [ -f userdata ]; then
- break
- true
- else
复制代码
否则,如果不存在 userdata 文件,则执行下面这段,生成扇区 1342 至扇区 16192,它们是空扇区,但要嵌入扇区序列号。
- #; 14851 empty sectors, from sector 1342 to sector 16192
- printf "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x$(printf %x $a)\x$(printf %x $b)"
- dd if=/dev/zero bs=496 count=1 2> /dev/null
- true
- fi
复制代码
下面是循环变量 j 递增,同时扇区序列号 i 递减。
- i=$((i-1))
- j=$((j+1))
- done
复制代码
如果存在 userdata 文件,则会执行下面这段。那就是用 userdata 生成扇区 1342 至扇区 16192,并嵌入扇区序列号。
- #; debug output: i=16224, j=0, a=0, b=0
- #; echo i=$i j=$j a=$a b=$b 1>&2
- #; exit
- if [ -f userdata ]; then
- i=14882; #; i=$((33+126+255*63-1342))
- j=1342
- cat userdata /dev/zero | while [ $i -gt 31 ]; do
- a=$((i&255))
- b=$(((i>>8)&255))
- if [ $j -eq 1342 ]; then
- #; 14851 sectors for USERDATA, from sector 1342 to sector 16192.
- dd bs=14 count=1 2> /dev/null
- printf "\x$(printf %x $a)\x$(printf %x $b)"
- dd bs=510 count=1 2> /dev/null
- true
- elif [ $j -lt 16192 ]; then
- printf "\x$(printf %x $a)\x$(printf %x $b)"
- dd bs=510 count=1 2> /dev/null
- true
- elif [ $j -eq 16192 ]; then
- printf "\x$(printf %x $a)\x$(printf %x $b)"
- dd bs=496 count=1 2> /dev/null
- #; end of USERDATA at sector 16192
- true
- else
- echo "It should not get here. Exit!" 1>&2
- exit 1
- true
- fi
- i=$((i-1))
- j=$((j+1))
- done
- fi
复制代码
以上告一段落,生成了 multimbr 的关键数据结构,其中每个扇区都带有扇区序列号。
以下生成的,则是起着辅助作用的 FAT32 数据结构。首先写 31 个 隐藏扇区。
- #; write 31 hidden sectors
- printf "\x52\x52\x61\x41"
- dd if=/dev/zero bs=480 count=1 2> /dev/null
- printf "\x72\x72\x41\x61\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x55\xAA"
- dd if=/dev/zero bs=510 count=1 2> /dev/null
- printf "\x55\xAA"
- dd if=/dev/zero bs=2046 count=1 2> /dev/null
- printf "\x55\xAA\x52\x52\x61\x41"
- dd if=/dev/zero bs=480 count=1 2> /dev/null
- printf "\x72\x72\x41\x61\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x55\xAA"
- dd if=/dev/zero bs=510 count=1 2> /dev/null
- printf "\x55\xAA"
- dd if=/dev/zero bs=11776 count=1 2> /dev/null
复制代码
然后写第一个 FAT 表:
- #; write the 1st FAT
- printf "\xF8\xFF\xFF\x0F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x0F"
- dd if=/dev/zero bs=$((fatsize*512-12)) count=1 2> /dev/null
复制代码
最后再写第二个 FAT 表,以及根目录的一个 cluster (8 个扇区)。至此,扇区数据全部生成完毕。
- #; write the 2nd FAT, and 8 more sectors(=1 cluster) for root dir
- printf "\xF8\xFF\xFF\x0F\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x0F"
- dd if=/dev/zero bs=$((fatsize*512-12+4096)) count=1 2> /dev/null
复制代码
|
|