关于启动引导的那些事儿(上) : Legacy Boot

前些天群友@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的整体过程大概就是这样了:

  1. BIOS启动,进行自检
  2. 按照顺序遍历设备,找到有MBR的启动设备
  3. MBR引导操作系统的引导程序(Linux通常是Grub,Windows则是bootmgr)
  4. 操作系统的引导程序引导操作系统

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

  1. BIOS – Wikipedia(https://en.wikipedia.org/wiki/BIOS#Operation
  2. Master boot record – Wikipedia(https://en.wikipedia.org/wiki/Master_boot_record
  3. GRUB Boot Manager MBR/Boot Sector(https://thestarman.pcministry.com/asm/mbr/GRUB.htm
分享到

KAAAsS

喜欢二次元的程序员,喜欢发发教程,或者偶尔开坑。(←然而并不打算填)

相关日志

  1. 没有图片
  2. 没有图片

评论

还没有评论。

在此评论中不能使用 HTML 标签。