前些天群友@Seraph_JACK在整引导,于是我也跟着云了一下。结果发现,我对引导相关的了解着实拉跨。所以趁此机会,正好完整学习一下引导相关的知识。本篇文章大致会涉及MBR、GPT、UEFI等内容,以使用Grub引导Linux为例,来分析启动的具体过程。
启动综述
对于PC来说,启动电脑的目的是为了启动操作系统。而操作系统无外乎就是某些特定的程序。但是如今,我们通常会将操作系统程序安装在外存,也就是硬盘上。因此,我们就需要先将操作系统程序读入并装载至内存,并设置初始环境,这一步被称为引导(Boot)操作系统。不过由于操作系统多种多样,引导操作系统的方式也不尽相同。因此,我们通常把引导操作系统的程序也随操作系统放在硬盘上。那么问题来了,谁来引导操作系统的引导程序呢?
很显然,这个引导程序不能安装在硬盘上。毕竟,我们就是要引导硬盘上的程序嘛!因此,这个程序通常安装在主板的ROM、Flash上。此外,由于引导时我们也需要访问、识别其他硬件设备(除了硬盘,还有光驱、USB等等),因此这个程序也需要具备访问其他硬件设备的能力。此外它还要在操作系统的启动过程中,给操作系统提供访问硬件设备的方法。因此事实上,这个引导程序已经承担起了部分操作系统的工作。
这个操作系统就BIOS(Basic Input/Output System),它运行在实模式,如今几乎只用于从其他设备引导系统或进行设备调试。它就是电脑通电后运行的第一个程序。
Legacy Boot与MBR
使用BIOS的引导方式就称为Legacy。从名字可以看出(Legacy指遗产),这种引导方式是非常古老的。在BIOS启动流程中,电脑通电后就会开始执行BIOS程序。BIOS在通电后首先进行自检(POST),然后BIOS会识别并加载各种设备,比如CPU、RAM、DMA、硬盘、光驱等等。
之后,BIOS将会查找所有引导设备,并尝试运行其上的引导程序。这个查找顺序就是我们在BIOS设置页面设定的启动顺序。对于每个引导设备,BIOS加载它的第一个扇区(共512字节,称为引导扇区),而这个扇区内就存放着我们的引导程序,即MBR(Master Boot Record,主引导记录)。
MBR总共分为三段:引导代码(446字节)、硬盘分区表(64字节)、MBR标志(2字节,固定是0x55AA)。不难看出,MBR实际上除了引导系统,还记录了磁盘的分区。
不过有一个问题,就是引导代码仅仅只有446字节。在BIOS被设计的那个年代,446字节也许够用,但是对于当代的PC系统446字节就显得捉襟见肘了。怎么办呢?其实也好办,我们只要再套一层娃——用MBR来引导操作系统的引导程序。
所以,Legacy Boot的整体过程大概就是这样了:
- BIOS启动,进行自检
- 按照顺序遍历设备,找到有MBR的启动设备
- MBR引导操作系统的引导程序(Linux通常是Grub,Windows则是bootmgr)
- 操作系统的引导程序引导操作系统
MBR In Practice
在Linux下,我们可以使用指令dump出MBR。以我的电脑为例。虽然我目前的磁盘是GPT的(不同的分区方式,下一篇就讲),但是为了防止为MBR设计的程序错误修改磁盘,因此GPT盘的第一个扇区通常会写入保护MBR(Protective MBR)。使用gdisk
指令就可以查看磁盘当前是否写入了MBR:
$ sudo gdisk /dev/nvme0n1 GPT fdisk (gdisk) version 1.0.5 The protective MBR's 0xEE partition is oversized! Auto-repairing. Partition table scan: MBR: protective BSD: not present APM: not present GPT: present Found valid GPT with protective MBR; using GPT. Command (? for help): q
可以看到MBR段是protective
。使用dd
指令,就可以将MBR dump到mbr.bin
文件。
$ sudo dd if=/dev/nvme0n1 of=mbr.bin bs=1 count=512 $ hexdump mbr.bin -C | tail -n 5 000001c0 02 00 ee ff ff ff 01 00 00 00 ff ff ff ff 00 00 |................| 000001d0 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
可以看到,末尾的标志确实是0x55AA
。不过由于保护性MBR只是为了防止磁盘受更改,因此它的代码段并没有实际用于启动的代码。
Real World MBR
实际使用中的MBR还真不太好找。如今,Windows已经唯一指定UEFI了,因此我们只能在其他阵营里找MBR。我最终选择采用Linux的Live OS的MBR来进行分析。Live OS通常是用来安装或检修Linux系统的阉割版Linux,类似于Windows阵营的PE系统。你可以在这里找到它的十六进制格式:https://pastebin.com/DE2hkQQj。
Linux通常使用Grub进行引导,比如这个Live OS。因此这一段MBR当然就是Grub写入的MBR了(grub-install
指令)。在偏移180h处,可以看到6位字符GRUB \0
,这就是Grub MBR的标志。
在Grub MBR的标志之后,是一些错误信息:“Geom”、“Hard”、“Disk”、“Read”、“ Error”。Grub用单词的形式存储这些错误信息来节省空间。而之后的代码段就是用来打印错误信息了。
我们可以从错误信息的打印开始对引导逻辑进行分析。在反汇编时要注意,由于BIOS是运行在实模式下的,因此要做特殊的配置。此外,MBR在运行时会被BIOS装载到内存地址0x7C00
,因此需要对反汇编器的段位置进行调整。反汇编后可以看到,错误文本后是一段打印字符串的子程序。
在其余位置,可以找到它的调用。一般的调用过程是先打印错误的项目,然后再打印“ Error”。
在显示错误之后,程序进入死循环,等待用户重启。既然知道了错误信息的打印,顺藤摸瓜,就可以找到相关的启动代码了。
Grub MBR为例的磁盘引导
最开始的若干代码仅仅是初始化运行环境。之后在7C8C
时,程序开始检查BIOS所支持的磁盘读写模式。
这里简单的进行说明。在BIOS下,通常有两种方式对磁盘进行读写:CHS模式和LBA模式。其中,CHS(cylinders-heads-sectors)是比较原始的读取方式,它使用(磁头, 磁道, 扇区)
对某一扇区进行定位。而LBA(Logical Block Address)是之后扩展的读取方式,它只需要给出目标区块的偏移就可以进行读取了。对于磁盘来说,一个区块等同于一个扇区。此处的一系列判断实际上是为了判断BIOS是否支持LBA读取,之后选择具体的读取模式。因为两个模式的读取实际上大同小异,所以我们直接来分析较为简单LBA模式。
这里拼装了调用所需的参数(即disk address packet),而7DB0
处就是下一阶段程序在硬盘上的位置了(严格来说是这个程序在硬盘上第一个扇区的位置)。
读取完毕后,程序跳转至[7C5A]
,也就是下一阶段代码在内存中的位置。对于Grub来说通常是0x8000
。由此,MBR所负责的引导部分(Grub中称为Stage1)结束。
BTW,这段程序的源码位于Grub项目的boot.S。所以你费那么大劲分析老半天是为了啥?
MBR只是程序
确实。你或许会问,那后66字节算什么呢,那些难道也是程序嘛?确实,一般来说那些都不是程序。但是你细品MBR的加载过程,其实只是运行了一段程序而已,所以你完全可以在后64字节写程序(最后2字节的标志还是要留的)。
之前我提到,BIOS实际上是一个低层次的操作系统。所以你完全可以用这510字节写点引导之外的程序。事实上,还真有人干过这事。YouTube频道主8-Bit Guy就介绍过两个写在引导扇区的游戏(B站熟肉:BV1gE411b7M4)。好家伙,系统都不进了,搁BIOS那玩游戏呢。
Reference
- BIOS – Wikipedia(https://en.wikipedia.org/wiki/BIOS#Operation)
- Master boot record – Wikipedia(https://en.wikipedia.org/wiki/Master_boot_record)
- GRUB Boot Manager MBR/Boot Sector(https://thestarman.pcministry.com/asm/mbr/GRUB.htm)
评论