Skip to content

Commit c2a5c3a

Browse files
committed
Auto merge of #101703 - nicholasbishop:bishop-add-uefi-ci-2, r=jyn514
Add QEMU test for x86_64-unknown-uefi 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 x86_64-uefi`
2 parents 6330c27 + 1e264ab commit c2a5c3a

File tree

4 files changed

+159
-2
lines changed

4 files changed

+159
-2
lines changed

src/ci/docker/host-x86_64/test-various/Dockerfile

+9-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins
1616
pkg-config \
1717
xz-utils \
1818
wget \
19-
patch
19+
patch \
20+
ovmf \
21+
qemu-system-x86
2022

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

67-
ENV SCRIPT $WASM_SCRIPT && $NVPTX_SCRIPT && $MUSL_SCRIPT
69+
COPY host-x86_64/test-various/uefi_qemu_test /uefi_qemu_test
70+
ENV UEFI_TARGETS=x86_64-unknown-uefi
71+
ENV UEFI_SCRIPT python3 /checkout/x.py --stage 2 build --host='' --target $UEFI_TARGETS && \
72+
python3 -u /uefi_qemu_test/run.py
73+
74+
ENV SCRIPT $WASM_SCRIPT && $NVPTX_SCRIPT && $MUSL_SCRIPT && $UEFI_SCRIPT
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "uefi_qemu_test"
3+
version = "0.0.0"
4+
edition = "2021"
5+
6+
[workspace]
7+
8+
[dependencies]
9+
r-efi = "4.1.0"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import shutil
5+
import subprocess
6+
import sys
7+
import tempfile
8+
9+
from pathlib import Path
10+
11+
12+
def run(*cmd, capture=False, check=True, env=None):
13+
"""Print and run a command, optionally capturing the output."""
14+
cmd = [str(p) for p in cmd]
15+
print(' '.join(cmd))
16+
return subprocess.run(cmd,
17+
capture_output=capture,
18+
check=check,
19+
env=env,
20+
text=True)
21+
22+
23+
def build_and_run(tmp_dir):
24+
host_artifacts = Path('/checkout/obj/build/x86_64-unknown-linux-gnu')
25+
stage0 = host_artifacts / 'stage0/bin'
26+
stage2 = host_artifacts / 'stage2/bin'
27+
28+
env = dict(os.environ)
29+
env['PATH'] = '{}:{}:{}'.format(stage2, stage0, env['PATH'])
30+
31+
# Copy the test create into `tmp_dir`.
32+
test_crate = Path(tmp_dir) / 'uefi_qemu_test'
33+
shutil.copytree('/uefi_qemu_test', test_crate)
34+
35+
# Build the UEFI executable.
36+
target = 'x86_64-unknown-uefi'
37+
run('cargo',
38+
'build',
39+
'--manifest-path',
40+
test_crate / 'Cargo.toml',
41+
'--target',
42+
target,
43+
env=env)
44+
45+
# Create a mock EFI System Partition in a subdirectory.
46+
esp = test_crate / 'esp'
47+
boot = esp / 'efi/boot'
48+
os.makedirs(boot, exist_ok=True)
49+
50+
# Copy the executable into the ESP.
51+
src_exe_path = test_crate / 'target' / target / 'debug/uefi_qemu_test.efi'
52+
shutil.copy(src_exe_path, boot / 'bootx64.efi')
53+
54+
# Run the executable in QEMU and capture the output.
55+
qemu = 'qemu-system-x86_64'
56+
ovmf_dir = Path('/usr/share/OVMF')
57+
ovmf_code = ovmf_dir / 'OVMF_CODE.fd'
58+
ovmf_vars = ovmf_dir / 'OVMF_VARS.fd'
59+
output = run(qemu,
60+
'-display',
61+
'none',
62+
'-serial',
63+
'stdio',
64+
'-drive',
65+
f'if=pflash,format=raw,readonly=on,file={ovmf_code}',
66+
'-drive',
67+
f'if=pflash,format=raw,readonly=on,file={ovmf_vars}',
68+
'-drive',
69+
f'format=raw,file=fat:rw:{esp}',
70+
capture=True,
71+
# Ubuntu 20.04 (which is what the Dockerfile currently
72+
# uses) provides QEMU 4.2.1, which segfaults on
73+
# shutdown under some circumstances. That has been
74+
# fixed in newer versions of QEMU, but for now just
75+
# don't check the exit status.
76+
check=False).stdout
77+
78+
if 'Hello World!' in output:
79+
print('VM produced expected output')
80+
else:
81+
print('unexpected VM output:')
82+
print('---start---')
83+
print(output)
84+
print('---end---')
85+
sys.exit(1)
86+
87+
88+
def main():
89+
# Create a temporary directory so that we have a writeable
90+
# workspace.
91+
with tempfile.TemporaryDirectory() as tmp_dir:
92+
build_and_run(tmp_dir)
93+
94+
95+
if __name__ == "__main__":
96+
main()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Code is adapted from this hello world example:
2+
// https://doc.rust-lang.org/nightly/rustc/platform-support/unknown-uefi.html
3+
4+
#![no_main]
5+
#![no_std]
6+
7+
use core::{panic, ptr};
8+
use r_efi::efi::{Char16, Handle, Status, SystemTable, RESET_SHUTDOWN};
9+
10+
#[panic_handler]
11+
fn panic_handler(_info: &panic::PanicInfo) -> ! {
12+
loop {}
13+
}
14+
15+
#[export_name = "efi_main"]
16+
pub extern "C" fn main(_h: Handle, st: *mut SystemTable) -> Status {
17+
let s = [
18+
0x0048u16, 0x0065u16, 0x006cu16, 0x006cu16, 0x006fu16, // "Hello"
19+
0x0020u16, // " "
20+
0x0057u16, 0x006fu16, 0x0072u16, 0x006cu16, 0x0064u16, // "World"
21+
0x0021u16, // "!"
22+
0x000au16, // "\n"
23+
0x0000u16, // NUL
24+
];
25+
26+
// Print "Hello World!".
27+
let r = unsafe { ((*(*st).con_out).output_string)((*st).con_out, s.as_ptr() as *mut Char16) };
28+
if r.is_error() {
29+
return r;
30+
}
31+
32+
// Shut down.
33+
unsafe {
34+
((*((*st).runtime_services)).reset_system)(
35+
RESET_SHUTDOWN,
36+
Status::SUCCESS,
37+
0,
38+
ptr::null_mut(),
39+
);
40+
}
41+
42+
// This should never be reached because `reset_system` should never
43+
// return, so fail with an error if we get here.
44+
Status::UNSUPPORTED
45+
}

0 commit comments

Comments
 (0)