菜单

kaoqi
kaoqi
发布于 2026-07-22 / 10 阅读
0
0

GRUB2的MOD_INIT机制

MOD_INIT是定义在每个模块c语言源文件中的一个块,这个块需要由别的东西来解释才会最终生效。

下面是一段示例代码:

GRUB_MOD_INIT(acpi)
{
  cmd = grub_register_extcmd_lockdown ("acpi", grub_cmd_acpi, 0,
                                       N_("[-1|-2] [--exclude=TABLE1,TABLE2|"
                                          "--load-only=TABLE1,TABLE2] FILE1"
                                          " [FILE2] [...]"),
                                       N_("Load host ACPI tables and tables "
                                          "specified by arguments."),
                                       options);
}

可以看到,在这里以GRUB_MODE_INIT的这种仿函数代码的方式定义了一个acpi的命令,这个模块初始化的函数应该要以某种方式添加到grub的command中。

它对应的宏展开应该位:(dl.h文件中)

#define GRUB_MOD_INIT(name)	\
static void grub_mod_init (grub_dl_t mod __attribute__ ((unused))) __attribute__ ((used)); \
static void \
grub_mod_init (grub_dl_t mod __attribute__ ((unused)))

这个含义就是说,这个东西被展开成一个grub_mod_init的函数。

当然,到此还远远谈不上完成,因为模块的动态加载才是这里init函数的主要目的,就是希望将整个的模块代码整合成一个带完整dl中描述module头的东西。

所以有关grub_mod_init这个函数,应该在/core/kern/dl.c文件的grub_dl_resolve_symbols函数中,通过对段的解析来完成真正的dl模块头。

grub_dl_resolve_symbols函数本身输入了一个当前要加载的mod,以及一个Elf_Ehdr的头指针。

// 32 位版本
typedef struct {
    unsigned char e_ident[EI_NIDENT];  // 魔数和其他标识信息
    Elf32_Half    e_type;              // 文件类型
    Elf32_Half    e_machine;           // 目标架构
    Elf32_Word    e_version;           // 文件版本
    Elf32_Addr    e_entry;             // 程序入口虚拟地址
    Elf32_Off     e_phoff;             // 程序头表在文件中的偏移
    Elf32_Off     e_shoff;             // 节头表在文件中的偏移
    Elf32_Word    e_flags;             // 处理器特定标志
    Elf32_Half    e_ehsize;            // ELF 头本身的大小
    Elf32_Half    e_phentsize;         // 程序头表中每个条目的大小
    Elf32_Half    e_phnum;             // 程序头表中的条目数量
    Elf32_Half    e_shentsize;         // 节头表中每个条目的大小
    Elf32_Half    e_shnum;             // 节头表中的条目数量
    Elf32_Half    e_shstrndx;          // 字符串表在节头表中的索引
} Elf32_Ehdr;

这里还有一个是mod的结构

struct grub_dl
{
  char *name;
  grub_uint64_t ref_count;
  int persistent;
  grub_dl_dep_t dep;
  grub_dl_segment_t  segment;
  Efl_Sym *symtab;
  grub_size_t symsize;
  void (*init) (struct grub_dl *mod);
  void (*fini) (void);
  void *base;
  grub_size sz;
  struct grub_dl *next;
}

这将会在grub_dl_resolve_symbols函数的后半段体现:

可以看到这里,随着dl的加载器里,具体的针对symbol的检索的进行,会在找到地方进行mod头的赋值。

  • sym->st_value 是该符号(“grub_mod_init”)在内存中的地址,即初始化函数的实际入口地址

  • (void (*) (grub_dl_t)) 是一个强制类型转换,将地址转换为函数指针类型

  • 这个函数指针指向一个接受 grub_dl_t 类型参数、无返回值的函数

  • 最终将转换后的函数指针赋值给 mod->init

这样就完成了原本C文件中的init函数向dl中头模块函数的具体的初始化函数的指针赋值了。

这里额外再补充一个具体的二进制文件展示。

代码是首先编译为kernel_exec-acpi.o,然后链接并生成为kernel.exec之后,最终转为efi文件的。这里的比如acpi的模块,将会被整合到kernel的内部。

在efi的源文件中,这里强烈建议通过阅读ELF头来做一个对应,下面是一个具体的对应关系展示:

1、首先,通过objdump命令可以确认最终编译出来的kaoqi.efi文件中有哪些段:

2、通过hexdump命令直接跳转到对应的mods段查看:

可以看到,这里0x20020的位置出现了一个明显的elf头的魔术字,这说明这里符合elf.h中定义的Elf64_Ehdr的格式(即,此处会将一系列的mods变成elf格式头的一个链表)

typedef struct
{
  unsigned char	e_ident[EI_NIDENT];	/* Magic number and other info */
  Elf64_Half	e_type;			/* Object file type */
  Elf64_Half	e_machine;		/* Architecture */
  Elf64_Word	e_version;		/* Object file version */
  Elf64_Addr	e_entry;		/* Entry point virtual address */
  Elf64_Off	e_phoff;		/* Program header table file offset */
  Elf64_Off	e_shoff;		/* Section header table file offset */
  Elf64_Word	e_flags;		/* Processor-specific flags */
  Elf64_Half	e_ehsize;		/* ELF header size in bytes */
  Elf64_Half	e_phentsize;		/* Program header table entry size */
  Elf64_Half	e_phnum;		/* Program header table entry count */
  Elf64_Half	e_shentsize;		/* Section header table entry size */
  Elf64_Half	e_shnum;		/* Section header table entry count */
  Elf64_Half	e_shstrndx;		/* Section header string table index */
} Elf64_Ehdr;

这里的Elf64_Ehdr正是grub_dl_resolve_symbols中所需要的关键ELF链表头:

此时,mod模块名的查找正是通过这里的Ehdr中指向偏移所代表的含义所拼接成的:

3、一个直接的证据证明这里的mod段就是Ehdr的头格式:

在Ehdr中有一个描述架构的字段,这里的值为00B7

可以在elf的头文件中找到,0xB7对应的十进制的值为183,刚好就是AARCH64的枚举值:

相应的,这个mod的shoffset可以找到节偏移对应的str的名称(节偏移为0x26F0)

也即从基地址+0x26F0的地方,会有这个section的地址:

可以看到一系列的modname:

这里最终也没能完成完整的ELF格式的二进制代码解释,可惜。

但是明确INIT作为模块的初始化函数,没有问题。

进一步补充的说,模块名应该由下面这两个东西确定位置

已经阵亡了,看不懂。


评论