grub-mkimage源码分析---2

grub-mkimage源码分析—2

上一张分析了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;
}

开头省略的代码在《grub-mkimage源码分析—1》中分析过了,该部分代码解析了参数数组argv,并将主要的解析结果保存在arguments结构中。接下来检查一些必填参数。
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
    },

prefix由参数中的-p选项指定,表示grub文件所在的目录,例如”/boot/grub”。output由参数中的-o选项指定,表示生成映像的保存地址,如果制定了,就通过grub_util_fopen函数打开改文件,其内部通过fopen函数打开文件,并返回文件描述符fp。
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函数较长,本章先分析前半部分,后半部分留在下一章再分析。

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 {
        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表示从映像开始处到模块数组的绝对偏移。

下面要分析的grub_mkimage_load_image32函数由于较长,分为多个部分分析。

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等等其他模块的加载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值