Linux的引导分析(分区表与stage1)

来源:互联网 发布:linux eagle 编辑:程序博客网 时间:2024/05/17 04:23
首先我们来描述一下引导流程:
1)BIOS将控制权交给硬盘的bootloader(stage1).
2)bootloader(stage1)将stage1_5加载到内存.
3)bootloader通过stage1.5,识别文件系统,将stage2加载到内存.
4)stage2此时就可以在文件系统中将menu.lst配置文件加载,进入启动内核的引导过程.



一)BIOS引导过程


BIOS的作用:
BIOS在计算机启动时负责和所有硬件沟通,并将计算机呈现在用户面前.


BIOS与南/北桥:
北桥主要控制内存和CPU,而南桥主要负责PCI,PCI-E,USB,VGA等所有外围设备.
在南桥里面有一块特殊的区域,负责存储CMOS的信息,CMOS是用户存储BIOS设备的地方.


BIOS的引导过程:
1)开启电源.
2)CPU先被激活并去寻找BIOS.
3)BIOS会初始化各种主板芯片组.
4)BIOS初始化键盘控制器8042.
5)初始化中断向量,中断服务例程.
6)初始化VGA BIOS控制器.
7)显示BIOS的版本和公司名称
8)扫描软驱和各种介质容量
9)读取CMOS的启动顺序配置,并检测启动装置是否正常
10)调用INT 19H的2号功能来读出硬盘MBR扇区的内容,将其读入到内存0000:7C00h,并跳转至此处执行.



二)MBR和它的分区表

MBR位于硬盘的0柱面,0磁头,1扇区,被称为主引导程序.
它由三个部分组成:主引导程序,硬盘分区表DPT(Disk Partition table)和硬盘有效标志(55AA).
在总共512字节的主引导扇区里主引导程序(boot loader)占446个字节,第二部分是Partition table区(分区表),即DPT,占64个字节,硬盘中分区有多少以及每一分区的大小都记在其中.第三部分是magic  number,占2个字节,固定为55AA.


我们可以用dd命令来将/dev/sda磁盘的MBR读出并写入到/tmp/d20文件,如下:
dd if=/dev/sda of=/tmp/d20 bs=512 count=1 



用hexdump打开/tmp/d20文件,如下:
注:-C的作用是用十六进制及ASCII的方式查看文件,并采用低/高位的方式显示

