Created
February 8, 2019 10:03
-
-
Save dvdhrm/9c09784a053cdb50301af43137580c20 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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