grub2 1.95 源码分析之一 —— boot.S 分析及注释
本文最早发布于CSDN:https://blog.csdn.net/cppgp/article/details/2060146
/* -*-Asm-*- */ /* * GRUB -- GRand Unified Bootloader * Copyright (C) 1999,2000,2001,2002,2005,2006 Free Software Foundation, Inc. * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ // cppgp 注释版 // 转载请注明原作者 // 注释版本号 : 1.00 // 日期 : 2008-01-22 // 联系方式 : // email yanyg02@163.com // msn yanyg02@hotmail.com // qq 281607998 // boot.S 流程及功能简介 : // boot.S 生成 512 字节的机器码存储在硬盘或者软盘的 MBR 中 , 被 BIOS 加载 // 到内存 0x7C00 处 , 然后 BIOS 设置 CPU 跳转至 0x7C00 处开始执行 , 控制权到了 // boot.S 手中 . boot.S 根据 BIOS 设置的寄存器初始值和一些 INT 中断调用 , 判断 // 引导驱动器是 LBA硬盘/CHS硬盘/软盘 中的哪一种 , 并保存磁盘参数到磁盘参数块地 // 址(BPB) , 然后读取由 kernel_sector 处的 8 字节确定的扇区块 ( 即diskboot.img // 由 bootdisk.S 生成的二进制码文件 )内存储的 512 字节的内容到内存地址 0x70000 // 处 , 再拷贝到内存 0x8000 处 , 然后跳转至 0x8000 开始执行 , 由 bootdisk.S 获 // 得控制权 , 而 boot.S 就功成身退了. 但是 boot.S 设置的实模式下的堆栈及寄 // 存器值,以及引导驱动器的模式和参数 , 在 bootdisk.S 中都有用到 . 具体请看代码 // 注释及 diskboot.S 的注释 . 如果在加载 diskboot.img 的过程中出错, 则输出错误 // 提示信息 , 进入死循环 , 等待手工重起或者关闭. // boot.S 生成的机器码被加载在 0x7C00~0x7DFF 处, 具体机器码见文档最后部分 // 这个头文件里面只定义了一个版本号 #include <grub/boot.h> // 这个头文件即../.././include/grub/i386/pc/boot.h , 定义引导需要的系统常量定义 #include <grub/machine/boot.h> // 在代码中使用宏的地方 , 我都做了宏的说明 , 读者不需要跟踪到对应 .h 文件中看宏定义 /* * defines for the code go here */ /* Absolute addresses This makes the assembler generate the address without support from the linker. (ELF can't relocate 16-bit addresses!) */ // ABS(x)计算x的绝对地址,用来生成不受连接器限制的地址(ELF不能生成可重入的地址). #define ABS(x) (x-_start+0x7c00) /* Print message string */ // 打印 x 指向的字符串到终端 #define MSG(x) movw $ABS(x), %si; call message /* XXX: binutils-2.9.1.0.x doesn't produce a short opcode for this. */ #define MOV_MEM_TO_AL(x) .byte 0xa0; .word x // binutils-2.9.1.0.x不能生成short操作码,使用这个宏传递x给AL .file "boot.S" .text /* Tell GAS to generate 16-bit instructions so that this code works in real mode. */ // 告知GAS生成16位的指令,使以下代码可以工作在实模式下 // 说明 : GAS是gcc的汇编器 .code16 // 以下代码被BIOS启动例程加载至0X7C00处并且跳转到此处执行,过程描述如下: // (为降低分析复杂度,我们假设从硬盘启动.) // BIOS读取硬盘的MBR扇区共512字节,并放在地址0X0000:0X7C00处 // 跳转到0X0000:0X7C00处去执行 // 此时设置寄存器如下: // CS:IP = 0X0000:0X7C00 !即代码段基址为0,偏移量为0X7C00 // ES:SI !指向BIOS中硬盘分区表的地址. // DL !引导设备number,00x80~0xFF .globl _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. */ // 扇区开始处和BIOS参数块都是FAT/HPFS格式兼容的. // 跳转到after_BPB内存处 jmp after_BPB nop /* do I care about this ??? */ // nop 永远不会执行! /* * This space is for the BIOS parameter block!!!! Don't change * the first jump, nor start the code anywhere but right after * this area. */ // 以下空间是留给BIOS参数块的!!! // 不要更改跳转指令 // 除了恰在BIOS参数块后的位置,也不要开始执行指令 . = _start + 4 // 保留空间 : 使本节代码占用的空间到_start+4处.如下分析: // jmp after_BPB 占用2字节 // nop 占用1字节 // 因此多保留一字节地址并以0填充. // 事实上,在生成的.img文件中,开始处四字节的机器码是:eb4b9000 // Address 0x7C00 0x7C01 0x7C02 0x7C03 // Value eb 4b 90 00 // Operator jmp sfter_BPB nop 00 /* scratch space */ //保留的空间 //占用 4 字节 , _start+0x00 ~ _start+0x03 // 磁盘信息参数块 // CHS 寻址方式用到 11 字节 _start+0x04 ~ _start+0x0E // mode = 0 ; 然后依次是通过 BIOS INT 0x13 , AH = 0x08 调用得来的"扇区/磁头/柱面"参数 // LBA 寻址方式用到 16 字节 _start+0x04 ~ _start+0x14 // mode = 1 ; 然后会通过此后数据块的值和调用要求填充磁盘参数块 // 此时 , 诸如 sectors/heads/cylinders 等标签是无用的 , 具体参数格式见 lba_mode 一节对代码的注释 mode: .byte 0 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... */ . = _start + GRUB_BOOT_MACHINE_BPB_END //保留空间 /* * End of BIOS parameter block. */ //BIOS参数块结束地址 // 现在,空间已经分配到了_start + GRUB_BOOT_MACHINE_BPB_END // 其中 // 数据空间是 : _start+0x04 ~ _start+0x12 // 保留空间是 : _start+0x13 ~ _start+0x3D // grub自身信息保存地址: // 共 14 字节 : _start + 0x3E ~ _start + 0x4B // boot_version !版本 // kernel_address !内核加载地址 // kernel_segment !内核加载段地址 // kernel_sector !扇区绝对地址,默认为0磁头0柱面1扇区 // boot_drive !从哪个磁盘加载,0xff表示使用boot驱动器加载 boot_version: .byte GRUB_BOOT_VERSION_MAJOR, GRUB_BOOT_VERSION_MINOR // 4 0 kernel_address: .word GRUB_BOOT_MACHINE_KERNEL_ADDR // 0x8000 kernel_segment: .word GRUB_BOOT_MACHINE_KERNEL_SEG // 0x800 kernel_sector: .long 1, 0 boot_drive: .byte 0xff /* the disk to load kernel from */ /* 0xff means use the boot drive */ 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, 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). */ boot_drive_check: // 跳转至下一个1处 , 此处即往下第5行处 jmp 1f // 以下三行有什么作用 ? 我不明白 ? 估计是残留下来的东西 , 上面英文注释说是为了修正布满 bug 的 BIOS testb $0x80, %dl jnz 1f movb $0x80, %dl 1: // 使 CS : IP = 0x0000 : 0x7C00 /* * ljmp to the next instruction because some bogus BIOSes * jump to 07C0:0000 instead of 0000:7C00. */ /* * ljmp跳转到$0, $ABS(real_start)指定的绝对位置执行指令 * 因为一些糟糕的BIOS使用07C0:0000代替0000:7C00来实现跳转 */ // 跳转到 real_start 节的绝对地址并执行那里的代码. // cs : ip = 0x0000 : $ABS(real_start) ljmp $0, $ABS(real_start) real_start: /* set up %ds and %ss as offset from 0 */ // ax/ds/ss 清零 xorw %ax, %ax movw %ax, %ds movw %ax, %ss // ds = 0 ; ss = 0 /* set up the REAL stack */ // GRUB_BOOT_MACHINE_STACK_SEG 是宏定义 , 等于 0x2000 // 设置实模式下的堆栈 , 设置表示栈顶指针的变址寄存器 sp 为 0x2000 // 现在 , ss : sp = 0x0000 : 0x2000 movw $GRUB_BOOT_MACHINE_STACK_SEG, %sp // 现在 数据段堆栈段设置正确 , 允许中断 // 中断标志位IF置1 sti /* we're safe again */ /* * Check if we have a forced disk reference here */ // 将 boot_drive 处的值存储到 al // address(boot_drive) = _start+0x4B = 0x7C00+0x4B = 0x7C4B // 此处值默认为 0xff , 可在 setup 过程填充 // 如果等于 0xff , 表示使用默认驱动 , 即从 BIOS 传递过来的驱动 // 如果不等于 , 则将值存入 DL , 表示使用由该值确定的引导驱动 MOV_MEM_TO_AL(ABS(boot_drive)) /* movb ABS(boot_drive), %al */ cmpb $0xff, %al je 1f movb %al, %dl 1: /* save drive reference first thing! */ //驱动信息压栈 : dl 中保存驱动number , dh 中什么都不是 pushw %dx /* print a notification message on the screen */ // 输出字符串 notification_string 到屏幕 // notification_string = "GRUB" // MSG宏经展开以后是这样的 : // movw $(notification_string-_start+0x7c00), %si; call message // 其中 $(notification_string-_start+0x7c00) 是字符串 notification_string 绝对地址 // 现在 , 我们知道 ds = 0x0000 , si = address(notification_string) // 而在函数 message 中 , 会通过 BIOS 提供的 INT 0x10 调用逐字符显示到终端 // 具体见 message 函数定义部分 // 注意 , 此时不需要设置 DF MSG(notification_string) /* set %si to the disk address packet */ // 设置变址寄存器si为 disk_address_packet 的绝对地址 // 宏展开以后是 : movw $(disk_address_packet-_start+0x7c00), %si movw $ABS(disk_address_packet), %si /* do not probe LBA if the drive is a floppy */ // 如果驱动器是软盘,则不判断是否是支持LBA的磁盘 , 而直接跳转到chs_mod // 根据前面资料 , 我们知道磁盘的 DL 值为 0X80~0XFF , DL 最高位必然为1 // GRUB_BOOT_MACHINE_BIOS_HD_FLAG宏定义 = 0x80 // 因此 , 如果是磁盘 , ZF = 0 ; // 否则 , 是软盘引导 , ZF = 1 , 将跳转至 chs_mod testb $GRUB_BOOT_MACHINE_BIOS_HD_FLAG, %dl jz chs_mode /* check if LBA is supported */ // 现在 , 已经确定是磁盘引导 , 判断是只支持 8G 寻址的老式的磁盘还是 LBA 磁盘 // 通过 BIOS 调用 INT 0x13 来确定是否支持扩展 , 详细见具体代码部分 // LBA 扩展功能分两个子集 , 如下 : // 第一个子集提供了访问大硬盘所必须的功能 , 包括 // 1.检查扩展是否存在 : ah = 41h , bx = 0x55aa , dl = drive( 0x80 ~ 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 //下面开始具体检测 , 首先检测扩展是否存在 // 此时寄存器的值和 BIOS 调用分别是 : // AH = 0x41 // BX = 0x55AA // DL = driver( 0x80 ~ 0xFF ) // INT 13H // 返回结果 : 如果支持 CF = 0 ; 否则 CF = 1 // CF = 0 (支持LBA) 时的寄存器值 : // ah : 扩展功能的主版本号( major version of extensions ) // al : 内部使用( internal use ) // bx : AA55h ( magic number ) // cx : // Bits Description // 0 extended disk access functions // 1 removable drive controller functions supported // 2 enhanced disk drive (EDD) functions (AH=48h,AH=4Eh) supported. // Extended drive parameter table is valid // 3~15 reserved (0) // CF = 1 (不支持LBA) 时的寄存器值 : // ah = 0x01 ( invalid function ) 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 可能已被 INT 13 , AH = 41H , BX = 55AAH 调用修改,重新改回! // 例如 AST BIOS 1.04 将存在这种修改 popw %dx pushw %dx /* use CHS if fails */ // 如果扩展检测调用失败 , 进入 chs_mod , 分别通过检测 CF/BX/CX 确认是否支持 LBA // 检测 CF 值 , 为 1 表示失败进入 chs_mod jc chs_mode // 检测 BX 值 , 如果不等于 0xAA55 , 进入 chs_mod cmpw $0xaa55, %bx jne chs_mode // 如果不支持扩展第一子集(见上) , CX.bit0 = 0 , 进入 chs_mod andw $1, %cx jz chs_mode lba_mode: // 本节代码的功能 : 加载 bootdisk.S 生成的机器码到绝对地址 0x7000 处 , 并跳转执行 copy_buffer // 进入到这里时 , 已探测到系统支持 LBA 扩展的第一子集 // 下面首先为扩展读的 BIOS 调用设置参数块值 // 关于扩展读调用的详细情况和结果值见调用部分注释 // 扩展读需要的磁盘参数块说明如下 : // 磁盘参数块的规定格式如下 : // Format of disk address packet: // Offset Size Bits Description // 00h BYTE 8 size of packet (10h or 18h) // 01h BYTE 8 reserved (0) // 02h WORD 16 number of blocks to transfer (max 007Fh for Phoenix EDD) // 04h DWORD 32 -> transfer buffer // 08h QWORD 64 starting absolute block number // 现在 , 我们的 ds = 0x0000 , si = $ABS(disk_address_packet) // 我们取扩展读磁盘参数块的地址标号是 ADDR_LBA_BPB(0x00) ~ ADDR_LBA_BPB(0x0F) 共 16 字节 // 对于各个值的设置见下面代码 xorw %ax, %ax movw %ax, 4(%si) // ADDR_LBA_BPB(0x04) = 0x00 // ADDR_LBA_BPB(0x05) = 0x00 // 设置 mode , 在 diskboot.S 里面会用到 // mode = 1 , LBA 扩展读 // mode = 0 , CHS 寻址读 incw %ax /* set the mode to non-zero */ // -1(%si)即为mode所在处地址 // 此时 AX = 0x0001 , AL = 0x01 movb %al, -1(%si) /* the blocks */ movw %ax, 2(%si) // ADDR_LBA_BPB(0x02) = 0x01 // ADDR_LBA_BPB(0x03) = 0x00 /* the size and the reserved byte */ movw $0x0010, (%si) // ADDR_LBA_BPB(0x00) = 0x10 // ADDR_LBA_BPB(0x01) = 0x00 /* the absolute address */ // 将(kernel_sector~kernel_sector+3)单元(绝对地址)的值给 ebx , 默认为 1 // 在 grub-mkimage 命令中被修改 ,存储 diskboot.S 生成代码的存储扇区 movl ABS(kernel_sector), %ebx movl %ebx, 8(%si) // ADDR_LBA_BPB(0x08~0x0B) = 0x0000 0001 // 将(kernel_sector+4~kernel_sector+7)单元(绝对地址)的值给 ebx , 默认为 0 movl ABS(kernel_sector + 4), %ebx movl %ebx, 12(%si) // ADDR_LBA_BPB(0x0C~0x0F) = 0x0000 0000 /* the segment of buffer address */ // GRUB_BOOT_MACHINE_BUFFER_SEG 宏定义 = 0x7000 movw $GRUB_BOOT_MACHINE_BUFFER_SEG, 6(%si) // ADDR_LBA_BPB(0x06~0x07) = 0x7000 // 对应如下 : // Offset Size/Bits Value Description // 00h BYTE/8 0x10 size of packet (10h or 18h) // 01h BYTE/8 0x00 reserved (0) // 02h WORD/16 0x0001 number of blocks to transfer (max 007Fh for Phoenix EDD) // 04h DWORD/32 0x70000000 -> transfer buffer // 08h QWORD/64 0x01 starting absolute block number /* * 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 // 寄存器和磁盘参数块设置完毕 // 寄存器值 : AH = 0x42 ; DL = driver number ; DS : SI = 0x0000 : ABS(disk_address_packet) // disk_address_packet 各个值见上面注释 // 这次 INT 0x13 调用将完成 : // 通过扩展读 , 从磁盘读取由 kernel_sector 处8字节长度确定的某一扇区的内容到内存地址 0x70000(0x7000:0x0000) 处 // 成功 : CF = 0 , AH = 0 // 失败 : CF = 1 , AH = 错误码 // 调用结束后 磁盘地址块的 block 计数项 (ADDR_LBA_BPB(0x02)) 存放成功成功传送的数据块数. int $0x13 // LBA扩展读失败 , 进入 chs_mod /* LBA read is not supported, so fallback to CHS. */ jc chs_mode // 存储 GRUB_BOOT_MACHINE_BUFFER_SEG 到 bx , 跳转至 copy_buffer // GRUB_BOOT_MACHINE_BUFFER_SEG = 0x7000 // 数据寄存器 BX 设置为 0x7000 movw $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx jmp copy_buffer chs_mode: // 进入此处 , 已经判定是不支持磁盘 LBA 扩展了 , 但是从磁盘引导还是软盘引导尚未确定 // 因此 , CHS磁盘引导 或者 软盘引导 均从此处处理 /* * Determine the hard disk geometry from the BIOS! * We do this first, so that LS-120 IDE floppies work correctly. */ // 通过 BIOS INT 0x13 , AH = 0x08 调用测定磁盘的物理参数 // 该的详细解释如下 : /* * 作用 : * 取得磁盘参数 * 寄存器值 : * AH = 0x08 * DL = drive (bit 7 set for hard disk) * ES:DI = 0x0000 : 0x0000 to guard against BIOS bugs * 成功 : * CF = 0 * AH = 00h * AL = 00h on at least some BIOSes * BL = drive type (AT/PS2 floppies only) * CH = low eight bits of maximum cylinder number * CL = maximum sector number (bits 5-0) , high two bits of maximum cylinder number (bits 7-6) * DH = maximum head number * DL = number of drives * ES:DI -> drive parameter table (floppies only) * 失败 : * CF = 1 * AH = status (07h) */ movb $8, %ah int $0x13 jnc final_init // 如果调用成功 , CF = 0 , 跳转到 final_init 处! // 现在已经确定不支持 CHS , 测试是否是软盘引导 // 如果是软盘引导 , 进入 floppy_probe // 否则出错 , 进入 hd_probe_error /* * The call failed, so maybe use the floppy probe instead. */ // 如果 DL 最高位为 0 ,则为软盘 testb $GRUB_BOOT_MACHINE_BIOS_HD_FLAG, %dl // 如果是软盘 , 跳转到 floppy_probe jz floppy_probe // 现在 , 知道我们确实是一个磁盘(硬盘) , 但是实在不知道该怎么读 , 去 hd_probe_error 玩玩吧 /* Nope, we definitely have a hard disk, and we're screwed. */ jmp hd_probe_error final_init: // 设置 mode = 0 , mode 在 diskboot.S 中用到 , 用来判断引导磁盘是 LBA 读还是 CHS 读 /* set the mode to zero */ // DH 存放磁头数 // 这句完成了2个功能 : dh 存放到 eax 的 al ; eax 的其它位置 0 movzbl %dh, %eax movb %ah, -1(%si) // 设置 mode = 0 // 保存 磁头/柱面/扇区数 /* save number of heads */ // BIOS 中磁头编号是按照 0 ~ n-1 , 此处加 1 并保存到磁盘参数块 incw %ax movl %eax, 4(%si) // cl 中 0~5 bits 存放扇区数 , 6~7 bits 存放柱面数的高 2 位 movzbw %cl, %dx shlw $2, %dx // 柱面数的低 8 位 movb %ch, %al // dh 现在是磁头数的高两位! movb %dh, %ah // 现在 ah 存放柱面数的高 2 位 // al 存放柱面数的低 8 位 // ax 存放整个柱面参数 /* save number of cylinders */ // BIOS 中柱面编号是 0 ~ n-1 , 此处加 1 并保存到磁盘参数块 incw %ax movw %ax, 8(%si) // dl 中 0~2bits 是0 ,5~7bits 是扇区数 movzbw %dl, %ax shrb $2, %al // 右移 2 位 , 现在 al 中 0~5 bits 是扇区数 , 6~7 bits 是 0 /* save number of sectors */ // BIOS 中扇区编号是 1 ~ n , 直接保存到磁盘参数块! movl %eax, (%si) setup_sectors: // 使用 CHS 模式下 BIOS 提供的读调用 , 加载由 kernel_sector 确定的一个扇区到内存 0x70000 处 /* load logical sector start (top half) */ // kernel_sector + 4 存储内存偏移量 // 在 CHS 读中必须为 0 , 否则出错 movl ABS(kernel_sector + 4), %eax orl %eax, %eax // 如果 eax != 0x0000 0000 0000 0000 , 则无法引导 , 跳转 geometry_error 处理 ! jnz geometry_error /* load logical sector start (bottom half) */ // kernel_sector 处值默认为 1 , 在 grub-mkimage 命令中设置为 diskboot.img(diskboot.S生成) 存储的扇区号 movl ABS(kernel_sector), %eax // 以下通过除法运算的商和余数 , 计算引导扇区所在的 磁头/柱面/扇区 // eax 现在存储的是引导扇区编号 /* zero %edx */ xorl %edx, %edx /* divide by number of sectors */ // (%si) 处存放扇区数 divl (%si) // (%si)中存放sectors , (%si)/%eax // eax 是 sectors/eax 的商 , kernel_sector 取默认值时等于 sectors // edx 中现在是余数 , kernel_sector 为默认时为 edx 为 0 /* save sector start */ // 存储起始扇区 , 因为扇区编号是 1~n ,需要 cl+=1(见后面操作) movb %dl, %cl xorw %dx, %dx /* zero %edx */ divl 4(%si) /* divide by number of heads */ // 4(%si) 是磁头数 , 磁头数除以扇区数 , eax 中存放柱面数 , edx 存放余数 , 表示起始磁头 /* do we need too many cylinders? */ // 8(%si)是柱面数 , 如果 ax 中数值大于柱面数 , 表示超出范围 , 跳转至 geometry_error 处理! cmpw 8(%si), %ax jge geometry_error /* normalize sector start (1-based) */ // start 扇区 += 1 , 因为扇区编号是 1~n incb %cl /* low bits of cylinder start */ // al 是柱面的低 8 位 movb %al, %ch /* high bits of cylinder start */ // ah 是柱面的高 2 位 , 操作之后 , cl 高 2 位存储柱面的高 2 位 xorb %al, %al shrw $2, %ax orb %al, %cl // 保存起始磁头到 al /* save head start */ movb %dl, %al // 重新设置DL为引导驱动器编号 /* restore %dl */ popw %dx /* head start */ // al 中现在是起始磁头到 dh 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 */ /* 寄存器 : * AH = 0x02 * AL = number of sectors to read (must be nonzero) * CH = low eight bits of cylinder number * CL = sector number 1-63 (bits 0-5),high two bits of cylinder (bits 6-7, hard disk only) * DH = head number * DL = drive number (bit 7 set for hard disk) * ES:BX -> data buffer * 成功 : * CF = 0 * AH = status * AL = number of sectors transferred (only valid if CF set for some BIOSes) * BX * 失败 : * CF = 1 * AH = 11h (corrected ECC error), AL = burst length */ // 设置磁盘读取缓冲区 // 操作完毕之后 , bx = 0x0000 , es = 0x7000 movw $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx movw %bx, %es /* load %es segment with disk buffer */ xorw %bx, %bx /* %bx = 0, put it at 0 in the segment */ movw $0x0201, %ax /* function 2 */ // CHS 模式读的 BIOS INT 0x13 调用 , 寄存器值已经设置如下 : // 寄存器名 值 意义 // AH 0x02 CHS模式读需要 // AL 0x01 需要读的扇区数 // CH 已设置 柱面数的低 8 位 // CL 已设置 0~5bits 扇区号 , bits 6-7bits 柱面的高 2 位 // DH 已设置 磁头数 // DL 已恢复 驱动号 // ES:BX 0x7000:0x0000 数据缓冲区 // 其中 , CH/CL/DH 保存起始 磁头/柱面/扇区 , 是由 kernel_sector 中设置的值经运算得来 , // 其中 , CH/CL/DH 保存起始 磁头/柱面/扇区 , 是由 kernel_sector 中设置的值经运算得来 , 具体见上面运算过程 int $0x13 // 如果 CHS 读失败 , 进入 read_error jc read_error // 设置 bx 为 0x7000 , 在 copy_buffer 用到 movw %es, %bx copy_buffer: // 以下代码把通过 BIOS 调用读进来的一扇区的数据移动到内存地址 0x8000 处 movw ABS(kernel_segment), %es // es = 0x800 /* * We need to save %cx and %si because the startup code in * kernel uses them without initializing them. */ // 这里需要保存 cx 和 si 的值 // 因为内核 startup 里面的代码未经初始化就使用了它( 其实是 diskboot.S 代码 ) pusha pushw %ds // 设置循环次数和源/目的串段寄存器和偏移指针 // 设置完毕之后 , 寄存器值 : // CX = 0x100 ; ES:SI=0x800:0x0000 // LBA读 : DS:SI = 0x7000:0x0000 // CHS读 : DS:SI = ? movw $0x100, %cx // cx = 0x100 = 256 movw %bx, %ds // ds = 0x7000 xorw %si, %si // si = 0x00 xorw %di, %di // di = 0 cld //DF = 0 (方向标志) // 操作串由 DS:SI 和 ES:DI 指定 // 将 DS:SI 指向的字传送到 ES:DI 指向的内存单元 // DF = 0 , 因此 SI += 2 ; DI += 2 rep movsw // 将 ds:si 处的连续的 512 字节数据搬移到 es:di 地址处 // 即将刚读进来的 512 字节的数据搬移到内存地址地址 0x8000 处 // 寄存器还原 popw %ds popa // 现在 boot.S 的所有任务全部完成 , 再执行一条跳转指令 , 把控制权交给 diskboot.S 后就功成身退了 /* boot kernel */ // kernel_address = 0x8000 jmp *(kernel_address) // 0x8000 处存放了 diskboot.S 生成的机器码 // 至于 0x8000 处的 512 字节代码完成什么功能 , 见 diskboot.S 注释 /* END OF MAIN LOOP */ /* * BIOS Geometry translation error (past the end of the disk geometry!). */ geometry_error: // 打印错误信息串 geometry_error_string 然后跳转到 general_error // 宏展开后是 : movw $(geometry_error_string-_start+0x7c00), %si; call message // geometry_error_string = "Geom" MSG(geometry_error_string) jmp general_error /* * Disk probe failure. */ hd_probe_error: // 打印错误信息串 hd_probe_error_string 然后跳转到 general_error // 宏展开后是 : movw $(hd_probe_error_string-_start+0x7c00), %si; call message // hd_probe_error_string = "Hard Disk" MSG(hd_probe_error_string) jmp general_error /* * Read error on the disk. */ read_error: // 打印错误信息串 read_error_string // 宏展开后是 : movw $(read_error_string-_start+0x7c00), %si; call message // read_error_string = "Read" MSG(read_error_string) general_error: // 打印错误信息串 general_error_string // 调用 BIOS 提供的调用 INT 0x18 告诉 BIOS 引导失败 // 进入死循环 , 等待手动重起 ( 下面第 4 行 处代码 ) // 宏展开后是 : movw $(general_error_string-_start+0x7c00), %si; call message 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 */ int $0x18 stop: jmp stop notification_string: .string "GRUB " geometry_error_string: .string "Geom" hd_probe_error_string: .string "Hard Disk" read_error_string: .string "Read" general_error_string: .string " Error" /* * message: write the string pointed to by %si * * WARNING: trashes %si, %ax, and %bx */ /* * Use BIOS "int 10H Function 0Eh" to write character in teletype mode * %ah = 0xe %al = character * %bh = page %bl = foreground color (graphics modes) */ // message 通过 BIOS 提供的 INT 0x10 , AH = 0x0E , 把 ds:si 字符串逐个输出到终端 1: movw $0x0001, %bx movb $0xe, %ah int $0x10 /* display a byte */ message: 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. */ // Windows NT 在此设置了幻数 , 此处保留已保证兼容性 // GRUB_BOOT_MACHINE_WINDOWS_NT_MAGIC = 0x1B8 . = _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? */ // 如果是硬盘 , 以下是分区表地址域 // 否则如果是软盘 , 则存储 part_start: // GRUB_BOOT_MACHINE_PART_START = 0x1BE . = _start + GRUB_BOOT_MACHINE_PART_START probe_values: .byte 36, 18, 15, 9, 0 // 软盘引导探测 floppy_probe: /* * Perform floppy probe. */ // 宏展开之后 : movw $(probe_values-1 -_start+0x7c00), %si movw $ABS(probe_values-1), %si probe_loop: /* reset floppy controller INT 13h AH=0 */ /* 磁盘/软盘复位调用 * INT 0x13 ; AH = 0x00 ; DL = drive number * 成功 : CF = 0 , AH = 0x00 * 失败 : CF = 1 , AH = 错误状态 * Note: * Forces controller to recalibrate drive heads (seek to track 0). * For PS/2 35SX, 35LS, 40SX and L40SX, as well as many other systems, * both the master drive and the slave drive respond to the Reset function * that is issued to either drive */ xorw %ax, %ax int $0x13 incw %si movb (%si), %cl /* if number of sectors is 0, display error and die */ // 如果扇区数为 0 , 出错 , 否则跳转至 1f cmpb $0, %cl jne 1f /* * Floppy disk probe failure. */ // 宏替换后 : movw $(fd_probe_error_string-_start+0x7c00), %si; call message // fd_probe_error_string = "Flobby" MSG(fd_probe_error_string) jmp general_error fd_probe_error_string: .string "Floppy" 1: // 软盘读取 , BIOS 调用和 CHS 读取的 BIOS 调用完全一致 , 详细见 chs_mod 处的注释 // 寄存器值 ah = 0x02 , al = 0x01 , ch = dh = 0 , cl = start sector , dl = driver number /* perform read */ // GRUB_BOOT_MACHINE_BUFFER_SEG = 0x7000 movw $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx movw $0x201, %ax movb $0, %ch movb $0, %dh int $0x13 /* if error, jump to "probe_loop" */ // 如果读失败返回 probe_loop 重读 jc probe_loop /* %cl is already the correct value! */ // 设置 扇区/柱面/磁头 // cl 即 start sector 已设置正确 // dh 设置为 79 , 表示柱面最大值为 79(80柱:0~79) // dh 设置为 1 , 表示磁头数最大值为 1(2头:0~1) // 然后跳转至 final_init // 查看 final_init , 知道保存时会把柱面和磁头分别加 1 , 扇区不变 // 因此 , 在软盘加载时 , 将设置 Cylinder : Head : Sector = 80 : 2 : start_sector movb $1, %dh movb $79, %ch jmp final_init // GRUB_BOOT_MACHINE_PART_END = 0x1FE . = _start + GRUB_BOOT_MACHINE_PART_END // 空间保留 , 分区信息占用内存地址是 _start + 0x1BE ~ _start + 0x1FD /* the last 2 bytes in the sector 0 contain the signature */ // 引导幻数 , 2 字节 , 等于 0xAA55 // GRUB_BOOT_MACHINE_SIGNATURE = 0xAA55 .word GRUB_BOOT_MACHINE_SIGNATURE //本段代码在编译链接成的机器码如下:( 0x7C00 ~ 0x7DFF 共512字节 ) /* * ADDR ---------------CODE---------------- * 7c00 eb4b9000 00000000 00000000 00000000 * 7c10 00000000 00000000 00000000 00000000 * 7c20 00000000 00000000 00000000 00000000 * 7c30 00000000 00000000 00000000 00000400 * 7c40 00800008 01000000 00000000 fffaeb07 * 7c50 f6c28075 02b280ea 5c7c0000 31c08ed8 * 7c60 8ed0bc00 20fba04c 7c3cff74 0288c252 * 7c70 be717de8 2301be05 7cf6c280 7448b441 * 7c80 bbaa55cd 135a5272 3d81fb55 aa753783 * 7c90 e1017432 31c08944 04408844 ff894402 * 7ca0 c7041000 668b1e44 7c66895c 08668b1e * 7cb0 487c6689 5c0cc744 060070b4 42cd1372 * 7cc0 05bb0070 eb73b408 cd13730a f6c2800f * 7cd0 84f000e9 8300660f b6c68864 ff406689 * 7ce0 44040fb6 d1c1e202 88e888f4 40894408 * 7cf0 0fb6c2c0 e8026689 0466a148 7c6609c0 * 7d00 754f66a1 447c6631 d266f734 88d131d2 * 7d10 66f77404 3b44087d 38fec188 c530c0c1 * 7d20 e80208c1 88d05a88 c6bb0070 8ec331db * 7d30 b80102cd 13722a8c c38e0642 7c601eb9 * 7d40 00018edb 31f631ff fcf3a51f 61ff2640 * 7d50 7cbe777d e84200eb 0ebe7c7d e83a00eb * 7d60 06be867d e83200be 8b7de82c 00cd18eb * 7d70 fe475255 42200047 656f6d00 48617264 * 7d80 20446973 6b005265 61640020 4572726f * 7d90 7200bb01 00b40ecd 10ac3c00 75f4c300 * 7da0 00000000 00000000 00000000 00000000 * 7db0 00000000 00000000 00000000 00002412 * 7dc0 0f0900be bd7d31c0 cd13468a 0c80f900 * 7dd0 750fbeda 7de8c1ff eb8d466c 6f707079 * 7de0 00bb0070 b80102b5 00b600cd 1372d7b6 * 7df0 01b54fe9 e0fe0000 00000000 000055aa */