Skip to content

Instantly share code, notes, and snippets.

@dvdhrm
Created February 8, 2019 10:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dvdhrm/9c09784a053cdb50301af43137580c20 to your computer and use it in GitHub Desktop.
Save dvdhrm/9c09784a053cdb50301af43137580c20 to your computer and use it in GitHub Desktop.
#!/bin/bash
#
# efi-devel - efirun
#
# This tool executes a given UEFI binary in an ad-hoc virtual machine. Since
# most hosts lack native emulation for UEFI binaries, this tool spawns an
# ad-hoc, temporary QEMU virtual machine with OVMF. It creates an ephemeral ESP
# with the target binary as default boot-target. This way OVMF behaves as if
# you directly invoke the target binary.
#
# XXX: This tools is still a major hack. The overall target is to make this
# tool forward the host console to the emulated machine, as well as return
# the exit-code and output of the machine to the host.
# Furthermore, it would be nice if there was a cleaner way to tell OVMF to
# execute an explicit binary. Currently, we use a read-only scsi-virtio
# vvFAT that provides the target binary as EFI/Boot/bootx64.efi to the
# virtual machine. This does not allow to forward a return code, or
# specify what happens if the execution fails.
#
# Hence, in its current form, this tool is nice for development and simple
# execution of EFI binaries, but still lacks crucial features to be used
# in test-suites, CI, and other automated tasks.
#
# Help Welcome!
#
set -e
#
# Configuration
EFIRUN_DEBUG=0
EFIRUN_FILE="efirun.efi"
EFIRUN_KVM=0
EFIRUN_QEMU=${EFIRUN_QEMU-"qemu-system-x86_64"}
EFIRUN_RUNDIR=${EFIRUN_RUNDIR-"/run/user/$(id -u)/efirun"}
EFIRUN_WD=
#
# Stderr/Stdout Helpers
out() { printf "$1 $2\n" "${@:3}"; }
error() { out "==> ERROR:" "$@"; } >&2
msg() { out "==>" "$@"; }
msg2() { out " ->" "$@";}
die() { error "$@"; exit 1; }
#
# Print Usage
usage() {
cat <<EOF
${0##*/} [OPTIONS..] -- {EXECUTABLE}
Run EFI-executable in an ad-hoc virtual machine.
Options:
-h Print this help message
-d Debug mode
-k Use KVM
EOF
}
#
# Parse Options
while getopts ':dhk' flag ; do
case $flag in
d)
# Debug mode
EFIRUN_DEBUG=1
;;
h)
# Print help
usage
exit 1
;;
k)
# Use kvm
EFIRUN_KVM=1
;;
:)
die '%s: option requires an argument -- '\''%s'\' "${0##*/}" "${OPTARG}"
;;
?)
die '%s: invalid option -- '\''%s'\' "${0##*/}" "${OPTARG}"
;;
esac
done
shift $(( OPTIND - 1 ))
#
# Verify remaining arguments
if (( $# < 1 )) ; then
die '%s: missing arguments' "${0##*/}"
fi
if (( $# > 1 )) ; then
die '%s: too many arguments' "${0##*/}"
fi
if (( $# > 0 )) ; then
EFIRUN_FILE="$(readlink -f "${1}")"
fi
#
# Verify target file
if [[ ! -r "${EFIRUN_FILE}" ]] ; then
die '%s: cannot read target file -- '\''%s'\' "${0##*/}" "${EFIRUN_FILE}"
fi
#
# Create rundir, if non-existant
[[ -d "${EFIRUN_RUNDIR}" ]] || mkdir "${EFIRUN_RUNDIR}"
#
# Create temporary ESP, if non-existant
[[ -d "${EFIRUN_RUNDIR}/esp" ]] || mkdir "${EFIRUN_RUNDIR}/esp"
[[ -d "${EFIRUN_RUNDIR}/esp/EFI" ]] || mkdir "${EFIRUN_RUNDIR}/esp/EFI"
[[ -d "${EFIRUN_RUNDIR}/esp/EFI/Boot" ]] || \
mkdir "${EFIRUN_RUNDIR}/esp/EFI/Boot"
[[ -L "${EFIRUN_RUNDIR}/esp/EFI/Boot/bootx64.efi" ]] || \
ln -s \
"/proc/self/cwd/efirun.efi" \
"${EFIRUN_RUNDIR}/esp/EFI/Boot/bootx64.efi"
#
# Create variable store, if non-existant
# XXX: Fedora: /usr/share/edk2/ovmf
[[ -f "${EFIRUN_RUNDIR}/OVMF_VARS.fd" ]] || \
cp -- \
"/usr/share/ovmf/x64/OVMF_VARS.fd" \
"${EFIRUN_RUNDIR}/OVMF_VARS.fd"
#
# Create working-dir
#
# Unfortunately, we couldn't figure out a way to create a fake ESP for
# arbitrary files without creating a temporary working directory. Hence, we
# need to create a temp-dir. We create it under /run and install a trap-handler
# to clean it up. This is less than ideal, so suggestions welcome!
EFIRUN_WD=$(mktemp -d -p "${EFIRUN_RUNDIR}" wd.XXXXXXXXXX)
function efirun_wd_cleanup {
rm -rf -- "${EFIRUN_WD}"
}
trap efirun_wd_cleanup EXIT
#
# Change directory, if requested
cd "${EFIRUN_WD}"
#
# Create symlink for target file
ln -s "${EFIRUN_FILE}" "${EFIRUN_WD}/efirun.efi"
#
# Collect arguments
#
# We use $ARGS to collect all the commandline arguments we eventually pass to
# qemu. We need to combine a suitable machine-setup, drive configuration,
# custom options provided by our caller, and more.
ARGS=()
#
# Machine Setup
ARGS+=("-nic" "none")
ARGS+=("-m" "1024")
#
# Possibly enable KVM
if (( EFIRUN_KVM )) ; then
ARGS+=("--enable-kvm")
fi
#
# Setup OVMF
ARGS+=("-drive" "if=pflash,format=raw,unit=0,readonly,file=/usr/share/ovmf/x64/OVMF_CODE.fd")
ARGS+=("-drive" "if=pflash,format=raw,unit=1,readonly,file=${EFIRUN_RUNDIR}/OVMF_VARS.fd")
#
# Configure ESP
ARGS+=("-drive" "if=none,id=hd0,format=raw,readonly,file=fat:${EFIRUN_RUNDIR}/esp")
ARGS+=("-device" "virtio-scsi-pci,id=scsi")
ARGS+=("-device" "scsi-hd,drive=hd0,bootindex=1")
#
# Invoke Qemu
${EFIRUN_QEMU} "${ARGS[@]}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment