diff options
Diffstat (limited to 'bin/debefivm-ukify')
-rwxr-xr-x | bin/debefivm-ukify | 329 |
1 files changed, 329 insertions, 0 deletions
diff --git a/bin/debefivm-ukify b/bin/debefivm-ukify new file mode 100755 index 0000000..9267158 --- /dev/null +++ b/bin/debefivm-ukify @@ -0,0 +1,329 @@ +#!/bin/sh +# SPDX-FileCopyrightText: 2023-2025 Helmut Grohne <helmut@subdivi.de> +# SPDX-License-Identifier: MIT +# + +: <<'POD2MAN' +=head1 NAME + +debefivm-ukify - Create Unified Kernel Images for UEFI systems in a similar way to systemd's ukify + +=head1 SYNOPSIS + +B<debefivm-ukify> B<build> B<--linux>=F<vmlinuz> B<--output>=F<uki.efi> [options] + +=head1 DESCRIPTION + +B<debefivm-ukify> is a reimplementation of parts of B<systemd-ukify> and mainly meant for backports. +Please prefer B<systemd>'s implementation. +It only supports the B<build> subcommand, does not implement any functionality related to secure boot and lacks configuration file support. +Other than that, it can be used to assemble a linux kernel image, an initrd and an EFI stub image from B<systemd> into an EFI-bootable Unified Kernel Image. + +=head1 OPTIONS + +=over 8 + +=item B<--deb-arch>=I<debian_architecture> + +Set the architecture of the supplied and produced artifacts using Debian's architecture nomenclature. +See B<--efi-arch> for another supplying it using the EFI nomenclature instead. +This option is not available on the B<systemd> implementation. + +=item B<--devicetree>=F<devicetree_blob> + +Add a device tree file as a dtb section to the generated UKI. +There can be at most one device tree. + +=item B<--cmdline>=I<kernel_cmdline> + +Supply extra arguments to be appended to the Linux kernel command line during boot. +Can be given given at most once. +If the value starts with an B<@>, it is treated as a filename whose contents will be added. + +=item B<--efi-arch>=I<efi_architecture> + +Set the architecture of the supplied and produced artifacts using EFI's architecture nomenclature. +See B<--deb-arch> for another supplying it using the Debian nomenclature instead. + +=item B<--initrd>=F<initrd> + +Supply an initrd image to be embedded. +There can be at most one initrd. + +=item B<--linux>=F<vmlinuz> + +Supply a Linux kernel image to be embedded. +This option must be given exactly once. + +=item B<--os-release>=I<release_information> + +Set the os-release description of in the UKI. +Can be given given at most once. +If the value starts with an B<@>, it is treated as a filename whose contents will be added. + +=item B<--output>=<uki.efi> + +Set the name of the resulting UKI image. +This option must be given exactly once and the target file must not exist already. + +=item B<--section>=<name:value> + +Add an arbitrary ELF section to the resulting UKI. +If the value starts with an B<@>, it is treated as a filename whose contents will be added. +Most image components actually are such ELF sections. +For instance B<--os-release>, adds a section named B<osrel>. + +=item B<--stub>=F<efi_stub> + +Supply the location of B<systemd>'s EFI stub image. +Unlike the B<systemd> implementation, this option accepts a directory containing stubs named by architecture. +By default, stubs will be looked up in B</usr/lib/systemd/boot/efi>. + +=item B<--verbose> + +Print diagnostic information during image conversion. +This option is not available on the B<systemd> implementation. + +=back + +=head1 SEE ALSO + + ukify(1) + +=cut +POD2MAN + +set -eu + +die() { + echo "$*" >&2 + exit 1 +} +usage() { + die "usage: $0 [build] <--linux=|--initrd=|--stub=> [--cmdline=|--deb-arch=|--devicetree=|--efi-arch=|--os-release=|--output=|--section=|--verbose]" +} +usage_error() { + echo "error: $*" >&2 + usage +} + +DEBARCH= +BOOTSTUB=/usr/lib/systemd/boot/efi +OUTPUT= +VERBOSE= + +TDIR= +cleanup() { + if test -n "$TDIR"; then + rm -Rf "$TDIR" + fi +} +cleanup_abort() { + cleanup + echo aborted >&2 + exit 2 +} +trap cleanup EXIT +trap cleanup_abort HUP INT TERM QUIT + +# The TDIR will contain one file per section. It can be a symbolic link or +# regular file. The section name is the filename with a leading dot. +TDIR=$(mktemp --directory --tmpdir mini-ukify.XXXXXXXXXX) + +add_section_content() { + test -e "$TDIR/$1" && die "section '$1' already defined" + printf "%s" "$2" > "$TDIR/$1" +} +add_section_file() { + test -e "$TDIR/$1" && die "section '$1' already defined" + test -f "$2" || die "file '$2' does not exist" + if test "${2#/}" = "$2"; then + ln --symbolic --relative "$2" "$TDIR/$1" + else + ln --symbolic "$2" "$TDIR/$1" + fi +} +add_section_either() { + if test "${2#@}" = "$2"; then + add_section_content "$1" "$2" + else + add_section_file "$1" "${2#@}" + fi +} + +opt_cmdline() { + add_section_either cmdline "$1" +} +opt_deb_arch() { + case "$1" in + amd64) + EFIARCH=x64 + GNU_TYPE=x86_64-linux-gnu + ;; + arm64) + EFIARCH=aa64 + GNU_TYPE=aarch64-linux-gnu + ;; + armhf) + EFIARCH=arm + GNU_TYPE=arm-linux-gnueabihf + ;; + i386) + EFIARCH=ia32 + GNU_TYPE=i686-linux-gnu + ;; + riscv64) + EFIARCH=riscv64 + GNU_TYPE=riscv64-linux-gnu + ;; + *) + die "unsupported Debian architecture: $1" + ;; + esac + DEBARCH="$1" +} +opt_devicetree() { + add_section_file dtb "$1" +} +opt_efi_arch() { + case "$1" in + aa64) opt_deb_arch arm64 ;; + arm) opt_deb_arch armhf ;; + ia32) opt_deb_arch i386 ;; + x64) opt_deb_arch amd64 ;; + riscv64) opt_deb_arch "$1" ;; + *) + die "unsupported EFI architecture: $1" + ;; + esac +} +opt_initrd() { + add_section_file initrd "$1" +} +opt_linux() { + add_section_file linux "$1" + test -z "$OUTPUT" && OUTPUT="$1.unsigned.efi" +} +opt_os_release() { + add_section_either osrel "$1" +} +opt_output() { + OUTPUT="$1" +} +opt_section() { + case "$1" in + *[!a-z]*:*) + die "invalid section name '${1%%:*}'" + ;; + *:*) + ;; + *) + die "missing section name separated by colon in '$1'" + ;; + esac + add_section_either "${1%%:*}" "${1#*:}" +} +opt_stub() { + BOOTSTUB="$1" +} +opt_verbose() { + VERBOSE=yes +} +positional=1 +positional_1() { + test "$1" = "build" || usage_error "command not understood: '$1'" +} +positional_2() { + usage_error "too many positional arguments" +} + +while test "$#" -gt 0; do + case "$1" in + --cmdline=*|--deb-arch=*|--devicetree=*|--efi-arch=*|--initrd=*|--linux=*|--os-release=*|--output=*|--section=*|--stub=*) + optname="${1%%=*}" + optname="${optname#--}" + test "${optname#*-}" = "$optname" || + optname="${optname%%-*}_${optname#*-}" + "opt_$optname" "${1#*=}" + ;; + --cmdline|--deb-arch|--devicetree|--efi-arch|--initrd|--linux|--os-release|--output|--section|--stub) + test "$#" -ge 2 || usage_error "missing argument for $1" + optname="${1#--}" + test "${optname#*-}" = "$optname" || + optname="${optname%%-*}_${optname#*-}" + "opt_$optname" "$2" + shift + ;; + --verbose) + "opt_${1#--}" + ;; + --*) + usage_error "unrecognized option $1" + ;; + *) + "positional_$positional" "$1" + positional=$((positional + 1)) + ;; + esac + shift +done + +test "$positional" = 2 || usage_error "missing subcommand, only 'build' is supported" +test -z "$DEBARCH" && opt_deb_arch "$(dpkg --print-architecture)" +test -e "$TDIR/linux" || usage_error "missing --linux argument" +test -e "$BOOTSTUB" || die "stub image or directory '$BOOTSTUB' does not exist" +test -e "$OUTPUT" && die "output '$OUTPUT' already exists" + +test -d "$BOOTSTUB" && BOOTSTUB="$BOOTSTUB/linux$EFIARCH.efi.stub" +test -f "$BOOTSTUB" || die "efi boot stub $BOOTSTUB not found" + +GNU_PREFIX="$GNU_TYPE-" +test "$(dpkg-query --showformat='${db:Status-Status}' --show binutils-multiarch)" = installed && + GNU_PREFIX= + +# Compute the next multiple of $2 greater than or equal to $1. +align_size() { + echo "$((($1) + ($2) - 1 - (($1) + ($2) - 1) % ($2)))" +} +alignment=$("${GNU_PREFIX}objdump" --private-headers "$BOOTSTUB" | sed 's/^SectionAlignment\s\+\([0-9]\)/0x/;t;d') +test -z "$alignment" && + die "failed to discover the alignment of the efi stub" +test -n "$VERBOSE" && + echo "determined efi vma alignment as $alignment" + +# Discover the last section in terms of vma + size. We'll append sections +# beyond. +lastoffset=0 +# shellcheck disable=SC2034 # unused variables serve documentation +lastoffset="$("${GNU_PREFIX}objdump" --section-headers "$BOOTSTUB" \ + | while read -r idx name size vma lma fileoff algn behind; do + test -z "$behind" -a "${algn#"2**"}" != "$algn" || continue + offset=$((0x$vma + 0x$size)) + test "$offset" -gt "$lastoffset" || continue + lastoffset="$offset" + echo "$lastoffset" + done | tail -n1)" +lastoffset=$(align_size "$lastoffset" "$alignment") +test -n "$VERBOSE" && + echo "determined minimum efi vma offset as $lastoffset" + +# Compute the objdump invocation that constructs the UKI. Successively add +# sections and compute non-overlapping VMAs. +set -- \ + "${GNU_PREFIX}objcopy" \ + --enable-deterministic-archives +for sectionfile in "$TDIR/"*; do + section="${sectionfile##*/}" + size=$(stat -Lc%s "$sectionfile") + size=$(align_size "$size" "$alignment") + set -- "$@" \ + --add-section ".$section=$sectionfile" \ + --change-section-vma ".$section=$lastoffset" + lastoffset=$((lastoffset + size)) +done +set -- "$@" "$BOOTSTUB" "$OUTPUT" +if test -n "$VERBOSE"; then + ls -lA "$TDIR" + printf "%s\n" "$*" +fi +"$@" || die "failed to construct UKI" |