启动阶段
在查看了start.S代码之后,就会对GRUB Legacy启动阶段有了更清晰的认识。在传统的GRUB启动中,一般分为stage1、stage1.5和stage2三个阶段,当然,stage1.5是可以忽略的,这样就直接从stage1跳转到了stage2。stage1.5主要是为stage2构建其所需要的文件系统。
目前只考虑GRUB legacy,不考虑GRUB 2.0的情况。像redhat/centos 5/6系列的系统一般使用的都是GRUB legacy代码,redhat/centos 7系列以后就开始使用GRUB 2.0(GRUB 2.0可以看作对stage1.5和stage2阶段代码进行了重构)。
在之前编写的《GRUB引导程序之第一阶段stage1.S分析》就是GRUB引导程序第一阶段完整的代码。本文所分析的start.S文件,虽然位于stage1.5代码中,但从功能上来看是不应该划分到1.5阶段的。
承前启后的start.S
start.S主要起到一个过渡功能,根据第一阶段分析可知,stage1.S的代码位于第一扇区,由BIOS加载到0x7c00地址处,该代码负责加载第二扇区的代码(start.S)至地址0x8000。
start.S主要从0x8000开始运行,并根据配置选项选择加载stage1.5阶段代码还是stage2.0阶段代码。如果只考虑三个阶段的情况,start.S加载的是stage1.5阶段代码,stage1.5将从第三个扇区开始,占用了若干个扇区的位置。start.S负责将stage1.5阶段的代码加载到0x2200地址处,并开启后面的引导之旅。
源码分析
#define ASM_FILE
#include <shared.h>
#ifndef STAGE1_5
#include <stage2_size.h>
#endif
#ifdef STAGE1_5
# define ABS(x) (x-_start+0x2000)
#else
# define ABS(x) (x-_start+0x8000)
#endif /* STAGE1_5 */
//打印信息
#define MSG(x) movw $ABS(x), %si; call message
.file "start.S"
.text
//通知GAS汇编器使用16位的指令集,因此现在工作在实模式。
.code16
//代码开始的位置,该部分代码被stage1.S加载到0x8000地址处
.globl start, _start
start:
_start:
//在stage1.S处我们在地址0x2000处开辟了堆栈,现在继续是该堆栈
//将DX进行压入堆栈,DX寄存器中存储的是磁盘号
pushw %dx
//将si压入堆栈,si在第一阶段指向的是sectors对应处的地址
pushw %si
//根据需要打印不同的信息
//我所找的代码是支持stage1.5阶段的,后续的分析均在支持1.5阶段基础之上
//此处将打印"Loading stage1.5",1.5阶段主要是用来构建文件系统供2阶段使用
//如果直接支持stage2,则打印“Loading stage2”
MSG(notification_string)
//打印的时候用到了si寄存器,现在将si寄存器的值还原
popw %si
//BOOTSEC_LISTSIZE的值为8,firstlist标号指向文件的尾部
//movw以为着将blocklist_default_start标号处的绝对地址赋值给了DI寄存器
movw $ABS(firstlist - BOOTSEC_LISTSIZE), %di
//blocklist_default_start标号处指向的值为下一个阶段所在逻辑扇区的号,此处为2
//扇区标号是从0开始的,此处意味着1.5阶段是从第三个扇区开始的。
//movl将1.5阶段的扇区号赋值给了EBP栈基址寄存器。
movl (%di), %ebp
//从该标号开始为一个循环,读取下个阶段(1.5阶段)的引导程序
bootloop:
//4(%di)是变址寻址,既4(%di)为di所代表的地址加4,指向的是blocklist_default_len处的地址
//该地址指向的值代表了后续扇区块的长度,现在是1.5阶段,默认是0值,在grub装载时会被填充修正为真正的stage1.5的扇区数
//cmp比较指令做算数减法运算,结果为0,将ZF设置为1,正确设置后将不为0ÿ