Skip to content

Add QEMU test for x86_64-unknown-uefi #101703

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/ci/docker/host-x86_64/test-various/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins
pkg-config \
xz-utils \
wget \
patch
patch \
ovmf \
qemu-system-x86

RUN curl -sL https://nodejs.org/dist/v15.14.0/node-v15.14.0-linux-x64.tar.xz | \
tar -xJ
Expand Down Expand Up @@ -64,4 +66,9 @@ ENV MUSL_TARGETS=x86_64-unknown-linux-musl \
CXX_x86_64_unknown_linux_musl=x86_64-linux-musl-g++
ENV MUSL_SCRIPT python3 /checkout/x.py --stage 2 test --host='' --target $MUSL_TARGETS

ENV SCRIPT $WASM_SCRIPT && $NVPTX_SCRIPT && $MUSL_SCRIPT
COPY host-x86_64/test-various/uefi_qemu_test /uefi_qemu_test
ENV UEFI_TARGETS=x86_64-unknown-uefi
ENV UEFI_SCRIPT python3 /checkout/x.py --stage 2 build --host='' --target $UEFI_TARGETS && \
python3 -u /uefi_qemu_test/run.py

ENV SCRIPT $WASM_SCRIPT && $NVPTX_SCRIPT && $MUSL_SCRIPT && $UEFI_SCRIPT
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "uefi_qemu_test"
version = "0.0.0"
edition = "2021"

[workspace]

[dependencies]
r-efi = "4.1.0"
96 changes: 96 additions & 0 deletions src/ci/docker/host-x86_64/test-various/uefi_qemu_test/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/usr/bin/env python3

import os
import shutil
import subprocess
import sys
import tempfile

from pathlib import Path


def run(*cmd, capture=False, check=True, env=None):
"""Print and run a command, optionally capturing the output."""
cmd = [str(p) for p in cmd]
print(' '.join(cmd))
return subprocess.run(cmd,
capture_output=capture,
check=check,
env=env,
text=True)


def build_and_run(tmp_dir):
host_artifacts = Path('/checkout/obj/build/x86_64-unknown-linux-gnu')
stage0 = host_artifacts / 'stage0/bin'
stage2 = host_artifacts / 'stage2/bin'

env = dict(os.environ)
env['PATH'] = '{}:{}:{}'.format(stage2, stage0, env['PATH'])

# Copy the test create into `tmp_dir`.
test_crate = Path(tmp_dir) / 'uefi_qemu_test'
shutil.copytree('/uefi_qemu_test', test_crate)

# Build the UEFI executable.
target = 'x86_64-unknown-uefi'
run('cargo',
'build',
'--manifest-path',
test_crate / 'Cargo.toml',
'--target',
target,
env=env)

# Create a mock EFI System Partition in a subdirectory.
esp = test_crate / 'esp'
boot = esp / 'efi/boot'
os.makedirs(boot, exist_ok=True)

# Copy the executable into the ESP.
src_exe_path = test_crate / 'target' / target / 'debug/uefi_qemu_test.efi'
shutil.copy(src_exe_path, boot / 'bootx64.efi')

# Run the executable in QEMU and capture the output.
qemu = 'qemu-system-x86_64'
ovmf_dir = Path('/usr/share/OVMF')
ovmf_code = ovmf_dir / 'OVMF_CODE.fd'
ovmf_vars = ovmf_dir / 'OVMF_VARS.fd'
output = run(qemu,
'-display',
'none',
'-serial',
'stdio',
'-drive',
f'if=pflash,format=raw,readonly=on,file={ovmf_code}',
'-drive',
f'if=pflash,format=raw,readonly=on,file={ovmf_vars}',
'-drive',
f'format=raw,file=fat:rw:{esp}',
capture=True,
# Ubuntu 20.04 (which is what the Dockerfile currently
# uses) provides QEMU 4.2.1, which segfaults on
# shutdown under some circumstances. That has been
# fixed in newer versions of QEMU, but for now just
# don't check the exit status.
check=False).stdout

if 'Hello World!' in output:
print('VM produced expected output')
else:
print('unexpected VM output:')
print('---start---')
print(output)
print('---end---')
sys.exit(1)


def main():
# Create a temporary directory so that we have a writeable
# workspace.
with tempfile.TemporaryDirectory() as tmp_dir:
build_and_run(tmp_dir)


if __name__ == "__main__":
main()
45 changes: 45 additions & 0 deletions src/ci/docker/host-x86_64/test-various/uefi_qemu_test/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Code is adapted from this hello world example:
// https://doc.rust-lang.org/nightly/rustc/platform-support/unknown-uefi.html

#![no_main]
#![no_std]

use core::{panic, ptr};
use r_efi::efi::{Char16, Handle, Status, SystemTable, RESET_SHUTDOWN};

#[panic_handler]
fn panic_handler(_info: &panic::PanicInfo) -> ! {
loop {}
}

#[export_name = "efi_main"]
pub extern "C" fn main(_h: Handle, st: *mut SystemTable) -> Status {
let s = [
0x0048u16, 0x0065u16, 0x006cu16, 0x006cu16, 0x006fu16, // "Hello"
0x0020u16, // " "
0x0057u16, 0x006fu16, 0x0072u16, 0x006cu16, 0x0064u16, // "World"
0x0021u16, // "!"
0x000au16, // "\n"
0x0000u16, // NUL
];

// Print "Hello World!".
let r = unsafe { ((*(*st).con_out).output_string)((*st).con_out, s.as_ptr() as *mut Char16) };
if r.is_error() {
return r;
}

// Shut down.
unsafe {
((*((*st).runtime_services)).reset_system)(
RESET_SHUTDOWN,
Status::SUCCESS,
0,
ptr::null_mut(),
);
}

// This should never be reached because `reset_system` should never
// return, so fail with an error if we get here.
Status::UNSUPPORTED
}