grub源码分析之boot.S

boot.S是第一个研究对象,因为boot.S将被编译成boot.img(512字节),安装时安装在0号扇区,即主引导扇区(MBR)。

boot.S函数主要流程如下:
1、BIOS引导时会把主引导扇区装载到0x7c00开始的512字节内存区域,并设置CS:IP为0x0000:7c00。接着CPU会执行0x7c00处的指令,即boot.img(boot.S)中的第一条指令: jmp LOCAL(after_BPB) /* 机器码: eb 63 */ ,跳转到偏移量0x65的地方。

2、接下去的代码检查并设置正确的引导驱动器号码。

3、设置实模式堆栈指针为0x2000。

4、打印提示信息,如果没按SHIFT键并且不是SILENT引导的话。

5、调用BIOS中断INT 0x13, AH 0x41检测BIOS是否支持磁盘LBA模式。
如果支持LBA模式,通过BIOS中断INT 0x13, AH 0x42来装载1号扇区到地址0x70000(0x7000:0000)开始的内存区域。

如果不支持LBA模式,就使用CHS模式。通过BIOS中断INT 0x13, AH 0x02装载1号扇区到地址0x70000(0x7000:0000)开始的内存区域。这里会先调用BIOS中断INT 0x13, AH 0x13来获取磁盘参数。如果失败了并且启动盘是软盘的话,会进行软盘探测。【有意思的是软盘探测的代码是放在偏移量0x1be开始的区域内的,也就是硬盘分区表在MBR中的相应位置。因为软盘是不使用硬盘分区表的,所以覆盖没有关系。而当boot.img被安装到硬盘上时,安装时这段代码将不会被复制,因为硬盘引导不需要进行软盘探测,失败就失败了。】

6、接下来跳转到被命名为copy_buffer的一段代码来将前面装载到地址0x70000的1号扇区数据复制到内存地址0x8000开始的内存区域。

7、最后,在偏移量0x016f处执行指令 jmp (kernel_address) / 机器码: ff 26 5a 7c */ 会跳转到0x8000执行下一条指令。

至此boot.S中的代码执行完毕。
实际上在512字节的MBR中,真正可用的空间并不多。除了一开始的跳转指令外,起始部分是一个被称为BPB的区域,即BIOS参数块(BISO Parameter Block)。主要是FAT和NTFS文件系统会使用这块区域。所以GRUB在安装时会避开BPB,这也是为什么一开始就跳转到偏移量0x65处,这里才是真正的开始代码。而MBR被装载到内存中后,BPB对应的内存区域就被GRUB利用来存放DAP,即磁盘地址数据包(Disk Address Packet),用于提供参数给BIOS中断0x13来读取磁盘数据。

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

下述内容转载于“https://blog.csdn.net/conansonic/article/details/78482766”

本章开始分析grub的源码,版本为2.02。
系统开机启动后,BIOS会将硬盘(假设从硬盘启动)的第一个扇区装载到内存0x7c00位置开始执行,该地址对应grub中的start函数,下面来看。

grub-core/boot/i386/pc/boot.S

boot start第一部分

start:
    jmp LOCAL(after_BPB)
    ...

LOCAL(after_BPB):
    cli
    .org GRUB_BOOT_MACHINE_DRIVE_CHECK
boot_drive_check:
        jmp     3f
        testb   $0x80, %dl
        jz      2f
3:
    testb   $0x70, %dl
    jz      1f
2:  
        movb    $0x80, %dl
1:
    ljmp    $0, $real_start

real_start:
    xorw    %ax, %ax
    movw    %ax, %ds
    movw    %ax, %ss
    movw    $GRUB_BOOT_MACHINE_STACK_SEG, %sp
    sti

    movb   boot_drive, %al
    cmpb    $0xff, %al
    je  1f
    movb    %al, %dl
1:
    pushw   %dx
    MSG(notification_string)

    movw    $disk_address_packet, %si
    movb    $0x41, %ah
    movw    $0x55aa, %bx
    int $0x13

    popw    %dx
    pushw   %dx

    jc  LOCAL(chs_mode)
    cmpw    $0xaa55, %bx
    jne LOCAL(chs_mode)

    andw    $1, %cx
    jz  LOCAL(chs_mode)

首先通过cli指令关闭中断。.org伪指令用来告诉汇编器下一条指令的地址,GRUB_BOOT_MACHINE_DRIVE_CHECK宏定义为0x66,因此表示从0x7c00到达下一条指令jmp 3f一共为102个字节。
jmp 3f一共两个字节,在grub安装到第一个扇区时有可能会被改写为两个nop指令,因此下面要对此做检查。
dl寄存器被BIOS设置为引导设备号,一般为0x80~0xff,0x80号对应第一个硬盘。如果jmp 3f被改写了,肯定是从0x80号开始的设备启动的(下面的检查也是,都是一些BIOS版本造成的,具体为什么,协议里应该有写,懒着查了),如果不满足,则直接设置为0x80。
再往下testb 0x70对引导设备号做进一步的检查,将其限制在0x00xf以及0x800x8f内,如果不在这个范围内,就将其设置为0x80。
因为进入这段代码时,有可能使用CS:IP=0x07C0:0x0000,接下来的长跳转指令将该值设置为CS:IP=0x0000:0x7C00,和下面的指令一起,将cs、ds以及ss段寄存器都置位0x0000。
接着设置当前的栈顶指针为GRUB_BOOT_MACHINE_STACK_SEG,宏定义为0x2000,再通过sti打开中断。至此完成了堆栈的建立,可以使用push、pop指令使用堆栈了。
boot_drive默认值为0xff,如果被改写,表示强制使用某设备引导,将前面计算的dl替换为该设备号。
然后保存dx寄存器到堆栈,也即设备号,因为dl可能被int 0x13中断更改,因此后面要从堆栈重新赋值。再利用MSG(notification_string)通过int 10中断向屏幕打印数据。

notification_string:    .asciz "GRUB "
#define MSG(x)  movw $x, %si; call LOCAL(message)
1:
    movw    $0x0001, %bx
    movb    $0xe, %ah
    int $0x10
LOCAL(message):
    lodsb
    cmpb    $0, %al
    jne 1b
    ret

lodsb将notification_string中的字符串按字节存入al寄存器,通过int10中断打印到屏幕上。
再往下保存disk_address_packet的地址到si寄存器中,该地址用于保存读取硬盘的参数,后面马上就看到。
int 0x13中断是计算机在实模式下提供读写磁盘信息的接口,其实是调用了BIOS的代码。
当中断参数为ah=0x41和bx=0x55aa时,该中断用于检查磁盘拓展模式。硬盘有LBA和CHS两种模式,简单说CHS模式支持的硬盘容量较小,并且完全按照硬盘的硬件结构进行读写,LBA模式支持的硬盘容量多达TB级别,因此现在大多都使用LBA模式了。当中断返回值CF=1时表示表示硬盘不支持LBA模式,直接跳转到LOCAL(chs_mode),如果CF=0表示支持LBA,继续检查。返回值bx中存储了魔数0xaa55,如果不相等,也直接跳转到LOCAL(chs_mode)。cx中的值存储了硬盘访问的接口位图,当为偶数时,表示不支持LBA的某些api,此时也跳转到CHS。
因为现在大多硬盘都支持LBA模式,下面只看LOCAL(lba_mode)的代码。

boot start第二部分

LOCAL(lba_mode):
    xorw    %ax, %ax
    movw    %ax, 4(%si)
    incw    %ax
    movb    %al, -1(%si)
    movw    %ax, 2(%si)
    movw    $0x0010, (%si)

    movl    LOCAL(kernel_sector), %ebx
    movl    %ebx, 8(%si)
    movl    LOCAL(kernel_sector_high), %ebx
    movl    %ebx, 12(%si)
    movw    $GRUB_BOOT_MACHINE_BUFFER_SEG, 6(%si)
    movb    $0x42, %ah
    int $0x13

    jc  LOCAL(chs_mode)
    movw    $GRUB_BOOT_MACHINE_BUFFER_SEG, %bx
    jmp LOCAL(copy_buffer)

LOCAL(chs_mode):
    ...

LOCAL(final_init):
    ...

这一部分主要是读取硬盘,首先设置读取硬盘的参数到disk_address_packet地址中,也即si寄存器指向的地址。

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:

首先将head的前两个字节清0,其实地址为4(%si)中。接着设置mode为1,对应的地址为-1(%si),表示LBA模式,如果为0,对应CHS模式。
再往下继续将sectors的高两字节2(%si)设置为0x0001,表示传输的扇区数,低两字节(%si)设置为$0x0010,其中高字节0x00为默认值,低字节0x10表示数据块的大小。
接下来从cylinders地址开始设置8(%si)和12(%si),两者一起决定了读取的起始扇区,该值默认为0x1,也即读取第二个扇区。
再设置GRUB_BOOT_MACHINE_BUFFER_SEG到heads的高两字节中,表示传输目的地址,默认值为0x7000。

#define GRUB_BOOT_MACHINE_BUFFER_SEG 0x7000
1
接着执行int 0x13中断,参数0x42表示通过LBA模式从硬盘读取数据。如果返回标志位cf=1,则不支持LBA读,此时跳转到CHS模式LOCAL(chs_mode)。
如果读取成功了就将前面的缓存地址GRUB_BOOT_MACHINE_BUFFER_SEG保存到bx中,跳转到jmp LOCAL(copy_buffer)继续执行。

boot start第三部分

LOCAL(copy_buffer):
    pusha
    pushw   %ds

    movw    $0x100, %cx
    movw    %bx, %ds
    xorw    %si, %si
    movw    $GRUB_BOOT_MACHINE_KERNEL_ADDR, %di
    movw    %si, %es

    cld
    rep
    movsw

    popw    %ds
    popa

    jmp *(LOCAL(kernel_address))

首先通过pusha指令压入ax、cx、dx、bx、sp、bp、si和di寄存器,pushw压入ds段寄存器,以便恢复。
然后向cx存入循环次数256。
接着设置ds段寄存器指向前面的缓存地址GRUB_BOOT_MACHINE_BUFFER_SEG,然后清空si寄存器。
接下来设置目的地址GRUB_BOOT_MACHINE_KERNEL_ADDR到di寄存器中,宏定义为

#define GRUB_BOOT_MACHINE_KERNEL_ADDR (GRUB_BOOT_MACHINE_KERNEL_SEG << 4)
GRUB_BOOT_MACHINE_KERNEL_SEG的最终宏定义为
#define GRUB_BOOT_I386_PC_KERNEL_SEG 0x800

因此目的地址GRUB_BOOT_MACHINE_KERNEL_ADDR为0x8000。
这段代码就是将0x7000:0x0000地址处的代码搬运256个字(512字节)到 0x0000:0x8000地址上去。
最后跳转到LOCAL(kernel_address)处继续执行,其实就是GRUB_BOOT_MACHINE_KERNEL_ADDR,即0x8000。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值