boot.S完成了对1号扇区的装载。而1号扇区安装的是bootdisk.img,也是正好512字节,由bootdisk.S编译生成。

bootdisk.S的任务是加载GRUB余下的所有映像到内存。通常情况下GRUB安装时除了0号、1号扇区外还会使用2-62号扇区。这些扇区不被硬盘分区和文件系统使用,应该来说是GRUB最佳的安装地点。但是有一些特殊的软件会在这里记录一些数据,如需要存放加密或授权信息的一些私有软件。所以并不能说完全安全。【利用里德-所罗门(Reed Solomon)算法对GRUB受保护数据进行检查和修复】

GRUB使用的扇区号会记录在一系列列表里,每个列表12个字节记录了一段连续的扇区的起始号码和长度。这些列表在GRUB安装时会添加在bootdisk.img代码段的后面。在bootdisk.S中通过两个嵌套循环,来把这些扇区装载在地址0x70000开始的内存区域。然后同样使用copy_buffer复制这些数据到地址0x8200开始的内存区域。这里0x70000开始的内存区域是作为缓冲区来使用的。因为BIOS装载扇区时不能越过64K字节的边界,如果直接装载到0x8200就可能会超过0x10000。

装载完成后,执行跳转指令ljmp $0, $(GRUB_BOOT_MACHINE_KERNEL_ADDR + 0x200)跳转到地址0x8200执行下一条指令。
至此bootdisk.img的代码执行完毕。

=================================================================

第二个扇区的代码在grub源码的diskboot.S中,下面来看。
grub-core/boot/i386/pc/diskboot.S

diskboot start第一部分

_start:
    pushw   %dx
    pushw   %si
    MSG(notification_string)
    popw    %si

    movw    $LOCAL(firstlist), %di
    movl    (%di), %ebp

LOCAL(bootloop):
    cmpw    $0, 8(%di)
    je  LOCAL(bootit)

LOCAL(setup_sectors):
    cmpb    $0, -1(%si)
    je  LOCAL(chs_mode)

    movl    (%di), %ebx
    movl    4(%di), %ecx

    xorl    %eax, %eax
    movb    $0x7f, %al

    cmpw    %ax, 8(%di)

    jg  1f

    movw    8(%di), %ax

1:
    subw    %ax, 8(%di)

    addl    %eax, (%di)
    adcl    $0, 4(%di)

    movw    $0x0010, (%si)
    movw    %ax, 2(%si)

    movl    %ebx, 8(%si)
    movl    %ecx, 12(%si)

    movw    $GRUB_BOOT_MACHINE_BUFFER_SEG, 6(%si)

    pushw   %ax
    movw    $0, 4(%si)

    movb    $0x42, %ah
    int $0x13

    jc  LOCAL(read_error)

    movw    $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx
    jmp LOCAL(copy_buffer)

首先将dx寄存器入栈,从前面可知,该寄存器此时保存了引导设备号。因为紧接着MSG打印要使用si寄存器,因此再保存si寄存器,对应前面disk_address_packet处的地址。

然后向屏幕打印notification_string字符串。
notification_string: .asciz “loading”

LOCAL(firstlist)是即将读取硬盘的参数的起始地址,分别赋值给di和ebp寄存器。

.org 0x200 - GRUB_BOOT_MACHINE_LIST_SIZE
LOCAL(firstlist):
blocklist_default_start:
    .long 2, 0
blocklist_default_len:
    .word 0
blocklist_default_seg:
    .word (GRUB_BOOT_MACHINE_KERNEL_SEG + 0x20)

0x200即512,即第二个扇区数据的末尾。GRUB_BOOT_MACHINE_LIST_SIZE为12,表示一个first list项读取参数的字节数,最大一共15个first list项(14个有效),从上面也可以看出4+4+2+2=12。因此LOCAL(firstlist)处的地址就为0x1f4,该地址对应最后一个first list项。

接着进入外层循环LOCAL(bootloop),外层循环每次遍历一个first list项。
8(%di)对应上面的blocklist_default_len,也即即将读取的扇区数,该值0会在生成该段程序时改写成正确的数值。因为后面循环会递减该值,如果该值等于0,表示将硬盘的所有数据搬到了内存中,此时跳转到LOCAL(bootit)中。

然后进入内层循环,int 0x13中断读取硬盘一次最大只能读取0x7f个扇区,因此需要循环读取。
首先从boot.S的mode地址处(-1(%si))获得硬盘的模式,如果为0,表示为CHS模式,跳转到LOCAL(chs_mode),否则继续执行。

把blocklist_default_start的高低位各4个字节保存在ebx和ecx寄存器中。表示硬盘LBA模式下扇区的逻辑起始地址,默认值为0x2,即第3个扇区。接着将al寄存器赋值0x7f,表示每次拷贝最大的扇区数,8(%di)表示剩余多少扇区没有拷贝,如果8(%di)小于al,则表示将要进行最后一次拷贝,此时将8(%di)赋值给al。

进入标号1,首先将剩余的扇区数8(%di)减去当前即将读出的扇区数al,起始扇区数(%di)加上%eax,如果有进位,则需要将进位加到4(%di)中,这三条指令都是为下一次拷贝做准备。

接下来就要设置本次硬盘读取的各个参数了,类似前面对boot.S代码的分析,(%si)指向disk_address_packet,将sectors设置为0x007f0010(默认),0x10是数据块的大小,0x00是默认值,0x007f是本次传输的扇区数;8(%si)和12(%si)保存了本次传输的起始扇区数,起始值为0x2;
6(%si)保存了缓存地址,默认值为GRUB_BOOT_MACHINE_BUFFER_SEG,也即0x7000,和boot.S中的缓存地址一样;最后将本次读取的扇区数ax入栈,将4(%si)清0。然后执行int 0x13中断,参数ah为0x42,执行读取。

如果CF置1,则发生错误,跳转到LOCAL(read_error)。
如果成功读取了数据,则将缓冲地址GRUB_BOOT_MACHINE_BUFFER_SEG存入bx寄存器,跳转到jmp LOCAL(copy_buffer)。

diskboot start第二部分

LOCAL(copy_buffer):
    movw    10(%di), %es
    popw    %ax

    shlw    $5, %ax
    addw    %ax, 10(%di)

    pusha
    pushw   %ds

    shlw    $3, %ax
    movw    %ax, %cx

    xorw    %di, %di
    xorw    %si, %si
    movw    %bx, %ds

    cld

    rep
    movsw

    popw    %ds
    MSG(notification_step)
    popa

    cmpw    $0, 8(%di)
    jne LOCAL(setup_sectors)

    subw    $GRUB_BOOT_MACHINE_LIST_SIZE, %di

    jmp LOCAL(bootloop)

10(%di)对应blocklist_default_seg,存储了GRUB_BOOT_MACHINE_KERNEL_SEG + 0x20目的地址,也即目的地址对应的段寄存器值为0x820,存入es寄存器。
然后出栈恢复ax寄存器,保存了本次读取的扇区数。shlw指令将ax寄存器左移5位加到段地址上,相当于将ax左移9位加到最终的目的地址上,也即将本次拷贝的扇区数转化为总字节数(2的9次方为512字节,对应一个扇区),累加到10(%di)中,表示下一次拷贝的目的地址。
接下来将对应寄存器入栈,用于恢复。
然后再将ax左移3位,前面已经左移5位,加在一起一共左移8位,表示本次拷贝的字数(1个字等于2个字节),存入cx寄存器中。

接着将di、si寄存器清0。bx存储了源地址GRUB_BOOT_MACHINE_BUFFER_SEG,也即0x7000,存入ds段寄存器,cld指令设置拷贝方向,然后循环直到拷贝完成。

拷贝完成后,恢复寄存器并向屏幕打印notification_step。

notification_step:  .asciz "."

然后检查8(%di)中,也即本次读取的first list项中是否还有数据未拷贝,如果有剩余数据,则跳转回LOCAL(setup_sectors)内循环再次读取0x7f个扇区。

如果8(%di)中为0,表示该first list项中没有可读取的数据了,此时将di寄存器减去GRUB_BOOT_MACHINE_LIST_SIZE,得到下一个first list项的起始地址,然后跳转到LOCAL(bootloop)外循环继续执行。

diskboot start第三部分

LOCAL(bootit):
    MSG(notification_done)
    popw    %dx
    ljmp    $0, $(GRUB_BOOT_MACHINE_KERNEL_ADDR + 0x200)

到达这里,表示所有的数据已经拷贝完成,首先打印notification_done。

notification_done:  .asciz "\r\n"

再从堆栈恢复dx寄存器,存储了引导设备号。
最后长跳转到GRUB_BOOT_MACHINE_KERNEL_ADDR + 0x200执行,也即从0x0820:0x0000开始执行。