zoukankan      html  css  js  c++  java
  • grub2 1.95 源码分析之一 —— boot.S 分析及注释

    /* -*-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>

    // 这个头文件即http://www.cnblogs.com/./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
     */
     

  • 相关阅读:
    FTP服务总结
    编译安装hpptd2.4
    搭建DNS服务
    定制简单的Linux系统
    建立私有CA
    关于/boot文件的修复实验
    shell脚本进阶(二)
    datetime模块日期转换和列表sorted排序
    linux操作命令
    Python 中的特殊双下划线方法
  • 原文地址:https://www.cnblogs.com/adylee/p/1240157.html
Copyright © 2011-2022 走看看