【GRUB】GRUB2代码初步解析

【GRUB】GRUB2代码初步解析初步解析 GRUB2 代码

目录说明

使用GRUB版本2.02,目录如下(非完全,还有文件没有包含):

【GRUB】GRUB2代码初步解析

asm-tests:这里面是几个汇编代码文件,似乎没有什么用;

build-aux:包含了编译时可能用到的脚本;

conf:编译需要用的文件,make的时候会用到;

docs:主要时帮助文件,还有grub.cfg示例模板;

grub-core:GRUB的主体代码;

include:GRUB主体需要使用到的头文件;

m4:包含m4文件,configure的时候会使用到;

po:这个目录下主要由两种文件,一种时.po文件,另一种时.gmo文件。它们是用来支持多语言的,这里简单说明下。

.po文件就是一个文本文件,翻译就在这里做,下面是一个例子:

#: grub-core/hello/hello.c:36 msgid "Hello World" msgstr "Hello World"

第一句指定了代码中的位置,第二句是代码中的原文,第三句就是不同语言下的翻译。

.gmo文件时将.po文件通过gettext工具生成的二进制,它们是实际使用的翻译。

tests:一些测试的脚本;

themes:GRUB界面的主题;

unicode:Unicode数据;

util:一些工具源的文件。

编译说明

编译主要分为3个部分。

首先是configure,下面是一种配置:

./configure --target=x86_64 --with-platform=efi

因为GRUB可以给不同的平台使用,所以这里需要指定目标硬件平台。目前比较常用的有x86_64位平台,通过–target来指定。x86_64位平台使用UEFI BIOS,所以通常还会带–with-platform来指定将GRUB编译成适用于UEFI的应用。

第二步是make,这个不用多说。

第三步是生成GRUB文件:

./grub-mkimage -p . -d ./grub-core/ -O x86_64-efi -o bootx64.efi normal minicmd

这里需要说明,我们编译的是用于x86平台适用于UEFI BIOS启动的GRUB应用,所以使用上述命令来生成独立的GRUB,实际上也可以直接安装到本地的电脑,可以使用编译生成的grub-install工具。

由于本文主要介绍代码,所以只做简单的编译介绍。

入口

当我们分析代码的时候,入口函数是非常重要的一部分,本节主要介绍GRUB的入口。

需要说明,从本节以及之后的代码,都是针对x86平台用于UEFI的GRUB应用为基础进行介绍的

GRUB在通常情况下是不可见的,当不存在GRUB配置文件(默认是grub.cfg)时,才会进入GRUB界面,如下所示:

【GRUB】GRUB2代码初步解析

通过这个界面,我们可以比较容易地找到对应的代码,它位于如下的文件中:

grub-core/normal/main.c

可以看到上述的界面其实是通过一个命令进入的,这个命令是normal,在如下的代码中注册:

GRUB_MOD_INIT(normal) { unsigned i; grub_boot_time ("Preparing normal module"); /* Previously many modules depended on gzio. Be nice to user and load it. */ grub_dl_load ("gzio"); grub_errno = 0; grub_normal_auth_init (); grub_context_init (); grub_script_init (); grub_menu_init (); grub_xputs_saved = grub_xputs; grub_xputs = grub_xputs_normal; /* Normal mode shouldn't be unloaded. */ if (mod) grub_dl_ref (mod); cmd_clear = grub_register_command ("clear", grub_mini_cmd_clear, 0, N_("Clear the screen.")); grub_set_history (GRUB_DEFAULT_HISTORY_SIZE); grub_register_variable_hook ("pager", 0, grub_env_write_pager); grub_env_export ("pager"); /* Register a command "normal" for the rescue mode. */ grub_register_command ("normal", grub_cmd_normal, 0, N_("Enter normal mode.")); grub_register_command ("normal_exit", grub_cmd_normal_exit, 0, N_("Exit from normal mode."));

可以看到不仅注册了normal,还注册了normal_exit,当你看到上面的界面之后,就可以通过normal_exit来退出这个界面:

【GRUB】GRUB2代码初步解析

退出之后来到了rescue界面,这个会在以后讨论。

根据normal这个点,我们可以往前追溯,来找到真正的入口。由于normal是一条命令,那么一定有一个地方会调用这个命令,

具体的代码位于:

grub-core/kern/main.c

如下所示:

/* The main routine. */ void __attribute__ ((noreturn)) grub_main (void) { /* First of all, initialize the machine. */ grub_machine_init (); grub_boot_time ("After machine init."); /* Hello. */ grub_setcolorstate (GRUB_TERM_COLOR_HIGHLIGHT); grub_printf ("Welcome to GRUB!\n\n"); grub_setcolorstate (GRUB_TERM_COLOR_STANDARD); grub_load_config (); grub_boot_time ("Before loading embedded modules."); /* Load pre-loaded modules and free the space. */ grub_register_exported_symbols (); #ifdef GRUB_LINKER_HAVE_INIT grub_arch_dl_init_linker (); #endif grub_load_modules (); grub_boot_time ("After loading embedded modules."); /* It is better to set the root device as soon as possible, for convenience. */ grub_set_prefix_and_root (); grub_env_export ("root"); grub_env_export ("prefix"); /* Reclaim space used for modules. */ reclaim_module_space (); grub_boot_time ("After reclaiming module space."); grub_register_core_commands (); grub_boot_time ("Before execution of embedded config."); if (load_config) grub_parser_execute (load_config); grub_boot_time ("After execution of embedded config. Attempt to go to normal mode"); grub_load_normal_mode (); grub_rescue_run (); }

该函数的最后有grub_load_normal_mode(),就是加载并执行normal命令。

还可以看到,在退出normal之后,就进入到了rescue模式,与前面的测试符合。

当然这还不是真正的入口,真正的入口在调用grub_main()的地方:

start: _start: movq %rcx, EXT_C(grub_efi_image_handle)(%rip) movq %rdx, EXT_C(grub_efi_system_table)(%rip) andq $~0xf, %rsp call EXT_C(grub_main) /* Doesn't return. */

对于UEFI来说,它会传入一个EFI_HANDLE和一个EFI_SYSTEM_TABLE作为参数,并赋值给这里的两个参数grub_efi_image_handle和grub_efi_system_table,供后续使用。

上述代码在startup.S文件中,这样的同名文件有很多个,不同的Configure会使用不同的文件,具体可以参考Makefile文件:

kernel_exec_SOURCES = kern/x86_64/efi/startup.S \ kern/i386/efi/tsc.c \ kern/i386/tsc_pmtimer.c \ kern/x86_64/efi/callwrap.S \ kern/i386/efi/init.c bus/pci.c \ kern/x86_64/dl.c disk/efi/efidisk.c \ kern/efi/efi.c kern/efi/init.c \ kern/efi/mm.c term/efi/console.c \ kern/acpi.c kern/efi/acpi.c \ kern/i386/tsc.c kern/i386/tsc_pit.c \ kern/compiler-rt.c kern/mm.c kern/time.c \ kern/generic/millisleep.c kern/command.c \ kern/corecmd.c kern/device.c kern/disk.c \ kern/dl.c kern/env.c kern/err.c \ kern/file.c kern/fs.c kern/list.c \ kern/main.c kern/misc.c kern/parser.c \ kern/partition.c kern/rescue_parser.c \ kern/rescue_reader.c kern/term.c

如此,我们就可以确定GRUB的入口。

GRUB中的UEFI调用

前面已经提到过多次,本文主要研究GRUB作为UEFI应用的使用,所以这里就开始说明GRUB下是如何调用到UEFI的内容的。

上一节已经提到,入口函数中会传递EFI_HANDLE和EFI_SYSTEM_TABLE作并赋值给GRUB中的变量,这也是UEFI应用的标配,通过这两个变量就可以调用到UEFI接口,定义在grub-core/kern/efi/efi.c中:

/* The handle of GRUB itself. Filled in by the startup code. */ grub_efi_handle_t grub_efi_image_handle; /* The pointer to a system table. Filled in by the startup code. */ grub_efi_system_table_t *grub_efi_system_table;

在GRUB中对UEFI的常用接口进行了包装,比如获取Protocol等,它们定义在include/grub/efi/efi.h中:

 #include 
  
    #include 
   
     #include 
    
      /* Functions. */ void *EXPORT_FUNC(grub_efi_locate_protocol) (grub_efi_guid_t *protocol, void *registration); grub_efi_handle_t * EXPORT_FUNC(grub_efi_locate_handle) (grub_efi_locate_search_type_t search_type, grub_efi_guid_t *protocol, void *search_key, grub_efi_uintn_t *num_handles); void *EXPORT_FUNC(grub_efi_open_protocol) (grub_efi_handle_t handle, grub_efi_guid_t *protocol, grub_efi_uint32_t attributes); int EXPORT_FUNC(grub_efi_set_text_mode) (int on); void EXPORT_FUNC(grub_efi_stall) (grub_efi_uintn_t microseconds); void * EXPORT_FUNC(grub_efi_allocate_pages) (grub_efi_physical_address_t address, grub_efi_uintn_t pages); void EXPORT_FUNC(grub_efi_free_pages) (grub_efi_physical_address_t address, grub_efi_uintn_t pages); int EXPORT_FUNC(grub_efi_get_memory_map) (grub_efi_uintn_t *memory_map_size, grub_efi_memory_descriptor_t *memory_map, grub_efi_uintn_t *map_key, grub_efi_uintn_t *descriptor_size, grub_efi_uint32_t *descriptor_version); grub_efi_loaded_image_t *EXPORT_FUNC(grub_efi_get_loaded_image) (grub_efi_handle_t image_handle); void EXPORT_FUNC(grub_efi_print_device_path) (grub_efi_device_path_t *dp); char *EXPORT_FUNC(grub_efi_get_filename) (grub_efi_device_path_t *dp); grub_efi_device_path_t * EXPORT_FUNC(grub_efi_get_device_path) (grub_efi_handle_t handle); grub_efi_device_path_t * EXPORT_FUNC(grub_efi_find_last_device_path) (const grub_efi_device_path_t *dp); grub_efi_device_path_t * EXPORT_FUNC(grub_efi_duplicate_device_path) (const grub_efi_device_path_t *dp); grub_err_t EXPORT_FUNC (grub_efi_finish_boot_services) (grub_efi_uintn_t *outbuf_size, void *outbuf, grub_efi_uintn_t *map_key, grub_efi_uintn_t *efi_desc_size, grub_efi_uint32_t *efi_desc_version); grub_err_t EXPORT_FUNC (grub_efi_set_virtual_address_map) (grub_efi_uintn_t memory_map_size, grub_efi_uintn_t descriptor_size, grub_efi_uint32_t descriptor_version, grub_efi_memory_descriptor_t *virtual_map); void *EXPORT_FUNC (grub_efi_get_variable) (const char *variable, const grub_efi_guid_t *guid, grub_size_t *datasize_out); grub_err_t EXPORT_FUNC (grub_efi_set_variable) (const char *var, const grub_efi_guid_t *guid, void *data, grub_size_t datasize); int EXPORT_FUNC (grub_efi_compare_device_paths) (const grub_efi_device_path_t *dp1, const grub_efi_device_path_t *dp2); extern void (*EXPORT_VAR(grub_efi_net_config)) (grub_efi_handle_t hnd, char device, char path); #if defined(__arm__) || defined(__aarch64__) void *EXPORT_FUNC(grub_efi_get_firmware_fdt)(void); #endif 
     
    
  

因此我们就可以在这里调用UEFI中实现的Protocol了。

在【UEFI基础】Protocol介绍中实现了一个EFI_HELLO_WORLD_PROTOCOL,下面就以它为例,在GRUB下调用Protocol,相关的代码可以在grub-2.02: GRUB2源代码,来自ftp://ftp.gnu.org/gnu/grub/,版本是grub-2.02.tar.gz。找到。

具体步骤如下:

1. 首先在api.h中定义相关的Protocol和GUID:

#define EFI_HELLO_WORLD_PROTOCOL_GUID \ { 0x038f1af5, 0x1c8d, 0x408f, \ { 0xab, 0x25, 0x30, 0xae, 0xb5, 0x96, 0x5d, 0x6e } \ } struct efi_hello_world { grub_efi_uint64_t revision; grub_efi_status_t (*hello) (struct efi_hello_world *this); }; typedef struct efi_hello_world efi_hello_world_t;

2. 然后在hello.c中增加相关的代码,首先是头文件:

#include 
  
    #include 
    
  

然后是对应函数的实现:

static void efi_hello (void) { grub_efi_uintn_t num_handles; grub_efi_handle_t *handles; grub_efi_handle_t *handle; grub_efi_guid_t guid = EFI_HELLO_WORLD_PROTOCOL_GUID; handles = grub_efi_locate_handle (GRUB_EFI_BY_PROTOCOL, &guid, 0, &num_handles); if (!handles) { return; } for (handle = handles; num_handles--; handle++) { efi_hello_world_t *hello_p; hello_p = grub_efi_open_protocol (*handle, &guid, GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL); if (hello_p) { efi_call_1 (hello_p->hello, hello_p); } } return; } static grub_err_t grub_cmd_hello (grub_extcmd_context_t ctxt __attribute__ ((unused)), int argc __attribute__ ((unused)), char args __attribute__ ((unused))) { grub_printf ("%s\n", _("Hello World")); efi_hello (); return 0; }

这样当我们在GRUB界面执行hello命令时,回去调用UEFI下的Protocol并执行,可以在串口看到相应的打印:

【GRUB】GRUB2代码初步解析

可以看到GRUB界面里面打印了一次,而串口里面有两次,其中一次就是在UEFI Protocol中的打印。

需要注意在grub-mkimage的时候增加hello模块,否则不会有hello命令。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/211475.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午10:20
下一篇 2026年3月18日 下午10:21


相关推荐

  • 验证码识别—云打码

    验证码识别—云打码这边采用云打码平台 api 接口识别图片

    2026年3月19日
    3
  • random函数的用法

    random函数的用法用法:1、random.random()随机生成(0,1)之间的浮点数2、random.randint(上限,下限)随机生成在范围之内的整数,两个参数分别表示上限和下限3、random.randrange(,,)在指定范围内,按指定基数递增的集合中获得一个随机数,有三个参数,前两个参数代表范围上限和下限,第三个参数是递增增量,不包括下限,包括上限使用方式如下:random.r…

    2022年6月12日
    58
  • chattr权限

    chattr权限chattr 权限命令格式 chattr 增加 删除 赋予 选项文件或目录名选项 i 给文件设置时 不能对文件进行删除 改名 添加 修改数据 给目录设置时 则在此目录下不能创建和删除原有的文件 只能修改文件的数据 此权限对 root 一样有效 可以有效防止用户对重要的文件数据进行误操作 示例 1 给文件设置 i 权限 2 给目录设置 i 选项 a 给文件设置时 只能给文

    2026年3月16日
    2
  • (建议收藏)TCP协议灵魂之问,巩固你的网路底层基础

    (建议收藏)TCP协议灵魂之问,巩固你的网路底层基础

    2022年2月13日
    51
  • Macbook m1环境配置终极版

    Macbook m1环境配置终极版导入项目后在 EventLog 中疯狂打印 Someerrorsoc 更新代码正常 但是只要代码有了 change 就会打印该错误 现在很少有人用 svn 了估计 https youtrack jetbrains com issue IDEA

    2026年3月18日
    2
  • navicat 15 激活码 mac【中文破解版】[通俗易懂]

    (navicat 15 激活码 mac)这是一篇idea技术相关文章,由全栈君为大家提供,主要知识点是关于2021JetBrains全家桶永久激活码的内容IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html1STL5S9V8F-eyJsaWN…

    2022年3月27日
    122

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号