[PATCHv7 13/13] tools/kexec: Introduce a tool to build zboot envelop
Pingfan Liu
piliu at redhat.com
Sat Mar 21 18:44:02 PDT 2026
The new tool builds a ELF container around zboot image. It contains
three key sections: .kernel, .initrd, .cmdline. Later zboot_bpf_parser
will parse this container format.
Signed-off-by: Pingfan Liu <piliu at redhat.com>
Cc: Baoquan He <bhe at redhat.com>
Cc: Dave Young <dyoung at redhat.com>
Cc: Andrew Morton <akpm at linux-foundation.org>
Cc: Philipp Rudo <prudo at redhat.com>
Cc: bpf at vger.kernel.org
To: kexec at lists.infradead.org
---
tools/kexec/Makefile | 31 ++-
tools/kexec/build_zboot_envelop.c | 362 ++++++++++++++++++++++++++++++
tools/kexec/zboot_envelop.h | 7 +
tools/kexec/zboot_parser_bpf.c | 7 +-
4 files changed, 401 insertions(+), 6 deletions(-)
create mode 100644 tools/kexec/build_zboot_envelop.c
create mode 100644 tools/kexec/zboot_envelop.h
diff --git a/tools/kexec/Makefile b/tools/kexec/Makefile
index c0e2ad44658e3..c168a6eeea09d 100644
--- a/tools/kexec/Makefile
+++ b/tools/kexec/Makefile
@@ -21,9 +21,21 @@ endif
CC = clang
CFLAGS = -O2
-BPF_PROG_CFLAGS = -g -fno-merge-all-constants -O2 -target bpf -Wall -I $(BPFDIR) -I .
+BPF_PROG_CFLAGS = -g -O2 -target bpf -Wall -I $(BPFDIR) -I .
BPFTOOL = bpftool
+# Host compiler for native tools (must not be the BPF clang cross-compiler)
+HOSTCC ?= gcc
+HOSTCFLAGS = -std=c11 -O2 -Wall -Wextra
+
+# ---------------------------------------------------------------------------
+# zboot ELF image parameters
+# INITRAMFS: path to the initramfs image (required)
+# CMDLINE: kernel command line string (optional, defaults to empty)
+# ---------------------------------------------------------------------------
+INITRAMFS ?=
+CMDLINE ?=
+
# ---------------------------------------------------------------------------
# Shared generated headers (common to all targets)
# ---------------------------------------------------------------------------
@@ -54,7 +66,7 @@ ALL_BPF_ARTIFACTS = $(foreach t,$(BPF_TARGETS),$(call BPF_ARTIFACTS,$(t)))
# ---------------------------------------------------------------------------
# Top-level phony targets
# ---------------------------------------------------------------------------
-zboot: $(HEADERS) $(call BPF_ARTIFACTS,zboot) build_zboot_image
+zboot: $(HEADERS) $(call BPF_ARTIFACTS,zboot) build_zboot_envelop
ifeq ($(ARCH),$(filter $(ARCH),arm64 riscv loongarch))
uki: $(HEADERS) zboot.bpf $(call BPF_ARTIFACTS,uki)
else
@@ -171,8 +183,21 @@ endef
$(eval $(call BPF_WRAPPER_RULE,zboot,ZBOOT))
$(eval $(call BPF_WRAPPER_RULE,uki,UKI))
+# ---------------------------------------------------------------------------
+# Host tool: build_zboot_envelop
+# Packs EFI_IMAGE (.kernel), INITRAMFS (.initrd) and CMDLINE (.cmdline)
+# into a single ELF file consumed by the kexec loader.
+# ---------------------------------------------------------------------------
+build_zboot_envelop: build_zboot_envelop.c
+ @$(HOSTCC) $(HOSTCFLAGS) -o $@ $<
+
+zboot_image.elf: build_zboot_envelop $(EFI_IMAGE) $(INITRAMFS)
+ $(if $(INITRAMFS),,$(error INITRAMFS is not set. Usage: make INITRAMFS=<path> [CMDLINE="..."]))
+ @./build_zboot_envelop $(EFI_IMAGE) $(INITRAMFS) "$(CMDLINE)"
+
# ---------------------------------------------------------------------------
# Clean
# ---------------------------------------------------------------------------
clean:
- @rm -f $(HEADERS) $(ALL_BPF_ARTIFACTS) *.base.o
+ @rm -f $(HEADERS) $(ALL_BPF_ARTIFACTS) *.base.o \
+ build_zboot_envelop zboot_image.elf
diff --git a/tools/kexec/build_zboot_envelop.c b/tools/kexec/build_zboot_envelop.c
new file mode 100644
index 0000000000000..d2d9ffc11fdc1
--- /dev/null
+++ b/tools/kexec/build_zboot_envelop.c
@@ -0,0 +1,362 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * build_zboot_envelop.c - Pack zboot image, initramfs and cmdline into an ELF file.
+ *
+ * Usage: build_zboot_envelop <zboot_image> <initramfs> <cmdline> [output.elf]
+ *
+ * Output ELF sections:
+ * .kernel - zboot image (PE signature preserved, no padding)
+ * .initrd - initramfs image
+ * .cmdline - kernel command line string (NUL-terminated)
+ */
+
+#include <elf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "zboot_envelop.h"
+
+#define DEFAULT_OUTPUT "zboot_image.elf"
+
+/*
+ * Section indices into the section header table.
+ */
+enum {
+ SHN_UNDEF_IDX = 0,
+ SHN_KERNEL_IDX = 1,
+ SHN_INITRD_IDX = 2,
+ SHN_CMDLINE_IDX = 3,
+ SHN_SHSTRTAB_IDX = 4,
+ SHN_NUM = 5,
+};
+
+/*
+ * String table layout (offsets are fixed at compile time):
+ *
+ * off 0 : '\0' (mandatory empty string)
+ * off 1 : ".kernel\0"
+ * off 9 : ".initrd\0"
+ * off 17 : ".cmdline\0"
+ * off 26 : ".shstrtab\0"
+ */
+#define SHSTR_OFF_KERNEL 1
+#define SHSTR_OFF_INITRD 9
+#define SHSTR_OFF_CMDLINE 17
+#define SHSTR_OFF_SHSTRTAB 26
+
+#define SHSTRTAB_CONTENT \
+ "\0" KERNEL_SECT_NAME \
+ "\0" INITRD_SECT_NAME \
+ "\0" CMDLINE_SECT_NAME \
+ "\0.shstrtab"
+
+/* sizeof() includes the final NUL from the string literal. */
+#define SHSTRTAB_SIZE (sizeof(SHSTRTAB_CONTENT))
+
+/*
+ * struct input_file - holds a memory-mapped input file together with its size.
+ * @data: pointer to the mapped region (or NULL if not mmap'd)
+ * @size: exact byte size of the file
+ * @fd: open file descriptor (-1 when closed)
+ */
+struct input_file {
+ const void *data;
+ size_t size;
+ int fd;
+};
+
+/*
+ * align8 - round @off up to the next multiple of 8.
+ */
+static inline size_t align8(size_t off)
+{
+ return (off + 7) & ~(size_t)7;
+}
+
+/*
+ * open_and_map - open @path read-only and mmap its entire content.
+ *
+ * Returns 0 on success, -1 on error (errno is set by the failing syscall and
+ * a diagnostic is printed to stderr).
+ */
+static int open_and_map(const char *path, struct input_file *f)
+{
+ struct stat st;
+
+ f->fd = open(path, O_RDONLY);
+ if (f->fd < 0) {
+ fprintf(stderr, "build_zboot_envelop: cannot open '%s': %s\n", path,
+ strerror(errno));
+ return -1;
+ }
+
+ if (fstat(f->fd, &st) < 0) {
+ fprintf(stderr, "build_zboot_envelop: cannot stat '%s': %s\n", path,
+ strerror(errno));
+ goto err_close;
+ }
+
+ f->size = (size_t)st.st_size;
+ f->data = mmap(NULL, f->size, PROT_READ, MAP_PRIVATE, f->fd, 0);
+ if (f->data == MAP_FAILED) {
+ fprintf(stderr, "build_zboot_envelop: cannot mmap '%s': %s\n", path,
+ strerror(errno));
+ goto err_close;
+ }
+
+ return 0;
+
+err_close:
+ close(f->fd);
+ f->fd = -1;
+ return -1;
+}
+
+static void close_and_unmap(struct input_file *f)
+{
+ if (f->data && f->data != MAP_FAILED)
+ munmap((void *)f->data, f->size);
+ if (f->fd >= 0)
+ close(f->fd);
+}
+
+static int write_all(int fd, const void *buf, size_t len)
+{
+ const uint8_t *p = buf;
+ ssize_t n;
+
+ while (len > 0) {
+ n = write(fd, p, len);
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ return -1;
+ }
+ p += n;
+ len -= n;
+ }
+ return 0;
+}
+
+/*
+ * write_padding - write @len zero bytes to @fd.
+ *
+ * Returns 0 on success, -1 on error.
+ */
+static int write_padding(int fd, size_t len)
+{
+ static const uint8_t zero[8];
+ size_t chunk;
+ ssize_t n;
+
+ while (len > 0) {
+ chunk = (len < sizeof(zero)) ? len : sizeof(zero);
+ n = write(fd, zero, chunk);
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ return -1;
+ }
+ len -= n;
+ }
+ return 0;
+}
+
+/*
+ * fill_shdr - populate a Elf64_Shdr for a SHT_PROGBITS section.
+ *
+ * @shdr: section header to fill
+ * @name_off: offset of the section name inside .shstrtab
+ * @offset: file offset at which the section data begins
+ * @size: exact byte count of the section data (no padding included)
+ * @flags: section flags (e.g. SHF_ALLOC)
+ */
+static void fill_shdr(Elf64_Shdr *shdr, uint32_t name_off, uint64_t offset,
+ uint64_t size, uint64_t flags)
+{
+ memset(shdr, 0, sizeof(*shdr));
+ shdr->sh_name = name_off;
+ shdr->sh_type = SHT_PROGBITS;
+ shdr->sh_flags = flags;
+ shdr->sh_offset = offset;
+ shdr->sh_size = size;
+ shdr->sh_addralign = 1;
+}
+
+int main(int argc, char *argv[])
+{
+ const char *kernel_path, *initrd_path, *cmdline, *output_path;
+ struct input_file kernel = { NULL, 0, -1 };
+ struct input_file initrd = { NULL, 0, -1 };
+ Elf64_Ehdr ehdr;
+ Elf64_Shdr shdrs[SHN_NUM];
+ size_t cmdline_size; /* includes terminating NUL */
+
+ /*
+ * File layout (all section-data offsets are 8-byte aligned except
+ * .kernel which must start directly after the headers to guarantee
+ * that no byte is inserted before the PE signature):
+ *
+ * [ELF header] 64 B
+ * [Section headers] SHN_NUM * sizeof(Elf64_Shdr)
+ * [.kernel data] kernel.size bytes (NO internal padding)
+ * <pad to 8B>
+ * [.initrd data] initrd.size bytes
+ * <pad to 8B>
+ * [.cmdline data] cmdline_size bytes
+ * <pad to 8B>
+ * [.shstrtab data] SHSTRTAB_SIZE bytes
+ */
+ size_t off_shdrs, off_kernel, off_initrd, off_cmdline, off_shstrtab;
+ size_t pad_after_kernel, pad_after_initrd, pad_after_cmdline;
+ int outfd;
+ int ret = EXIT_FAILURE;
+
+ if (argc < 4 || argc > 5) {
+ fprintf(stderr,
+ "Usage: %s <zboot_image> <initramfs> <cmdline> "
+ "[output.elf]\n",
+ argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ kernel_path = argv[1];
+ initrd_path = argv[2];
+ cmdline = argv[3];
+ output_path = (argc == 5) ? argv[4] : DEFAULT_OUTPUT;
+ cmdline_size = strlen(cmdline) + 1; /* +1 for NUL terminator */
+
+ /* ------------------------------------------------------------------ */
+ /* 1. Map input files */
+ /* ------------------------------------------------------------------ */
+ if (open_and_map(kernel_path, &kernel) < 0)
+ goto out;
+
+ if (open_and_map(initrd_path, &initrd) < 0)
+ goto out;
+
+ /* Compute file layout */
+ off_shdrs = sizeof(Elf64_Ehdr);
+ off_kernel = off_shdrs + SHN_NUM * sizeof(Elf64_Shdr);
+
+ /*
+ * .kernel must not contain any padding - its sh_size equals the
+ * exact file size so that any PE authenticode signature is intact.
+ * Alignment padding goes *after* the raw bytes, between sections.
+ */
+ pad_after_kernel = 0;
+ off_initrd = off_kernel + kernel.size + pad_after_kernel;
+
+ pad_after_initrd =
+ align8(off_initrd + initrd.size) - (off_initrd + initrd.size);
+ off_cmdline = off_initrd + initrd.size + pad_after_initrd;
+
+ pad_after_cmdline = align8(off_cmdline + cmdline_size) -
+ (off_cmdline + cmdline_size);
+ off_shstrtab = off_cmdline + cmdline_size + pad_after_cmdline;
+
+ memset(&ehdr, 0, sizeof(ehdr));
+
+ ehdr.e_ident[EI_MAG0] = ELFMAG0;
+ ehdr.e_ident[EI_MAG1] = ELFMAG1;
+ ehdr.e_ident[EI_MAG2] = ELFMAG2;
+ ehdr.e_ident[EI_MAG3] = ELFMAG3;
+ ehdr.e_ident[EI_CLASS] = ELFCLASS64;
+ ehdr.e_ident[EI_DATA] = ELFDATA2LSB;
+ ehdr.e_ident[EI_VERSION] = EV_CURRENT;
+ ehdr.e_ident[EI_OSABI] = ELFOSABI_NONE;
+
+ ehdr.e_type = ET_EXEC;
+ ehdr.e_machine = EM_AARCH64;
+ ehdr.e_version = EV_CURRENT;
+ ehdr.e_ehsize = sizeof(Elf64_Ehdr);
+ ehdr.e_shentsize = sizeof(Elf64_Shdr);
+ ehdr.e_shnum = SHN_NUM;
+ ehdr.e_shoff = (Elf64_Off)off_shdrs;
+ ehdr.e_shstrndx = SHN_SHSTRTAB_IDX;
+
+ /* Build section headers */
+ memset(shdrs, 0, sizeof(shdrs));
+
+ /* [0] SHN_UNDEF - mandatory null entry */
+
+ /* [1] .kernel */
+ fill_shdr(&shdrs[SHN_KERNEL_IDX], SHSTR_OFF_KERNEL,
+ (uint64_t)off_kernel, (uint64_t)kernel.size, SHF_ALLOC);
+
+ /* [2] .initrd */
+ fill_shdr(&shdrs[SHN_INITRD_IDX], SHSTR_OFF_INITRD,
+ (uint64_t)off_initrd, (uint64_t)initrd.size, SHF_ALLOC);
+
+ /* [3] .cmdline */
+ fill_shdr(&shdrs[SHN_CMDLINE_IDX], SHSTR_OFF_CMDLINE,
+ (uint64_t)off_cmdline, (uint64_t)cmdline_size, SHF_ALLOC);
+
+ /* [4] .shstrtab */
+ memset(&shdrs[SHN_SHSTRTAB_IDX], 0, sizeof(Elf64_Shdr));
+ shdrs[SHN_SHSTRTAB_IDX].sh_name = SHSTR_OFF_SHSTRTAB;
+ shdrs[SHN_SHSTRTAB_IDX].sh_type = SHT_STRTAB;
+ shdrs[SHN_SHSTRTAB_IDX].sh_offset = (Elf64_Off)off_shstrtab;
+ shdrs[SHN_SHSTRTAB_IDX].sh_size = (uint64_t)SHSTRTAB_SIZE;
+ shdrs[SHN_SHSTRTAB_IDX].sh_addralign = 1;
+
+ outfd = open(output_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (outfd < 0) {
+ fprintf(stderr, "build_zboot_envelop: cannot create '%s': %s\n", output_path,
+ strerror(errno));
+ goto out;
+ }
+
+ if (write_all(outfd, &ehdr, sizeof(ehdr)) < 0)
+ goto err_write;
+
+ if (write_all(outfd, shdrs, sizeof(shdrs)) < 0)
+ goto err_write;
+
+ /* .kernel - raw bytes, no padding inside the section */
+ if (write_all(outfd, kernel.data, kernel.size) < 0)
+ goto err_write;
+ if (write_padding(outfd, pad_after_kernel) < 0)
+ goto err_write;
+
+ /* .initrd */
+ if (write_all(outfd, initrd.data, initrd.size) < 0)
+ goto err_write;
+ if (write_padding(outfd, pad_after_initrd) < 0)
+ goto err_write;
+
+ /* .cmdline - NUL-terminated string */
+ if (write_all(outfd, cmdline, cmdline_size) < 0)
+ goto err_write;
+ if (write_padding(outfd, pad_after_cmdline) < 0)
+ goto err_write;
+
+ /* .shstrtab */
+ if (write_all(outfd, SHSTRTAB_CONTENT, SHSTRTAB_SIZE) < 0)
+ goto err_write;
+
+ printf("build_zboot_envelop: wrote '%s' (.kernel=%zuB .initrd=%zuB "
+ ".cmdline=%zuB)\n",
+ output_path, kernel.size, initrd.size, cmdline_size - 1);
+
+ close(outfd);
+ ret = EXIT_SUCCESS;
+ goto out;
+
+err_write:
+ fprintf(stderr, "build_zboot_envelop: write error on '%s': %s\n", output_path,
+ strerror(errno));
+ close(outfd);
+ unlink(output_path);
+
+out:
+ close_and_unmap(&kernel);
+ close_and_unmap(&initrd);
+ return ret;
+}
diff --git a/tools/kexec/zboot_envelop.h b/tools/kexec/zboot_envelop.h
new file mode 100644
index 0000000000000..813723c64ecf3
--- /dev/null
+++ b/tools/kexec/zboot_envelop.h
@@ -0,0 +1,7 @@
+#ifndef ZBOOT_ENVELOP_H
+#define ZBOOT_ENVELOP_H
+
+#define KERNEL_SECT_NAME ".kernel"
+#define INITRD_SECT_NAME ".initrd"
+#define CMDLINE_SECT_NAME ".cmdline"
+#endif
diff --git a/tools/kexec/zboot_parser_bpf.c b/tools/kexec/zboot_parser_bpf.c
index 10098dca2a27a..16fbad83a9bde 100644
--- a/tools/kexec/zboot_parser_bpf.c
+++ b/tools/kexec/zboot_parser_bpf.c
@@ -16,6 +16,7 @@
#define RINGBUF4_SIZE MIN_BUF_SIZE
#include "template.c"
+#include "zboot_envelop.h"
#define ELF_SCAN_MAX 8
@@ -43,9 +44,9 @@ struct linux_pe_zboot_header {
unsigned int pe_header_offset;
} __attribute__((packed));
-static const char linux_sect_name[] = ".kernel";
-static const char initrd_sect_name[] = ".initrd";
-static const char cmdline_sect_name[] = ".cmdline";
+static const char linux_sect_name[] = KERNEL_SECT_NAME;
+static const char initrd_sect_name[] = INITRD_SECT_NAME;
+static const char cmdline_sect_name[] = CMDLINE_SECT_NAME;
/*
* fill_cmd - overwrite the cmd_hdr at the start of @buf and copy @data_len
--
2.49.0
More information about the kexec
mailing list