当前位置: 首页 > 工具软件 > GRUB > 使用案例 >

grub源码分析之grub-mkimage

娄嘉石
2023-12-01

转载于
https://blog.csdn.net/conansonic/article/details/78760614“
https://blog.csdn.net/conansonic/article/details/78937331“

本章开始分析grub-mkimage的源码,首先来看grub-mkimage文件的生成过程,从Makefile开始看。grub-mkimage目标定义在grub源码的顶层Makefile文件中。

grub-mkimage
Makefile

grub-mkimage$(EXEEXT): $(grub_mkimage_OBJECTS) $(grub_mkimage_DEPENDENCIES) $(EXTRA_grub_mkimage_DEPENDENCIES) 
    @rm -f grub-mkimage$(EXEEXT)
    $(AM_V_CCLD)$(grub_mkimage_LINK) $(grub_mkimage_OBJECTS) $(grub_mkimage_LDADD) $(LIBS)

grub_mkimage_OBJECTS目标包含了链接grub-mkimage所需的最主要的目标文件。grub_mkimage_DEPENDENCIES目标包含了诸多grub生成的静态库文件。EXTRA_grub_mkimage_DEPENDENCIES为空定义。grub_mkimage_LINK为链接命令,grub_mkimage_LDADD包含了链接时所需的选项。下面重点看一下grub_mkimage_OBJECTS的定义。

grub-mkimage->grub_mkimage_OBJECTS
Makefile

grub_mkimage_OBJECTS = $(am_grub_mkimage_OBJECTS) \
    $(nodist_grub_mkimage_OBJECTS)

其中nodist_grub_mkimage_OBJECTS为空定义。am_grub_mkimage_OBJECTS的定义如下,

am_grub_mkimage_OBJECTS = util/grub_mkimage-grub-mkimage.$(OBJEXT) \
    util/grub_mkimage-mkimage.$(OBJEXT) \
    util/grub_mkimage-grub-mkimage32.$(OBJEXT) \
    util/grub_mkimage-grub-mkimage64.$(OBJEXT) \
    util/grub_mkimage-resolve.$(OBJEXT) \
    grub-core/kern/emu/grub_mkimage-argp_common.$(OBJEXT) \
    grub-core/osdep/grub_mkimage-init.$(OBJEXT) \
    grub-core/osdep/grub_mkimage-config.$(OBJEXT) \
    util/grub_mkimage-config.$(OBJEXT)

grub_mkimage-grub-mkimage.o由util/grub-mkimage.c编译而成。
util/grub_mkimage-grub-mkimage32.o由util/grub-mkimage32.c编译而成。
util/grub_mkimage-grub-mkimage64.o由util/grub-mkimage64.c编译而成。
util/grub_mkimage-resolve.o由util/resolve.c编译而成。
grub-core/kern/emu/grub_mkimage-argp_common.o由grub-core/kern/emu/argp_common.c编译而成。
grub-core/osdep/grub_mkimage-init.o由grub-core/osdep/init.c编译而成。
grub-core/osdep/grub_mkimage-config.o由grub-core/osdep/config.c编译而成。
util/grub_mkimage-config.o由util/config.c编译而成。

编译链接后,生成了grub-mkimage可执行文件,其入口函数定义在grub-mkimage.c文件中,下面开始看源码。

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

main
util/grub-mkimage.c

int main (int argc, char *argv[]) {
    FILE *fp = stdout;
    struct arguments arguments;
    unsigned i;

    grub_util_host_init (&argc, &argv);

    memset (&arguments, 0, sizeof (struct arguments));
    arguments.comp = GRUB_COMPRESSION_AUTO;
    arguments.modules_max = argc + 1;
    arguments.modules = xmalloc ((arguments.modules_max + 1)
        * sizeof (arguments.modules[0]));
    memset (arguments.modules, 0, (arguments.modules_max + 1)
        * sizeof (arguments.modules[0]));

    if (argp_parse (&argp, argc, argv, 0, 0, &arguments) != 0) {
        fprintf (stderr, "%s", _("Error in parsing command line arguments\n"));
        exit(1);
    }

    ...

本章只看main函数的第一部分。stdout为标准输出文件句柄,用来输出执行过程中的信息。grub_util_host_init函数的主要作用是将应用程序名,即”grub-mkimage”设置到全局变量program_name中。arguments结构保存了用于制作映像的各个参数,首先清0该结构,然后设置压缩方式comp为GRUB_COMPRESSION_AUTO。
设置模块的最大数量为参数的最大数量加1,并分配内存。然后调用argp_parse对传入的参数进行解析,将解析结果保存在arguments中,用于后面的处理,传入的参数argp是grub-mkimage这个可执行程序自定义的参数解析器。

【补充】grub-mkimage参数具体是如何解析的不做分析,感兴趣的可以阅读源码和原博文。

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

上一章分析了grub-mkimage命令对应的main函数如何对输入的参数进行解析,本章分析余下的代码。

main
util/grub-mkimage.c

int main (int argc, char *argv[]) {

    ...

    if (!arguments.image_target) {
        ...
        exit(1);
    }

    if (!arguments.prefix) {
        ...
        exit(1);
    }

    if (arguments.output) {
        fp = grub_util_fopen (arguments.output, "wb");
    }

    if (!arguments.dir) {
        const char *dn = grub_util_get_target_dirname (arguments.image_target);
        const char *pkglibdir = grub_util_get_pkglibdir ();
        char *ptr;
        arguments.dir = xmalloc (grub_strlen (pkglibdir) + grub_strlen (dn) + 2);
        ptr = grub_stpcpy (arguments.dir, pkglibdir);
        *ptr++ = '/';
        strcpy (ptr, dn);
    }

    grub_install_generate_image (arguments.dir, arguments.prefix, fp,
                                arguments.output, arguments.modules,
                                arguments.memdisk, arguments.pubkeys,
                                arguments.npubkeys, arguments.config,
                                arguments.image_target, arguments.note,
                                arguments.comp);

    grub_util_file_sync  (fp);
    fclose (fp);

    for (i = 0; i < arguments.nmodules; i++)
        free (arguments.modules[i]);

    free (arguments.dir);
    free (arguments.prefix);
    free (arguments.modules);

    if (arguments.output)
        free (arguments.output);

    return 0;
}

开头省略的部分代码解析了参数数组argv,并将主要的解析结果保存在arguments结构中。接下来检查一些必填参数。

1)image_target由选项-O指定,表示平台信息,由grub_install_get_image_target函数生成,最终返回一个grub_install_image_target_desc结构,对于i386而言,默认的image_target如下,

{
  .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
},

2)prefix由参数中的-p选项指定,表示grub文件所在的目录,例如”/boot/grub”。
3)output由参数中的-o选项指定,表示生成映像的保存地址,如果指定了,就通过grub_util_fopen函数打开改文件,其内部通过fopen函数打开文件,并返回文件描述符fp。
4)dir由参数-d选项指定,也表示grub一些文件所在的目录,例如”/usr/lib/grub/i386-pc”,如果未指定,就通过平台信息找到默认的目录。

grub_util_get_target_dirname返回image_target中的dirname,这里假设为”i386-pc”。

const char * grub_util_get_target_dirname (const struct grub_install_image_target_desc *t) {
    return t->dirname;
}

grub_util_get_pkglibdir返回”/usr/local/lib/grub”

const char * grub_util_get_pkglibdir (void)
{
  return GRUB_LIBDIR "/" PACKAGE;
}

#define GRUB_LIBDIR "/usr/local/lib"
PACKAGE = grub

如果没有指定arguments.dir,则最后的dir目录名为”/usr/local/lib/grub/i386-pc/”。

完成了一些初始化和检测工作后,接下来就调用最核心的grub_install_generate_image函数生成默认为core.img的映像文件。最后做一些收尾工作。

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

grub_install_generate_image第一部分
main->grub_install_generate_image
util/mkimage.c

void grub_install_generate_image (const char *dir, const char *prefix,
                 FILE *out, const char *outname, char *mods[],
                 char *memdisk_path, char **pubkey_paths,
                 size_t npubkeys, char *config_path,
                 const struct grub_install_image_target_desc *image_target,
                 int note,
                 grub_compression_t comp) {

    char *kernel_img, *core_img;
    size_t total_module_size, core_size;
    size_t memdisk_size = 0, config_size = 0;
    size_t prefix_size = 0;
    char *kernel_path;
    size_t offset;
    struct grub_util_path_list *path_list, *p;
    size_t decompress_size = 0;
    struct grub_mkimage_layout layout;

    if (comp == GRUB_COMPRESSION_AUTO)
        comp = image_target->default_compression;

    if (image_target->id == IMAGE_I386_PC
        || image_target->id == IMAGE_I386_PC_PXE
        || image_target->id == IMAGE_I386_PC_ELTORITO)
        comp = GRUB_COMPRESSION_LZMA;

    path_list = grub_util_resolve_dependencies (dir, "moddep.lst", mods);

    kernel_path = grub_util_get_path (dir, "kernel.img");

    if (image_target->voidp_sizeof == 8)
        total_module_size = sizeof (struct grub_module_info64);
    else
        total_module_size = sizeof (struct grub_module_info32);

    size_t i;
    for (i = 0; i < npubkeys; i++) {
        size_t curs;
        curs = ALIGN_ADDR (grub_util_get_image_size (pubkey_paths[i]));
        total_module_size += curs + sizeof (struct grub_module_header);
    }

    if (memdisk_path) {
        memdisk_size = ALIGN_UP(grub_util_get_image_size (memdisk_path), 512);
        total_module_size += memdisk_size + sizeof (struct grub_module_header);
    }

    if (config_path) {
        config_size = ALIGN_ADDR (grub_util_get_image_size (config_path) + 1);
        total_module_size += config_size + sizeof (struct grub_module_header);
    }

    if (prefix) {
        prefix_size = ALIGN_ADDR (strlen (prefix) + 1);
        total_module_size += prefix_size + sizeof (struct grub_module_header);
    }

    for (p = path_list; p; p = p->next)
        total_module_size += (ALIGN_ADDR (grub_util_get_image_size (p->name))
                        + sizeof (struct grub_module_header));

    ...

参数comp代表压缩方式,默认为GRUB_COMPRESSION_AUTO,在上一章初始化arguments结构时设置,此时设置comp为对应平台结构的default_compression,对于i386系列,设置为GRUB_COMPRESSION_LZMA,也即LZMA压缩算法。

grub_util_resolve_dependencies函数将传入的模块mods添加到返回结果path_list中,同时也根据moddep.lst文件将每个模块依赖的所有模块也添加到path_list中。mods数组也即arguments结构的modules变量,内部保存了各个模块名。

接下来通过grub_util_get_path函数获得kernel.img的完整路径,保存在kernel_path中。
对于i386而言,total_module_size为grub_module_info32结构的大小。

再往下,pubkey_paths和npubkeys由命令行的-k选项指定,memdisk_path,config_path和prefix分别由命令行的-m、-c和-p选项指定,这里假设都未假定。
最后遍历path_list中的模块,通过grub_util_get_image_size函数获得每个模块的大小,累计在total_module_size中。

main->grub_install_generate_image->grub_util_resolve_dependencies
util/resolve.c

struct grub_util_path_list * grub_util_resolve_dependencies (const char *prefix,
                const char *dep_list_file, char *modules[]) {
    char *path;
    FILE *fp;
    struct dep_list *dep_list;
    struct mod_list *mod_list = 0;
    struct grub_util_path_list *path_list = 0;

    path = grub_util_get_path (prefix, dep_list_file);
    fp = grub_util_fopen (path, "r");
    free (path);
    dep_list = read_dep_list (fp);
    fclose (fp);

    while (*modules){
        add_module (prefix, dep_list, &mod_list, &path_list, *modules);
        modules++;
    }

    free_dep_list (dep_list);
    free_mod_list (mod_list);

    struct grub_util_path_list *p, *prev, *next;

    for (p = path_list, prev = NULL; p; p = next) {
        next = p->next;
        p->next = prev;
        prev = p;
    }

    return prev;
}

传入的参数prefix默认为”/usr/local/lib/grub/i386-pc”,参数dep_list_file为”moddep.lst”文件名,内部保存了模块的依赖关系。modules为用户输入的需要编译进core.img的模块名数组。

首先通过grub_util_get_path函数连接路径prefix和文件名dep_list_file,最终的路径path为”/usr/local/lib/grub/i386-pc/moddep.lst”。接着通过grub_util_fopen函数以只读模式打开moddep.lst文件,然后调用read_dep_list函数读取依赖列表,返回的dep_list中保存了moddep.lst文件中设置的各个模块以及对应的依赖模块信息。

再往下循环将命令行中的模块modules以及根据”moddep.lst”文件记录的对应的依赖模块加入到path_list链表中。最后通过一个for循环将path_list中的模块反向,例如模块A依赖于模块B,加载时模块B先加载,再加载模块A。

main->grub_install_generate_image->grub_util_resolve_dependencies->read_dep_list
util/resolve.c

static struct dep_list * read_dep_list (FILE *fp) {
    struct dep_list *dep_list = 0;

    while (fgets (buf, sizeof (buf), fp)) {
        char *p;
        struct dep_list *dep;
        p = strchr (buf, ':');

        *p++ = '\0';

        dep = xmalloc (sizeof (*dep));
        dep->name = xstrdup (buf);
        dep->list = 0;

        dep->next = dep_list;
        dep_list = dep;

        while (*p) {
            struct mod_list *mod;
            char *name;

            while (*p && grub_isspace (*p))
                p++;

            if (! *p)
                break;

            name = p;

            while (*p && ! grub_isspace (*p))
                p++;

            *p++ = '\0';

            mod = (struct mod_list *) xmalloc (sizeof (*mod));
            mod->name = xstrdup (name);
            mod->next = dep->list;
            dep->list = mod;
        }
    }

    return dep_list;
}

首先在while循环中通过fgets函数逐行读取文件,并存入缓存buf中。”moddep.lst”文件的每行由冒号标识依赖的起始位置,
冒号之前是模块名name,冒号后边是其依赖的各个模块信息。接下来将冒号处的字符设置为空字符,以便通过xstrdup函数获得name字符串,再初始化dep_list链表结构。

然后从冒号后边的第一个字符开始,遍历各个模块,第一个while循环找到第一个有效字符,grub_isspace用于检测当前字符是否为有效字符,接下来通过第二个while循环找到第一个无效字符,将该无效字符设置为空字符,然后通过xstrdup获取模块名,最后将该模块信息插入到dep的list链表中。

最后返回的dep_list是个双层链表,第一个层链表的每个元素标识模块,然后每个模块的list链表存储了该模块依赖的所有模块名。

main->grub_install_generate_image->grub_util_resolve_dependencies->add_module
util/resolve.c

static void add_module (const char *dir, struct dep_list *dep_list, struct mod_list **mod_head,
        struct grub_util_path_list **path_head, const char *name) {
    char *mod_name;
    struct grub_util_path_list *path;
    struct mod_list *mod;
    struct dep_list *dep;

    mod_name = get_module_name (name);

    for (mod = *mod_head; mod; mod = mod->next)
        if (strcmp (mod->name, mod_name) == 0) {
            free (mod_name);
            return;
        }

    for (dep = dep_list; dep; dep = dep->next)
        if (strcmp (dep->name, mod_name) == 0) {
            for (mod = dep->list; mod; mod = mod->next)
                add_module (dir, dep_list, mod_head, path_head, mod->name);

            break;
        }

    mod = (struct mod_list *) xmalloc (sizeof (*mod));
    mod->name = mod_name;
    mod->next = *mod_head;
    *mod_head = mod;

    path = (struct grub_util_path_list *) xmalloc (sizeof (*path));
    path->name = get_module_path (dir, name);
    path->next = *path_head;
    *path_head = path;
}

传入的参数name为命令行中输入的模块,dep_list是通过read_dep_list函数从”moddep.lst”文件中读取的依赖关系,mod_head链表用于避免模块的重复,path_head链表用于返回结果。

首先通过get_module_name函数对命令行中的模块名作简单处理,例如删除模块名中的拓展名”.mod”。接下来的第一个for循环利用mod_head链表比较待分析的模块是否已经在链表中,如果已经存在,则直接返回。

然后遍历记录了依赖关系的链表dep_list,如果模块名mod_name和链表中的某个模块名相同,则递归add_module函数将该模块和”moddep.lst”文件中对应的依赖模块加入到path_head对应的链表中。

最后将模块名mod_name分别加入到mod_head和path_head链表中。

main->grub_install_generate_image->grub_util_get_image_size
grub-core/kern/emu/misc.c

size_t grub_util_get_image_size (const char *path) {
    FILE *f;
    size_t ret;
    off_t sz;

    f = grub_util_fopen (path, "rb");

    fseeko (f, 0, SEEK_END);
    sz = ftello (f);
    ret = (size_t) sz;

    fclose (f);

    return ret;
}

首先通过grub_util_fopen函数打开文件path,然后通过fseeko函数将文件指针指向文件末尾,fseeko函数和feek函数类似,不同之处在于偏移offset的数据类型,这里传入的SEEK_END表示将文件指针指向文件末尾。接着通过ftello函数获得文件开始指针到当前指针的大小sz,也即文件长度,保存在ret中并返回。ftello函数和ftell函数类似,不同之处在于返回类型。

grub_install_generate_image第二部分
main->grub_install_generate_image
util/mkimage.c

...

if (image_target->voidp_sizeof == 4)
    kernel_img = grub_mkimage_load_image32 (kernel_path, total_module_size,
                            &layout, image_target);
else
    kernel_img = grub_mkimage_load_image64 (kernel_path, total_module_size,
                            &layout, image_target);

if (image_target->voidp_sizeof == 8){

    ...

} else {
    /* Fill in the grub_module_info structure.  */
    struct grub_module_info32 *modinfo;
    if (image_target->flags & PLATFORM_FLAGS_MODULES_BEFORE_KERNEL)
        modinfo = (struct grub_module_info32 *) kernel_img;
    else
        modinfo = (struct grub_module_info32 *) (kernel_img + layout.kernel_size);

    modinfo->magic = grub_host_to_target32 (GRUB_MODULE_MAGIC);
    modinfo->offset = grub_host_to_target_addr (sizeof (struct grub_module_info32));
    modinfo->size = grub_host_to_target_addr (total_module_size);

    if (image_target->flags & PLATFORM_FLAGS_MODULES_BEFORE_KERNEL)
        offset = sizeof (struct grub_module_info32);
    else
        offset = layout.kernel_size + sizeof (struct grub_module_info32);
}

...

下面假设为32位系统,因此接下来通过grub_mkimage_load_image32函数读取kernel.img文件,并返回文件的起始指针kernel_img,该指针对应的内存除了存放kernel.img文件中的代码段、数据段、bss段之外,还为模块预留了空间

再往下进入else语句,flags用于表示kernel.img和模块存放的先后顺序,下面假设为先存kernel.img,再存各个模块,因此modinfo结构的起始地址是内存映像kernel_img的起始地址加上kernel.img,也即kernel_size的大小。

接下来设置模块列表头部grub_module_info32结构的魔数magic,模块数组的相对起始地址offset,所有模块的总大小total_module_size。最后计算的offset表示从映像开始处到模块数组的绝对偏移。

main->grub_install_generate_image->grub_mkimage_load_image第一部分
util/grub-mkimagexx.c

char * SUFFIX (grub_mkimage_load_image) (const char *kernel_path,
                  size_t total_module_size, struct grub_mkimage_layout *layout,
                  const struct grub_install_image_target_desc *image_target) {
    char *kernel_img, *out_img;
    const char *strtab;
    Elf_Ehdr *e;
    Elf_Shdr *sections;
    Elf_Addr *section_addresses;
    Elf_Addr *section_vaddresses;
    int i;
    Elf_Shdr *s;
    Elf_Half num_sections;
    Elf_Off section_offset;
    Elf_Half section_entsize;
    grub_size_t kernel_size;
    Elf_Shdr *symtab_section = 0;

    grub_memset (layout, 0, sizeof (*layout));

    layout->start_address = 0;

    kernel_size = grub_util_get_image_size (kernel_path);
    kernel_img = xmalloc (kernel_size);
    grub_util_load_image (kernel_path, kernel_img);

    e = (Elf_Ehdr *) kernel_img;
    if (! SUFFIX (check_elf_header) (e, kernel_size, image_target))
        grub_util_error ("invalid ELF header");

    section_offset = grub_target_to_host (e->e_shoff);
    section_entsize = grub_target_to_host16 (e->e_shentsize);
    num_sections = grub_target_to_host16 (e->e_shnum);

    sections = (Elf_Shdr *) (kernel_img + section_offset);

    s = (Elf_Shdr *) ((char *) sections
                + grub_host_to_target16 (e->e_shstrndx) * section_entsize);
    strtab = (char *) e + grub_host_to_target_addr (s->sh_offset);

    section_addresses = SUFFIX (locate_sections) (e, kernel_path,
                                        sections, section_entsize,
                                        num_sections, strtab,
                                        layout, image_target);

    section_vaddresses = xmalloc (sizeof (*section_addresses) * num_sections);

    for (i = 0; i < num_sections; i++)
        section_vaddresses[i] = section_addresses[i] + image_target->vaddr_offset;

    Elf_Addr current_address = layout->kernel_size;

    for (i = 0, s = sections; i < num_sections;
            i++, s = (Elf_Shdr *) ((char *) s + section_entsize))
        if (grub_target_to_host32 (s->sh_type) == SHT_NOBITS) {
            Elf_Word sec_align = grub_host_to_target_addr (s->sh_addralign);
            const char *name = strtab + grub_host_to_target32 (s->sh_name);

            if (sec_align)
                current_address = ALIGN_UP (current_address
                        + image_target->vaddr_offset, sec_align) - image_target->vaddr_offset;

            current_address = grub_host_to_target_addr (s->sh_addr) - image_target->link_addr;

            section_vaddresses[i] = current_address + image_target->vaddr_offset;
            current_address += grub_host_to_target_addr (s->sh_size);
        }

    current_address = ALIGN_UP (current_address + image_target->vaddr_offset,
                image_target->section_align) - image_target->vaddr_offset;
    layout->bss_size = current_address - layout->kernel_size;


    ...

这部分代码省略了is_relocatable的判断,该函数用于检测将elf文件中对应的section加载到内存后是否需要重定位,对于i386而言,不需要重定位。

首先清空grub_mkimage_layout结构,该结构用来保存映像中的各个参数。

参数kernel_path记录了kernel.img所在路径,通过grub_util_get_image_size函数计算该文件大小kernel_size,并分配内存kernel_img。然后通过grub_util_load_image函数读取kernel.img文件至kernel_img中,再通过check_elf_header函数验证kernel.img文件的elf头部结构,包括魔数,版本的信息。

接下来的section_offset表示section列表的起始地址,section_entsize表示每个section的头部结构的大小,num_sections表示section的个数。然后通过section_offset获得section列表在内存中的地址sections。

elf头部的e_shstrndx变量记录了字符串表的段索引,乘以section_entsize表示字符串表对应section的相对起始地址,再加上sections就获得字符串表在section列表中的绝对地址s,该section的sh_offset变量记录了字符串表在elf文件中的偏移,最终得到字符串表在内存kernel_img中的位置strtab。

接下来调用locate_sections函数定位代码段、数据段、bss段所在的位置,其中每个段的在内核映像中的起始地址保存在返回的数组section_addresses中,每个段的长度信息保存在数据结构layout中。

接下来将每个段的起始地址section_addresses加上image_target中指示的虚拟地址vaddr_offset,得到每个段的虚拟地址的起始地址数组section_vaddresses,对于i386,vaddr_offset为0。最后累加的current_address标识了整个内存映像的结束地址,对齐后减去kernel_size(根据下面locate_sections函数的分析,此时的kernel_size为bss段的起始地址),获得bss段在内存映像中的长度,保存在bss_size中。

main->grub_install_generate_image->grub_mkimage_load_image->locate_sections
util/grub-mkimagexx.c

static Elf_Addr * SUFFIX (locate_sections) (Elf_Ehdr *e, const char *kernel_path,
              Elf_Shdr *sections, Elf_Half section_entsize,
              Elf_Half num_sections, const char *strtab,
              struct grub_mkimage_layout *layout,
              const struct grub_install_image_target_desc *image_target) {
    int i;
    Elf_Addr *section_addresses;
    Elf_Shdr *s;

    layout->align = 1;

    section_addresses = xmalloc (sizeof (*section_addresses) * num_sections);
    memset (section_addresses, 0, sizeof (*section_addresses) * num_sections);

    layout->kernel_size = 0;

    for (i = 0, s = sections; i < num_sections;
            i++, s = (Elf_Shdr *) ((char *) s + section_entsize))
        if ((grub_target_to_host (s->sh_flags) & SHF_ALLOC) 
            && grub_host_to_target32 (s->sh_addralign) > layout->align)
            layout->align = grub_host_to_target32 (s->sh_addralign);

    for (i = 0, s = sections; i < num_sections;
        i++, s = (Elf_Shdr *) ((char *) s + section_entsize))
        if (SUFFIX (is_text_section) (s, image_target)) {
            layout->kernel_size = SUFFIX (put_section) (s, i, layout->kernel_size,
                                            section_addresses, strtab, image_target);
            if ( grub_host_to_target_addr (s->sh_addr) != image_target->link_addr) {
                ...
            }
        }

    layout->kernel_size = ALIGN_UP (layout->kernel_size + image_target->vaddr_offset,
                  image_target->section_align) - image_target->vaddr_offset;
    layout->exec_size = layout->kernel_size;

    for (i = 0, s = sections; i < num_sections; 
        i++, s = (Elf_Shdr *) ((char *) s + section_entsize))
        if (SUFFIX (is_data_section) (s, image_target))
            layout->kernel_size = SUFFIX (put_section) (s, i, layout->kernel_size,
                          section_addresses, strtab, image_target);

    layout->bss_start = layout->kernel_size;
    layout->end = layout->kernel_size;


    for (i = 0, s = sections; i < num_sections;
        i++, s = (Elf_Shdr *) ((char *) s + section_entsize))
        if (SUFFIX (is_bss_section) (s, image_target))
            layout->end = SUFFIX (put_section) (s, i, layout->end,
                      section_addresses, strtab, image_target);

    layout->end = ALIGN_UP (layout->end + image_target->vaddr_offset,
                        image_target->section_align) - image_target->vaddr_offset;

    layout->kernel_size = layout->end;

    return section_addresses;
}

首先分配内存section_addresses,用于存放每个section在内存中相对于image_target的link_addr的起始地址。对于i386而言,该地址为GRUB_KERNEL_I386_PC_LINK_ADDR,即0x9000

接下来遍历所有section,取所有sh_addralign的最大值,保存在layout->align中。sh_addralign该字段表示该节中的地址应该按多少个字节对齐,也即在进程地址空间中的映射地址sh_addr必须是一个向sh_addralign对齐的值,sh_addralign字段的取值只能是0、1、或2的整数倍,如果该字段的值是0或1,表示不需要对齐。

再往下的for循环遍历所有代码段,is_text_section函数根据section的sh_flags标志位检测对应的section是否为代码段。当为代码段时,首先通过put_section函数计算section的加载到内存中时的相对起始地址section_addresses,最后的返回值,也即传入的参数layout->kernel_size表示代码段加载到内存中后的相对结束地址,其实这里就是代码段在内存中的长度。最后将代码段的总长度保存到exec_size中。注意对于i386而言,kernel.img的makefile文件将代码段起始地址设置到0x9000,如果这里不相等,就报错。

接下来通过is_data_section函数确定并遍历数据段,最后返回的layout->kernel_size表示数据段在内存中相对于link_addr的结束地址,也即bss段在内存中相对于link_addr的起始地址。

同样的,针对bss段,最终返回的layout->end保存了bss段的结束地址,存了kernel_size中,也即整个kernel.img在内存映像中的长度。但是对于i386而言,is_bss_section始终返回0,因此不会进行这部分计算。

因此,针对一般的cpu架构而言,layout的exec_size字段标识了代码段的结束地址,bss_start字段标识了数据段的结束地址,end字段标识了bss段的结束地址。最后返回section_addresses标识了每个段在内存映像中相对于link_addr的起始地址。

main->grub_install_generate_image->grub_mkimage_load_image->locate_sections->put_section
util/grub-mkimagexx.c

static Elf_Addr SUFFIX (put_section) (Elf_Shdr *s, int i, Elf_Addr current_address,
                            Elf_Addr *section_addresses, const char *strtab,
                            const struct grub_install_image_target_desc *image_target) {
    Elf_Word align = grub_host_to_target_addr (s->sh_addralign);
    const char *name = strtab + grub_host_to_target32 (s->sh_name);

    if (align)
      current_address = ALIGN_UP (current_address + image_target->vaddr_offset,
                      align)
        - image_target->vaddr_offset;

    current_address = grub_host_to_target_addr (s->sh_addr) - image_target->link_addr;
    section_addresses[i] = current_address;
    current_address += grub_host_to_target_addr (s->sh_size);
    return current_address;
}

首先通过字符串表strtab获得该段的名称name,current_address计算该段加载到内存中并对齐后相对于link_addr的起始地址。如果是i386的代码段,其最终加载地址就为link_addr的0x9000,也即current_address为0。最后返回的current_address存储了每个section相对于link_addr的结束地址,也即section的大小。

main->grub_install_generate_image->grub_mkimage_load_image第二部分
util/grub-mkimagexx.c

    ...

    layout->reloc_size = 0;
    layout->reloc_section = NULL;

    out_img = xmalloc (layout->kernel_size + total_module_size);
    memset (out_img, 0, layout->kernel_size + total_module_size);

    for (i = 0, s = sections; i < num_sections;
        i++, s = (Elf_Shdr *) ((char *) s + section_entsize))
        if (SUFFIX (is_data_section) (s, image_target)
            || (SUFFIX (is_bss_section) (s, image_target) && (image_target->id != IMAGE_UBOOT))
            || SUFFIX (is_text_section) (s, image_target)) {
            if (grub_target_to_host32 (s->sh_type) == SHT_NOBITS)
                memset (out_img + section_addresses[i], 0,
                grub_host_to_target_addr (s->sh_size));
            else
                memcpy (out_img + section_addresses[i],
                        kernel_img + grub_host_to_target_addr (s->sh_offset),
                        grub_host_to_target_addr (s->sh_size));
        }

    free (kernel_img);
    free (section_vaddresses);
    free (section_addresses);

    return out_img;
}

首先分配内存out_img,用于保存kernel.img和module,然后遍历elf文件中的代码段、数据段和bss段,根据section_addresses中计算的内存起始地址,将每个段中的内容拷贝到out_img,如果是bss段,直接清0即可。注意对i386而言,由于is_bss_section默认返回0,这里只拷贝了代码段和数据段。最后返回刚刚分配的out_img。

grub_install_generate_image第三部分

...

for (p = path_list; p; p = p->next) {
    struct grub_module_header *header;
    size_t mod_size;

    mod_size = ALIGN_ADDR (grub_util_get_image_size (p->name));

    header = (struct grub_module_header *) (kernel_img + offset);
    header->type = grub_host_to_target32 (OBJ_TYPE_ELF);
    header->size = grub_host_to_target32 (mod_size + sizeof (*header));
    offset += sizeof (*header);

    grub_util_load_image (p->name, kernel_img + offset);
    offset += mod_size;
}

...

接下来遍历每个模块以及其依赖模块链表path_list,通过grub_util_get_image_size函数根据模块名name获得模块大小mod_size。offset初始值是前面计算的在内存映像kernel_img内模块数组相对于起始地址的偏移,接下来初始化每个模块头grub_module_header,设置模块类型type为OBJ_TYPE_ELF,模块大小size为模块的大小mod_size加上头部header的大小。最后通过grub_util_load_image加载模块,并递增offset。
除了模块,这部分代码省略了对memdisk和config等等其他模块的加载。

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

上一章分析了如何将kernel.img文件以及各个的模块和对应的依赖模块读取并写入内存映像kernel_img中,本章分析如何将kernel_img中的数据以及解压缩程序写入最终的core.img文件中。

grub_install_generate_image第四部分
util/mkimage.c

...

compress_kernel (image_target, kernel_img, layout.kernel_size + total_module_size,
       &core_img, &core_size, comp);
free (kernel_img);

if (image_target->flags & PLATFORM_FLAGS_DECOMPRESSORS) {
    char *full_img;
    size_t full_size;
    char *decompress_path, *decompress_img;
    const char *name;

    switch (comp) {
    case GRUB_COMPRESSION_XZ:
        name = "xz_decompress.img";
        break;
    case GRUB_COMPRESSION_LZMA:
        name = "lzma_decompress.img";
        break;
    case GRUB_COMPRESSION_NONE:
        name = "none_decompress.img";
        break;
    default:
        grub_util_error (_("unknown compression %d"), comp);
    }

    decompress_path = grub_util_get_path (dir, name);
    decompress_size = grub_util_get_image_size (decompress_path);
    decompress_img = grub_util_read_image (decompress_path);

    if ((image_target->id == IMAGE_I386_PC
        || image_target->id == IMAGE_I386_PC_PXE
        || image_target->id == IMAGE_I386_PC_ELTORITO)
        && decompress_size > GRUB_KERNEL_I386_PC_LINK_ADDR - 0x8200)
        grub_util_error ("%s", _("Decompressor is too big"));

    *((grub_uint32_t *) (decompress_img + image_target->decompressor_compressed_size))
        = grub_host_to_target32 (core_size);

    *((grub_uint32_t *) (decompress_img + image_target->decompressor_uncompressed_size))
        = grub_host_to_target32 (layout.kernel_size + total_module_size);

    full_size = core_size + decompress_size;
    full_img = xmalloc (full_size);

    memcpy (full_img, decompress_img, decompress_size);
    memcpy (full_img + decompress_size, core_img, core_size);

    free (core_img);
    core_img = full_img;
    core_size = full_size;
    free (decompress_img);
    free (decompress_path);
}

...

首先通过compress_kernel函数将内核映像kernel_img压缩成core_img,返回的core_size中保存了core_img映像的大小,compress_kernel函数往下涉及到压缩方面的知识,这里就不往下分析了。传入的参数comp默认为GRUB_COMPRESSION_LZMA,因此解压缩模块的名称为lzma_decompress.img。lzma_decompress.img也由grub自身编译而成,查看grub-core下的makefile文件可知该文件由boot/i386/pc/startup_raw.S文件编译而来。

接下来计算lzma_decompress.img文件所在路径decompress_path,文件大小decompress_size,并通过grub_util_read_image 函数将该文件载入内存decompress_img。
对于i386而言,lzma_decompress.img文件的大小不能大于0x9000减去0x8200的值,其中0x8200是最终core.img的装载起始地址,头部就是lzma_decompress.img文件对应的映像,0x9000是core.img中kernel.img的装载起始地址,因此lzma_decompress.img的大小不能大于两者之差。

再往下,在lzma_decompress.img文件对应的内存映像decompress_img中设置压缩文件的大小core_size,以及解压缩后文件的大小layout.kernel_size + total_module_size,可以查看对应的源文件startup_raw.S,在该文件的头部预留了该空间。对应的文件偏移decompressor_compressed_size和decompressor_uncompressed_size分别由下列宏定义确定,

#define GRUB_DECOMPRESSOR_I386_PC_COMPRESSED_SIZE   0x08
#define GRUB_DECOMPRESSOR_I386_PC_UNCOMPRESSED_SIZE 0x0c

准备工作完成后,接下来要合并两个文件,full_size计算压缩映像core_img和解压缩映像decompress_img大小的和,然后分配该大小的内存空间,并通过memcpy依次存入映像decompress_img和core_img,最后更新core_img和core_size的值。

grub_install_generate_image第五部分
util/mkimage.c

    ...

    switch (image_target->id) {
    case IMAGE_I386_PC:
    case IMAGE_I386_PC_PXE:
    case IMAGE_I386_PC_ELTORITO:
        unsigned num;
        char *boot_path, *boot_img;
        size_t boot_size;

        num = ((core_size + GRUB_DISK_SECTOR_SIZE - 1) >> GRUB_DISK_SECTOR_BITS);

        boot_path = grub_util_get_path (dir, "diskboot.img");
        boot_size = grub_util_get_image_size (boot_path);
        if (boot_size != GRUB_DISK_SECTOR_SIZE)
            grub_util_error ();

        boot_img = grub_util_read_image (boot_path);


        struct grub_pc_bios_boot_blocklist *block;
        block = (struct grub_pc_bios_boot_blocklist *) (boot_img
                              + GRUB_DISK_SECTOR_SIZE
                              - sizeof (*block));
        block->len = grub_host_to_target16 (num);


        grub_util_write_image (boot_img, boot_size, out, outname);
        free (boot_img);
        free (boot_path);
        break;

    ...

    }

    grub_util_write_image (core_img, core_size, out, outname);

    free (core_img);
    free (kernel_path);
    free (layout.reloc_section);
    grub_util_free_path_list (path_list);
}

首先计算core_img大小对应的扇区数num,然后获取diskboot.img文件路径boot_path,获取该文件大小boot_size,其必须为一个扇区的大小GRUB_DISK_SECTOR_BITS,默认为512字节,接着读取该文件至内存boot_img,
num为扇区数,向上取整。GRUB_DISK_SECTOR_BITS表示每个扇区的字节数,为512字节。然后设置diskboot.img文件中block列表,最后一项block的字段len(对应diskboot.S文件的blocklist_default_len标号处)设置为core_img对应的扇区大小num。

再往下通过grub_util_write_image将该diskboot.img文件对应的内存映像写入out中,最后继续调用grub_util_write_image将core_img也写入out中。

diskboot.img由boot/i386/pc/diskboot.S编译而来,由第一扇区的boot.S源码对应的内存映像负责装入内存。

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

总结
这里简单总结一下grub-mkimage生成的文件core.img的结构,首先是diskboot.img,接下来是解压缩程序lzma_decompress.img,再往下是kernel.img,最后是各个模块module对应的映像。

下面其实也可以猜到计算机启动后是怎么加载这些文件的了,首先读取第一扇区的boot.img,该文件处的某处一定保存了整个core.img的起始扇区,然后从该起始扇区处首先读取一个扇区,即diskboot.img,该映像的结尾处保存了后续映像的长度,根据该长度读取这些数据,数据的头部即lzma_decompress.img映像又保存了加压缩的参数,例如压缩文件的大小,以及解压缩后文件的大小,根据这些参数对后续数据进行解压缩,得到kernel.img以及各个module模块的映像起始地址,最后进入kernel.img的入口函数继续执行。

 类似资料: