stage1
我最近学习linux 基础,有时候看到一些新的东西,就会去找资料,关于喜欢刨根问底的同学,这个博主的文章是很好的,很棒的,真心的大牛,虚心去学习,原来学的东西都感觉记忆不深刻,一知半解,而这位博主的文章很好的让我理解, 还远远达不上透彻,前路慢慢,努力学习,莫问前程
闲言少叙。我们首先看下Linux的启动过程流程图:
这个流程图是大牛M. Tim Jones在Inside Linux boot process中绘制的。清楚的show出了我们从摁开机键开始,计算机所做的事情。今天我要分享的是是stage1 bootloader部分,以及stage2 bootloader的部分。
需知,刚刚进入stage1 bootloader的时候,操作系统还没有开始运行(废话,GRBU他老人家就是召唤操作系统的),也没有文件系统的概念,认为直接运行操作系统可执行文件的,就可以洗洗睡了,GRUB他老人家面临的是一穷二白的局面啊。
下面我开始学习GRUB。当然了 ,现在主流的桌面都已经使用GRUB 2,GRUB 0.9x系统的目前统称为GRUB legacy,只修复bug,不再开发新的feature了。但是很多公司的服务器上跑的还是GRUB。比如我们产品用的还是GRUB。下面我做的实验都是在基于2.6.24内核的操作系统上做的,和家用的ubuntu操作系统不同。
MBR和stage1
MBR 是master boot record的缩写,也就是主引导记录。熟悉我博文的知道,我去年写过一篇分析MBR里面分区信息的文章。我们跳到/boot/grub/grub目录下,可以看到有个文件叫stage1,当然了还有stage2,稍安勿燥,我们会慢慢提到。我们下载一份GRUB 0.95或者GRUB 0.97的代码,我们可以看到在stage1目录下有个stage1.S的汇编文件。他们之间是什么关系。
先说MBR。MBR是磁盘的0柱面,0磁道,1扇区(扇区从1开始计数),既然是一个扇区,大小就确定了,就是512个字节。MBR严格的说有三部分组成
- [0,0x1be)前446字节是Bootstrap code area,是一段程序
- ·[0x1be,0x1fe),64个字节,是分区表信息。我前面的博文有重点分析。
- [0x1fe,0x1ff] ,签名信息,是这两个字节是55AA。
细心的筒子可以发现,/boot/grub/stage1文件的大小也是512字节一个扇区那么大。 我们可以比较下MBR和/boot/grub/stage1文件的内容。获取MBR方法比较简单,dd就可以了,如下:
dd if=/dev/sda of=mbr_0_512 bs=512 count=1
这样,我们就获得了磁盘的0柱面,0磁道 1扇区的内容,即MBR,存放在了mbr_0_512文件中: 看一下文件的内容:
在看下/boot/grub/目录下的是stage1文件。(不要被图片误导,我只是将stage1文件拷贝到了我的工作目录)
可以看到这两个文件大部分是相同的,其实这部分code部分是一样的,至于不一样的地方是
2 [0x40 ,0x50]中有部分不同,我暂时不懂
结论是/boot/grub/stage1文件和主引导记录MBR的code部分是相同的。事实上这份代码是从grub源代码的stage1/stage1.S汇编出来的。stage1.S是grub的第一个文件,便以后编译后产生的代码,正好是512字节,不是正好,是必须,否则无法放入1个扇区。
这个MBR的信息是grub安装上去的,方法如下:
stage1 源码分析
stage1阶段的源代码就是grub源码中stage/stage1.S,可惜他老人家是爱at&t风格的汇编,折磨的我七荤八素,死去活来,看了网上一些前辈的文章,总算有了一些心得体会。还是我常说的那句话,光荣属于前辈!
故事从哪里讲起呢,还是从我们按下电源开关开始讲起。呵呵。
当我们按下开机键,进入系统启动阶段,什么BiOS, POST(Power-On Self Test加电自检),反正是一陀名词一陀事,这些我们统统不管,我们就从系统BIOS做的最后一件事开始讲起,BIOS最后一件事:根据用户指定的启动顺序从软盘、硬盘或光驱启动MBR。在这个过程中会按照启动顺序顺序比较其放置MBR的位置的结尾两位是否为0xAA55,通过这种方式判断从哪个引导设备进行引导。在确定之后,将该引导设备的MBR内容读入到0x7C00的位置,并再次判断其最后两位,当检测正确之后,进行阶段1的引导,从此进入第二阶段 stage1 bootloader阶段。
简单地说,就是BIOS执行INT 0x19,加载MBR内容至0x7c00,然后跳转执行
且慢,为啥是0x7c00位置呢?我边访名医,终于找到了一篇相关的博文《为嘛BIOS将MBR读入0x7C00地址处(x86平台下)》,这兄弟对系统启动也颇有兴趣,有好多博文写的很优秀,我跟他学了很多。英文好的筒子可以直接看Why BIOS loads MBR into 0x7C00 in x86 ?
简单的说,0x7c00=32KB-1024B,是32K的最后一个KB,这个magic number不是intel决定的,所以我们在X86相关的文档中无法找到这个magic number的说明,这个magic number属于 BIOS specifiction 。这个0x7c00 是 IBM PC 5150 BIOS developer team 决定的。
BIOS developer team decided 0x7C00 because:
- They wanted to leave as much room as possible for the OS to load itself within the 32KiB.
- 8086/8088 used 0x0 - 0x3FF for interrupts vector, and BIOS data area was after it.
- The boot sector was 512 bytes, and stack/data area for boot program needed more 512 bytes. So, 0x7C00, the last 1024B of 32KiB was chosen.
跑了半天题,我们继续。我们把MBR的code加载到了0x7c00,开始执行MBR处的代码,下面重点分析MBR处的代码,即grub源码中的stage1/stage1.S
jmp after_BPB
nop /* do I care about this ??? */
. = _start + 4
一开始是个跳转指令,直接跳转到after_BPB,后面的NOP就执行不到了。ater_BPB在后面有定义:对于mbr二进制文件而言:
头两个字节0xeb48,eb是JMP指令,第三个字节是0x90,这个字节是NOP指令。
after_BPB:
/* general setup */
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, do
* "orb $0x80, %dl", otherwise "orb $0x00, %dl" (i.e. nop).
*/
.byte 0x80, 0xca
首先是cli指令,禁用中断然后是显示80ca这个二进制码。看下注释,这个80ca的含义是orb $0x80,%dl.意思是给dl寄存器的赋值80。
DL = 00h 1st floppy disk ( “drive A:” )
DL = 01h 2nd floppy disk ( “drive B:” )
DL = 80h 1st hard disk
DL = 81h 2nd hard disk
因为我们是磁盘加载的MBR,所以我们dl里面存的是0x80。接下来分析:
ljmp $0, $ABS(real_start)
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(ABS(boot_drive)) /* movb ABS(boot_drive), %al */
cmpb $GRUB_INvalid_DRIVE, %al
je 1f
movb %al, %dl
进入real_start了,ax清零,ds赋值0,ss赋值0,将STAGE1_STACKSEG(0×2000)赋值给sp,这样就设置了实模式下的堆栈段地址(栈顶位置)ss:sp = 0×0000:0×2000。接着置中断允许位。然后将dl寄存器中的值拷贝到al寄存器,然后将al寄存器的值和0xFF比较,对于我们的场景来说,我们是al里面存的是0x80,所以,不等于0xFF,不用跳转,继续执行将al的0x80拷贝到dl寄存器中。
1:
/* save drive reference first thing! */
pushw %dx
/* print a notification message on the screen */
MSG(notification_string)
/* do not probe LBA if the drive is a floppy */
testb $STAGE1_BIOS_HD_FLAG, %dl
jz chs_mode
/* check if LBA is supported */
movb $0x41, %ah
movw $0x55aa, %bx
int $0x13
notification_string是GRUB,这一段截至到MSG是显示GRUB到屏幕上,因为这个MSG是细节,我们按下不表。总是作用是屏幕显示GRUB。我们还记得,MBR内容里面有如下信息:
testb 这部分是探测drive是否是硬盘,如果不是硬盘是软盘,直接采用CHS_MODE,就不用费事判断了。我们知道,80h和81h是硬盘,所以探测对应bit位。如果我们的启动设备是硬盘,按么我们需要检测LBA是否支持。
通过 BIOS 调用 INT 0x13 来确定是否支持扩展,LBA 扩展功能分两个子集 , 如下 :
第一个子集提供了访问大硬盘所必须的功能 , 包括:
****************************************************************
1.检查扩展是否存在 : ah = 41h , bx = 0x55aa , dl = drive( 0×80 ~ 0xff )
2.扩展读 : ah = 42h
3.扩展写 : ah = 43h
4.校验扇区 : ah = 44h
5.扩展定位 : ah = 47h
6.取得驱动器参数 : ah = 48h
****************************************************************
第二个子集提供了对软件控制驱动器锁定和弹出的支持 ,包括:
****************************************************************
1.检查扩展 : ah = 41h
2.锁定/解锁驱动器 : ah = 45h
3.弹出驱动器 : ah = 46h
4.取得驱动器参数 : ah = 48h
5.取得扩展驱动器改变状态: ah = 49h
****************************************************************
我们采用的是ah=41h,bx=0x55aa,dl=0x80,所以是检查扩展是否存在。这个操作会改变CF标志位的值。如果支持LBA,那么CF=0,否则CF=1。
/* use CHS if fails */
jc chs_mode
cmpw $0xaa55, %bx
jne chs_mode
/* check if AH=0x42 is supported if FORCE_LBA is zero */
MOV_MEM_TO_AL(ABS(force_lba)) /* movb ABS(force_lba), %al */
testb %al, %al
jnz lba_mode
andw $1, %cx
jz chs_mode
我们刚才BIOS用 INT 0x13探查是否采用LBA模式。存在下面集中情况:
- 启动设备不是0x80,0x81,压根不探查,直接采用CHS 模式
- 探查结果 CF=1,二话不说,跳转到CHS模式
- CF=0是否就采用LBA呢?也不一定,还需要判断bx==0x55aa,bx==0x55aa,采用LBA模式,否则CHS模式
有一个FORCE_LBA Byte,如果这个位是1,那么直接采用LBA MODE,这个位是哪个呢?
0x41对应的00,表示FORCE_LBA是zero。
接下来,就是花开两朵,各表一枝,一枝叫CHS,另一枝叫LBA模式。 CHS已经人老珠黄,它是硬盘容量很小的那个时代留下的遗产。
C表示Cylinders
H表示Heads
S表示Sectors
其中:
磁头数(Heads)表示硬盘总共有几个磁头,也就是有几面盘片, 最大数为 255 (用 8 个二进制位存储)。从0开始编号。
柱面数(Cylinders) 表示硬盘每一面盘片上有几条磁道,最大数为 1023(用 10 个二进制位存储)。从0开始编号。
扇区数(Sectors) 表示每一条磁道上有几个扇区, 最大数为 63(用 6个二进制位存储)。从1始编号。
而现在的硬盘远大于8.414 GB(按照硬盘厂商常用的单位的计算) ,CHS寻址方式已不能满足要求。可到目前为止, 人们常说的硬盘参数还是这古老的 CHS参数。那么为什么还要使用这些参数?向下兼容。
既然CHS已经人老珠黄,我们也没必要在它身上浪费时间了(这话说的,怎么和陈世美这么像?!)我们关注的重点是LBA MODE.
lba_mode:
/* save the total number of sectors */
movl 0x10(%si), %ecx
/* set %si to the disk address packet */
movw $ABS(disk_address_packet), %si
/* set the mode to non-zero */
movb $1, -1(%si)
movl ABS(stage2_sector), %ebx
/* the size and the reserved byte */
movw $0x0010, (%si)
/* the blocks */
movw $1, 2(%si)
/* the absolute address (low 32 bits) */
movl %ebx, 8(%si)
/* the segment of buffer address */
movw $STAGE1_BUFFERSEG, 6(%si)
xorl %eax, %eax
movw %ax, 4(%si)
movl %eax, 12(%si)
/*
* BIOS call "INT 0x13 Function 0x42" to read sectors from disk into memory
* Call with %ah = 0x42
* %dl = drive number
* %ds:%si = segment:offset of disk address packet
* Return:
* %al = 0x0 on success; err code on failure
*/
movb $0x42, %ah
int $0x13
/* LBA read is not supported, so fallback to CHS. */
jc chs_mode
movw $STAGE1_BUFFERSEG, %bx
jmp copy_buffer
然后将标号disk_address_packet处的地址赋给si,再接着将[si-1]内存处置1(也就是mode被置1,表示LBA扩展读;如果是0,就是CHS寻址读).
movl ABS(stage2_sector), %ebx,把要加载或拷贝的扇区数传给ebx寄存器。
由si及其偏移量指向的内存保存着磁盘参数块,如下:
******************************************************************
偏移量 大小 位数 描述
00h BYTE 8 数据块的大小 (10h or 18h)
01h BYTE 8 保留,必须为0
02h word 16 传输数据块数,传输完成后保存传输的块数
04h dword 32 传输时的数据缓存地址
08h QWORD 64 起始绝对扇区号(即起始扇区的LBA号码)
******************************************************************
或者如下图所示:
movw $0x0010, (%si) 执行的结果是si[0] =10h, si[1]=00h
movw $1, 2(%si) 执行的结果是si[2] =01h, si[3]=00h
/* the segment of buffer address */
movw $STAGE1_BUFFERSEG, 6(%si) 执行的记过是si·[6]=00h si[7]=07h
movl ABS(stage2_sector), %ebx
movl %ebx, 8(%si)
stage2_sector:
.long 1
这个stage2_sector在二进制文件中的偏移量是0x44
我们si[8]存储的long类型是起始扇区的LBA号码,从1号扇区也就是0柱面,0磁道,2扇区。,si·[2]记录着要传输多少个扇区,值为1,只传输一个数据块,读取后,将扇区的内容存储到si偏移量为04h 05h 6h、7h确定的内存区域0x7000:0x0000上了。这是int 13h(42)的作用。
最后一段代码是
copy_buffer:
movw ABS(stage2_segment), %es
/*
* We need to save %cx and %si because the startup code in
* stage2 uses them without initializing them.
*/
pusha
pushw %ds
movw $0x100, %cx //循环0x100次,即256次
movw %bx, %ds
xorw %si, %si
xorw %di, %di
cld
rep
movsw //每次拷贝2字节,一个word
popw %ds
popa
/* boot stage2 */
jmp *(stage2_address)
这段代码的含义是将刚才搬到0x7000:000的512字节,再次搬到0x8000:0000
OK,我们很痛苦的跟踪了stage1.S的代码,最后得到的结论是: stage1.S这汇编出来的512个字节代码的作用是将0柱面,0磁道,2扇区的512字节copy到0x8000处。
很失望吧,费了半天劲,最后只得到这么一点点结论。人生就是如此,付出不一定有回报,对于我们而言,只管努力,莫问前程,才能活得心平气和。
我们读到的512字节是干嘛的呢?啥时候才能看到GRUB的选择OS的界面呢?江湖传说的stage2到底是怎么回事,江湖传说的stage1.5是怎么回事,且听下回分解,我是累了,不写了。
参考文献:
1 Stage1.s源代码分析 (这篇文章非常棒,很多内容都是受惠于这篇博文)
2 维基百科
3 GRUB 源代码分析 (很棒的一个文档)
4 The mysteries arround "0x7C00" in x86 architecture bios bootloader
5 Linux/Unix系统的引导过程(从加电到操作系统运行
还要多多向这位大佬学习
转载出处:GRUB启动分析之stage1
文章最后发布于: 2019-03-11 21:03:14
相关阅读
人工智能有多火?2018年,联想杨元庆(全国人大代表)表示将要把北京打造成AI之城;搜狗公司王小川提出将人工智能应用到医疗行业,构建新
1.题目链接。题目大意:给出两条空间中不平行的直线,求出这两条直线的距离和对应的点。 2.分析:在空间中我们知道,直线有三种关系:相交,
在premiere cc2017版本中字幕不见了,变成了图形层,感觉没有老版本好用,该怎么给视频添加字幕呢?下面我们就来看看详细的教程。1、首先
老实说,知道唐麦已经许久了,因为一位恨不得把所有能换的都换成某米产品的基友在第一次入耳了一副唐麦A 8之后竟然相见恨晚地成了
文章来源 |《哈