From 6bf745946e65886c055e7ebd24a1497cdbcb2699 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Thu, 2 Oct 2025 00:24:19 -0700 Subject: [PATCH] add efi shell so that chainloading multiple items can be tested --- boot/Dockerfile | 2 ++ hack/boot.sh | 14 ++++++++- hack/build.sh | 4 +++ hack/configs/kernel.sprout.toml | 5 +++- src/config.rs | 2 ++ src/modules/chainloader.rs | 53 +++++++++++++++++---------------- src/utils.rs | 24 +++++++++++++-- vm/Dockerfile.ovmf | 5 ++-- 8 files changed, 78 insertions(+), 31 deletions(-) diff --git a/boot/Dockerfile b/boot/Dockerfile index 007172f..2782e49 100644 --- a/boot/Dockerfile +++ b/boot/Dockerfile @@ -8,6 +8,7 @@ WORKDIR /work COPY sprout.efi /work/${EFI_NAME}.EFI COPY sprout.toml /work/SPROUT.TOML COPY kernel.efi /work/KERNEL.EFI +COPY shell.efi /work/SHELL.EFI RUN truncate -s256MiB sprout.img && \ parted --script sprout.img mklabel gpt > /dev/null 2>&1 && \ parted --script sprout.img mkpart primary fat32 1MiB 100% > /dev/null 2>&1 && \ @@ -17,6 +18,7 @@ RUN truncate -s256MiB sprout.img && \ mmd -i sprout.img ::/EFI/BOOT && \ mcopy -i sprout.img ${EFI_NAME}.EFI ::/EFI/BOOT/ && \ mcopy -i sprout.img KERNEL.EFI ::/EFI/BOOT/ && \ + mcopy -i sprout.img SHELL.EFI ::/EFI/BOOT/ && \ mcopy -i sprout.img SPROUT.TOML ::/ && \ mv sprout.img /sprout.img diff --git a/hack/boot.sh b/hack/boot.sh index aa9d6c6..46dae95 100755 --- a/hack/boot.sh +++ b/hack/boot.sh @@ -22,7 +22,19 @@ set -- "${@}" -smp 2 -m 4096 if [ "${NO_GRAPHICAL_BOOT}" = "1" ]; then set -- "${@}" -nographic else - set -- "${@}" -serial stdio -vga none -device "virtio-gpu,edid=on,xres=1024,yres=768" + set -- "${@}" \ + -device virtio-serial-pci,id=vs0 \ + -chardev stdio,id=stdio0 \ + -device virtconsole,chardev=stdio0,id=console0 + + if [ "${TARGET_ARCH}" = "x86_64" ]; then + set -- "${@}" \ + -vga std + else + set -- "${@}" \ + -vga none \ + -device "virtio-gpu,edid=on,xres=1024,yres=768" + fi fi rm -f "${FINAL_DIR}/ovmf-boot.fd" diff --git a/hack/build.sh b/hack/build.sh index 7004e45..2ddd068 100755 --- a/hack/build.sh +++ b/hack/build.sh @@ -73,6 +73,7 @@ if [ "${SKIP_VM_BUILD}" != "1" ]; then echo "[vm build] ${TARGET_ARCH} ${RUST_PROFILE}" docker build --platform="${DOCKER_TARGET}" -t "${DOCKER_PREFIX}/sprout-ovmf-${TARGET_ARCH}:${DOCKER_TAG}" -f vm/Dockerfile.ovmf "${FINAL_DIR}" copy_from_image "${DOCKER_PREFIX}/sprout-ovmf-${TARGET_ARCH}" "ovmf.fd" "${FINAL_DIR}/ovmf.fd" + copy_from_image "${DOCKER_PREFIX}/sprout-ovmf-${TARGET_ARCH}" "shell.efi" "${FINAL_DIR}/shell.efi" fi if [ "${SKIP_SPROUT_BUILD}" != "1" ]; then @@ -84,6 +85,9 @@ if [ "${SKIP_SPROUT_BUILD}" != "1" ]; then if [ -f "${FINAL_DIR}/kernel.efi" ]; then cp "${FINAL_DIR}/kernel.efi" "${FINAL_DIR}/efi/EFI/BOOT/KERNEL.EFI" fi + if [ -f "${FINAL_DIR}/shell.efi" ]; then + cp "${FINAL_DIR}/shell.efi" "${FINAL_DIR}/efi/EFI/BOOT/SHELL.EFI" + fi cp "hack/configs/kernel.sprout.toml" "${FINAL_DIR}/efi/SPROUT.TOML" fi diff --git a/hack/configs/kernel.sprout.toml b/hack/configs/kernel.sprout.toml index c93ff1d..43fb14c 100644 --- a/hack/configs/kernel.sprout.toml +++ b/hack/configs/kernel.sprout.toml @@ -1,2 +1,5 @@ [[modules]] -chainloader = { path = "\\EFI\\BOOT\\KERNEL.EFI" } +chainloader = { path = "\\EFI\\BOOT\\SHELL.EFI" } + +[[modules]] +chainloader = { path = "\\EFI\\BOOT\\KERNEL.EFI", options = ["tty=hvc0"] } diff --git a/src/config.rs b/src/config.rs index 11cb8d8..ec45c68 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,6 +16,8 @@ pub struct ModuleConfiguration { #[derive(Serialize, Deserialize, Default)] pub struct ChainloaderConfiguration { pub path: String, + #[serde(default)] + pub options: Vec, } pub fn load() -> RootConfiguration { diff --git a/src/modules/chainloader.rs b/src/modules/chainloader.rs index 4bf6b37..5535971 100644 --- a/src/modules/chainloader.rs +++ b/src/modules/chainloader.rs @@ -1,10 +1,8 @@ use crate::config::ChainloaderConfiguration; -use crate::utils::text_to_device_path; +use crate::utils; use log::info; -use uefi::proto::device_path::{ - DevicePath, LoadedImageDevicePath, - text::{AllowShortcuts, DisplayOnly}, -}; +use uefi::CString16; +use uefi::proto::device_path::LoadedImageDevicePath; use uefi::proto::loaded_image::LoadedImage; pub fn chainloader(configuration: ChainloaderConfiguration) { @@ -13,28 +11,12 @@ pub fn chainloader(configuration: ChainloaderConfiguration) { uefi::boot::open_protocol_exclusive::(sprout_image) .expect("unable to open loaded image device path protocol"); - let image_device_path: &DevicePath = &image_device_path_protocol; - let mut full_path = image_device_path - .node_iter() - .filter_map(|item| { - let item = item - .to_string(DisplayOnly(false), AllowShortcuts(false)) - .expect("unable to convert device path to string"); - if item.to_string().contains("(") { - Some(item) - } else { - None - } - }) - .map(|item| item.to_string()) - .collect::>() - .join("/"); - full_path.push('/'); + let mut full_path = utils::device_path_root(&image_device_path_protocol); full_path.push_str(&configuration.path); info!("path={}", full_path); - let device_path = text_to_device_path(&full_path); + let device_path = utils::text_to_device_path(&full_path); let image = uefi::boot::load_image( sprout_image, @@ -45,10 +27,31 @@ pub fn chainloader(configuration: ChainloaderConfiguration) { ) .expect("failed to load image"); - let image_device_path_protocol = uefi::boot::open_protocol_exclusive::(image) + let mut loaded_image_protocol = uefi::boot::open_protocol_exclusive::(image) .expect("unable to open loaded image protocol"); - let (base, size) = image_device_path_protocol.info(); + let options = configuration.options.join(" "); + if !options.is_empty() { + let options = Box::new( + CString16::try_from(&options[..]) + .expect("unable to convert chainloader options to CString16"), + ); + info!("options={}", options); + + if options.num_bytes() > u32::MAX as usize { + panic!("chainloader options too large"); + } + + // SAFETY: options size is checked to validate it is safe to pass. + // Additionally, the pointer is allocated and retained on the heap which makes + // passing the options pointer safe to the next image. + unsafe { + loaded_image_protocol + .set_load_options(options.as_ptr() as *const u8, options.num_bytes() as u32); + } + } + + let (base, size) = loaded_image_protocol.info(); info!("loaded image base={:#x} size={:#x}", base.addr(), size); uefi::boot::start_image(image).expect("failed to start image"); } diff --git a/src/utils.rs b/src/utils.rs index 1bea02e..2a04f06 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,7 +1,7 @@ use uefi::CString16; use uefi::fs::{FileSystem, Path}; -use uefi::proto::device_path::PoolDevicePath; -use uefi::proto::device_path::text::DevicePathFromText; +use uefi::proto::device_path::text::{AllowShortcuts, DevicePathFromText, DisplayOnly}; +use uefi::proto::device_path::{DevicePath, PoolDevicePath}; use uefi::proto::media::fs::SimpleFileSystem; pub fn text_to_device_path(path: &str) -> PoolDevicePath { @@ -17,6 +17,26 @@ pub fn text_to_device_path(path: &str) -> PoolDevicePath { .expect("unable to convert text to device path") } +pub fn device_path_root(path: &DevicePath) -> String { + let mut path = path + .node_iter() + .filter_map(|item| { + let item = item + .to_string(DisplayOnly(false), AllowShortcuts(false)) + .expect("unable to convert device path to string"); + if item.to_string().contains("(") { + Some(item) + } else { + None + } + }) + .map(|item| item.to_string()) + .collect::>() + .join("/"); + path.push('/'); + path +} + pub fn read_file_contents(path: &str) -> Vec { let fs = uefi::boot::open_protocol_exclusive::( uefi::boot::get_handle_for_protocol::().expect("no filesystem protocol"), diff --git a/vm/Dockerfile.ovmf b/vm/Dockerfile.ovmf index 647b420..7ac7c1a 100644 --- a/vm/Dockerfile.ovmf +++ b/vm/Dockerfile.ovmf @@ -1,9 +1,10 @@ FROM alpine:3.22@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1 AS build ARG TARGETPLATFORM RUN if [ "${TARGETPLATFORM}" = "linux/amd64" ] || [ "${TARGETPLATFORM}" = "linux/x86_64" ]; then \ - apk --no-cache add ovmf; cp /usr/share/ovmf/bios.bin /ovmf.fd; fi + apk --no-cache add ovmf edk2-shell; cp /usr/share/ovmf/bios.bin /ovmf.fd; fi RUN if [ "${TARGETPLATFORM}" = "linux/arm64" ] || [ "${TARGETPLATFORM}" = "linux/aarch64" ]; then \ - apk --no-cache add aavmf; cp /usr/share/AAVMF/QEMU_EFI.fd /ovmf.fd; fi + apk --no-cache add aavmf edk2-shell; cp /usr/share/AAVMF/QEMU_EFI.fd /ovmf.fd; fi FROM scratch AS final COPY --from=build /ovmf.fd /ovmf.fd +COPY --from=build /usr/share/edk2-shell/ShellFull.efi /shell.efi