From 845662e1c0f2315980592ef57beb2e087d3c181c Mon Sep 17 00:00:00 2001 From: "Cliff L. Biffle" Date: Wed, 19 Oct 2022 13:16:29 -0700 Subject: [PATCH] Magic functions for writing to stdout/stderr. This enables I/O in no_std contexts (or, really, any Miri-specific OS-independent context). Combined with the `abort` intrinsic it should allow a reasonable test framework in no_std. --- src/tools/miri/README.md | 10 +++++ src/tools/miri/src/shims/foreign_items.rs | 19 ++++++++- src/tools/miri/tests/fail/panic/no_std.rs | 41 +++++++++++++++++++ src/tools/miri/tests/fail/panic/no_std.stderr | 19 +++++++++ src/tools/miri/tests/pass/no_std.rs | 24 ++++++++++- src/tools/miri/tests/pass/no_std.stdout | 1 + 6 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 src/tools/miri/tests/fail/panic/no_std.rs create mode 100644 src/tools/miri/tests/fail/panic/no_std.stderr create mode 100644 src/tools/miri/tests/pass/no_std.stdout diff --git a/src/tools/miri/README.md b/src/tools/miri/README.md index 32e616cb074..4f5d406288f 100644 --- a/src/tools/miri/README.md +++ b/src/tools/miri/README.md @@ -542,6 +542,16 @@ extern "Rust" { /// In particular, users should be aware that Miri will periodically attempt to garbage collect the /// contents of all stacks. Callers of this function may wish to pass `-Zmiri-tag-gc=0` to disable the GC. fn miri_print_stacks(alloc_id: u64); + + /// Miri-provided extern function to print (from the interpreter, not the + /// program) the contents of a section of program memory, as bytes. Bytes + /// written using this function will emerge from the interpreter's stdout. + fn miri_write_to_stdout(bytes: &[u8]); + + /// Miri-provided extern function to print (from the interpreter, not the + /// program) the contents of a section of program memory, as bytes. Bytes + /// written using this function will emerge from the interpreter's stderr. + fn miri_write_to_stderr(bytes: &[u8]); } ``` diff --git a/src/tools/miri/src/shims/foreign_items.rs b/src/tools/miri/src/shims/foreign_items.rs index 7e6a9595161..a49e6ba4ce3 100644 --- a/src/tools/miri/src/shims/foreign_items.rs +++ b/src/tools/miri/src/shims/foreign_items.rs @@ -1,4 +1,4 @@ -use std::{collections::hash_map::Entry, iter}; +use std::{collections::hash_map::Entry, io::Write, iter}; use log::trace; @@ -462,6 +462,23 @@ fn emulate_foreign_item_by_name( this.handle_miri_resolve_frame_names(abi, link_name, args)?; } + // Writes some bytes to the interpreter's stdout/stderr. See the + // README for details. + "miri_write_to_stdout" | "miri_write_to_stderr" => { + let [bytes] = this.check_shim(abi, Abi::Rust, link_name, args)?; + let (ptr, len) = this.read_immediate(bytes)?.to_scalar_pair(); + let ptr = ptr.to_pointer(this)?; + let len = len.to_machine_usize(this)?; + let msg = this.read_bytes_ptr_strip_provenance(ptr, Size::from_bytes(len))?; + + // Note: we're ignoring errors writing to host stdout/stderr. + let _ignore = match link_name.as_str() { + "miri_write_to_stdout" => std::io::stdout().write_all(msg), + "miri_write_to_stderr" => std::io::stderr().write_all(msg), + _ => unreachable!(), + }; + } + // Standard C allocation "malloc" => { let [size] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; diff --git a/src/tools/miri/tests/fail/panic/no_std.rs b/src/tools/miri/tests/fail/panic/no_std.rs new file mode 100644 index 00000000000..b6a5c075570 --- /dev/null +++ b/src/tools/miri/tests/fail/panic/no_std.rs @@ -0,0 +1,41 @@ +#![feature(lang_items, start, core_intrinsics)] +#![no_std] +// windows tls dtors go through libstd right now, thus this test +// cannot pass. When windows tls dtors go through the special magic +// windows linker section, we can run this test on windows again. +//@ignore-target-windows + +// Plumbing to let us use `writeln!` to host stderr: + +extern "Rust" { + fn miri_write_to_stderr(bytes: &[u8]); +} + +struct HostErr; + +use core::fmt::Write; + +impl Write for HostErr { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + unsafe { + miri_write_to_stderr(s.as_bytes()); + } + Ok(()) + } +} + +// Aaaand the test: + +#[start] +fn start(_: isize, _: *const *const u8) -> isize { + panic!("blarg I am dead") +} + +#[panic_handler] +fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! { + writeln!(HostErr, "{panic_info}").ok(); + core::intrinsics::abort(); //~ ERROR: the program aborted execution +} + +#[lang = "eh_personality"] +fn eh_personality() {} diff --git a/src/tools/miri/tests/fail/panic/no_std.stderr b/src/tools/miri/tests/fail/panic/no_std.stderr new file mode 100644 index 00000000000..568b286e1d3 --- /dev/null +++ b/src/tools/miri/tests/fail/panic/no_std.stderr @@ -0,0 +1,19 @@ +panicked at 'blarg I am dead', $DIR/no_std.rs:LL:CC +error: abnormal termination: the program aborted execution + --> $DIR/no_std.rs:LL:CC + | +LL | core::intrinsics::abort(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^ the program aborted execution + | + = note: inside `panic_handler` at $DIR/no_std.rs:LL:CC +note: inside `start` at RUSTLIB/core/src/panic.rs:LL:CC + --> $DIR/no_std.rs:LL:CC + | +LL | panic!("blarg I am dead") + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to previous error + diff --git a/src/tools/miri/tests/pass/no_std.rs b/src/tools/miri/tests/pass/no_std.rs index 10632c2cce4..0203edfe181 100644 --- a/src/tools/miri/tests/pass/no_std.rs +++ b/src/tools/miri/tests/pass/no_std.rs @@ -5,10 +5,30 @@ // windows linker section, we can run this test on windows again. //@ignore-target-windows +// Plumbing to let us use `writeln!` to host stdout: + +extern "Rust" { + fn miri_write_to_stdout(bytes: &[u8]); +} + +struct Host; + +use core::fmt::Write; + +impl Write for Host { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + unsafe { + miri_write_to_stdout(s.as_bytes()); + } + Ok(()) + } +} + +// Aaaand the test: + #[start] fn start(_: isize, _: *const *const u8) -> isize { - for _ in 0..10 {} - + writeln!(Host, "hello, world!").unwrap(); 0 } diff --git a/src/tools/miri/tests/pass/no_std.stdout b/src/tools/miri/tests/pass/no_std.stdout new file mode 100644 index 00000000000..270c611ee72 --- /dev/null +++ b/src/tools/miri/tests/pass/no_std.stdout @@ -0,0 +1 @@ +hello, world!