grub-install源码分析---4

grub-install源码分析—4

上一章重点分析了grub是如何探测一个文件系统的,本章分析grub-install的最后一部分代码,该代码包含了最主要的业务逻辑,下面来看。

grub2-install第六部分
util/grub-install.c

    ...

    char mkimage_target[200];
    const char *core_name = NULL;

    switch (platform){
        ...
    case GRUB_INSTALL_PLATFORM_I386_PC:
    snprintf (mkimage_target, sizeof (mkimage_target), "%s-%s",
        grub_install_get_platform_cpu (platform),
        grub_install_get_platform_platform (platform));
    core_name = "core.img";
    break;

        ...
    }

    char *imgfile = grub_util_path_concat (2, platdir, core_name);
    char *prefix = xasprintf ("%s%s", prefix_drive ? : "",
                relative_grubdir);
    grub_install_make_image_wrap (grub_install_source_directory,
                prefix, imgfile, NULL, NULL, mkimage_target, 0);

    ...

前面的章节分析过,grub_install_get_platform_cpu函数最终返回i386,grub_install_get_platform_platform函数最终返回pc,通过snprintf函数拼接在一起,最终的mkimage_target字符串为i386-pc。设置imgfile为”/boot/grub/i386-pc/core.img”,prefix为”/boot/grub”,变量grub_install_source_directory为”/usr/local/lib/grub/i386-pc”,最后通过grub_install_make_image_wrap函数生成core.img。

grub2-install->grub_install_make_image_wrap
util/grub-install-common.c

void grub_install_make_image_wrap (const char *dir, const char *prefix,
                  const char *outname, char *memdisk_path,
                  char *config_path,
                  const char *mkimage_target, int note) {
    FILE *fp;

    fp = grub_util_fopen (outname, "wb");
    grub_install_make_image_wrap_file (dir, prefix, fp, outname,
                     memdisk_path, config_path,
                     mkimage_target, note);
    grub_util_file_sync (fp);
    fclose (fp);
}

首先打开将要写入的文件”/boot/grub/i386-pc/core.img”,然后主体调用grub_install_make_image_wrap_file函数,最后的grub_util_file_sync函数将文件从缓存中同步到硬盘中。下面重点来看grub_install_make_image_wrap_file函数。

grub2-install->grub_install_make_image_wrap->grub_install_make_image_wrap_file
util/grub-install-common.c

void grub_install_make_image_wrap_file (const char *dir, const char *prefix,
                   FILE *fp, const char *outname,
                   char *memdisk_path,
                   char *config_path,
                   const char *mkimage_target, int note){

    const struct grub_install_image_target_desc *tgt;
    const char *const compnames[] = {
        [GRUB_COMPRESSION_AUTO] = "auto",
        [GRUB_COMPRESSION_NONE] = "none",
        [GRUB_COMPRESSION_XZ] = "xz",
        [GRUB_COMPRESSION_LZMA] = "lzma",
    };
    grub_size_t slen = 1;
    char *s, *p;
    char **pk, **md;
    int dc = decompressors ();

    for (md = modules.entries; *md; md++){
        slen += 10 + grub_strlen (*md);
    }

    p = xmalloc (slen);

    for (md = modules.entries; *md; md++) {
        *p++ = '\'';
        p = grub_stpcpy (p, *md);
        *p++ = '\'';
        *p++ = ' ';
    }

    *p = '\0';

    tgt = grub_install_get_image_target (mkimage_target);

    grub_install_generate_image (dir, prefix, fp, outname,
                   modules.entries, memdisk_path,
                   pubkeys, npubkeys, config_path, tgt,
                   note, compression);
    while (dc--)
        grub_install_pop_module ();
}

decompressors函数根据压缩函数指针compress_func加入相应的压缩模块。接下来将全局变量modules中的字符串添加到新分配的内存空间p中。grub_install_get_image_target根据cpu体系mkimage_target获得对应的grub_install_image_target_desc结构,然后调用grub_install_generate_image函数生成core.img,该函数的《grub-mkimage源码分析》文章中分析了。最后通过grub_install_pop_module函数将decompressors函数中添加的模块删除。

grub2-install->grub_install_make_image_wrap->grub_install_make_image_wrap_file->decompressors
util/grub-install-common.c

static int decompressors (void) {
    if (compress_func == grub_install_compress_gzip){
        grub_install_push_module ("gzio");
        return 1;
    }
    if (compress_func == grub_install_compress_xz){
        grub_install_push_module ("xzio");
        grub_install_push_module ("gcry_crc");
        return 2;
    }
    if (compress_func == grub_install_compress_lzop){
        grub_install_push_module ("lzopio");
        grub_install_push_module ("adler32");
        grub_install_push_module ("gcry_crc");
        return 3;
    }
    return 0;
}

decompressors函数就是根据compress_func函数指针,选择需要向core.img映像中添加的模块,例如压缩函数为grub_install_compress_gzip时,就需要添加gzio模块。

grub2-install->grub_install_make_image_wrap->grub_install_make_image_wrap_file->grub_install_get_image_target
util/mkimage.c

const struct grub_install_image_target_desc * grub_install_get_image_target (const char *arg){
    unsigned i, j;
    for (i = 0; i < ARRAY_SIZE (image_targets); i++)
        for (j = 0; j < ARRAY_SIZE (image_targets[i].names) &&
            image_targets[i].names[j]; j++)
            if (strcmp (arg, image_targets[i].names[j]) == 0)
                return &image_targets[i];
    return NULL;
}

grub_install_get_image_target函数比较cpu体系字符串arg,如果和全局的image_targets数组中的每个项的name字段对应,就返回该项。假设参数arg为i386-pc,最终返回的结构如下

    {
        .dirname = "i386-pc",
        .names = { "i386-pc", NULL },
        .voidp_sizeof = 4,
        .bigendian = 0,
        .id = IMAGE_I386_PC, 
        .flags = PLATFORM_FLAGS_DECOMPRESSORS,
        .total_module_size = TARGET_NO_FIELD,
        .decompressor_compressed_size = GRUB_DECOMPRESSOR_I386_PC_COMPRESSED_SIZE,
        .decompressor_uncompressed_size = GRUB_DECOMPRESSOR_I386_PC_UNCOMPRESSED_SIZE,
        .decompressor_uncompressed_addr = TARGET_NO_FIELD,
        .section_align = 1,
        .vaddr_offset = 0,
        .link_addr = GRUB_KERNEL_I386_PC_LINK_ADDR,
        .default_compression = GRUB_COMPRESSION_LZMA
    }

grub2-install第七部分
util/grub-install.c

    ...

    switch (platform){
    case GRUB_INSTALL_PLATFORM_I386_PC:
    char *boot_img_src = grub_util_path_concat (2, 
                grub_install_source_directory, "boot.img");
    char *boot_img = grub_util_path_concat (2, platdir, "boot.img");
    grub_install_copy_file (boot_img_src, boot_img, 1);

    if (install_bootsector)
        grub_util_bios_setup (platdir, "boot.img", "core.img",
                install_drive, force,
                fs_probe, allow_floppy, add_rs_codes);
    break;

    ...

    }

    grub_gcry_fini_all ();
    grub_fini_all ();

    return 0;
}

继续往下看,接下来获取boot.img所在路径boot_img_src,默认为”/usr/local/lib/grub/i386-pc/boot.img”,boot_img为该文件需要拷贝到的路径,默认为”/boot/grub/i386-pc/boot.img”。然后就通过grub_install_copy_file函数将boot_img_src处的boot.img拷贝到boot_img路径中。

变量install_bootsector默认为1,因此接下来通过grub_util_bios_setup函数执行grub文件的安装操作。最后调用grub_gcry_fini_all和grub_fini_all函数做一些收尾工作,本章不关心。

下面重点分析grub_util_bios_setup函数,该函数较长,分为多个部分分析。

grub_util_bios_setup

grub_util_bios_setup第一部分
util/setup.c

void SETUP (const char *dir, const char *boot_file, const char *core_file,
    const char *dest, int force, int fs_probe, int allow_floppy,
    int add_rs_codes __attribute__ ((unused))) {

    char *core_path;
    char *boot_img, *core_img, *boot_path;
    char *root = 0;
    size_t boot_size, core_size;
    grub_uint16_t core_sectors;
    grub_device_t root_dev = 0, dest_dev, core_dev;
    grub_util_fd_t fp;
    struct blocklists bl;

    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_path = grub_util_get_path (dir, boot_file);
    boot_size = grub_util_get_image_size (boot_path);

    boot_img = grub_util_read_image (boot_path);
    free (boot_path);

    core_path = grub_util_get_path (dir, core_file);
    core_size = grub_util_get_image_size (core_path);
    core_sectors = ((core_size + GRUB_DISK_SECTOR_SIZE - 1) >> GRUB_DISK_SECTOR_BITS);
    core_img = grub_util_read_image (core_path);

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

    ...

current_segment定义为0x820,其实是后面解压缩程序的装载地址。boot_path为grub目录下boot.img所在路径,默认为”/boot/grub/i386-pc/boot.img”。然后通过grub_util_get_image_size函数获取该文件大小boot_size,必须为512字节。,再通过grub_util_read_image函数读取该文件到内存boot_img中。

同理,core_path为/boot/grub/i386-pc/core.img,该文件由前面的grub_install_generate_image函数生成,内部包含了diskboot.img,lzma_decompress.img和grub的核心代码kernel.img。core_size为core.img文件的大小,对应的扇区数core_sectors。最后读取该文件到内存core_img中。

core.img的前512字节为diskboot.img,由diskboot.S编译而来,first_block指向diskboot.S文件中的标号blocklist_default_start处,该标号处于grub_boot_blocklist数组最后一项的起始地址,grub_boot_blocklist内部保存了如何从硬盘读取后续文件的信息。

grub_util_bios_setup第二部分
util/setup.c


    ...

    dest_dev = grub_device_open (dest);
    core_dev = dest_dev;

    char **root_devices = grub_guess_root_devices (dir);
    char **cur;
    int found = 0;

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

        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);
    }

    for (cur = root_devices; *cur; cur++)
        free (*cur);
    free (root_devices);


    grub_env_set ("root", root);

    ...

传入的参数dest是要将grub安装到的硬盘,例如hd0。参数dir是grub文件所在目录,例如”/boot/grub/i386-pc/”。首先通过grub_guess_root_devices函数获得grub文件夹所在的根设备,返回对应的设备文件,例如”/dev/sdb”。

接下来遍历可能的根设备文件root_devices,调用grub_util_get_grub_dev函数获得对应的设备,例如hd0。调用grub_device_open打开该设备,获取设备参数,保存在try_dev中。

接下来设置根设备root_dev为新打开的设备try_dev,设置环境变量root指向根设备,例如hd0,如果grub目录所在的根设备try_dev和将要安装的根设备dest_dev相同,就设置found为1。最后设置环境变量root并返回。

grub_util_bios_setup第三部分
util/setup.c

    ...

    char *tmp_img;
    grub_uint8_t *boot_drive_check;

    tmp_img = xmalloc (GRUB_DISK_SECTOR_SIZE);
    grub_disk_read (dest_dev->disk, 0, 0, GRUB_DISK_SECTOR_SIZE, tmp_img);

    boot_drive_check = (grub_uint8_t *) (boot_img
                + GRUB_BOOT_MACHINE_DRIVE_CHECK);

    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 (!allow_floppy && !grub_util_biosdisk_is_floppy (dest_dev->disk)){
        boot_drive_check[0] = 0x90;
        boot_drive_check[1] = 0x90;
    }

    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);

    ...

    fs = grub_fs_probe (dest_dev);
    is_ldm = grub_util_is_ldm (dest_dev->disk);

    ...

首先读取目的设备dest_dev的引导扇区至新分配的内存tmp_img中。根据GRUB_BOOT_MACHINE_DRIVE_CHECK偏移将boot_drive_check指向boot.S文件的标号boot_drive_check处。首先拷贝BIOS参数到tmp_img中,allow_floppy表示是否允许从软盘启动,如果不允许,并且grub_util_biosdisk_is_floppy函数检测出目的设备dest_dev不是软盘,则改变boot.img中boot_drive_check偏移处的起始jmp语句为nop(0x90),jmp语句之下会检测设备是否为硬盘,即是否大于0x80,如果小于0x80,则此时将设备号设置为0x80。

接着通过grub_partition_iterate函数遍历分区,假设最后返回的ctx中的dest_partmap保存了分区类型,例如grub_msdos_partition_map。往下省略的部分是对分区进行检查,grub_fs_probe探测文件系统,假设为ext2。
grub_util_is_ldm和硬盘的LVM相关,这里假设最后返回false。

grub_util_bios_setup第四部分
util/setup.c

    if (fs_probe){
        ...

        if (ctx.dest_partmap && !ctx.dest_partmap->embed){
            goto unable_to_embed;
        }

        ..
    }

unable_to_embed:

    grub_fs_t fs;
    fs = grub_fs_probe (root_dev);

    ...

    bl.block = bl.first_block;
    while (bl.block->len){
        bl.block->start = 0;
        bl.block->len = 0;
        bl.block->segment = 0;
        bl.block--;
    }

    bl.block = bl.first_block;

    grub_install_get_blocklist (root_dev, core_path, core_img, core_size,
                  save_blocklists, &bl);
    write_rootdev (root_dev, boot_img, bl.first_sector);
    fp = grub_util_fd_open (core_path, GRUB_UTIL_FD_O_WRONLY);
    grub_util_fd_write (fp, core_img, GRUB_DISK_SECTOR_SIZE * 2);
    grub_util_fd_sync (fp);
    grub_util_fd_close (fp);
    grub_util_biosdisk_flush (root_dev->disk);
    grub_disk_cache_invalidate_all ();

    ...

    grub_disk_write (dest_dev->disk, BOOT_SECTOR,
            0, GRUB_DISK_SECTOR_SIZE, boot_img);

    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);
}

假设上面一个函数grub_partition_iterate函数返回的分区类型为grub_msdos_partition_map,此时直接跳到unable_to_embed标号处执行。然后获得grub目录对应根设备的文件系统fs。

bl的block字段指向diskboot.S源文件中blocklist数组的最后一个block处。最后一个block(也即first_block)的len变量记录了需要的block大小,该值在grub_install_generate_image函数的最后设置(《grub-mkimage源码分析—3》)。根据len值,通过while循环初始化blocklist数组。然后调用grub_install_get_blocklist函数根据core_img的大小设置该blocklist数组,内部调用函数指针save_blocklists将其写入内存core_img。

接下来调用write_rootdev更新boot.img文件在内存中的映像boot_img,first_sector表示core.img在硬盘上第一个扇区的扇区号,也即diskboot.S代码所在的扇区起始号。再往下以只写方式打开”/boot/grub/i386-pc/core.img”文件,再通过grub_util_fd_write函数向其中写入core_img的前两个扇区。其中第一个扇区是diskboot.img。根据前面的分析,这里写入一个扇区也行,因为前面的操作只修改了第一个扇区,也即diskboot.img中的值,写入两个扇区应该是考虑了其他不同的情况,这里不往下深究了。

省略的部分是对刚刚写入硬盘的core.img作检查,其做法是首先读取第一个扇区,也即diskboot.img,然后根据diskboot.img最后部分的blocklist,依次读取接下来的扇区,然后比较一共读取的数据长度和总长度core_size,如果不相等,则说明blocklist发生了错误。

接下来通过grub_disk_write函数将boot_img写入根设备的主引导扇区中,传入的宏定义BOOT_SECTOR为0标识了该引导扇区为主引导扇区。最后同步文件,关闭设备。

grub_util_bios_setup->grub_install_get_blocklist
util/setup.c

void grub_install_get_blocklist (grub_device_t root_dev, const char *core_path,
            const char *core_img __attribute__ ((unused)),
            size_t core_size,
            void (*callback) (grub_disk_addr_t sector,
                    unsigned offset,
                    unsigned length,
                    void *data),
            void *hook_data){

    grub_partition_t container = root_dev->disk->partition;
    grub_uint64_t container_start = grub_partition_get_start (container);
    struct fiemap fie1;
    int fd;

    fd = open (core_path, O_RDONLY);

    grub_memset (&fie1, 0, sizeof (fie1));
    fie1.fm_length = core_size;
    fie1.fm_flags = FIEMAP_FLAG_SYNC;

    if (ioctl (fd, FS_IOC_FIEMAP, &fie1) < 0){
        int nblocks, i;
        int bsize;
        int mul;

        ioctl (fd, FIGETBSZ, &bsize);

        mul = bsize >> GRUB_DISK_SECTOR_BITS;
        nblocks = (core_size + bsize - 1) / bsize;

        for (i = 0; i < nblocks; i++){
            unsigned blk = i;
            int rest;
            ioctl (fd, FIBMAP, &blk);

            rest = core_size - ((i * mul) << GRUB_DISK_SECTOR_BITS);
            if (rest <= 0)
                break;
            if (rest > GRUB_DISK_SECTOR_SIZE * mul)
                rest = GRUB_DISK_SECTOR_SIZE * mul;

            callback (((grub_uint64_t) blk) * mul
                + container_start, 0, rest, hook_data);
        }
    }else{
        ...
    }
    close (fd);
}

首先获得grub目录所在分区container,通过grub_partition_get_start函数获得该分区的起始地址container_start。然后通过参数为FS_IOC_FIEMAP的ioctl判断文件系统的block管理方式,else部分表示使用extent来管理,即不使用block或者说block bitmap来管理block,这里不关心。下面只看if部分的代码。

首先通过参数为FIGETBSZ的ioctl函数获得文件系统一个block的大小bsize,mul表示一个block大小占用多少扇区数,nblocks表示core.img的大小占用多少block。再通过参数为FIBMAP的ioctl函数根据逻辑块号i获取物理块号,保存在blk中。变量rest表示grub一次读取的block数,默认为一次读取一个block。

参数callback函数指针指向save_blocklists,调用该函数设置内存映像core_img中的blocklist。

grub_util_bios_setup->grub_install_get_blocklist->save_blocklists
util/setup.c

static void save_blocklists (grub_disk_addr_t sector, unsigned offset, 
            unsigned length, void *data){

    struct blocklists *bl = data;
    struct grub_boot_blocklist *prev = bl->block + 1;
    grub_uint64_t seclen;

    if (bl->first_sector == (grub_disk_addr_t) -1){
        bl->first_sector = sector;
        sector++;
        length -= GRUB_DISK_SECTOR_SIZE;
        if (!length)
            return;
    }

    seclen = (length + GRUB_DISK_SECTOR_SIZE - 1) >> GRUB_DISK_SECTOR_BITS;

    if (bl->block != bl->first_block && (grub_target_to_host64 (prev->start)
        + grub_target_to_host16 (prev->len)) == sector){
        grub_uint16_t t = grub_target_to_host16 (prev->len);
        t += seclen;
        prev->len = grub_host_to_target16 (t);
    }else{
        bl->block->start = grub_host_to_target64 (sector);
        bl->block->len = grub_host_to_target16 (seclen);
        bl->block->segment = grub_host_to_target16(bl->current_segment);
        bl->block--;
    }

    bl->last_length = length & (GRUB_DISK_SECTOR_SIZE - 1);
    bl->current_segment += seclen << (GRUB_DISK_SECTOR_BITS - 4);
}

sector表示本次读取的扇区起始地址,length是一次读取的字节长度,seclen是length对应的扇区长度,prev表示上一个block结构的地址。if语句里表示的是文件的内容在物理block上是连续的,此时只要增加上一个block结构的len变量即可。
进入else部分就表示文件的内容在物理block上不连续,或者是第一次进入进入该函数,即此时block和firstblock相等,此时设置start表示绝对扇区标号,len是读取的以扇区为单位的长度,segment是将这段文件内容装在到内存时的段地址。last_length是最后一个扇区的字节长度。

grub_util_bios_setup->write_rootdev
util/setup.c

static void write_rootdev (grub_device_t root_dev, char *boot_img, 
            grub_uint64_t first_sector){

    grub_uint8_t *boot_drive;
    void *kernel_sector;
    boot_drive = (grub_uint8_t *) (boot_img + GRUB_BOOT_MACHINE_BOOT_DRIVE);
    kernel_sector = (boot_img + GRUB_BOOT_MACHINE_KERNEL_SECTOR);
    *boot_drive = 0xFF;
    grub_set_unaligned64 (kernel_sector, grub_cpu_to_le64 (first_sector));
}

首先初始化boot_img中的GRUB_BOOT_MACHINE_BOOT_DRIVE地址处的值标识启动硬盘,GRUB_BOOT_MACHINE_KERNEL_SECTOR宏表示diskboot映像所在的扇区。

你可能感兴趣的:(glibc+linux源码分析,linux逆向编程)