转载于
”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的入口函数继续执行。