我将 GRUB 用于Linux Live OS(由 Ubuntu Focal 或 Bionic 或 Jammy 制造)的UEFI PXE 引导或UEFI HTTP 引导。我grub.cfg
对这两种方法都使用了相同的方法。它在 UEFI PXE 引导下运行良好,但在 UEFI HTTP 引导期间失败。
然后我发现了一些有趣的事情,关于谁在哪个阶段进行了http访问。
- 在UEFI PXE启动时,HTTP访问日志是这样的:
"GET /liveos/focal/kernel HTTP/1.1" 200 11780639 "-" "GRUB 2.06-2ubuntu14.1"
"GET /liveos/focal/initrd HTTP/1.1" 200 89168508 "-" "GRUB 2.06-2ubuntu14.1"
"GET /liveos/focal/squashfs HTTP/1.1" 200 388669747 "-" "Wget"
所以 GRUB 加载内核和 initrd,然后 initrd 使用 wget 加载 squashfs。好的,这是有道理的。
- 但是在UEFI HTTP启动时,HTTP访问日志是这样的:
"GET /bootx64.efi HTTP/1.1" 200 955941 "-" "UefiHttpBoot/1.0"
"GET /grubx64.efi HTTP/1.1" 200 1493150 "-" "UefiHttpBoot/1.0"
"GET /grub/grub.cfg HTTP/1.1" 200 1823 "-" "UefiHttpBoot/1.0"
"GET /liveos/focal/kernel HTTP/1.1" 200 11780639 "-" "UefiHttpBoot/1.0"
"GET /liveos/focal/initrd HTTP/1.1" 200 449680 "-" "UefiHttpBoot/1.0"
所以 GRUB 使用 http 代理“UefiHttpBoot/1.0”来加载内核和 initrd。不幸的是,访问日志显示服务器没有向 UEFI HTTP 引导客户端响应完整大小的数据。全尺寸响应应该是喜欢的89168508
,但现在只是449680
。
问题来了:
- http代理“UefiHttpBoot/1.0”在哪里实现?在固件中?
- 为什么 GRUB2 不使用与 UEFI PXE 引导相同的逻辑,即使用它自己的 http 代理来获取 initrd?
编辑:看起来像这样,在 UEFI HTTP 引导中,从一开始,加载 bootx64.efi 的是 http 代理“UefiHttpBoot/1.0”,因此 “UefiHttpBoot/1.0”应该来自固件,但是,它应该通过执行控制权给了bootx64.efi(GRUB2),那么GRUB2应该使用自带的http工具来读取initrd,但实际上不是,为什么呢?似乎 UEFI HTTP 引导在某种程度上与 Linux 引导协议协作。
编辑:更多信息:
[ 1.947449] Unpacking initramfs...
[ 1.950954] Initramfs unpacking failed: junk in compressed archive
or
[ 1.943484] Unpacking initramfs...
[ 1.946991] Initramfs unpacking failed: broken padding
grub.cfg 是这样的:(请记住它在 UEFI PXE 引导下运行良好):
set default=0
set timeout=1
menuentry boot_liveos {
linux (http,100.0.0.101)/liveos/focal/kernel nomodeset ro root=squash:http://100.0.0.101/liveos/focal/squashfs ip=::::hostname:BOOTIF ip6=off overlayroot=tmpfs overlayroot_cfgdisk=disabled apparmor=0 ds=nocloud console=tty0 console=ttyS1,115200 BOOTIF=01-${net_default_mac}
initrd (http,100.0.0.101)/liveos/focal/initrd
}
PS,不仅focal
,而且bionic
,jammy
都是同样的问题。我也尝试过不同的 GRUB2 版本,从 20200320.0 到 20230222.0,都是同样的问题。
问题是“UefiHttpBoot/1.0”有问题,它不能接受大数据。
编辑:如果这是一个固件问题,那么有没有告诉 grub 自己加载 http 文件?
编辑:继续调查这个问题。
$ strings bootx64.efi |grep UefiHttpBoot
UefiHttpBoot/1.0
$ strings grubx64.efi |grep UefiHttpBoot
UefiHttpBoot/1.0
GRUB2 二进制文件嵌入了 http 代理字符串“UefiHttpBoot/1.0”,因此执行控制权应该已传递给 GRUB2,只是 GRUB2 继续使用相同的代理字符串进行 http 访问。我已经在http://archive.ubuntu.com/ubuntu/pool/main/g/grub2-unsigned/grub2-unsigned_2.06.orig.tar.xz检查了源代码(父页面是https://packages.ubuntu .com/focal-updates/grub-efi-amd64)但没有找到代理字符串,但找到了一个常量 var PACKAGE_STRING
,它可能是在构建二进制文件时定义的。
http_establish (struct grub_file *file, grub_off_t offset, int initial)
{
http_data_t data = file->data;
grub_uint8_t *ptr;
int i;
struct grub_net_buff *nb;
grub_err_t err;
nb = grub_netbuff_alloc (GRUB_NET_TCP_RESERVE_SIZE
+ sizeof ("GET ") - 1
+ grub_strlen (data->filename)
+ sizeof (" HTTP/1.1\r\nHost: ") - 1
+ grub_strlen (file->device->net->server)
+ sizeof ("\r\nUser-Agent: " PACKAGE_STRING
"\r\n") - 1
+ sizeof ("Range: bytes=XXXXXXXXXXXXXXXXXXXX"
"-\r\n\r\n"));
if (!nb)
return grub_errno;
在 GRUB2 的 debian 补丁源中找到 http 代理字符串:http://archive.ubuntu.com/ubuntu/pool/main/g/grub2-unsigned/grub2-unsigned_2.06-2ubuntu14.1.debian.tar.xz,它是一个补丁文件suse-add-support-for-UEFI-network-protocols.patch,替换了一些http相关的函数来使用efi固件函数。
+static grub_err_t
+efihttp_request (grub_efi_http_t *http, char *server, char *name, int use_https, int headeronly, grub_off_t *file_size)
+{
+ grub_efi_http_request_data_t request_data;
+ grub_efi_http_message_t request_message;
+ grub_efi_http_token_t request_token;
+ grub_efi_http_response_data_t response_data;
+ grub_efi_http_message_t response_message;
+ grub_efi_http_token_t response_token;
+ grub_efi_http_header_t request_headers[3];
+
+ grub_efi_status_t status;
+ grub_efi_boot_services_t *b = grub_efi_system_table->boot_services;
+ char *url = NULL;
+
+ request_headers[0].field_name = (grub_efi_char8_t *)"Host";
+ request_headers[0].field_value = (grub_efi_char8_t *)server;
+ request_headers[1].field_name = (grub_efi_char8_t *)"Accept";
+ request_headers[1].field_value = (grub_efi_char8_t *)"*/*";
+ request_headers[2].field_name = (grub_efi_char8_t *)"User-Agent";
+ request_headers[2].field_value = (grub_efi_char8_t *)"UefiHttpBoot/1.0";
+
...
+ /* request token */
+ request_token.event = NULL;
+ request_token.status = GRUB_EFI_NOT_READY;
+ request_token.message = &request_message;
+
+ request_callback_done = 0;
+ status = efi_call_5 (b->create_event,
+ GRUB_EFI_EVT_NOTIFY_SIGNAL,
+ GRUB_EFI_TPL_CALLBACK,
+ grub_efi_http_request_callback,
+ NULL,
+ &request_token.event);
+
+
...
编辑:几乎到了那里。我使用的 GRUB2 进行了优化,使用底层 efi 固件功能来访问 http 文件。听起来不错,但它有几个缺点:
- 它似乎确实很好地支持 url querystring。
- 它不能很好地处理大数据。我会将此报告给 GRUB2 开发人员。原来有很多TCP重传和窗口大小调整。
“UefiHttpBoot/1.0”HTTP 客户端确实在 UEFI 固件(UEFI 规范版本 2.5 或更新版本)中实现。
当使用 UEFI PXE 引导时,固件将
bootx64.efi
使用 TFTP 加载初始文件,因为这可能是固件可以自行执行的唯一网络文件传输协议。所以如果你想在这种情况下使用 HTTP,GRUB必须携带它自己的 HTTP 客户端实现。但是通过快速
grep
浏览 GRUB 源代码树,我找不到任何对 UEFI HTTP 协议的引用。GRUB 的 UEFI 网络驱动程序grub-core/net/drivers/efi/efinet.c仅依赖早期 UEFI 规范版本的固件提供的简单网络协议。所以在我看来,当使用 UEFI HTTP 引导时,GRUB 显然根本不知道它正在使用网络。UEFI 规范说,通过 HTTP 引导,固件提供了一个支持 UEFI LoadFile 协议的 UEFI 设备句柄,因此 GRUB 可以使用它来加载文件,就像从磁盘加载文件一样。当然,GRUB可以进一步调查设备句柄以查看它实际上是一个网络设备——但它不必为了完成其主要工作而这样做。
这也清楚地表明 UEFI 引导无法加载 initrd 绝对是固件问题。
让我自己回答吧。
解决方案是找到一个不包含单词的旧 grubx64.efi
UefiHttpBoot
,幸运的是我20200320.0
在本地历史记录中找到了一个版本,它运行良好。(它似乎不再存在于 Ubuntu 的 repo 中或在互联网上实现。)相应的 bootx64.efi 仍然包含 wordUefiHttpBoot
,但这并不重要,因为它只是一个垫片,它只是加载 grubx64.efi 然后将控制权传递给它。http访问是这样的: