分析数据采集自bochs+FreeBSD7.0环境,分析过程中参考了Luonix Luo和qiuhanty关于boot0.s的分析文章。
#
# Copyright (c) 1999 Robert Nordier
# All rights reserved.
#
# Redistribution and use in source and binary forms are freely
# permitted provided that the above copyright notice and this
# paragraph and the following disclaimer are duplicated in all
# such forms.
#
# This software is provided "AS IS" and without any express or
# implied warranties, including, without limitation, the implied
# warranties of merchantability and fitness for a particular
# purpose.
#
# $FreeBSD: src/sys/boot/i386/mbr/mbr.s,v 1.7 2004/08/28 08:39:35 yar Exp $
# A 512 byte MBR boot manager that simply boots the active partition.
.set LOAD,0x7c00 # Load address
.set EXEC,0x600 # Execution address
.set PT_OFF,0x1be # Partition table
.set MAGIC,0xaa55 # Magic: bootable
.set FL_PACKET,0x80 # Flag: try EDD
.set NHRDRV,0x475 # Number of hard drives
.globl start # Entry point
.code16
#
# Setup the segment registers for flat addressing and setup the stack.
#
# 在/sys/boot/i386/mbr/Makefile中定义的LDFLAGS包含了
# "-e start -Ttext ${ORG}",即指定程序入口为"start",
# 并指定.text section的起始为止在绝对地址ORG处,而ORG
# 在同一Makefile中被定义为0x600,因此,出现在汇编指令
# 中的"$start"即表示0x600。这部分引导代码是被bios加载到
# 0x7c00的,所以此处"start:"标号对应的"cld"指令的地址
# 就是0x7c00,这是第一条引导指令。此时,cpu主要寄存器的
# 内容如下:
# rax: 0x00000000:0000aa55 rcx: 0x00000000:00000000
# rdx: 0x00000000:00000080 rbx: 0x00000000:00000000
# rsp: 0x00000000:0000ffda rbp: 0x00000000:00000000
# rsi: 0x00000000:ffff0000 rdi: 0x00000000:0008ffac
# r8 : 0x00000000:00000000 r9 : 0x00000000:00000000
# r10: 0x00000000:00000000 r11: 0x00000000:00000000
# r12: 0x00000000:00000000 r13: 0x00000000:00000000
# r14: 0x00000000:00000000 r15: 0x00000000:00000000
# rip: 0x00000000:00007c00
# eflags 0x00000082
start: cld # String ops inc # 保证后续字符串操作为增向。
xorw %ax,%ax # Zero
movw %ax,%es # Address
movw %ax,%ds # data
movw %ax,%ss # Set up
movw $LOAD,%sp # stack # 将ax、es、ds、ss清0,并将sp指向"LOAD",LOAD在本文中被
# 定义为0x7c00,这是bios加载引导扇区的地址,也即"start:"
# 标号处的"cld"指令的地址。此时,cpu主要寄存器的内容如下:
# rax: 0x00000000:00000000 rcx: 0x00000000:00000000
# rdx: 0x00000000:00000080 rbx: 0x00000000:00000000
# rsp: 0x00000000:00007c00 rbp: 0x00000000:00000000
# rsi: 0x00000000:ffff0000 rdi: 0x00000000:0008ffac
# r8 : 0x00000000:00000000 r9 : 0x00000000:00000000
# r10: 0x00000000:00000000 r11: 0x00000000:00000000
# r12: 0x00000000:00000000 r13: 0x00000000:00000000
# r14: 0x00000000:00000000 r15: 0x00000000:00000000
# rip: 0x00000000:00007c0c
# eflags 0x00000046
#
# Relocate ourself to a lower address so that we are out of the way when
# we load in the bootstrap from the partition to boot.
#
movw $main-EXEC+LOAD,%si # Source
movw $main,%di # Destination
movw $0x200-(main-start),%cx # Byte count
rep # Relocate
movsb # code # 由于bios将本段引导代码的入口加载至0x7c00,而本段代码链接时
# 指定的绝对入口地址是0x600,因此须将本段代码拷贝至0x600处才能
# 正确执行后续与地址相关的指令。下面的jmp指令是在0x7c00区域
# 执行的最后一条指令,jmp之后就开始在0x600区域继续执行新建的
# 引导代码复本了,而在0x600区域执行的第一条语句就是"main:"标号
# 对应的xorw指令,因此0x7c00区域中从"start:"到"main:"之间的
# 内容是不用拷贝到0x600的。EXEC在本文件中定义为0x600,这是
# "start:"标号的链接地址,LOAD则是bios对本段引导代码的初始加载
# 位置,即0x7c00,因此,main-EXEC+LOAD就是0x7c00加上"main:"距
# "start:"的偏移,即"xorw %si,%si"指令在0x7c00区域的地址,引导
# 代码的拷贝从这里开始。拷贝的目的地则直接使用"main:"标号的链接
# 地址,即"xorw %si,%si"指令在0x600区域的地址。在计算需要拷贝的
# 字节数时需要从引导扇区尺寸512字节(0x200)中减去
# "main:"和"start:" 之间的内容。执行实际拷贝指令之前cpu主要
# 寄存器的内容如下:
# rax: 0x00000000:00000000 rcx: 0x00000000:000001e6
# rdx: 0x00000000:00000080 rbx: 0x00000000:00000000
# rsp: 0x00000000:00007c00 rbp: 0x00000000:00000000
# rsi: 0x00000000:ffff7c1a rdi: 0x00000000:0008061a
# r8 : 0x00000000:00000000 r9 : 0x00000000:00000000
# r10: 0x00000000:00000000 r11: 0x00000000:00000000
# r12: 0x00000000:00000000 r13: 0x00000000:00000000
# r14: 0x00000000:00000000 r15: 0x00000000:00000000
# rip: 0x00000000:00007c15
# eflags 0x00000046
# 此时,源地址是0x7c1a,目的地址是0x061a,分别是0x7c00和0x600
# 偏移0x1a的位置,这正是"main:"和"start:"之间的距离。拷贝完成
# 之后cpu主要寄存器的内容如下:
# rax: 0x00000000:00000000 rcx: 0x00000000:00000000
# rdx: 0x00000000:00000080 rbx: 0x00000000:00000000
# rsp: 0x00000000:00007c00 rbp: 0x00000000:00000000
# rsi: 0x00000000:ffff7e00 rdi: 0x00000000:00080800
# r8 : 0x00000000:00000000 r9 : 0x00000000:00000000
# r10: 0x00000000:00000000 r11: 0x00000000:00000000
# r12: 0x00000000:00000000 r13: 0x00000000:00000000
# r14: 0x00000000:00000000 r15: 0x00000000:00000000
# rip: 0x00000000:00007c17
# eflags 0x00000046
#
# Jump to the relocated code.
#
jmp main-LOAD+EXEC # To relocated code # 跳转至0x600区域中"main:"标号对应的指令位置,这是在0x7c00
# 区域执行的最后一条指令。跳转完成之后cpu主要寄存器的内容如下:
# rax: 0x00000000:00000000 rcx: 0x00000000:00000000
# rdx: 0x00000000:00000080 rbx: 0x00000000:00000000
# rsp: 0x00000000:00007c00 rbp: 0x00000000:00000000
# rsi: 0x00000000:ffff7e00 rdi: 0x00000000:00080800
# r8 : 0x00000000:00000000 r9 : 0x00000000:00000000
# r10: 0x00000000:00000000 r11: 0x00000000:00000000
# r12: 0x00000000:00000000 r13: 0x00000000:00000000
# r14: 0x00000000:00000000 r15: 0x00000000:00000000
# rip: 0x00000000:0000061a
# eflags 0x00000046
#
# Scan the partition table looking for an active entry. Note that %ch is
# zero from the repeated string instruction above. We save the offset of
# the active partition in %si and scan the entire table to ensure that only
# one partition is marked active.
#
main: xorw %si,%si # No active partition
movw $partbl,%bx # Partition table
movb $0x4,%cl # Number of entries # 首先清除si寄存器。然后将位于本段引导扇区代码尾端的分区表
# 的地址写入bx寄存器,将分区表的条目数写入cl。此时cpu主要
# 寄存器的内容如下:
# rax: 0x00000000:00000000 rcx: 0x00000000:00000004
# rdx: 0x00000000:00000080 rbx: 0x00000000:000007be
# rsp: 0x00000000:00007c00 rbp: 0x00000000:00000000
# rsi: 0x00000000:ffff0000 rdi: 0x00000000:00080800
# r8 : 0x00000000:00000000 r9 : 0x00000000:00000000
# r10: 0x00000000:00000000 r11: 0x00000000:00000000
# r12: 0x00000000:00000000 r13: 0x00000000:00000000
# r14: 0x00000000:00000000 r15: 0x00000000:00000000
# rip: 0x00000000:00000621
# eflags 0x00000046
# 此处写入bx的分区表地址是0x7be,这是距引导扇区尾端66字节
# 的地方,分区表一共四个条目,因此cl的值是4。每个条目占用
# 16字节,共64字节,最后的2个字节是用来存放幻数0xaa55的。
# 参见本文末尾对"partbl:"的定义。
# 此时,0x7be之后66个字节的内容如下:
# 0x7be : 0x80 0x01 0x01 0x00 0xa5 0x0f 0xff 0xff
# 0x7c6 : 0x3f 0x00 0x00 0x00 0xa1 0xff 0x7f 0x00
# 0x7ce : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
# 0x7d6 : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
# 0x7de : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
# 0x7e6 : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
# 0x7ee : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
# 0x7f6 : 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
# 0x7fe : 0x55 0xaa
main.1: cmpb %ch,(%bx) # Null entry? # 通过分区条目第一字节判断该分区是否为活动分区,0x80为是,0为否
je main.2 # Yes # 此分区非活动分区,准备查询下一分区
jg err_pt # If 0x1..0x7f # 分区条目第一字节的合法值只能是0x80或0,否则报错
testw %si,%si # Active already found? # 通过si是否为0判断是否已找到一个活动分区
jnz err_pt # Yes # 只能有一个活动分区,否则报错
movw %bx,%si # Point to active # 让si指向活动分区表条目
# 对于示例情况,分区表第一条目第一字节为0x80,为活动分区,
# 因此在这里对si进行设置,此时cpu主要寄存器的内容如下:
# rax: 0x00000000:00000000 rcx: 0x00000000:00000004
# rdx: 0x00000000:00000080 rbx: 0x00000000:000007be
# rsp: 0x00000000:00007c00 rbp: 0x00000000:00000000
# rsi: 0x00000000:ffff07be rdi: 0x00000000:00080800
# r8 : 0x00000000:00000000 r9 : 0x00000000:00000000
# r10: 0x00000000:00000000 r11: 0x00000000:00000000
# r12: 0x00000000:00000000 r13: 0x00000000:00000000
# r14: 0x00000000:00000000 r15: 0x00000000:00000000
# rip: 0x00000000:0000062d
# eflags 0x00000046
main.2: addb $0x10,%bl # Till # 让bx指向下一个分区表条目
loop main.1 # done # "main.1:"一共执行4次,依次分析分区表中的4个条目
testw %si,%si # Active found? # 判断是否找到了一个活动分区(有多个活动分区的情况已在前面报错)
jnz main.3 # Yes # 找到活动分区则跳转至"main.3"
int $0x18 # BIOS: Diskless boot # 没有找到活动分区则尝试无盘引导
#
# Ok, we've found a possible active partition. Check to see that the drive
# is a valid hard drive number.
#
main.3: cmpb $0x80,%dl # Drive valid? # dl中存放的是bios传入的引导设备编号,从0x80开始表示硬盘
jb main.4 # No # 若bios传入的不是硬盘则将之前找到的活动分区的第一字节存入dl
movb NHRDRV,%dh # Calculate the highest # bios将硬盘数目保存在NHRDRV,即0x475处,此处将其写入dh
addb $0x80,%dh # drive number available # 硬盘号从0x80开始,加上硬盘数目即得到最大可能硬盘号的上限
cmpb %dh,%dl # Within range? # 判断bios传入的硬盘号是否在合法范围之内
jb main.5 # Yes # 硬盘及分区有效,准备读入对应扇区
main.4: movb (%si),%dl # Load drive # 若bios传入的不是硬盘或者硬盘号非法则将之前找到的活动分区的
# 第一字节存入dl
#
# Ok, now that we have a valid drive and partition entry, load the CHS from
# the partition entry and read the sector from the disk.
#
# 执行至此已经找到了一个有效的硬盘分区入口,此时cpu主要
# 寄存器的内容如下:
# rax: 0x00000000:00000000 rcx: 0x00000000:00000000
# rdx: 0x00000000:00008180 rbx: 0x00000000:000007fe
# rsp: 0x00000000:00007c00 rbp: 0x00000000:00000000
# rsi: 0x00000000:ffff07be rdi: 0x00000000:00080800
# r8 : 0x00000000:00000000 r9 : 0x00000000:00000000
# r10: 0x00000000:00000000 r11: 0x00000000:00000000
# r12: 0x00000000:00000000 r13: 0x00000000:00000000
# r14: 0x00000000:00000000 r15: 0x00000000:00000000
# rip: 0x00000000:0000064a
# eflags 0x00000097
# 此时si指向的是之前找到的有效分区条目
main.5: movw %sp,%di # Save stack pointer # 将sp保存到di
movb 0x1(%si),%dh # Load head # 分区条目第1(从0开始计算)字节存放的是磁头号,将其存入dh
movw 0x2(%si),%cx # Load cylinder:sector # 分区条目第2、3字节存放的是柱面号和扇区号,其中,第2字节的
# 低6位存储扇区号,第2字节的高2位和第3字节的8位共同存储
# 柱面号。之前找到的有效分区条目的起始地址是0x7be,它的前
# 4个字节的内容如下:
# 0x00000000000007be : 0x80 0x01 0x01 0x00
# 表示的就是第0柱面第1磁头第1扇区。此处将这两个字节的内容
# 存入cx,此时cpu主要寄存器的内容如下:
# rax: 0x00000000:00000000 rcx: 0x00000000:00000001
# rdx: 0x00000000:00000180 rbx: 0x00000000:000007fe
# rsp: 0x00000000:00007c00 rbp: 0x00000000:00000000
# rsi: 0x00000000:ffff07be rdi: 0x00000000:00087c00
# r8 : 0x00000000:00000000 r9 : 0x00000000:00000000
# r10: 0x00000000:00000000 r11: 0x00000000:00000000
# r12: 0x00000000:00000000 r13: 0x00000000:00000000
# r14: 0x00000000:00000000 r15: 0x00000000:00000000
# rip: 0x00000000:00000652
# eflags 0x00000097
movw $LOAD,%bx # Transfer buffer # 把bios初始加载引导代码的地址0x7c00赋给bx
# 至此已基本完成使用0x13中断的0x02功能读入磁盘扇区的准备,
# 即cx存放柱面号和扇区号,dh存放磁头号,dl存放磁盘号,
# es:bx指向用于存放读入的扇区数据的缓冲区地址。不过在决定
# 使用0x13中断的0x02功能之前,此处要先尝试是否能使用0x13
# 中断的0x42功能读入相应的扇区数据。在无法使用0x42功能的
# 情况下才会到"main.7:"去使用0x02功能。
testb $FL_PACKET,flags # Try EDD? # 判断是否需要尝试BIOS Enhanced Disk Drive Services。
# "flags:"标号在本文尾端定义,位于紧邻分区表之前的一个字节,
# 内容为FLAGS,这个宏在/sys/boot/i386/mbr/Makefile中定义为
# BOOT_MBR_FLAGS,即0x80。BOOT_MBR_FLAGS在本文开始处定义为0x80。
jz main.7 # No. # 若上述标志的设置决定不去判断能否使用EDD功能读入扇区,则跳转
# 至"main.7"使用0x13中断的0x02功能读入扇区。由于到此为止的
# 判断尚未对cx和bx进行压栈,所以可以直接跳转至"main.7:",
# 而后续判断中发现无法使用EDD功能时,由于已经对cx、bx进行了
# 压栈和修改,则需跳转至"main.6:"先恢复cx、bx的内容再到
# "main.7:"处使用0x13中断的0x02功能。
pushw %cx # Save %cx # 测试是否支持EDD扩展功能,中断号0x13,入参ah存放功能号0x41,
pushw %bx # Save %bx # bx存放幻数0x55aa。部分返回结果需要写入cx,因此先保存cx、bx。
movw $0x55aa,%bx # Magic
movb $0x41,%ah # BIOS: EDD extensions
int $0x13 # present?
jc main.6 # No. # CF为1表示不支持
cmpw $0xaa55,%bx # Magic ok? # 回写至bx的幻数应为0xaa55
jne main.6 # No. # 幻数不正确则跳转至"main.6:"
testb $0x1,%cl # Packet mode present? # 返回值cl第1位表示是否使用packet结构访问设备
jz main.6 # No. # 不使用packet结构则跳转至"main.6:"
popw %bx # Restore %bx # 恢复bx。此时cpu主要寄存器的内容如下:
# rax: 0x00000000:00003000 rcx: 0x00000000:00000007
# rdx: 0x00000000:00000180 rbx: 0x00000000:00007c00
# rsp: 0x00000000:00007bfe rbp: 0x00000000:00000000
# rsi: 0x00000000:ffff07be rdi: 0x00000000:00087c00
# r8 : 0x00000000:00000000 r9 : 0x00000000:00000000
# r10: 0x00000000:00000000 r11: 0x00000000:00000000
# r12: 0x00000000:00000000 r13: 0x00000000:00000000
# r14: 0x00000000:00000000 r15: 0x00000000:00000000
# rip: 0x00000000:00000673
# eflags 0x00000002
pushl $0x0 # Set the LBA
pushl 0x8(%si) # address
pushw %es # Set the address of
pushw %bx # the transfer buffer
pushw $0x1 # Read 1 sector
pushw $0x10 # Packet length # 准备使用0x13中断的0x42功能以扩展方式从磁盘读入扇区,
# 这一组压栈指令就是在栈上构造磁盘地址包(Disk Address Packet)。
# 压栈完成之后,cpu主要寄存器的内容如下:
# rax: 0x00000000:00004200 rcx: 0x00000000:00000007
# rdx: 0x00000000:00000180 rbx: 0x00000000:00007c00
# rsp: 0x00000000:00007bee rbp: 0x00000000:00000000
# rsi: 0x00000000:ffff7bee rdi: 0x00000000:00087c00
# r8 : 0x00000000:00000000 r9 : 0x00000000:00000000
# r10: 0x00000000:00000000 r11: 0x00000000:00000000
# r12: 0x00000000:00000000 r13: 0x00000000:00000000
# r14: 0x00000000:00000000 r15: 0x00000000:00000000
# rip: 0x00000000:00000685
# eflags 0x00000002
# sp寄存器指向0x7bee,此时栈顶附近16字节的内容如下:
# 0x0000000000007bee : 0x10 0x00 0x01 0x00 0x00 0x7c 0x00 0x00
# 0x0000000000007bf6 : 0x3f 0x00 0x00 0x00 0x00 0x00 0x00 0x00
# 第0字节表示DAP的大小,此处为0x10,即16字节,第1字节未使用,应填0,
# 这两个字节是由"pushw $0x10"指令压入的。第2字节表示需要读入的扇区数目,
# 此处为0x01,只读入1个扇区,第3字节未使用,应填0,这两个字节是由
# "pushw $0x1"指令压入的。第4到7字节是以"segment: offset"的形式给出的
# 用于存放读入的扇区数据的缓冲区的地址,此处是0x0000:7c00,这实际上
# 就是bios初始加载主引导记录的地址,即本段代码被搬移到0x600区域之前
# 所在的位置,这4个字节是由"pushw %es"和"pushw %bx"指令压入的。
# 第8到15字节表示的是读入操作的起始扇区号(从0开始计算),此处是
# 0x000000000000003f,本次读入的是第63扇区,这8个字节是由
# "pushl $0x0"和"pushl 0x8(%si)"指令压入的,si指向的是之前在分区表中
# 找到的活动分区条目,它的第8到11字节存放的就是该分区的起始扇区号:
# 0x00000000000007be : 0x80 0x01 0x01 0x00 0xa5 0x0f 0xff 0xff
# 0x00000000000007c6 : 0x3f 0x00 0x00 0x00 0xa1 0xff 0x7f 0x00
movw %sp,%si # Packer pointer # 在栈上完成DAP的构造之后,将其起始地址赋给si
movw $0x4200,%ax # BIOS: LBA Read from disk # 写入功能号0x42
jmp main.8 # Skip the CHS setup # 跳转到"main.8:"调用0x13中断
main.6: popw %bx # Restore %bx # 若不支持EDD功能,则与此恢复之前压栈的bx和cx,压栈之前cx
# 存放的是柱面号和扇区号,bx存放的是用于存放读入的扇区数据的
# 缓冲区地址,都是0x13中断的0x02功能所需的入参。
popw %cx # Restore %cx
main.7: movw $0x201,%ax # BIOS: Read from disk # 填写0x13中断的0x02功能所需的最后一点入参。al存放需要读入的
# 扇区数目,此处为1,只读入一个扇区。ah存放功能号0x02。
main.8: int $0x13 # Call the BIOS # 不管是使用0x02功能读入,还是使用0x42功能读入,至此已完成各自
# 入参的设置,统一运行至此执行0x13中断。
movw %di,%sp # Restore stack # 将sp恢复至扇区拷贝之前的位置,即在"main.5:"处保存在di中的内容
jc err_rd # If error # CF为1表示读入过程出错
#
# Now that we've loaded the bootstrap, check for the 0xaa55 signature. If it
# is present, execute the bootstrap we just loaded.
#
# 至此已完成扇区的读入,cpu主要寄存器的内容如下:
# rax: 0x00000000:00000000 rcx: 0x00000000:00000007
# rdx: 0x00000000:00000180 rbx: 0x00000000:00007c00
# rsp: 0x00000000:00007c00 rbp: 0x00000000:00000000
# rsi: 0x00000000:ffff7bee rdi: 0x00000000:00087c00
# r8 : 0x00000000:00000000 r9 : 0x00000000:00000000
# r10: 0x00000000:00000000 r11: 0x00000000:00000000
# r12: 0x00000000:00000000 r13: 0x00000000:00000000
# r14: 0x00000000:00000000 r15: 0x00000000:00000000
# rip: 0x00000000:00000692
# eflags 0x00000002
cmpw $MAGIC,0x1fe(%bx) # Bootable? # 前面已经通过0x13中断从磁盘读入了一个512字节的扇区到
# 0x7c00区域,此处判断该扇区的最后两个字节是否为0xaa55,
# 以确定该扇区是否可引导。
jne err_os # No # 读入扇区不可引导则报错
jmp *%bx # Invoke bootstrap # 跳转至0x7c00执行刚读入的bootstrap代码
#
# Various error message entry points.
#
err_pt: movw $msg_pt,%si # "Invalid partition
jmp putstr # table"
err_rd: movw $msg_rd,%si # "Error loading
jmp putstr # operating system"
err_os: movw $msg_os,%si # "Missing operating
jmp putstr # system"
#
# Output an ASCIZ string to the console via the BIOS.
#
putstr.0: movw $0x7,%bx # Page:attribute
movb $0xe,%ah # BIOS: Display
int $0x10 # character
putstr: lodsb # Get character
testb %al,%al # End of string?
jnz putstr.0 # No
putstr.1: jmp putstr.1 # Await reset
msg_pt: .asciz "Invalid partition table"
msg_rd: .asciz "Error loading operating system"
msg_os: .asciz "Missing operating system"
.org PT_OFF-1,0x90
flags: .byte FLAGS # Flags
partbl: .fill 0x10,0x4,0x0 # Partition table
.word MAGIC # Magic number
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有