hexdump -C /tmp/d20
00000000  eb 48 90 10 8e d0 bc 00  b0 b8 00 00 8e d8 8e c0  |.H..............|
00000010  fb be 00 7c bf 00 06 b9  00 02 f3 a4 ea 21 06 00  |...|.........!..|
00000020  00 be be 07 38 04 75 0b  83 c6 10 81 fe fe 07 75  |....8.u........u|
00000030  f3 eb 16 b4 02 b0 01 bb  00 7c b2 80 8a 74 03 02  |.........|...t..|
00000040  ff 00 00 20 01 00 00 00  00 02 fa 90 90 f6 c2 80  |... ............|
00000050  75 02 b2 80 ea 59 7c 00  00 31 c0 8e d8 8e d0 bc  |u....Y|..1......|
00000060  00 20 fb a0 40 7c 3c ff  74 02 88 c2 52 be 7f 7d  |. ..@|<.t...R..}|
00000070  e8 34 01 f6 c2 80 74 54  b4 41 bb aa 55 cd 13 5a  |.4....tT.A..U..Z|
00000080  52 72 49 81 fb 55 aa 75  43 a0 41 7c 84 c0 75 05  |RrI..U.uC.A|..u.|
00000090  83 e1 01 74 37 66 8b 4c  10 be 05 7c c6 44 ff 01  |...t7f.L...|.D..|
000000a0  66 8b 1e 44 7c c7 04 10  00 c7 44 02 01 00 66 89  |f..D|.....D...f.|
000000b0  5c 08 c7 44 06 00 70 66  31 c0 89 44 04 66 89 44  |\..D..pf1..D.f.D|
000000c0  0c b4 42 cd 13 72 05 bb  00 70 eb 7d b4 08 cd 13  |..B..r...p.}....|
000000d0  73 0a f6 c2 80 0f 84 ea  00 e9 8d 00 be 05 7c c6  |s.............|.|
000000e0  44 ff 00 66 31 c0 88 f0  40 66 89 44 04 31 d2 88  |D..f1...@f.D.1..|
000000f0  ca c1 e2 02 88 e8 88 f4  40 89 44 08 31 c0 88 d0  |........@.D.1...|
00000100  c0 e8 02 66 89 04 66 a1  44 7c 66 31 d2 66 f7 34  |...f..f.D|f1.f.4|
00000110  88 54 0a 66 31 d2 66 f7  74 04 88 54 0b 89 44 0c  |.T.f1.f.t..T..D.|
00000120  3b 44 08 7d 3c 8a 54 0d  c0 e2 06 8a 4c 0a fe c1  |;D.}<.T.....L...|
00000130  08 d1 8a 6c 0c 5a 8a 74  0b bb 00 70 8e c3 31 db  |...l.Z.t...p..1.|
00000140  b8 01 02 cd 13 72 2a 8c  c3 8e 06 48 7c 60 1e b9  |.....r*....H|`..|
00000150  00 01 8e db 31 f6 31 ff  fc f3 a5 1f 61 ff 26 42  |....1.1.....a.&B|
00000160  7c be 85 7d e8 40 00 eb  0e be 8a 7d e8 38 00 eb  ||..}.@.....}.8..|
00000170  06 be 94 7d e8 30 00 be  99 7d e8 2a 00 eb fe 47  |...}.0...}.*...G|
00000180  52 55 42 20 00 47 65 6f  6d 00 48 61 72 64 20 44  |RUB .Geom.Hard D|
00000190  69 73 6b 00 52 65 61 64  00 20 45 72 72 6f 72 00  |isk.Read. Error.|
000001a0  bb 01 00 b4 0e cd 10 ac  3c 00 75 f4 c3 00 00 00  |........<.u.....|
000001b0  00 00 00 00 00 00 00 00  c2 b3 09 00 00 00 80 01  |................|
000001c0  01 00 83 fe ff ff 3f 00  00 00 41 29 54 02 00 fe  |......?...A)T...|
000001d0  ff ff 05 fe ff ff 80 29  54 02 6e b1 6b 74 00 00  |.......)T.n.kt..|
000001e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|
00000200


我们先来看下分区表,它的位置起始于MBR的446(0x1BE)个字节的位置,终止于510(0x1fd)个字节的位置.

每个分区是16个字节,最多有4个分区,也就是说分区表中的第一个分区表项从第一行的0x1bf(80 01)开始,到第二行的54 02结束,在/dev/sda磁盘中我们一共有2个分区,所以这里有两条记录.
如下:
地址      0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F
000001b0  00 00 00 00 00 00 00 00  c2 b3 09 00 00 00 80 01  |................|
000001c0  01 00 83 fe ff ff 3f 00  00 00 41 29 54 02 00 fe  |......?...A)T...|
000001d0  ff ff 05 fe ff ff 80 29  54 02 6e b1 6b 74 00 00  |.......)T.n.kt..|
000001e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|

分析:
分区表的具体含义如下:
字节 含义 值
00 分区是否可引导。 0,不可引导。80,可引导

01-03 分区开始的CHS值(Head,Sector,Cylinder,Sector的高 位是Cylinder的第9,10位)

04 分区类型 7,NTFS,C, FAT32,5,F 扩展分区等等
05-07 分区结束的CHS值(Head,Sector,Cylinder,Sector的高 位是Cylinder的第9,10位)

08-11 LBA方式下,相对的起始扇区号
  在MBR分区里边,相对0扇区,
  扩展分区表示相对于扩展分区的起
  始扇区(little endion 格式)
12-15 该分区拥有的扇区数量 (little endion)

我们先来看一下扇区数量,也就是每条分区记录的最后4个字节,我们看上例中的第一个分区,这里是41 29 54 02,把高位和低位转换后,也就是0x2542941个扇区,转换成十进制是39070017个扇区,再转换成字节就是39070017*512=20003848704.
20003848704也就是20GB.所以我们第一个分区是20GB

下面来看一下该分区在线性寻址方式下(LBA)的绝对起始扇区号(相对于0扇区).
LBA即(Logic Block Address)又称为线性寻址模式,在这种模式下,磁盘不再有柱面,磁头,扇区的三维定义,而是将磁盘上所有的扇区依次从0开始编号.所以CHS已经不是磁盘分区的唯一依据,且更没有必要用CHS来划分磁盘,因为CHS本身来讲也是一个逻辑三维.
我们看到第一个分区的这四个字节为3f 00  00 00,把高低和低位转换后,也就是0x3f个扇区,转换成十进制就是63个扇区,再转换成字节就是63*512=32256.
32256就是32kb,所以我们第一个分区是从磁盘第63个扇区开始存放数据.
那么第二个分区的这四个字节应该是第一个分区的扇区数(39070017)加上第一个分区的起始扇区(63),结果就是39070080与0x2542980相符.

下面是起始CHS和结束CHS
CHS是过去的一种寻址方式,也就是用柱面(Cylinder)/磁头(Head)/扇区(Sector)三个参数来定位一个唯一的扇区.
而CHS是一种逻辑上的三维定义,并不是和硬盘物理结构一一对应,比如我们看到的第一个分区表项从第1(0x01)磁头开始,从fe个磁头处结束,事实上我们不可能有这么多磁头的硬盘.
我们看到第一个分区的起始CHS地址为01 01 00,其中第一个字节0x01表示开始磁头号,第二个字节的低6位00 0001(0x01=0000 0001),第二个字节的高2位作为起始柱面号的高2位,第三个字节的8个bit作为分区起始柱面号的低8位.

我们现在已经不用CHS做为磁盘的寻址方式了,因为采用CHS寻址的分区最大也不会超过8GB,也就是柱面数最多(1024)*扇区最大数(64)*磁头最大数(256)*每扇区字节数(512)/1024/1024=8192MB,也就是8G.
为了使用8GB以上的分区,BIOS中开始支持LBA,而CHS这种方式已经被放弃,成为历史,在1998年以后的BIOS都会支持LBA方式.

LBA的寻址方式可以让我们支持2TB,这是因为分区相对起始扇区号(分区项08-11个字节)和分区最大扇区数(分区项12-15个字节)的位数都是32bit.也就是0xFFFFFFFF*512/1024/1024/1024=2048GB,



三)stage1,stage1.5,stage2

stage1即是mbr中的bootloader.
我们可以在系统中看到stage1文件,比如:
ls -l /boot/grub/stage1 
-rw-r--r-- 1 root root 512 2009-08-23 07:11 /boot/grub/stage1

stage1的大小是512个字节,正是MBR的大小,但stage1的最后66个字节与mbr不同,mbr的最后66个字节分别是64个字节的分区表,和MBR标记55AA.
我们可以比对一下stage1与MBR的前446,它们的内容是完全一样的.我们可以理解stage1文件是MBR中bootloader的一个备份. 


stage1.5

stage1功能有限,不能完成grub的很多功能,例如多重引导,选择内核文件等.
stage1将stage1.5读入到内存,来过渡整个引导过程,即通过stage1.5来识别文件系统,从而能加载stage2,完成上述的功能.

为了识别多种文件系统,所以有多个stage1_5文件
ls /boot/grub/*1_5
/boot/grub/e2fs_stage1_5  /boot/grub/jfs_stage1_5    /boot/grub/reiserfs_stage1_5
/boot/grub/fat_stage1_5   /boot/grub/minix_stage1_5  /boot/grub/xfs_stage1_5


那么stage1无法识别文件系统,又是怎么能找到stage1.5呢
因为在bootloader知道stage1.5放在哪里,stage1.5一般紧跟在mbr后面,即512个字节后面就放着stage1.5,如上面我们分析mbr那样,第一个分区是从线性寻址的63个扇区开始的,也就是线性地址32kb的地方,
所以用dd命令读取sda设备的最前面32kb到临时文件,如下:
dd if=/dev/sda of=/tmp/stage1.5 bs=32k count=1

00000000  eb 48 90 10 8e d0 bc 00  b0 b8 00 00 8e d8 8e c0  |.H..............|
00000010  fb be 00 7c bf 00 06 b9  00 02 f3 a4 ea 21 06 00  |...|.........!..|
00000020  00 be be 07 38 04 75 0b  83 c6 10 81 fe fe 07 75  |....8.u........u|
00000030  f3 eb 16 b4 02 b0 01 bb  00 7c b2 80 8a 74 03 02  |.........|...t..|
00000040  ff 00 00 20 01 00 00 00  00 02 fa 90 90 f6 c2 80  |... ............|
00000050  75 02 b2 80 ea 59 7c 00  00 31 c0 8e d8 8e d0 bc  |u....Y|..1......|
00000060  00 20 fb a0 40 7c 3c ff  74 02 88 c2 52 be 7f 7d  |. ..@|<.t...R..}|
00000070  e8 34 01 f6 c2 80 74 54  b4 41 bb aa 55 cd 13 5a  |.4....tT.A..U..Z|
00000080  52 72 49 81 fb 55 aa 75  43 a0 41 7c 84 c0 75 05  |RrI..U.uC.A|..u.|
00000090  83 e1 01 74 37 66 8b 4c  10 be 05 7c c6 44 ff 01  |...t7f.L...|.D..|
000000a0  66 8b 1e 44 7c c7 04 10  00 c7 44 02 01 00 66 89  |f..D|.....D...f.|
000000b0  5c 08 c7 44 06 00 70 66  31 c0 89 44 04 66 89 44  |\..D..pf1..D.f.D|
000000c0  0c b4 42 cd 13 72 05 bb  00 70 eb 7d b4 08 cd 13  |..B..r...p.}....|
000000d0  73 0a f6 c2 80 0f 84 ea  00 e9 8d 00 be 05 7c c6  |s.............|.|
000000e0  44 ff 00 66 31 c0 88 f0  40 66 89 44 04 31 d2 88  |D..f1...@f.D.1..|
000000f0  ca c1 e2 02 88 e8 88 f4  40 89 44 08 31 c0 88 d0  |........@.D.1...|
00000100  c0 e8 02 66 89 04 66 a1  44 7c 66 31 d2 66 f7 34  |...f..f.D|f1.f.4|
00000110  88 54 0a 66 31 d2 66 f7  74 04 88 54 0b 89 44 0c  |.T.f1.f.t..T..D.|
00000120  3b 44 08 7d 3c 8a 54 0d  c0 e2 06 8a 4c 0a fe c1  |;D.}<.T.....L...|
00000130  08 d1 8a 6c 0c 5a 8a 74  0b bb 00 70 8e c3 31 db  |...l.Z.t...p..1.|
00000140  b8 01 02 cd 13 72 2a 8c  c3 8e 06 48 7c 60 1e b9  |.....r*....H|`..|
00000150  00 01 8e db 31 f6 31 ff  fc f3 a5 1f 61 ff 26 42  |....1.1.....a.&B|
00000160  7c be 85 7d e8 40 00 eb  0e be 8a 7d e8 38 00 eb  ||..}.@.....}.8..|
00000170  06 be 94 7d e8 30 00 be  99 7d e8 2a 00 eb fe 47  |...}.0...}.*...G|
00000180  52 55 42 20 00 47 65 6f  6d 00 48 61 72 64 20 44  |RUB .Geom.Hard D|
00000190  69 73 6b 00 52 65 61 64  00 20 45 72 72 6f 72 00  |isk.Read. Error.|
000001a0  bb 01 00 b4 0e cd 10 ac  3c 00 75 f4 c3 00 00 00  |........<.u.....|
000001b0  00 00 00 00 00 00 00 00  c2 b3 09 00 00 00 80 01  |................|
000001c0  01 00 83 fe ff ff 3f 00  00 00 41 29 54 02 00 fe  |......?...A)T...|
000001d0  ff ff 05 fe ff ff 80 29  54 02 6e b1 6b 74 00 00  |.......)T.n.kt..|
000001e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|
注意下面就是stage1.5,这和/boot/grub/e2fs_stage1_5的内容是一样的
00000200  52 56 be 03 21 e8 2a 01  5e bf f8 21 66 8b 2d 83  |RV..!.*.^..!f.-.|
00000210  7d 04 00 0f 84 ca 00 80  7c ff 00 74 3e 66 8b 1d  |}.......|..t>f..|
00000220  66 31 c0 b0 7f 39 45 04  7f 03 8b 45 04 29 45 04  |f1...9E....E.)E.|
00000230  66 01 05 c7 04 10 00 89  44 02 66 89 5c 08 c7 44  |f.......D.f.\..D|
00000240  06 00 70 50 66 31 c0 89  44 04 66 89 44 0c b4 42  |..pPf1..D.f.D..B|
00000250  cd 13 0f 82 9f 00 bb 00  70 eb 56 66 8b 05 66 31  |........p.Vf..f1|
00000260  d2 66 f7 34 88 54 0a 66  31 d2 66 f7 74 04 88 54  |.f.4.T.f1.f.t..T|
00000270  0b 89 44 0c 3b 44 08 7d  74 8b 04 2a 44 0a 39 45  |..D.;D.}t..*D.9E|
00000280  04 7f 03 8b 45 04 29 45  04 66 01 05 8a 54 0d c0  |....E.)E.f...T..|
00000290  e2 06 8a 4c 0a fe c1 08  d1 8a 6c 0c 5a 52 8a 74  |...L......l.ZR.t|
000002a0  0b 50 bb 00 70 8e c3 31  db b4 02 cd 13 72 46 8c  |.P..p..1.....rF.|
000002b0  c3 8e 45 06 58 c1 e0 05  01 45 06 60 1e c1 e0 04  |..E.X....E.`....|
000002c0  89 c1 31 ff 31 f6 8e db  fc f3 a4 1f be 14 21 e8  |..1.1.........!.|
000002d0  60 00 61 83 7d 04 00 0f  85 3c ff 83 ef 08 e9 2e  |`.a.}....<......|
000002e0  ff be 16 21 e8 4b 00 5a  ea 00 22 00 00 be 19 21  |...!.K.Z.."....!|
000002f0  e8 3f 00 eb 06 be 1e 21  e8 37 00 be 23 21 e8 31  |.?.....!.7..#!.1|
00000300  00 eb fe 4c 6f 61 64 69  6e 67 20 73 74 61 67 65  |...Loading stage|
00000310  31 2e 35 00 2e 00 0d 0a  00 47 65 6f 6d 00 52 65  |1.5......Geom.Re|
00000320  61 64 00 20 45 72 72 6f  72 00 bb 01 00 b4 0e cd  |ad. Error.......|
00000330  10 46 8a 04 3c 00 75 f2  c3 00 00 00 00 00 00 00  |.F..<.u.........|
00000340  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000003f0  00 00 00 00 00 00 00 00  02 00 00 00 0e 00 20 02  |.............. .|
00000400  ea 70 22 00 00 00 03 02  ff ff ff 00 00 00 00 00  |.p".............|
00000410  02 00 30 2e 39 37 00 ff  ff 00 ff 2f 62 6f 6f 74  |..0.97...../boot|
00000420  2f 67 72 75 62 2f 73 74  61 67 65 32 20 2f 62 6f  |/grub/stage2 /bo|
00000430  6f 74 2f 67 72 75 62 2f  6d 65 6e 75 2e 6c 73 74  |ot/grub/menu.lst|
00000440  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
后面略


下面我们来尝试覆盖mbr中的引导程序,并将它恢复.

1)首先覆盖mbr中的stage1:
dd if=/dev/zero of=/dev/sda bs=446 count=1  

2)导出/dev/sda磁盘的mbr,如下:
dd if=/dev/sda of=/tmp/cmbr bs=512 count=1
1+0 records in
1+0 records out
512 bytes (512 B) copied, 0.0023115 s, 222 kB/s

3)我们查看导出的mbr文件,如下:
hexdump -C /tmp/cmbr 
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000001b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 80 01  |................|
000001c0  01 00 83 fe ff ff 3f 00  00 00 41 29 54 02 00 fe  |......?...A)T...|
000001d0  ff ff 05 fe ff ff 80 29  54 02 6e b1 6b 74 00 00  |.......)T.n.kt..|
000001e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|
00000200

4)安装stage1,如下:
grub> root (hd0,0)
 Filesystem type is ext2fs, partition type 0x83

grub> setup (hd0)
 Checking if "/boot/grub/stage1" exists... yes
 Checking if "/boot/grub/stage2" exists... yes
 Checking if "/boot/grub/e2fs_stage1_5" exists... yes
 Running "embed /boot/grub/e2fs_stage1_5 (hd0)"...  15 sectors are embedded.
succeeded
 Running "install /boot/grub/stage1 (hd0) (hd0)1+15 p (hd0,0)/boot/grub/stage2 /boot/grub/menu.lst"... succeeded
Done.

5)我们再次导出mbr,发现已经成功的恢复了.
dd if=/dev/sda of=/tmp/nmbr bs=512 count=1   
1+0 records in
1+0 records out
512 bytes (512 B) copied, 0.00329825 s, 155 kB/s

