* Add AArch64 tests to CI This commit enhances our CI with an AArch64 builder. Currently we have no physical hardware to run on so for now we run all tests in an emulator. The AArch64 build is cross-compiled from x86_64 from Linux. Tests all happen in release mode with a recent version of QEMU (recent version because it's so much faster, and in release mode because debug mode tests take quite a long time in an emulator). The goal here was not to get all tests passing on CI, but rather to get AArch64 running on CI and get it green at the same time. To achieve that goal many tests are now ignored on aarch64 platforms. Many tests fail due to unimplemented functionality in the aarch64 backend (#1521), and all wasmtime tests involving compilation are also disabled due to panicking attempting to generate generate instruction offset information for trap symbolication (#1523). Despite this, though, all Cranelift tests and other wasmtime tests should be runnin on AArch64 through QEMU with this PR. Additionally we'll have an AArch64 binary release of Wasmtime for Linux, although it won't be too useful just yet since it will panic on almost all wasm modules. * Review comments
133 lines
4.1 KiB
Rust
133 lines
4.1 KiB
Rust
// To handle out-of-bounds reads and writes we use segfaults right now. We only
|
|
// want to catch a subset of segfaults, however, rather than all segfaults
|
|
// happening everywhere. The purpose of this test is to ensure that we *don't*
|
|
// catch segfaults if it happens in a random place in the code, but we instead
|
|
// bail out of our segfault handler early.
|
|
//
|
|
// This is sort of hard to test for but the general idea here is that we confirm
|
|
// that execution made it to our `segfault` function by printing something, and
|
|
// then we also make sure that stderr is empty to confirm that no weird panics
|
|
// happened or anything like that.
|
|
|
|
use std::env;
|
|
use std::process::{Command, ExitStatus};
|
|
use wasmtime::*;
|
|
|
|
const VAR_NAME: &str = "__TEST_TO_RUN";
|
|
const CONFIRM: &str = "well at least we ran up to the segfault\n";
|
|
|
|
fn segfault() -> ! {
|
|
unsafe {
|
|
print!("{}", CONFIRM);
|
|
*(0x4 as *mut i32) = 3;
|
|
unreachable!()
|
|
}
|
|
}
|
|
|
|
fn overrun_the_stack() -> usize {
|
|
let mut a = [0u8; 1024];
|
|
if a.as_mut_ptr() as usize == 1 {
|
|
return 1;
|
|
} else {
|
|
return a.as_mut_ptr() as usize + overrun_the_stack();
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
// Skip this tests if it looks like we're in a cross-compiled situation and
|
|
// we're emulating this test for a different platform. In that scenario
|
|
// emulators (like QEMU) tend to not report signals the same way and such.
|
|
if std::env::vars()
|
|
.filter(|(k, _v)| k.starts_with("CARGO_TARGET") && k.ends_with("RUNNER"))
|
|
.count()
|
|
> 0
|
|
{
|
|
return;
|
|
}
|
|
|
|
let tests: &[(&str, fn())] = &[
|
|
("normal segfault", || segfault()),
|
|
("make instance then segfault", || {
|
|
let store = Store::default();
|
|
let module = Module::new(&store, "(module)").unwrap();
|
|
let _instance = Instance::new(&module, &[]).unwrap();
|
|
segfault();
|
|
}),
|
|
("make instance then overrun the stack", || {
|
|
let store = Store::default();
|
|
let module = Module::new(&store, "(module)").unwrap();
|
|
let _instance = Instance::new(&module, &[]).unwrap();
|
|
println!("stack overrun: {}", overrun_the_stack());
|
|
}),
|
|
];
|
|
match env::var(VAR_NAME) {
|
|
Ok(s) => {
|
|
let test = tests
|
|
.iter()
|
|
.find(|p| p.0 == s)
|
|
.expect("failed to find test")
|
|
.1;
|
|
test();
|
|
}
|
|
Err(_) => {
|
|
for (name, _test) in tests {
|
|
runtest(name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn runtest(name: &str) {
|
|
let me = env::current_exe().unwrap();
|
|
let mut cmd = Command::new(me);
|
|
cmd.env(VAR_NAME, name);
|
|
let output = cmd.output().expect("failed to spawn subprocess");
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
let mut desc = format!("got status: {}", output.status);
|
|
if !stdout.trim().is_empty() {
|
|
desc.push_str("\nstdout: ----\n");
|
|
desc.push_str(" ");
|
|
desc.push_str(&stdout.replace("\n", "\n "));
|
|
}
|
|
if !stderr.trim().is_empty() {
|
|
desc.push_str("\nstderr: ----\n");
|
|
desc.push_str(" ");
|
|
desc.push_str(&stderr.replace("\n", "\n "));
|
|
}
|
|
if is_segfault(&output.status) {
|
|
assert!(
|
|
stdout.ends_with(CONFIRM) && stderr.is_empty(),
|
|
"failed to find confirmation in test `{}`\n{}",
|
|
name,
|
|
desc
|
|
);
|
|
} else if name.contains("overrun the stack") {
|
|
assert!(
|
|
stderr.contains("thread 'main' has overflowed its stack"),
|
|
"bad stderr: {}",
|
|
stderr
|
|
);
|
|
} else {
|
|
panic!("\n\nexpected a segfault on `{}`\n{}\n\n", name, desc);
|
|
}
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
fn is_segfault(status: &ExitStatus) -> bool {
|
|
use std::os::unix::prelude::*;
|
|
|
|
match status.signal() {
|
|
Some(libc::SIGSEGV) | Some(libc::SIGBUS) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
fn is_segfault(status: &ExitStatus) -> bool {
|
|
match status.code().map(|s| s as u32) {
|
|
Some(0xc0000005) => true,
|
|
_ => false,
|
|
}
|
|
}
|