OS开发笔记(4)——grub-bios-setup代码分析(基于i386-pc平台)

前言

读完grub-install的代码发现grub安装的核心部分还是在于grub-bios-setupgrub-mkimage仅仅是用于生成core.img所以可以直接使用,而grub-bios-setup则是只能操作设备所以需要被“取缔”


grub启动的流程

grub的启动有三个部分:

  1. 第一部分的boot.S(编译成boot.img)保存在引导扇区,由BIOS加载
  2. 第二部分根据启动设备的不同,有cdboot.Sdiskboot.Slnxboot.Spxeboot.S等几种不同的程序,通常只有一个扇区大,将扇区号和大小写入boot.img后由boot.S加载
  3. 第三部分就是grub的核心grub-core了,grub-core由第二部分的程序(从磁盘启动就是diskboot.S)加载,如果从磁盘启动会把blocklists写入到diskboot的末尾

Blocklists

grub中,grub-core在磁盘中的位置通过blocklists来描述。在bios-setup中,blocklists的结构如下:

struct blocklists
{
  struct grub_boot_blocklist *first_block, *block;
  grub_uint16_t current_segment;
  grub_uint16_t last_length;
  grub_disk_addr_t first_sector;
};

在i386-pc平台上,grub_boot_blocklist就是grub_pc_bios_boot_blocklist

struct grub_pc_bios_boot_blocklist
{
  grub_uint64_t start;
  grub_uint16_t len;
  grub_uint16_t segment;
} GRUB_PACKED;

如果grub-core保存在一块连续的空间中(比如放在第一个分区和引导扇区之间的空间中),那么只需要一个grub_boot_blocklist就能表示;如果grub-core被分割开存储(如存在文件系统中),就可以通过多个grub_boot_blocklist来表示。

代码

了解完这些知识后,就可以开始读grub-bios-setup的核心代码——grub_util_bios_setup()

能够嵌入的情况

bl.first_sector = (grub_disk_addr_t) -1;

// 设定启动时加载的目标地址
bl.current_segment =
    GRUB_BOOT_I386_PC_KERNEL_SEG + (GRUB_DISK_SECTOR_SIZE >> 4);
bl.last_length = 0;

// 打开boot.img和core.img文件,略

// core.img第一个扇区末尾就是diskboot的末尾,存着blocklist结构
/* Have FIRST_BLOCK to point to the first blocklist.  */
bl.first_block = (struct grub_boot_blocklist *) (core_img
                                                 + GRUB_DISK_SECTOR_SIZE
                                                 - sizeof (*bl.block));

// 打开目标设备
grub_util_info ("Opening dest `%s'", dest);
dest_dev = grub_device_open (dest);
if (! dest_dev)
    grub_util_error ("%s", grub_errmsg);

core_dev = dest_dev;

// 遍历寻找合适的设备作为根设备
{
    char **root_devices = grub_guess_root_devices(dir);
    char **cur;
    int found = 0;

    if (!root_devices)
        grub_util_error(_("cannot find a device for %s (is /dev mounted?)"), dir);

    for (cur = root_devices; *cur; cur++) {
        char *drive;
        grub_device_t try_dev;

        drive = grub_util_get_grub_dev(*cur);
        if (!drive)
            continue;
        try_dev = grub_device_open(drive);
        if (!try_dev) {
            free(drive);
            continue;
        }
        if (!found && try_dev->disk->id == dest_dev->disk->id &&
            try_dev->disk->dev->id == dest_dev->disk->dev->id) {
            if (root_dev)
                grub_device_close(root_dev);
            free(root);
            root_dev = try_dev;
            root = drive;
            found = 1;
            continue;
        }
        if (!root_dev) {
            root_dev = try_dev;
            root = drive;
            continue;
        }
        grub_device_close(try_dev);
        free(drive);
    }
    if (!root_dev) {
        grub_util_error("guessing the root device failed, because of `%s'",
                        grub_errmsg);
    }
    grub_util_info("guessed root_dev `%s' from "
                   "dir `%s'",
                   root_dev->disk->name, dir);

    for (cur = root_devices; *cur; cur++)
        free(*cur);
    free(root_devices);
}
// 设置grub运行时的root环境变量
grub_util_info("setting the root device to `%s'", root);
if (grub_env_set("root", root) != GRUB_ERR_NONE)
    grub_util_error("%s", grub_errmsg);


{
    // 写入boot.img
    
    char *tmp_img;
    grub_uint8_t *boot_drive_check;

    /* Read the original sector from the disk.  */
    tmp_img = xmalloc(GRUB_DISK_SECTOR_SIZE);
    if (grub_disk_read(dest_dev->disk, 0, 0, GRUB_DISK_SECTOR_SIZE, tmp_img))
        grub_util_error("%s", grub_errmsg);

    boot_drive_check =
        (grub_uint8_t *)(boot_img + GRUB_BOOT_MACHINE_DRIVE_CHECK);
    /* Copy the possible DOS BPB.  */
    memcpy(boot_img + GRUB_BOOT_MACHINE_BPB_START,
           tmp_img + GRUB_BOOT_MACHINE_BPB_START,
           GRUB_BOOT_MACHINE_BPB_END - GRUB_BOOT_MACHINE_BPB_START);

    /* If DEST_DRIVE is a hard disk, enable the workaround, which is
       for buggy BIOSes which don't pass boot drive correctly. Instead,
       they pass 0x00 or 0x01 even when booted from 0x80.  */
    // 如果不需要软盘支持,则替换原指令为nop指令
    if (!allow_floppy && !grub_util_biosdisk_is_floppy(dest_dev->disk)) {
        /* Replace the jmp (2 bytes) with double nop's.  */
        boot_drive_check[0] = 0x90;
        boot_drive_check[1] = 0x90;
    }
    
    // 写入core.img
    
    struct identify_partmap_ctx ctx = {.dest_partmap = NULL,
                                       .container = dest_dev->disk->partition,
                                       .multiple_partmaps = 0};
    int is_ldm;
    grub_err_t err;
    grub_disk_addr_t *sectors;
    int i;
    grub_fs_t fs;
    unsigned int nsec, maxsec;

    // 遍历所有分区
    grub_partition_iterate(dest_dev->disk, identify_partmap, &ctx);

    /* Copy the partition table.  */
    if (ctx.dest_partmap ||
        (!allow_floppy && !grub_util_biosdisk_is_floppy(dest_dev->disk)))
      memcpy(boot_img + GRUB_BOOT_MACHINE_WINDOWS_NT_MAGIC,
             tmp_img + GRUB_BOOT_MACHINE_WINDOWS_NT_MAGIC,
             GRUB_BOOT_MACHINE_PART_END - GRUB_BOOT_MACHINE_WINDOWS_NT_MAGIC);

    free(tmp_img);

    if (ctx.container &&
        grub_strcmp(ctx.container->partmap->name, "msdos") == 0 &&
        ctx.dest_partmap &&
        (ctx.container->msdostype == GRUB_PC_PARTITION_TYPE_NETBSD ||
         ctx.container->msdostype == GRUB_PC_PARTITION_TYPE_OPENBSD)) {
      grub_util_warn("%s",
                     _("Attempting to install GRUB to a disk with multiple "
                       "partition labels or both partition label and "
                       "filesystem.  This is not supported yet."));
      goto unable_to_embed;
    }
    
    // 找到一个可用的文件系统
    fs = grub_fs_probe(dest_dev);
    if (!fs)
      grub_errno = GRUB_ERR_NONE;

    is_ldm = grub_util_is_ldm(dest_dev->disk);
    
    // 中间错误处理,略
    
    nsec = core_sectors;

    if (add_rs_codes)
        maxsec = 2 * core_sectors;
    else
        maxsec = core_sectors;

    if (maxsec >
        ((0x78000 - GRUB_KERNEL_I386_PC_LINK_ADDR) >> GRUB_DISK_SECTOR_BITS))
        maxsec =
        ((0x78000 - GRUB_KERNEL_I386_PC_LINK_ADDR) >> GRUB_DISK_SECTOR_BITS);
    if (is_ldm)
        // 忽略
    else if (ctx.dest_partmap)
        // 如果有分区表则用该分区表的嵌入方法
        err = ctx.dest_partmap->embed(dest_dev->disk, &nsec, maxsec,
                                      GRUB_EMBED_PCBIOS, &sectors, warn_small);
    else
        // 没有则尝试嵌入到文件系统中
        err = fs->fs_embed(dest_dev, &nsec, maxsec, GRUB_EMBED_PCBIOS, &sectors);
    if (!err && nsec < core_sectors) {
        err = grub_error(GRUB_ERR_OUT_OF_RANGE,
                         N_("Your embedding area is unusually small.  "
                            "core.img won't fit in it."));
    }

    if (err) {
        grub_util_warn("%s", grub_errmsg);
        grub_errno = GRUB_ERR_NONE;
        goto unable_to_embed;
    }
    
    // 清除原来的blocklists
    /* Clean out the blocklists.  */
    bl.block = bl.first_block;
    while (bl.block->len) {
      grub_memset(bl.block, 0, sizeof(*bl.block));

      bl.block--;

      if ((char *)bl.block <= core_img)
        grub_util_error("%s", _("no terminator in the core image"));
    }

    bl.block = bl.first_block;
    for (i = 0; i < nsec; i++)
      save_blocklists(sectors[i] + grub_partition_get_start(ctx.container), 0,
                      GRUB_DISK_SECTOR_SIZE, &bl);
	// 确保在最后一个blocklist作为终止
    /* Make sure that the last blocklist is a terminator.  */
    if (bl.block == bl.first_block)
      bl.block--;
    bl.block->start = 0;
    bl.block->len = 0;
    bl.block->segment = 0;
    
    // 将xxboot如diskboot的位置信息写入boot.img
    write_rootdev(root_dev, boot_img, bl.first_sector);

    // 重新分配并对齐core.img的大小到扇区大小的倍数
    /* Round up to the nearest sector boundary, and zero the extra memory */
    core_img = xrealloc(core_img, nsec * GRUB_DISK_SECTOR_SIZE);
    assert(core_img && (nsec * GRUB_DISK_SECTOR_SIZE >= core_size));
    memset(core_img + core_size, 0, nsec * GRUB_DISK_SECTOR_SIZE - core_size);

    bl.first_block =
        (struct grub_boot_blocklist *)(core_img + GRUB_DISK_SECTOR_SIZE -
                                       sizeof(*bl.block));

    // 未使用reed solomon编码的大小
    grub_size_t no_rs_length;
    no_rs_length = 
        grub_get_unaligned16(core_img + GRUB_DISK_SECTOR_SIZE +
                             GRUB_KERNEL_I386_PC_NO_REED_SOLOMON_LENGTH);
    if (no_rs_length == 0xffff)
        grub_util_error("%s", _("core.img version mismatch"));
    
    if (add_rs_codes) {
        // 使用reed solomon进行编码,略
    }
    
    // 将修改完的core.img写到磁盘中
    /* Write the core image onto the disk.  */
    for (i = 0; i < nsec; i++)
        grub_disk_write(dest_dev->disk, sectors[i], 0, GRUB_DISK_SECTOR_SIZE,
                        core_img + i * GRUB_DISK_SECTOR_SIZE);
    
    grub_free(sectors);
    goto finish;
}

不能嵌入的情况

上面是能够成功嵌入的情况,下面是对不能嵌入时的处理:

unable_to_embed:
// 不能嵌入的情况
if (dest_dev->disk->dev->id != root_dev->disk->dev->id)
    grub_util_error("%s",
                    _("embedding is not possible, but this is required for "
                      "RAID and LVM install"));

// 判断grub所在分区是否可以使用blocklist方式安装grub-core
{
    grub_fs_t fs;
    fs = grub_fs_probe(root_dev);
    if (!fs)
        grub_util_error(_("can't determine filesystem on %s"), root);

    if (!fs->blocklist_install)
        grub_util_error(_("filesystem `%s' doesn't support blocklists"),
                        fs->name);
}

// 输出调试与错误信息,略

grub_util_biosdisk_flush(root_dev->disk);

// 清除blocklists
/* Clean out the blocklists.  */
bl.block = bl.first_block;
while (bl.block->len) {
    bl.block->start = 0;
    bl.block->len = 0;
    bl.block->segment = 0;

    bl.block--;

    if ((char *)bl.block <= core_img)
        grub_util_error("%s", _("no terminator in the core image"));
}

bl.block = bl.first_block;

// 生成blocklists
grub_install_get_blocklist(root_dev, core_path, core_img, core_size,
                           save_blocklists, &bl);

if (bl.first_sector == (grub_disk_addr_t)-1)
    grub_util_error("%s", _("can't retrieve blocklists"));

write_rootdev(root_dev, boot_img, bl.first_sector);

/* Write the first two sectors of the core image onto the disk.  */
grub_util_info("opening the core image `%s'", core_path);
fp = grub_util_fd_open(core_path, GRUB_UTIL_FD_O_WRONLY);

// 输出错误信息,略

// 刷新缓存,应用之前的更改
grub_util_biosdisk_flush(root_dev->disk);

// 禁用缓存
grub_disk_cache_invalidate_all();

// 检查blocklists是否有效
{
    char *buf, *ptr = core_img;
    size_t len = core_size;
    grub_uint64_t blk, offset = 0;
    grub_partition_t container = core_dev->disk->partition;
    grub_err_t err;

    core_dev->disk->partition = 0;

    buf = xmalloc(core_size);
    blk = bl.first_sector;
    err = grub_disk_read(core_dev->disk, blk + offset, 0, GRUB_DISK_SECTOR_SIZE,
                         buf);
    if (err)
        grub_util_error(_("cannot read `%s': %s"), core_dev->disk->name,
                        grub_errmsg);
    if (grub_memcmp(buf, ptr, GRUB_DISK_SECTOR_SIZE) != 0)
        grub_util_error("%s", _("blocklists are invalid"));

    ptr += GRUB_DISK_SECTOR_SIZE;
    len -= GRUB_DISK_SECTOR_SIZE;

    bl.block = bl.first_block;
    while (bl.block->len) {
        size_t cur = grub_target_to_host16(bl.block->len)
            << GRUB_DISK_SECTOR_BITS;
        blk = grub_target_to_host64(bl.block->start);

        if (cur > len)
            cur = len;

        err = grub_disk_read(core_dev->disk, blk + offset, 0, cur, buf);
        if (err)
            grub_util_error(_("cannot read `%s': %s"), core_dev->disk->name,
                            grub_errmsg);

        if (grub_memcmp(buf, ptr, cur) != 0)
            grub_util_error("%s", _("blocklists are invalid"));

        ptr += cur;
        len -= cur;
        bl.block--;

        if ((char *)bl.block <= core_img)
            grub_util_error("%s", _("no terminator in the core image"));
    }
    if (len)
        grub_util_error("%s", _("blocklists are incomplete"));
    core_dev->disk->partition = container;
    free(buf);
}

对这两种情况处理完了之后,就是收尾工作了

finish:
// 写入boot.img
if (grub_disk_write(dest_dev->disk, BOOT_SECTOR, 0, GRUB_DISK_SECTOR_SIZE,
                    boot_img))
    grub_util_error("%s", grub_errmsg);

// 刷新缓存
grub_util_biosdisk_flush(root_dev->disk);
grub_util_biosdisk_flush(dest_dev->disk);

// 释放资源
free(core_path);
free(core_img);
free(boot_img);
grub_device_close(dest_dev);
grub_device_close(root_dev);
posted @   迷路的鹿1202  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示