hexdump -C /tmp/nmbr 
00000000  eb 48 90 00 00 00 00 00  00 00 00 00 00 00 00 00  |.H..............|
00000010  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000030  00 00 00 00 00 00 00 00  00 00 00 00 00 00 03 02  |................|
00000040  ff 00 00 20 01 00 00 00  00 02 fa 90 90 f6 c2 80  |... ............|
00000050  75 02 b2 80 ea 59 7c 00  00 31 c0 8e d8 8e d0 bc  |u....Y|..1......|
00000060  00 20 fb a0 40 7c 3c ff  74 02 88 c2 52 be 7f 7d  |. ..@|<.t...R..}|
00000070  e8 34 01 f6 c2 80 74 54  b4 41 bb aa 55 cd 13 5a  |.4....tT.A..U..Z|
00000080  52 72 49 81 fb 55 aa 75  43 a0 41 7c 84 c0 75 05  |RrI..U.uC.A|..u.|
00000090  83 e1 01 74 37 66 8b 4c  10 be 05 7c c6 44 ff 01  |...t7f.L...|.D..|
000000a0  66 8b 1e 44 7c c7 04 10  00 c7 44 02 01 00 66 89  |f..D|.....D...f.|
000000b0  5c 08 c7 44 06 00 70 66  31 c0 89 44 04 66 89 44  |\..D..pf1..D.f.D|
000000c0  0c b4 42 cd 13 72 05 bb  00 70 eb 7d b4 08 cd 13  |..B..r...p.}....|
000000d0  73 0a f6 c2 80 0f 84 ea  00 e9 8d 00 be 05 7c c6  |s.............|.|
000000e0  44 ff 00 66 31 c0 88 f0  40 66 89 44 04 31 d2 88  |D..f1...@f.D.1..|
000000f0  ca c1 e2 02 88 e8 88 f4  40 89 44 08 31 c0 88 d0  |........@.D.1...|
00000100  c0 e8 02 66 89 04 66 a1  44 7c 66 31 d2 66 f7 34  |...f..f.D|f1.f.4|
00000110  88 54 0a 66 31 d2 66 f7  74 04 88 54 0b 89 44 0c  |.T.f1.f.t..T..D.|
00000120  3b 44 08 7d 3c 8a 54 0d  c0 e2 06 8a 4c 0a fe c1  |;D.}<.T.....L...|
00000130  08 d1 8a 6c 0c 5a 8a 74  0b bb 00 70 8e c3 31 db  |...l.Z.t...p..1.|
00000140  b8 01 02 cd 13 72 2a 8c  c3 8e 06 48 7c 60 1e b9  |.....r*....H|`..|
00000150  00 01 8e db 31 f6 31 ff  fc f3 a5 1f 61 ff 26 42  |....1.1.....a.&B|
00000160  7c be 85 7d e8 40 00 eb  0e be 8a 7d e8 38 00 eb  ||..}.@.....}.8..|
00000170  06 be 94 7d e8 30 00 be  99 7d e8 2a 00 eb fe 47  |...}.0...}.*...G|
00000180  52 55 42 20 00 47 65 6f  6d 00 48 61 72 64 20 44  |RUB .Geom.Hard D|
00000190  69 73 6b 00 52 65 61 64  00 20 45 72 72 6f 72 00  |isk.Read. Error.|
000001a0  bb 01 00 b4 0e cd 10 ac  3c 00 75 f4 c3 00 00 00  |........<.u.....|
000001b0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 80 01  |................|
000001c0  01 00 83 fe ff ff 3f 00  00 00 41 29 54 02 00 fe  |......?...A)T...|
000001d0  ff ff 05 fe ff ff 80 29  54 02 6e b1 6b 74 00 00  |.......)T.n.kt..|
000001e0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|
00000200



下面我们来尝试覆盖stage1.5,并将它恢复,如下:

1)导出sda设备节点的stage1.5
dd if=/dev/sda of=/tmp/mbr bs=512 count=64 
64+0 records in
64+0 records out
32768 bytes (33 kB) copied, 3.1594e-05 s, 1.0 GB/s

2)覆盖sda设备节点的stage1.5
这里我们将MBR之后的63个扇区至0.
dd if=/dev/zero of=/dev/sda bs=512 count=63 seek=1
63+0 records in
63+0 records out
32256 bytes (32 kB) copied, 0.000339256 s, 95.1 MB/s

3)输出这63个扇区的数据,如下:
dd if=/dev/sda of=/tmp/stage1_5_1 bs=512 count=63 skip=1
63+0 records in
63+0 records out


4)我们查看sda磁盘stage1_5数据已经被清0
hexdump -C /tmp/stage1_5_1                                      
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00008000

5)我们尝试用grub程序来重新安装stage1.5,如下:
grub
Probing devices to guess BIOS drives. This may take a long time.


    GNU GRUB  version 0.97  (640K lower / 3072K upper memory)

       [ Minimal BASH-like line editing is supported.   For
         the   first   word,  TAB  lists  possible  command
         completions.  Anywhere else TAB lists the possible
         completions of a device/filename. ]

grub> root (hd0,0)
 Filesystem type is ext2fs, partition type 0x83

grub> setup (hd0)
 Checking if "/boot/grub/stage1" exists... yes
 Checking if "/boot/grub/stage2" exists... yes
 Checking if "/boot/grub/e2fs_stage1_5" exists... yes
 Running "embed /boot/grub/e2fs_stage1_5 (hd0)"...  15 sectors are embedded.
succeeded
 Running "install /boot/grub/stage1 (hd0) (hd0)1+15 p (hd0,0)/boot/grub/stage2 /boot/grub/menu.lst"... succeeded
Done.

grub> quit


6)再次输出stage1_5到文件,并查看,如下:
dd if=/dev/sda of=/tmp/stage1_5_1 bs=512 count=63 skip=1
63+0 records in
63+0 records out
32256 bytes (32 kB) copied, 0.00589645 s, 5.5 MB/s
test2:~# hexdump -C /tmp/stage1_5_1 
00000000  52 56 be 03 21 e8 2a 01  5e bf f8 21 66 8b 2d 83  |RV..!.*.^..!f.-.|
00000010  7d 04 00 0f 84 ca 00 80  7c ff 00 74 3e 66 8b 1d  |}.......|..t>f..|
00000020  66 31 c0 b0 7f 39 45 04  7f 03 8b 45 04 29 45 04  |f1...9E....E.)E.|
00000030  66 01 05 c7 04 10 00 89  44 02 66 89 5c 08 c7 44  |f.......D.f.\..D|
00000040  06 00 70 50 66 31 c0 89  44 04 66 89 44 0c b4 42  |..pPf1..D.f.D..B|
00000050  cd 13 0f 82 9f 00 bb 00  70 eb 56 66 8b 05 66 31  |........p.Vf..f1|
00000060  d2 66 f7 34 88 54 0a 66  31 d2 66 f7 74 04 88 54  |.f.4.T.f1.f.t..T|
00000070  0b 89 44 0c 3b 44 08 7d  74 8b 04 2a 44 0a 39 45  |..D.;D.}t..*D.9E|
00000080  04 7f 03 8b 45 04 29 45  04 66 01 05 8a 54 0d c0  |....E.)E.f...T..|
00000090  e2 06 8a 4c 0a fe c1 08  d1 8a 6c 0c 5a 52 8a 74  |...L......l.ZR.t|
000000a0  0b 50 bb 00 70 8e c3 31  db b4 02 cd 13 72 46 8c  |.P..p..1.....rF.|
000000b0  c3 8e 45 06 58 c1 e0 05  01 45 06 60 1e c1 e0 04  |..E.X....E.`....|
000000c0  89 c1 31 ff 31 f6 8e db  fc f3 a4 1f be 14 21 e8  |..1.1.........!.|
000000d0  60 00 61 83 7d 04 00 0f  85 3c ff 83 ef 08 e9 2e  |`.a.}....<......|
000000e0  ff be 16 21 e8 4b 00 5a  ea 00 22 00 00 be 19 21  |...!.K.Z.."....!|
000000f0  e8 3f 00 eb 06 be 1e 21  e8 37 00 be 23 21 e8 31  |.?.....!.7..#!.1|
00000100  00 eb fe 4c 6f 61 64 69  6e 67 20 73 74 61 67 65  |...Loading stage|
00000110  31 2e 35 00 2e 00 0d 0a  00 47 65 6f 6d 00 52 65  |1.5......Geom.Re|
00000120  61 64 00 20 45 72 72 6f  72 00 bb 01 00 b4 0e cd  |ad. Error.......|
00000130  10 46 8a 04 3c 00 75 f2  c3 00 00 00 00 00 00 00  |.F..<.u.........|
00000140  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
后面略


stage2
stage2是grub的核心程序,包括选项操作系统加载,新增参数,修改选项.
stage1将/boot/grub/stage2读入到内存,并通过stage2进行引导.

grub> root (hd0,0)
 Filesystem type is ext2fs, partition type 0x83

grub> setup (hd0)
 Checking if "/boot/grub/stage1" exists... yes
 Checking if "/boot/grub/stage2" exists... yes
 Checking if "/boot/grub/e2fs_stage1_5" exists... yes
 Running "embed /boot/grub/e2fs_stage1_5 (hd0)"...  15 sectors are embedded.
succeeded
 Running "install /boot/grub/stage1 (hd0) (hd0)1+15 p (hd0,0)/boot/grub/stage2 /boot/grub/menu.lst"... succeeded
Done.

我们可以看到grub到底安装了哪些东西:
/boot/grub/stage1 (hd0) ---->它安装stage1到磁盘的MBR

(hd0)1+15 p ---->将stage1_5写入到hd0磁盘MBR后面的15个扇区中.

(hd0,0)/boot/grub/stage2 ----->告知bootloader,当需要加载stage2时,其位置在第一块硬盘的第一个分区的/boot/grub/目录下.

/boot/grub/menu.lst---->将menu.lst的位置告诉stage2



四)stage1的代码分析

/*****************************************************/
#include <stage1.h>

/*
 *  defines for the code go here
 */

        /* Absolute addresses
           This makes the assembler generate the address without support
           from the linker. (ELF can't relocate 16-bit addresses!) */
/*取当前指令的绝对地址,即当前指令的地址减去程序的启始地址再加上0x7c00,这样的作法是为了得到物理地址,不依赖于编译器.*/
#define ABS(x) (x-_start+0x7c00)

        /* Print message string */
#define MSG(x)  movw $ABS(x), %si; call message

/*是将内存数据赋给al寄存器,这里使用机器码是为了实现兼容性*/
        /* XXX: binutils-2.9.1.0.x doesn't produce a short opcode for this. */
#define MOV_MEM_TO_AL(x)        .byte 0xa0;  .word x

        .file   "stage1.S"

        .text

        /* Tell GAS to generate 16-bit instructions so that this code works
           in real mode. */
        .code16

/*BIOS调用INT 19H中断将本程序移到0:0x7c00内存地址处开始执行,这是本程序的入口*/
.globl _start; _start:
        /*
         * _start is loaded at 0x7c00 and is jumped to with CS:IP 0:0x7c00
         */

        /*
         * Beginning of the sector is compatible with the FAT/HPFS BIOS
         * parameter block.
         */
         
/*跳转到after_BPB标记处,也就是跳过BPB,nop是为了指令对齐
*一般用来内存地址偶数对齐,比如有一条指令,占3字节,这时候使用nop指令,cpu 就可以从第四个字节处读取指令了*/
        jmp     after_BPB
        nop     /* do I care about this ??? */

        /*
         * This space is for the BIOS parameter block!!!!  Don't change
         * the first jump, nor start the code anywhere but right after
         * this area.
         */
         
/*下面这个.是一个特殊的标号,代表当前的地址,从开始处的_start处填充空间至_start+4处,相当于4个字节的空间.
但是,从_start开始后的jmp after_BPB和nop已经占用了3个字节的空间,相当于在它们的后面再用0填充1个字节的空间即可.*/ 
        . = _start + 4

/*下面定义了BPB,BPB(BIOS Parameter Block)表,描述逻辑盘结构组成*/
        /* scratch space */
mode:
        .byte   0
disk_address_packet:
sectors:
        .long   0
heads:
        .long   0
cylinders:
        .word   0
sector_start:
        .byte   0
head_start:
        .byte   0
cylinder_start:
        .word   0
        /* more space... */

/*和上面的_start+4一样,会把上条指令处填充0,直到STAGE1_BPBEND,因为STAGE1_BPBEND为0x3e,所以我们会把上条语句.word   0后面的空间填充为0.
        . = _start + STAGE1_BPBEND

        /*
         * End of BIOS parameter block.
         */

stage1_version:
        .byte   COMPAT_VERSION_MAJOR, COMPAT_VERSION_MINOR
boot_drive:
        .byte   GRUB_INVALID_DRIVE      /* the disk to load stage2 from */
force_lba:
        .byte   0
stage2_address:
        .word   0x8000
stage2_sector:
        .long   1
stage2_segment:
        .word   0x800

/*跳转到这里了,程序继续运行*/
after_BPB:

/* general setup */
/*cli是禁用中断,当你修改中断向量或修改堆栈指针时一般要禁用它,因为在进行这些时如果有中断产生,那么会破坏当前的环境,造成程序崩溃.当干完这些时就可以sti恢复中断*/
        cli             /* we're not safe here! */

        /*
         * This is a workaround for buggy BIOSes which don't pass boot
         * drive correctly. If GRUB is installed into a HDD, check if
         * DL is masked correctly. If not, assume that the BIOS passed
         * a bogus value and set DL to 0x80, since this is the only
         * possible boot drive. If GRUB is installed into a floppy,
         * this does nothing (only jump).
         */
/*下面的设定主要为了解决bios的bug,而不能正确引导的问题,如果安装在HDD(硬盘驱动器)上,将会覆盖掉jmp lf这条指令,在开机初始,BIOS加载完启动代码会把%dl寄存器设置成启动盘号,
* 通过testb判断dl寄存器是否是0x80,如果不是0x80,则将0x80赋值给dl寄存器.通过这种方式我们保证grub安装到硬盘,则DL必须是0x80,原因是0x80表示第一块硬盘,也就是MBR的所在.
* dl寄存器的值在bios通过int 19H时,它的值应该是80H,如果不是bootloader也会巧妙的解决这个问题.
BIOS引导时会把引导介质的代码赋值给DL寄存器
DL = 00h 1st floppy disk ( "drive A:" ) 
DL = 01h 2nd floppy disk ( "drive B:" ) 
DL = 80h 1st hard disk 
DL = 81h 2nd hard disk 
jmp 1f表示如果我们没有安装在硬盘驱动器,比如通过软盘引导,将会通过jmp跳转到标识符为1的地方.
*/
boot_drive_check:
        jmp     1f
        testb   $0x80, %dl
        jnz     1f
        movb    $0x80, %dl
1:

        /*
         * ljmp to the next instruction because some bogus BIOSes
         * jump to 07C0:0000 instead of 0000:7C00.
         */
         
/*这里通过ljmp(长跳转)到cs:ip = 0x0000:$ABS(real_start)这个地方执行指令.相当于real_start-_start+0x7c00.
在正常情况下BIOS会将MBR被加载到cs:ip = 0x0000:0x7c00处,而对映的物理地址就是(Segment value * 16) + Offset value,
而有些糟糕的BIOS会将其加载到07c0:0000上,其实这两个代表的物理地址是完全一样的,
即:0x0000*16+0x7c00=0x0000+0x7c00=0x7c00
0x07c0*16+0x0000=0x7c00+0x0000=0x7c00
有些人从来就不考虑这种事实,那就是大多数人常常把segment值设为0,这样引导代码就可以假定任何段寄存器都是0从而只对付ip里的偏移量,所以,在grub里,加上这么一个长转移,就防止了这类糟糕的BIOS带来的麻烦. */
        ljmp    $0, $ABS(real_start)


/*进入real_start了,ax清零,ds赋值0,ss赋值0,将STAGE1_STACKSEG(0x2000)赋值给sp,这样就设置了实模式下的堆栈段地址(栈顶位置)ss:sp = 0x0000:0x2000*/
real_start:

        /* set up %ds and %ss as offset from 0 */
        xorw    %ax, %ax
        movw    %ax, %ds
        movw    %ax, %ss

        /* set up the REAL stack */
        movw    $STAGE1_STACKSEG, %sp

/*置中断允许位*/
        sti             /* we're safe again */

/*先用MOV_MEM_TO_AL宏将boot_drive量存到al中,然后与0xff进行比较,用的是cmpb $0xff,%al ;je 1f.
cmpb指令是将两个操作数进行相减,对标志位的影响同sub指令,但是不保存结果.其中,此处用到的是zf标志位(因为是je指令),这样,当操作数相等(即相减为零时)zf被置1.
所以,cmpb和je一起使用时,就是指当操作数相等时,跳转至je制定的标号.
所以,在这里,若boot_drive等于0xff,则使用BIOS传递过来的默认的驱动器进行启动.
如果不是则运行movb %al,%dl指令,将boot_drive的值保存至dl中,表示由boot_drive的值确定启动设备.
在这里宏GRUB_INVALID_DRIVE的值为0xff,而boot_drive的指向的值也是0xff,所以执行je 1f,跳转到下面的标号1.*/

        MOV_MEM_TO_AL(ABS(boot_drive))  /* movb ABS(boot_drive), %al */
        cmpb    $GRUB_INVALID_DRIVE, %al
        je      1f
        movb    %al, %dl
1:

/*驱动器号信息压栈,这里的%dl是0x80*/  
        pushw   %dx

/*这里的MSG(notification_string)会输出GRUB字串到屏幕上,
MSG宏在程序上方定义,即#define MSG(x)  movw $ABS(x), %si; call message
通过movw将标号notification_string的地址赋值给寄存器%si,注:notification_string标号后面定义了GRUB字符串.
然后通过call调用子程序message,message子程序通过lodsb将si寄存器的内容逐一读出到%al寄存器,然后检查al是否为零.
如果不为零,表明字符还未传输完,此时会跳转到int 10h中断前,用int 10h的0xe子功能在屏幕上以telemode模式写字符,其中ah是子功能,这里是0xe,al是字符,bh是页,bl是前背景色.
如果为零,表示字符已经传输完成了(.string伪指令会在指定的字符串后加入一个字节的0).此时调用ret返回.*/

        MSG(notification_string)


/*首先判断是硬盘还是软盘或者根本就没有盘(如果不是硬盘,判断LBA或者CHS模式就没有意义了),所以,在判断硬盘是否支持LBA时,先判断是不是硬盘.
这里用testb $STAGE1_BIOS_HD_FLAG,%dl来判断,dl寄存器里装载的是磁盘号,有三大类情况:硬盘(0x80,0x81),软盘(0x00,0x01),无效的盘(0xff).
而前面的宏就是0x80,所以通过testb和jz指令判断,如果dl中不是80或81(也就是不是硬盘),就跳转到chs_mode函数下面.
在我们这里%dl为0x80,而$STAGE1_BIOS_HD_FLAG也是0x80,所以testb指令执行后,标志位z位为0,testb指令是按位与运算,如果寄存器%dl的值是0,那么z位将是1,即跳转到chs_mode*/

        testb   $STAGE1_BIOS_HD_FLAG, %dl
        jz      chs_mode

/*如果判断出是硬盘的话,再接着判断是否支持LBA,使用的工具就是BIOS的int 13h中断.通过BIOS调用 INT 0x13 来确定是否支持LBA.
ah = 41h表示检查LBA是否存在.
bx = 0x55aa表示MBR的标记为0x55AA
dl = 0x80表示第一台HDD(范围是0x80 ~ 0xff)
返回结果:
如果支持LBA,CF=0,下面的寄存器会受到影响,如下:
ah:扩展功能的主版本号( major version of extensions ) 
al:内部使用( internal use ) 
bx :AA55h ( magic number ) 
cx:Bits Description 
0 extended disk access functions 
1 removable drive controller functions supported 
2 enhanced disk drive (EDD) functions (AH=48h,AH=4Eh) supported. 
Extended drive parameter table is valid 
3~15 reserved (0) 
如果不支持LBA,CF=1,下面的寄存器值为:
ah = 0x01 ( invalid function ) 
*/
        movb    $0x41, %ah
        movw    $0x55aa, %bx
        int     $0x13

/*有的bios的int 13h中断会影响到dl,所以此处用pop和push指令将其保护起来*/
        popw    %dx
        pushw   %dx

/*如果不支持LBA,即CF位为1,那么将会通过jc chs_mode跳转到chs_mode标志处,再判断bx是不是aa55h,使用cmpw $0xaa55,%bx;jne chs_mode再判断一次,
如果bx里存的不是预期的返回值,同样不支持lba,也要进入chs_mode函数*/
        jc      chs_mode
        cmpw    $0xaa55, %bx
        jne     chs_mode

/*使用MOV_MEM_TO_AL宏将force_lba变量值传递到al,判断是否为0,不为0强行进入lba_mode.我们这里force_lba为0,所以不跳转到lba_mode.
然后判断cx,如果cx为0表明不支持扩展第一子集,这时也进入chs_mode函数,andw是对两个操作数进行与运算,并将与操作的结果赋值给寄存器,这里是%cx,如果结果为0,则将标志位z置为1.
最后总结进入chs_mode的条件,如下:
一)磁盘号非80h或81h,进入chs_mode 
二)int13h,41h子功能,返回cf为0,进入进入chs_mode 
三)int13h,41h子功能,返回bx不为aa55,进入chs_mode 
四)如果没有设置强制LBA,而且也不支持扩展第一子集,进入chs_mode 
五)其它情况,进入lba模式
*/
        MOV_MEM_TO_AL(ABS(force_lba))   /* movb ABS(force_lba), %al */
        testb   %al, %al
        jnz     lba_mode
        andw    $1, %cx
        jz      chs_mode

/*进入了LBA模式.下面的工作是把数据填加到上面的BPB中.*/
lba_mode:
        
        /*这个代码就是个废话,ecx寄存器被置入了一个无意义的值*/
        movl    0x10(%si), %ecx

        /*将标号disk_address_packet处的地址赋给si*/
        movw    $ABS(disk_address_packet), %si

        /*将[si-1]内存处置1,我们看到disk_adress_packet标号上面就是mode标号,此时值是0,我们在这里将它置为1,表示LBA扩展读,如果是0,就是CHS寻址读)*/
        movb    $1, -1(%si)

/*将stage2所在的扇区赋予ebx,这里stage2_sector是1*/
        movl    ABS(stage2_sector), %ebx

        /*在[si]和[si+1]处存放10和00(movw $0x0010,(si))
        movw    $0x0010, (%si)

        /*在[si+2]和[si+3]处存放01和00*/
        movw    $1, 2(%si)

        /*在[si+8][si+9][si+A][si+B]处存放0x01/0x00/0x00/0x00*/
        movl    %ebx, 8(%si)

        /*[si+6]和[si+7]处存放0x00和0x70 (#define STAGE1_BUFFERSEG        0x7000)*/
        movw    $STAGE1_BUFFERSEG, 6(%si)

/*在[si+4]和[si+5]处存放00和00,在[si+c][si+d][si+e][si+f]处存放0x00/0x00/0x00/0x00*/
        xorl    %eax, %eax
        movw    %ax, 4(%si)
        movl    %eax, 12(%si)


/*功能号AH:   41H 检测是否支持扩展INT 13H调用 BX=55AAH AH=0支持

42H 读磁盘扇区 
DL=磁盘号
DS:SI=调用结构的地址

调用结构:
disk_addr_pkt      struc
packet_size    db   16  ; disk_addr_pkt结构长度通常为16,大小为1字节
reserved      db   0  ; 保留,必须为0,大小为1字节
block_count   dw  ?    ; 读取扇区数目,本例为1个扇区,大小为2字节
buffer_addr   dd   ?  ; 缓冲区地址,这里是0000:7000,大小为4字节
block_num    dq   ?    ; 读取的绝对扇区起始号,这里是1,也就是相对1扇区的第二个扇区,大小为8字节.
disk_addr_pkt     ends

成功:AH=0
失败:AH=错误代码,设置标志位c*/

        movb    $0x42, %ah
        int     $0x13

/*如果出错,就跳转到chs_mode处*/
        jc      chs_mode

/*将7000赋值给bx寄存器*/
        movw    $STAGE1_BUFFERSEG, %bx
        jmp     copy_buffer


/*进入chs模式时,进行一些检测,来确定具体的情况.
首先就是int13h的08功能号的使用,使用08功能可以检测chs模式中硬盘的参数,保存在各寄存器里:
DL:驱动器类型,第一块软盘是0x00,第一块硬盘是0x80.
DH:最大磁头号(或说磁面数目).0表示有1个磁面,1表示有2个磁面.
CH:存放10位磁道柱面数的低8位(高2位在CL的D7,D6中).1表示有1个柱面,2表示有2个柱面,依次类推. 
CL:0~5位存放每磁道的扇区数目.6和7位表示10位磁道柱面数的高2位.
AX=0 
BH=0 
BL表示驱动器类型: 
1=360K 5.25 
2=1.2M 5.25 
3=720K 3.5 
4=1.44M 3.5 
ES:SI 指向软盘参数表 
如果成功返回参数,则进入final_init函数;但是如果调用失败,进位标志CF=1,AH存放错误信息码.表明不支持硬盘的chs模式(前面也判断了不支持lba),那就要考虑是不是软盘了.
再使用testb和jz指令,若dl是00或01,则认为是软盘,就跳转到floppy_probe函数执行(后成讨论此过程).
但是若连软盘也不是,只好准备报错了,跳转到hd_probe_error函数,这个函数调用MSG函数连同general_error函数一道输出"hard disk error"的字符。*/
chs_mode:
        movb    $8, %ah
        int     $0x13
        jnc     final_init

        testb   $STAGE1_BIOS_HD_FLAG, %dl
        jz      floppy_probe

        jmp     hd_probe_error


final_init:
/*将扇区数地址保存到si寄存器*/
        movw    $ABS(sectors), %si
        
        /*将mode设置为0,即sectors-1的地址*/
        movb    $0, -1(%si)

/*将dh寄存器中的磁头号保存到al寄存器,通过incw %ax将磁头号加1(因为磁头数是以0开始的),movl %eax,$(%si)将磁头数保存至[si+4][si+5][si+6][si+7]内存地址上*/
        xorl    %eax, %eax
        movb    %dh, %al
        incw    %ax
        movl    %eax, 4(%si)

/*cl中的0~5位存放的是扇区数,cl的6~7位存放的是柱面数高2位,先把cl寄存器的值保存dl寄存器中,所以dx逻辑左移2位后在dh中出现的两位就是柱面数的高2位,并且把这2位移到ah中,
  ch存放的柱面数低8为移至al中,这样ax里就是柱面数了.*/
        xorw    %dx, %dx
        movb    %cl, %dl
        shlw    $2, %dx
        movb    %ch,%al
        movb    %dh,%ah

/*同样的道理要进行incw %ax操作,并且把真正的柱面数放到地址为[si+8][si+9]的内存上*/
        incw    %ax
        movw    %ax, 8(%si)

/*由于我们之前把dl寄存器左移了2位,所以这里把dl寄存器的值保存在al寄存器中,再把al寄存器右移2位,最后把真正的扇区数保存在地址为[si][si+1][si+2][si+3]的内存上.
到这里我们把扇区数保存在[si][si+1][si+2][si+3]
把磁头数保存在[si+4][si+5][si+6][si+7]
把柱面数保存在[si+8][si+9]*/
        xorw    %ax, %ax
        movb    %dl, %al
        shrb    $2, %al

        movl    %eax, (%si)

setup_sectors:
/*然后在使用int 13h*0x02)功能前要进行必备的参数设置:
eax存放stage2的扇区编号(stage2_sector,这里为1)
清空edx寄存器,然后通过(stage2扇区数)/(扇区数)获得引导扇区数.注意对于div指令来说,eax恒定存放被除数,div后面的寄存器存放的是除数.
余数在edx中存放,第一个余数(扇区数)放到地址为[si+10]的内存上,将edx清零,再用(上一步除法的商)/(磁头数)得到的余数为磁头数,存放在[si+11]内存地址上.
商为柱面数并存放在eax中并同时保存至[si+12][si+13]内存地址上.然后将之前中断获得的柱面数与此处stage2所占柱面数相比较,如果stage2柱面数大,那么明显错误,程序将跳至geometry_error处
这里我们将stage2所在的扇区放在内存地址[si+10]上.
将stage2所有的磁头数放在内存地址[si+11].
将stage2所有的柱面数放在内存地址[si+12][si+13]
*/
        movl    ABS(stage2_sector), %eax
        
        xorl    %edx, %edx
        divl    (%si)

        movb    %dl, 10(%si)

        xorl    %edx, %edx 
        divl    4(%si)   

        movb    %dl, 11(%si)

        movw    %ax, 12(%si)

        cmpw    8(%si), %ax
        jge     geometry_error

/*
为完成int 13h中断(将stage2所在的扇区读入到7000:0000这段内存地址)
我们要做如果的设置:
%al = number of sectors(需要读的扇区数) 
%ah= 0x02(功能号) 
%ch = cylinder(起始柱面数) 
%cl = sector (bits 6-7 are high bits of "cylinder") 
%dh = head 
%dl = drive (0x80 for hard disk, 0x0 for floppy disk) 
%es:%bx = segment:offset of buffer */

        /*将[si+13]的内容赋值给dl(柱面数的高2位)并且左移6位*/
        movb    13(%si), %dl
        shlb    $6, %dl         /* shift left by 6 bits */
        
        /*将[si+10]的内容赋值给cl(扇区数),将扇区数放到cl中再增1*/
        movb    10(%si), %cl    /* get sector */
        incb    %cl             /* normalize sector (sectors go
                                        from 1-N, not 0-(N-1) ) */
                                        
        /*现在dl寄存器存放着柱面数高2位,cl寄存器的底6位存放着扇区数,通过orb将dl和cl两个寄存器组合在一起存放在cl寄存器中*/
        orb     %dl, %cl        /* composite together */
        
        /*将[si+12]的内容赋值给ch(柱面数的低8位)*/
        movb    12(%si), %ch    /* sector+hcyl in cl, cylinder in ch */

        /*然后恢复驱动器号(popw %dx),然后将磁头数放置到dh中*/
        popw    %dx
        movb    11(%si), %dh

/*将0x7000赋值给es并将bx清零,所以这里就是%es:%bx=0x7000:0x0000*/
        movw    $STAGE1_BUFFERSEG, %bx
        movw    %bx, %es        /* load %es segment with disk buffer */

        xorw    %bx, %bx        /* %bx = 0, put it at 0 in the segment */
        
        /*将功能号02赋值给ah寄存器,将al赋值给al寄存器,开始调用int 13h中断*/
        movw    $0x0201, %ax    /* function 2 */
        int     $0x13

        jc      read_error

/*将0x7000赋值给bx寄存器,这里同lba的方式是一样的*/
        movw    %es, %bx


/*程序进入copy_buffer,在chs和lba两种模式下都要来到这里,此时bx寄存器为0x7000*/
copy_buffer:

然后。然后popw %ds; popa还原寄存器。 */
        movw    ABS(stage2_segment), %es

        /*pusha:将所有的16位通用寄存器压入堆栈*/
        pusha
        /*将ds寄存器压力堆栈*/
        pushw   %ds

/*给cx赋值0x100*/
        movw    $0x100, %cx
        
        /*将bx寄存器内容赋值给ds寄存器,因为bx寄存器为0x7000,所ds寄存器为0x7000*/
        movw    %bx, %ds
        
        /*将si和di寄存器清零*/
        xorw    %si, %si
        xorw    %di, %di

/*CLD是清方向标志,把标识寄存器的D位置设置为零*/
        cld

/*使用rep和movsw指令将ds:si处连续的512字节内容传输到es:di指定的内存地址(0x8000:0x0000).其中,rep指令的含义就是重复执行后一句指令,每执行一次.cx减1,直至cx为0.
这也是前面cx赋值0x100(256)的原因.movsw每次传输一个字,256次就是512字节.*/
        rep
        movsw

/*还原ds寄存器,popa还原所有16位通用寄存器*/
        popw    %ds
        popa

        /* 跳转到0x8000:0x0000)继续运行stage2,代码中*(stage2_address)的星号是at&t汇编的规范:绝对跳转/调用(相对于与程序计数器有关的跳转/调用)操作数前面要加星号"*".
        到这里stage1就算彻底执行完了.*/
        jmp     *(stage2_address)

/* END OF MAIN LOOP */

/*
 * BIOS Geometry translation error (past the end of the disk geometry!).
 */
geometry_error:
        MSG(geometry_error_string)
        jmp     general_error

/*
 * Disk probe failure.
 */
hd_probe_error:
        MSG(hd_probe_error_string)
        jmp     general_error

/*
 * Read error on the disk.
 */
read_error:
        MSG(read_error_string)

general_error:
        MSG(general_error_string)

/* go here when you need to stop the machine hard after an error condition */
stop:   jmp     stop

notification_string:    .string "GRUB "
geometry_error_string:  .string "Geom"
hd_probe_error_string:  .string "Hard Disk"
read_error_string:      .string "Read"
general_error_string:   .string " Error"

/*
 * message: write the string pointed to by %si
 *
 *   WARNING: trashes %si, %ax, and %bx
 */

        /*
         * Use BIOS "int 10H Function 0Eh" to write character in teletype mode
         *      %ah = 0xe       %al = character
         *      %bh = page      %bl = foreground color (graphics modes)
         */
1:
        movw    $0x0001, %bx
        movb    $0xe, %ah
        int     $0x10           /* display a byte */
message:
        lodsb
        cmpb    $0, %al
        jne     1b      /* if not end of string, jmp to display */
        ret

        /*
         *  Windows NT breaks compatibility by embedding a magic
         *  number here.
         */

        . = _start + STAGE1_WINDOWS_NT_MAGIC
nt_magic:
        .long 0
        .word 0

        /*
         *  This is where an MBR would go if on a hard disk.  The code
         *  here isn't even referenced unless we're on a floppy.  Kinda
         *  sneaky, huh?
         */

part_start:
        . = _start + STAGE1_PARTSTART

probe_values:
        .byte   36, 18, 15, 9, 0


/*开始对软盘进行检查*/
floppy_probe:

/*此时si寄存器的值为part_start标识符地址*/
        movw    $ABS(probe_values-1), %si

/*此时要使用int 13h(0x00功能号)来进行软驱的复位.成功的话cf=0*/
probe_loop:
        /* reset floppy controller INT 13h AH=0 */
        xorw    %ax, %ax
        int     $0x13

/*软驱复位后,将[si]处的值赋给cl(cl是起始扇区数),由于循环,我们给了cl 4次机会,因为循环中有incw %si指令,所以si中的值是递增的,第一次运行incw后,si由part_start标识符地址转移到了probe_values标识符地址,依次给cl赋予0x24,0x12,0x0f,0x09,0x00五个值.*/
        incw    %si
        movb    (%si), %cl

        /*当cl的值是0时,说明4次循环都没有检测成功,这里会打印软盘检测失败*/
        cmpb    $0, %cl
        jne     1f

        MSG(fd_probe_error_string)
        jmp     general_error

fd_probe_error_string:  .string "Floppy"

1:
        /*准备调用int 13h(功能号是0x02),这和chs中的int 13h,ah=0x02是一样的.
          将0x7000赋值给bx寄存器
          功能号02赋值给ah寄存器
          因为读取1个扇区,这里将1赋值给al寄存器
          因为是软盘,起始柱面和磁头都是0
          之前我们已经把起始扇区(0x24或0x12等四个值)存入cl寄存器
          最后调用int 13h,将该扇区写入到内存地址0x7000处*/
        movw    $STAGE1_BUFFERSEG, %bx
        movw    $0x201, %ax
        movb    $0, %ch
        movb    $0, %dh
        int     $0x13

        /* if error, jump to "probe_loop" */
        jc      probe_loop


        /*成功后,dh赋值1,ch赋值0x4f,dh 设置为 79,
        表示柱面最大值为 79(80柱:0~79),dh 设置为 1 , 表示磁头数最大值为 1(2头:0~1),然后跳转至 final_init,
        在上文中关于final_init的分析 , 我们知道保存时会把柱面和磁头分别加 1 , 扇区不变,因此 , 在软盘加载时 , 将设置 Cylinder : Head : Sector = 80 : 2 : start_sector.
        最终就跳转至final_init函数处执行了.
        /* %cl is already the correct value! */
        movb    $1, %dh
        movb    $79, %ch

        jmp     final_init

        . = _start + STAGE1_PARTEND

/* the last 2 bytes in the sector 0 contain the signature */
        .word   STAGE1_SIGNATURE
        
        
/***********************************************************************/

参考:
http://zhumeng8337797.blog.163.com/blog/static/1007689142011828012299/
http://blog.chinaunix.net/uid-24774106-id-3500759.html
0 0
原创粉丝点击