]> git.lizzy.rs Git - rust.git/commitdiff
Add QEMU test for x86_64-unknown-uefi
authorNicholas Bishop <nbishop@nbishop.net>
Sun, 11 Sep 2022 22:02:54 +0000 (18:02 -0400)
committerNicholas Bishop <nicholasbishop@google.com>
Tue, 20 Sep 2022 22:57:28 +0000 (18:57 -0400)
The UEFI targets don't have std support yet, so the normal tests don't
work. However, we can compile a simple no-std program and run it under
QEMU to at least check that the target compiles, links, and runs.

Tested locally with: src/ci/docker/run.sh test-various

src/ci/docker/host-x86_64/test-various/Dockerfile
src/ci/docker/host-x86_64/test-various/uefi_qemu_test/Cargo.toml [new file with mode: 0644]
src/ci/docker/host-x86_64/test-various/uefi_qemu_test/run.py [new file with mode: 0644]
src/ci/docker/host-x86_64/test-various/uefi_qemu_test/src/main.rs [new file with mode: 0644]

index b75e2f085cd3b73f2f66ea7323bea08fc8edb6eb..b0f35bcb9ccf505580aaba4f966ad089a4864b4a 100644 (file)
@@ -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
@@ -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
diff --git a/src/ci/docker/host-x86_64/test-various/uefi_qemu_test/Cargo.toml b/src/ci/docker/host-x86_64/test-various/uefi_qemu_test/Cargo.toml
new file mode 100644 (file)
index 0000000..fa8e5b3
--- /dev/null
@@ -0,0 +1,9 @@
+[package]
+name = "uefi_qemu_test"
+version = "0.0.0"
+edition = "2021"
+
+[workspace]
+
+[dependencies]
+r-efi = "4.1.0"
diff --git a/src/ci/docker/host-x86_64/test-various/uefi_qemu_test/run.py b/src/ci/docker/host-x86_64/test-various/uefi_qemu_test/run.py
new file mode 100644 (file)
index 0000000..46793ce
--- /dev/null
@@ -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()
diff --git a/src/ci/docker/host-x86_64/test-various/uefi_qemu_test/src/main.rs b/src/ci/docker/host-x86_64/test-various/uefi_qemu_test/src/main.rs
new file mode 100644 (file)
index 0000000..2ec554c
--- /dev/null
@@ -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
+}