转载注明出处(cppgp: http://blog.csdn.net/cppgp )
boot.S位于grub-1.98/boot/i386/pc/目录,采用AT&T汇编语法编写。
- /* -*-Asm-*- */
- /*
- * GRUB -- GRand Unified Bootloader
- * Copyright (C) 1999,2000,2001,2002,2005,2006,2007,2008,2009 Free Software Foundation, Inc.
- *
- * GRUB is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * GRUB is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
- */
- /*
- * cppgp 注释
- *
- * 转载请注明原作者
- *
- * 日期: 2011-04-22
- *
- * cppgp@qq.com,
- * yanyg02@163.com
- *
- * GRUB version:
- * gnu grub-1.98
- */
- /*
- * boot.S
- *
- * boot.S生成boot.img, 共512字节
- *
- * 安装程序根据实际情况, 改写kernel_sector所在LBA地址,
- * kernel_sector占用8字节, 首先是低4字节,然后是高4字节,
- * 它指明diskboot.img所在绝对扇区位置.
- *
- * 安装在硬盘上时,DPT部分(0x1BE~0x1FD)保留不变,
- *
- * 安装在软盘上时, DPT部分(0x1BE~0x1FD)是软盘复位和
- * 扇区探测代码. 扇区末尾两字节
- *
- * 扇区最后两字节 写入0xAA55 (小端表示, 0x1FE位置为0x55,
- * 0x1FF位置为0xAA).
- *
- * boot.img开机时加载到0x7C00~0x7DFF, 并以CS:IP=0x0000:0x7C00
- * 跳转执行, 它将加载diskboot.img到内存0x8000位置,并以
- * CS:IP=0x0000:0x8000跳转执行.
- *
- * boot.img保存磁盘参数和模式到BPB(BIOS parameter block)块.
- * diskboot.img和kernel.img都用到这些参数.
- *
- * boot.img设置堆栈和一些寄存器, diskboot.img和kernel.img都
- * 使用到这些设置. 包括:
- * SP=0x2000 --> stack
- * DL=boot-disk-driver
- * SI=DAP --> disk address packet, -1(%si) is mode of disk read
- * DS=SS=0 --> data segment and stack segment
- */
- /*
- *
- * 关于宏LOCAL
- *
- * 所在文件: grub-1.98/include/grub/symbol.h
- *
- * 描述
- * #define LOCAL(sym) L_ ## sym
- * ##在C语言中其粘贴作用
- * LOCAL(sym)在sym符号前附加L_
- * 它以一种更易读的方式暗示这是一个局部(本地)符号
- * 例如, LOCAL(after_BPB)等价于L_after_BPB
- *
- */
- #include <grub/symbol.h>
- #include <grub/boot.h>
- #include <grub/machine/boot.h>
- /*
- * defines for the code go here
- */
- /* Print message string */
- /*
- * MSG宏设置源串指针并调用LOCAL(message)函数
- * LOCAL(message)函数通过BIOS INT 10H, AH=0EH
- * 向终端输出提示信息
- * 关于BIOS INT 10H, AH=0EH参考2.1.6节
- */
- #define MSG(x) movw $x, %si; call LOCAL(message)
- /* 汇编文件名*/
- .file "boot.S"
- /* 代码段声明*/
- .text
- /* Tell GAS to generate 16-bit instructions so that this code works
- in real mode. */
- /*
- * 告知编译器生成实模式下工作的16位指令
- */
- .code16
- /*
- * .globl指令用来声明外部程序可以访问的标签
- * 程序入口点必须用.globl来声明
- * _start是GNU链接器的默认入口点
- */
- .globl _start, start;
- _start:
- start:
- /*
- * _start is loaded at 0x7c00 and is jumped to with CS:IP 0:0x7c00
- */
- /*
- * Beginning of the sector is compatible with the FAT/HPFS BIOS
- * parameter block.
- */
- /*
- *
- * 跳转到LOCAL(after_BPB)
- * LOCAL(after_BPB)之前空间保留
- * 用来保存磁盘读模式, 以及
- * LBA模式下的参数DAP结构体空间, 或者
- * CHS模式下保存柱面/磁头/扇区参数值
- *
- * BPB保留的空间足够多
- * 目前最多只用到17字节, 分别是
- * 1字节保存磁盘读模式(LBA模式为1, CHS模式为0)
- * 16字节保存LBA下的DAP参数
- *
- */
- jmp LOCAL(after_BPB)
- /*
- * nop占用1字节空间, 空指令
- */
- nop /* do I care about this ??? */
- /*
- * This space is for the BIOS parameter block!!!! Don't change
- * the first jump, nor start the code anywhere but right after
- * this area.
- */
- /*
- * GRUB_BOOT_MACHINE_BPB_START=0x03
- * BPB参数块开始地址: 0x7C04
- */
- . = _start + GRUB_BOOT_MACHINE_BPB_START
- . = _start + 4
- /* scratch space */
- /*
- * mode保存磁盘读模式
- */
- mode:
- .byte 0
- /*
- * disk_address_packet, BIOS LBA读DAP标签
- * sectors/heads/cylinders 保存驱动器C/H/S参数
- * 通过INT 13H, AH=0x08H获取
- * sector_start/head_start/cylinder_start
- * 代码中没有用到, 可能是残留下来的标签吧
- */
- disk_address_packet:
- sectors:
- .long 0
- heads:
- .long 0
- cylinders:
- .word 0
- sector_start:
- .byte 0
- head_start:
- .byte 0
- cylinder_start:
- .word 0
- /* more space... */
- /*
- * GRUB_BOOT_MACHINE_BPB_END=0x5A
- * 用来给BPB预留更多空间
- * BPB字节数=0x5A-0x04=0x56=86 Bytes
- */
- . = _start + GRUB_BOOT_MACHINE_BPB_END
- /*
- * End of BIOS parameter block.
- */
- /*
- * GRUB_BOOT_MACHINE_KERNEL_ADDR=0x8000
- * GRUB第二步指令装载后位于这个位置
- */
- kernel_address:
- .word GRUB_BOOT_MACHINE_KERNEL_ADDR
- /*
- * GRUB_BOOT_MACHINE_KERNEL_SECTOR=0x5C
- * 确保到达此处时占用字节数等于0x5C
- * 如果超过编译器会发出抱怨
- * 如果不够0x5C则预留至0x5C
- */
- . = _start + GRUB_BOOT_MACHINE_KERNEL_SECTOR
- /*
- * 8字节用来表示LBA绝对扇区地址
- * boot.S加载位于此处的扇区到0x8000位置
- * 注意两个4字节值的顺序
- * 先是低4字节,再是高4字节
- * 默认值是LBA第1扇区
- * 在安装过程,安装程序会改写成实际的安装扇区
- */
- kernel_sector:
- .long 1, 0
- /*
- * GRUB_BOOT_MACHINE_BOOT_DRIVE=0x64
- * 上面的kernel_sector使用0x5C~0x63
- * 此处只是确保没有越界
- * 如果越界编译器会发出警告
- */
- . = _start + GRUB_BOOT_MACHINE_BOOT_DRIVE
- /*
- * 磁盘驱动器号
- * 第一块软盘是0x00, 第二块软盘是0x01, 依此类推
- * 第一块硬盘是0x80, 第二块硬盘是0x81, 依此类推
- * 默认设置为0xff
- * 在安装过程会更改为实际的磁盘驱动器
- * 代码中如果探测到这里依然是0xff, 则会当做0x80处理
- */
- boot_drive:
- .byte 0xff /* the disk to load kernel from */
- /* 0xff means use the boot drive */
- /*
- * 0x7C00处的跳转到达这里
- */
- LOCAL(after_BPB):
- /* general setup */
- /*
- * cli 禁止中断
- */
- 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, check if
- * DL is masked correctly. If not, assume that the BIOS passed
- * a bogus value and set DL to 0x80, since this is the only
- * possible boot drive. If GRUB is installed into a floppy,
- * this does nothing (only jump).
- */
- /*
- * 确保地址安排GRUB_BOOT_MACHINE_DRIVE_CHECK=0x66
- * cli指令占用1字节, 现在正好到达0x66
- */
- . = _start + GRUB_BOOT_MACHINE_DRIVE_CHECK
- /*
- * BIOS在开机后设置DL为引导磁盘驱动器号
- * 有些BIOS会传递错误的驱动器号
- * 硬盘驱动器号是从0x80开始的
- * 正确的驱动器号和0x80执行testb不会把ZF置0
- * testb对两个操作数执行逻辑与操作,
- * 并设置正确的SF/ZF/PF等状态位
- * testb $0x80, %dl等于0表示dl<0x80
- * 如果GRUB安装在硬盘上,这很明显是一个错误的驱动器号
- * 因此会重置为0x80
- *
- */
- boot_drive_check:
- /*
- * 安装程序可能会改写jmp
- * 例如,安装在硬盘上时,
- * 可能改写为两个nop
- * 这样就可以解决一些BIOS传递错误驱动器号的BUG
- * 在当前版本中
- * 专门开辟了一字节空间(boot_drive)保存驱动器号
- * 除非系统上接入了128块硬盘,
- * 且使用最后一块硬盘引导(此时驱动器号为0xFF)
- * 否则后面的代码会重置DL为boot_drive中保存的驱动器号
- * 系统上接入128块硬盘且使用最后一块引导的概率非常低
- * 再者,如果真使用0xFF引导, 即使检测到有BUG且改成了0x80,
- * 也解决不了问题--因为实际上引导盘不是0x80而是0xFF
- *
- * 因此,是否改写jmp有些无关紧要.
- * 为了完整性, 我对这个检测的代码逻辑也做了注释
- */
- jmp 1f /* grub-setup may overwrite this jump */
- /*
- * 这几行代码是针对安装在硬盘上的GRUB的
- * 用来解决某些BIOS无法提供正确驱动器号的BUG
- * testb结果: 如果dl<0x80则操作结果为0, 因此jnz不执行跳转
- * movb重置DL为0x80
- *
- */
- testb $0x80, %dl
- jnz 1f
- movb $0x80, %dl
- 1:
- /*
- * ljmp to the next instruction because some bogus BIOSes
- * jump to 07C0:0000 instead of 0000:7C00.
- */
- /*
- * 某些存在BUG的BIOS,
- * 使用CS:IP=0x07C0:0x0000执行代码
- * 通过ljmp解决这个BUG
- * ljmp segment, offset
- * 其中segment是段寄存器, offset是偏移值
- */
- ljmp $0, $real_start
- real_start:
- /* set up %ds and %ss as offset from 0 */
- /*
- * 数据段/堆栈段寄存器置0
- * 前面通过ljmp, 已经确保数据段寄存器置0了
- */
- xorw %ax, %ax
- movw %ax, %ds
- movw %ax, %ss
- /* set up the REAL stack */
- /*
- * GRUB_BOOT_MACHINE_STACK_SEG=0x2000
- * 设置实模式下的堆栈
- */
- movw $GRUB_BOOT_MACHINE_STACK_SEG, %sp
- /* 打开中断 */
- sti /* we're safe again */
- /*
- * 现在CS/DS/SS都是0x0000
- * 并且设置好了实模式堆栈SP=0x2000
- * 下面可以安全的使用pushx/popx等指令了
- */
- /*
- * Check if we have a forced disk reference here
- */
- /*
- * 如果boot_drive依然为0xff, 则使用BIOS传递的驱动器号
- * 否则使用boot_drive中保存的驱动器号
- * 这样就允许GRUB的第一步和后续不在同一块磁盘也能工作
- *
- */
- movb boot_drive, %al
- cmpb $0xff, %al
- je 1f
- movb %al, %dl
- 1:
- /* save drive reference first thing! */
- /*
- * 压栈DX,
- * DL中保存有驱动器号
- * BIOS调用都有可能会更改DL寄存器
- */
- pushw %dx
- /* print a notification message on the screen */
- /*
- * 向终端输出提示信息notification_string
- * notification_string="GRUB"
- * 如果注意,可以看到在启动GRUB引导的Linux时,
- * 屏幕上有GRUB字样一闪而过.
- */
- MSG(notification_string)
- /* set %si to the disk address packet */
- /*
- * 设置SI寄存器
- * 下面的BIOS调用及结果保存都会用到
- * movw将disk_address_packet地址保存到SI
- */
- movw $disk_address_packet, %si
- /* do not probe LBA if the drive is a floppy */
- /*
- * 探测如果驱动器小于0x80, 不再执行LBA探测,
- * 因为小于0x80为软盘,软盘只支持CHS.
- */
- testb $GRUB_BOOT_MACHINE_BIOS_HD_FLAG, %dl
- jz LOCAL(chs_mode)
- /* check if LBA is supported */
- /*
- * BIOS INT 13H, AH=41H
- * 检测磁盘扩展读支持情况
- * 详细查看2.1.1节
- */
- movb $0x41, %ah
- movw $0x55aa, %bx
- int $0x13
- /*
- * %dl may have been clobbered by INT 13, AH=41H.
- * This happens, for example, with AST BIOS 1.04.
- */
- /*
- * 驱动器DL重置, 因为BIOS调用可能已经更改了它
- */
- popw %dx
- pushw %dx
- /* use CHS if fails */
- /*
- * 检测结果, 如果支持LBA,则有:
- * 1. CF清零; 2. BX==AA55H; 3. CX=1
- *
- * 如果BIOS调用失败按照CHS处理
- */
- jc LOCAL(chs_mode)
- cmpw $0xaa55, %bx
- jne LOCAL(chs_mode)
- andw $1, %cx
- jz LOCAL(chs_mode)
- lba_mode:
- /*
- * 初步探测支持LBA扩展读
- * 如果LBA读失败依然进入CHS处理
- */
- /*
- * 如下代码设置LBA读的DAP结构及寄存器
- * 并调用LBA读BIOS例程
- * 如果读失败进入CHS处理
- * 成功则数据被读到了0x7000:0x0000(内存0x700000)位置
- * 跳转到LOCAL(copy_buffer)拷贝至0x8000并跳转执行
- */
- /*
- * BIOS INT 13H, AH=42H
- * LBA读
- * 详细查看2.1.2节
- */
- xorw %ax, %ax
- movw %ax, 4(%si)
- // 支持LBA读,保存LBA读到mode地址,LBA读标记为0
- /*
- * 保存硬盘读模式为LBA
- * mode保存1表示支持LBA
- */
- incw %ax
- /* set the mode to non-zero */
- movb %al, -1(%si)
- // 设置BIOS调用参数
- /*
- * 以下是填充LBA读的DAP结构
- * 2.1.2节对此有详细描述
- */
- /* the blocks */
- /*
- * 读取扇区数,只读1扇区
- */
- movw %ax, 2(%si)
- /* the size and the reserved byte */
- /*
- * DAP结构体大小,当前为16=0x10
- * 把需要读取扇区数的高字节置0
- */
- movw $0x0010, (%si)
- /* the absolute address */
- /*
- * 8字节的LBA绝对扇区地址
- */
- movl kernel_sector, %ebx
- movl %ebx, 8(%si)
- movl kernel_sector + 4, %ebx
- movl %ebx, 12(%si)
- /* the segment of buffer address */
- /*
- * GRUB_BOOT_MACHINE_BUFFER_SEG=0x7000
- * 缓冲区段地址
- */
- movw $GRUB_BOOT_MACHINE_BUFFER_SEG, 6(%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
- */
- /*
- * 调用INT 13H, AH=42H执行LBA读
- * 缓冲区segment:offset=0x7000:0x0000
- */
- movb $0x42, %ah
- int $0x13
- /*
- * LBA读失败跳转到LOCAL(chs_mode)尝试CHS读
- */
- /* LBA read is not supported, so fallback to CHS. */
- jc LOCAL(chs_mode)
- /*
- * GRUB_BOOT_MACHINE_BUFFER_SEG=0x7000
- * 设置BX为缓冲区段地址0x7000
- * 跳转执行LOCAL(copy_buffer)
- * 它将搬运0x7000:0x0000处一扇区的数据到0x8000处
- */
- movw $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx
- jmp LOCAL(copy_buffer)
- /*
- * CHS模式读处理
- * 1. BIOS INT 13H, AH=08H获取磁盘CHS参数
- * 2. BIOS INT 13H, AH=02H实现CHS读
- * 3. 可能尝试软盘驱动器复位和CHS读
- */
- LOCAL(chs_mode):
- /*
- * Determine the hard disk geometry from the BIOS!
- * We do this first, so that LS-120 IDE floppies work correctly.
- */
- /*
- * 获取磁盘CHS参数
- * 有关该调用细节查看2.1.3节
- * 如果成功直接跳转到LOCAL(final_init)
- */
- movb $8, %ah
- int $0x13
- jnc LOCAL(final_init)
- /*
- * The call failed, so maybe use the floppy probe instead.
- */
- /*
- * GRUB_BOOT_MACHINE_BIOS_HD_FLAG=0x80
- * 检测如果驱动器DL小于0x80,
- * 则尝试软盘复位和读
- * 如果大于等于0x80, 表明硬盘错误,跳转进入LOCAL(hd_probe_error)处理
- */
- testb $GRUB_BOOT_MACHINE_BIOS_HD_FLAG, %dl
- jz LOCAL(floppy_probe)
- /* Nope, we definitely have a hard disk, and we're screwed. */
- jmp LOCAL(hd_probe_error)
- LOCAL(final_init):
- /* set the mode to zero */
- /*
- * 保存磁盘CHS参数
- * S索引从1开始,直接保存它即可
- * 柱面C使用寄存器CH表示低8位
- * 使用CL高两位表示高2位
- * 磁头H使用DH
- * 扇区S使用CL低6位
- * 注意,获取到的C/H/S都是最大索引
- * C/H索引都是从0开始,因此它们的值应该加1保存
- * 有关细节见2.1.3节
- */
- /*
- * movzbl保存dh到al,同时eax其他三字节置0
- * movb将保存0到mode, 表示支持CHS读
- */
- movzbl %dh, %eax
- movb %ah, -1(%si)
- /* save number of heads */
- /*
- * 保存磁头参数H到BPB的heads
- * 磁头索引从0开始,递增表示磁盘磁头数
- * SI在前面已经设置成DAP地址了
- * 而CHS和DAP是复用的
- */
- incw %ax
- movl %eax, 4(%si)
- /*
- * 移位操作
- * 10位的柱面C保存到AX中
- */
- movzbw %cl, %dx
- shlw $2, %dx
- movb %ch, %al
- movb %dh, %ah
- /* save number of cylinders */
- /*
- * 柱面索引从0开始,递增表示磁盘柱面数
- */
- incw %ax
- movw %ax, 8(%si)
- /*
- * 上面保存柱面C时使用了DX
- * DX曾左移2位,因此:
- * DH低2位保存柱面8~9位
- * DL高6位保存扇区数
- * movzbw让AH为0,AL为DL
- * AL右移两位,正好表示扇区
- * 扇区索引从1开始,因此不需要递增
- */
- movzbw %dl, %ax
- shrb $2, %al
- /* save number of sectors */
- /*
- * 保存磁盘扇区数
- */
- movl %eax, (%si)
- /*
- * 现在磁盘的C/H/S数已保存
- * 下面的读使用它们来判断需要读取的扇区
- * 是否越界(CHS 7.88Gib屏障,参考2.1节)
- *
- * 待读取的扇区采用LBA表示,占用8字节
- * 需要转换成C/H/S表示的方法,
- * 且确保在C/H/S寻址范围之内
- * 转换算法:
- *
- */
- setup_sectors:
- /* load logical sector start (top half) */
- /*
- * LBA表示的高4字节地址加载到EAX
- * 如果EAX不为0, 则肯定超过CHS寻址范围
- * 因此直接报告错误
- */
- movl kernel_sector + 4, %eax
- /*
- * orl没什么作用
- * EAX与自身执行或运算,EAX结果不会有变化
- * 但是会设置标识寄存器EFLAGS的某些位
- * 例如SF/ZF等
- * 如果EAX不等于0,则CHS越界,跳转到LOCAL(geometry_error)
- */
- orl %eax, %eax
- jnz LOCAL(geometry_error)
- /* load logical sector start (bottom half) */
- /*
- * LBA表示的低4字节地址
- */
- movl kernel_sector, %eax
- /* zero %edx */
- /*
- * EDX清零,因为32位的div操作的被除数是EDX:EAX
- */
- xorl %edx, %edx
- /* divide by number of sectors */
- /*
- * 操作数
- * 被除数: EDX:EAX=0:EAX
- * 除数: (%si) 磁盘扇区数S
- * 结果
- * 商: EAX
- * 余数: EDX
- */
- divl (%si)
- /* save sector start */
- /*
- * 余数表示起始扇区
- * 保存到CL
- * 因为扇区起始索引为1,需要递增
- * 递增操作见稍后代码
- */
- movb %dl, %cl
- /*
- * EAX是上次div的结果,
- * 再除以磁头数H,
- * 就可得到起始柱面Cylinder
- */
- /*
- * 清零DX
- * 在上面的divl操作中,
- * 除数是扇区数(6位)
- * 因此余数不会超过一字节
- * 清零DX则EDX全部为0
- */
- xorw %dx, %dx /* zero %edx */
- /*
- * 操作数
- * 被除数: EDX:EAX=0:EAX
- * 除数: 4(%si) 磁盘磁头数H
- * 结果
- * 商: EAX
- * 余数: EDX
- */
- divl 4(%si) /* divide by number of heads */
- /*
- * 现在,
- * AX中保存有起始柱面C
- * DX中保存有起始磁头S
- */
- /* do we need too many cylinders? */
- /*
- * 判断起始柱面有否越界
- * 如果越界跳转到LOCAL(geometry_error)
- */
- cmpw 8(%si), %ax
- jge LOCAL(geometry_error)
- /*
- * 注意,
- * 众所周知,余数不可能超过除数
- * 上面的除法中除数是磁盘磁头数H
- * 因此起始磁头不会越界,不需要检测是否越界
- */
- /* normalize sector start (1-based) */
- /*
- * 扇区索引从1开始,
- * 因此起始扇区需要加1
- */
- incb %cl
- /*
- * 现在
- * AX=起始柱面C
- * DX=起始磁头H
- * CL=起始扇区S
- *
- * 下面通过移位运算,完成
- * CHS读的寄存器设置
- * 详细查看2.1.4节
- *
- */
- /* low bits of cylinder start */
- /*
- * 保存起始柱面C的0~7位到CH
- */
- movb %al, %ch
- /* high bits of cylinder start */
- /*
- * 保存起始柱面C的8~9位到CL高2位
- * CL中低6位已经包含合法的起始扇区
- * orb或操作起始柱面8~9位和起始扇区0~5位
- */
- xorb %al, %al
- shrw $2, %ax
- orb %al, %cl
- /* save head start */
- /*
- * divl操作后,
- * Dl存有起始磁头,保存到AL中
- */
- movb %dl, %al
- /* restore %dl */
- /*
- * DL值已被更改,还原磁盘驱动器值
- */
- popw %dx
- /* head start */
- /*
- * 保存起始磁头到AH中
- */
- movb %al, %dh
- /*
- * BIOS call "INT 0x13 Function 0x2" to read sectors from disk into memory
- * Call with %ah = 0x2
- * %al = number of sectors
- * %ch = cylinder
- * %cl = sector (bits 6-7 are high bits of "cylinder")
- * %dh = head
- * %dl = drive (0x80 for hard disk, 0x0 for floppy disk)
- * %es:%bx = segment:offset of buffer
- * Return:
- * %al = 0x0 on success; err code on failure
- */
- /*
- * GRUB_BOOT_MACHINE_BUFFER_SEG=0x7000
- * 缓冲区段地址
- */
- movw $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx
- movw %bx, %es /* load %es segment with disk buffer */
- /*
- * 调用INT 13H, AH=02H执行CHS读
- * 缓冲区segment:offset=0x7000:0x0000
- */
- xorw %bx, %bx /* %bx = 0, put it at 0 in the segment */
- movw $0x0201, %ax /* function 2 */
- int $0x13
- /*
- * 检测CHS读是否成功
- * CHS读成功CF清零,失败则置1
- */
- jc LOCAL(read_error)
- /*
- * 设置BX为缓冲区段地址
- */
- movw %es, %bx
- LOCAL(copy_buffer):
- /*
- * We need to save %cx and %si because the startup code in
- * kernel uses them without initializing them.
- */
- /*
- * 无论CHS或LBA,一扇区的内容都被放置在
- * segment:offset=%bx:0处
- * 这里将数据搬运到0x0000:0x8000并跳转执行
- *
- */
- /*
- * 寄存器压栈, 因为
- * 数据搬运操作将使用这些寄存器
- * 而GRUB第二步将假定这些寄存器包含正确值
- * 并直接使用
- */
- pusha
- pushw %ds
- /*
- * movsw指令:
- * 搬运两字节数据
- * 使用segment:offset=DS:SI作为源操作数
- * 使用segment:offset=ES:DI作为目的操作数
- *
- * rep指令:
- * 使用CX寄存器作为循环标记,
- * 循环执行紧跟其后的指令CX次
- */
- /*
- * GRUB_BOOT_MACHINE_KERNEL_ADDR=0x8000
- * 设置循环次数为100次
- * 设置DS:SI=0x7000:0x0000即CHS/LBA读的缓冲区
- * 设置ES:DI=0x0000:0x8000, 数据将搬运到这里
- */
- movw $0x100, %cx
- movw %bx, %ds
- xorw %si, %si
- movw $GRUB_BOOT_MACHINE_KERNEL_ADDR, %di
- movw %si, %es
- /*
- * DF清零
- * movsw自动增加源和目的操作数值
- * 清零后每次执行movsw, SI和DI都加2
- */
- cld
- /*
- * 循环搬运
- * 把0x7000:0x0000处的512字节搬运到0x0000:0x8000处
- */
- rep
- movsw
- /*
- * 还原寄存器
- */
- popw %ds
- popa
- /* boot kernel */
- /*
- * 跳转到0x0000:0x8000执行
- * 这里存放的是刚才得到的指令数据
- * 一次成功的GRUB引导都会执行到这里
- * 此后将进入第二步执行
- * 这里设置的堆栈和保存的BPB数据
- * 在后来的第二步(diskboot.S)及第三步(startup.S)中还会用到
- */
- jmp *(kernel_address)
- /* END OF MAIN LOOP */
- /*
- * BIOS Geometry translation error (past the end of the disk geometry!).
- */
- /*
- * 以下是几个错误处理函数
- * 向终端输出错误提示信息并等待手动关机或重启
- */
- /*
- * 向终端输出错误提示"Geom"并跳转到LOCAL(general_error)
- * 磁盘物理结构错误(一般是C/H/S参数越界导致的错误)
- */
- LOCAL(geometry_error):
- MSG(geometry_error_string)
- jmp LOCAL(general_error)
- /*
- * Disk probe failure.
- */
- /*
- * 向终端输出错误提示"Hard Disk"并跳转到LOCAL(general_error)
- * 不支持LBA或者LBA读失败的硬盘,在获取CHS参数时错误
- */
- LOCAL(hd_probe_error):
- MSG(hd_probe_error_string)
- jmp LOCAL(general_error)
- /*
- * Read error on the disk.
- */
- /*
- * 向终端输出错误提示"Hard Disk"并跳转到LOCAL(general_error)
- * CHS读失败错误
- */
- LOCAL(read_error):
- MSG(read_error_string)
- /*
- * 向终端输出错误提示"Error/r/n"
- */
- LOCAL(general_error):
- MSG(general_error_string)
- /* go here when you need to stop the machine hard after an error condition */
- /* tell the BIOS a boot failure, which may result in no effect */
- /*
- * 向BIOS报告引导失败
- */
- int $0x18
- /*
- * BIOS对引导失败没有响应到达这里
- * 此时用户需要手动关机/重启
- */
- LOCAL(stop):
- jmp LOCAL(stop)
- /*
- * 错误提示字符串
- */
- notification_string: .asciz "GRUB "
- geometry_error_string: .asciz "Geom"
- hd_probe_error_string: .asciz "Hard Disk"
- read_error_string: .asciz "Read"
- general_error_string: .asciz " Error/r/n"
- /*
- * message: write the string pointed to by %si
- *
- * WARNING: trashes %si, %ax, and %bx
- */
- /*
- * 错误提示函数
- * 使用BIOS INT 10H, AH=0EH调用向终端输出错误提示字符串
- * 关于该中断细节查看2.1.6节
- */
- /*
- * Use BIOS "int 10H Function 0Eh" to write character in teletype mode
- * %ah = 0xe %al = character
- * %bh = page %bl = foreground color (graphics modes)
- */
- 1:
- movw $0x0001, %bx
- movb $0xe, %ah
- int $0x10 /* display a byte */
- LOCAL(message):
- /*
- * lodsb加载DS:SI指向的一字节数据到AL
- * 加载后自动更新SI值(指向下一字符)
- * 如果字符为'/0'返回,
- * 否则调用BIOS中断输出该字符
- */
- lodsb
- cmpb $0, %al
- jne 1b /* if not end of string, jmp to display */
- ret
- /*
- * Windows NT breaks compatibility by embedding a magic
- * number here.
- */
- /*
- * WindowsNT在这里插入一个幻数
- * 在Linux下, 把这个位置的6个字节清零
- * 不会导致任何问题
- * 这个测试详见2.3节末尾部分
- */
- . = _start + GRUB_BOOT_MACHINE_WINDOWS_NT_MAGIC
- nt_magic:
- .long 0
- .word 0
- /*
- * This is where an MBR would go if on a hard disk. The code
- * here isn't even referenced unless we're on a floppy. Kinda
- * sneaky, huh?
- */
- /*
- * GRUB_BOOT_MACHINE_PART_START=0x1BE
- * 从这里开始是硬盘MBR的DPT区
- * 如果是安装在软盘,则这里存放
- * 软盘驱动器复位和CHS读取的指令
- */
- . = _start + GRUB_BOOT_MACHINE_PART_START
- part_start:
- /*
- * 解决软盘获取CHS参数错误的问题
- *
- * 一系列可能的软盘扇区参数. 即:
- * 软盘可能的最大扇区数
- * 当软盘的CHS参数获取调用失败以后,
- * 设置柱面和磁头均为0,然后
- * 遍历尝试读取可能的最大扇区
- * 成功以后, 设置CHS参数为C/H/S=0/0/CL
- * 并进入LOCAL(final_init)并执行正常的CHS流程
- *
- * 例如, 假设第一次复位/读测试成功,则
- * 软盘C/H/S=0/0/36. 可寻址范围36*512=18KiB
- */
- probe_values:
- .byte 36, 18, 15, 9, 0
- LOCAL(floppy_probe):
- /*
- * Perform floppy probe.
- */
- /*
- * LOCAL(probe_loop)循环中总是先递增SI
- * 然后取值,因此设置SI初始值为$probe_values-1
- * 这样可以防止错过probe_values中的第一个数据
- */
- movw $probe_values - 1, %si
- LOCAL(probe_loop):
- /* reset floppy controller INT 13h AH=0 */
- /*
- * 复位软盘驱动器: BIOS INT 13H, AH=00H
- */
- xorw %ax, %ax
- int $0x13
- incw %si
- movb (%si), %cl
- /* if number of sectors is 0, display error and die */
- /*
- * 起始扇区是否为0
- * 如果为0则提示错误结束
- *
- * LOCAL(probe_loop)将依次遍历probe_values中设定的值,
- * 直到出现0则结束遍历, 并输出错误提示
- */
- cmpb $0, %cl
- jne 1f
- /*
- * Floppy disk probe failure.
- */
- /*
- * 终端输出"Floppy"并跳转到LOCAL(general_error)
- */
- MSG(fd_probe_error_string)
- jmp LOCAL(general_error)
- /* "Floppy" */
- fd_probe_error_string: .asciz "Floppy"
- 1:
- /* perform read */
- /*
- * BIOS INT 13H, AH=02H读(和硬盘CHS读使用同一BIOS中断)
- * 设置CH为0, CL为起始扇区,柱面C为0
- * 设置CL中为probe_values中的其中一个
- * 设置DH为0, 磁头H为0
- * 设置AL为1, 只读取一扇区
- * 设置AH为2, BIOS INT 13H CHS读函数序号
- * 设置缓冲区segment:offset=ES:BX=0x0000:0x7000
- * INT 13H调用将读取软盘0柱面0磁头CL扇区
- * 如果读成功,跳转到LOCAL(final_init)
- * 将再次执行CHS读
- * 因此这里的缓冲区数据并不会被处理
- */
- movw $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx
- movw $0x201, %ax
- movb $0, %ch
- movb $0, %dh
- int $0x13
- /* if error, jump to "LOCAL(probe_loop)" */
- /*
- * 失败跳转回LOCAL(probe_loop)继续尝试
- * 成功跳转到LOCAL(final_init)
- * 下次讲尝试读取probe_values设定的另一扇区
- * 如果probe_values所有值都读失败,提示错误信息
- */
- jc LOCAL(probe_loop)
- /* %cl is already the correct value! */
- movb $1, %dh
- movb $79, %ch
- /*
- * 幸运的,判断出0柱面0磁头CL扇区是可以工作的
- * 因此跳转回LOCAL(final_init)
- * 在那儿将保存软盘的CHS参数为C/H/S=0/0/CL
- */
- jmp LOCAL(final_init)
- . = _start + GRUB_BOOT_MACHINE_PART_END
- /* the last 2 bytes in the sector 0 contain the signature */
- /*
- * MBR幻数,小端(little endian) 下总是等于0xAA55
- */
- .word GRUB_BOOT_MACHINE_SIGNATURE