From 94d41f0cc19dce4215070ce75deb5a45f4abab38 Mon Sep 17 00:00:00 2001 From: DebugSteven Date: Mon, 11 Jul 2022 09:29:54 -0600 Subject: [PATCH] implement minimal epoll_create1 shim --- src/machine.rs | 1 + src/shims/unix/fs.rs | 19 ++- src/shims/unix/linux/foreign_items.rs | 37 ++++++ src/shims/unix/linux/fs.rs | 167 ++++++++++++++++++++++++++ src/shims/unix/linux/fs/epoll.rs | 42 +++++++ src/shims/unix/linux/fs/event.rs | 39 ++++++ src/shims/unix/linux/fs/socketpair.rs | 29 +++++ src/shims/unix/linux/mod.rs | 1 + tests/fail/crates/tokio_mvp.rs | 7 -- tests/fail/crates/tokio_mvp.stderr | 19 --- tests/pass-dep/tokio_mvp.rs | 9 ++ 11 files changed, 338 insertions(+), 32 deletions(-) create mode 100644 src/shims/unix/linux/fs.rs create mode 100644 src/shims/unix/linux/fs/epoll.rs create mode 100644 src/shims/unix/linux/fs/event.rs create mode 100644 src/shims/unix/linux/fs/socketpair.rs delete mode 100644 tests/fail/crates/tokio_mvp.rs delete mode 100644 tests/fail/crates/tokio_mvp.stderr create mode 100644 tests/pass-dep/tokio_mvp.rs diff --git a/src/machine.rs b/src/machine.rs index fc9a1170d2..ff20025edb 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -33,6 +33,7 @@ use crate::{ // Some global facts about the emulated machine. pub const PAGE_SIZE: u64 = 4 * 1024; // FIXME: adjust to target architecture +pub const SIGRTMAX: i32 = 42; // should be between 32 and 64 pub const STACK_ADDR: u64 = 32 * PAGE_SIZE; // not really about the "stack", but where we start assigning integer addresses to allocations pub const STACK_SIZE: u64 = 16 * PAGE_SIZE; // whatever diff --git a/src/shims/unix/fs.rs b/src/shims/unix/fs.rs index 0610f65db1..66a7e51391 100644 --- a/src/shims/unix/fs.rs +++ b/src/shims/unix/fs.rs @@ -18,20 +18,25 @@ use crate::shims::os_str::bytes_to_os_str; use crate::*; use shims::os_str::os_str_to_bytes; use shims::time::system_time_to_duration; +use shims::unix::linux::fs::epoll::Epoll; #[derive(Debug)] -struct FileHandle { +pub struct FileHandle { file: File, writable: bool, } -trait FileDescriptor: std::fmt::Debug { +pub trait FileDescriptor: std::fmt::Debug { fn name(&self) -> &'static str; fn as_file_handle<'tcx>(&self) -> InterpResult<'tcx, &FileHandle> { throw_unsup_format!("{} cannot be used as FileHandle", self.name()); } + fn as_epoll_handle<'tcx>(&mut self) -> InterpResult<'tcx, &mut Epoll> { + throw_unsup_format!("not an epoll file descriptor"); + } + fn read<'tcx>( &mut self, _communicate_allowed: bool, @@ -275,7 +280,7 @@ impl FileDescriptor for NullOutput { #[derive(Debug)] pub struct FileHandler { - handles: BTreeMap>, + pub handles: BTreeMap>, } impl VisitTags for FileHandler { @@ -298,7 +303,7 @@ impl FileHandler { FileHandler { handles } } - fn insert_fd(&mut self, file_handle: Box) -> i32 { + pub fn insert_fd(&mut self, file_handle: Box) -> i32 { self.insert_fd_with_min_fd(file_handle, 0) } @@ -333,7 +338,9 @@ impl FileHandler { } impl<'mir, 'tcx: 'mir> EvalContextExtPrivate<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} -trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { +pub(super) trait EvalContextExtPrivate<'mir, 'tcx: 'mir>: + crate::MiriInterpCxExt<'mir, 'tcx> +{ fn macos_stat_write_buf( &mut self, metadata: FileMetadata, @@ -1908,7 +1915,7 @@ fn extract_sec_and_nsec<'tcx>( /// Stores a file's metadata in order to avoid code duplication in the different metadata related /// shims. -struct FileMetadata { +pub(super) struct FileMetadata { mode: Scalar, size: u64, created: Option<(u64, u32)>, diff --git a/src/shims/unix/linux/foreign_items.rs b/src/shims/unix/linux/foreign_items.rs index 5d000f9d12..a84637a1e6 100644 --- a/src/shims/unix/linux/foreign_items.rs +++ b/src/shims/unix/linux/foreign_items.rs @@ -4,6 +4,7 @@ use rustc_target::spec::abi::Abi; use crate::*; use shims::foreign_items::EmulateByNameResult; use shims::unix::fs::EvalContextExt as _; +use shims::unix::linux::fs::EvalContextExt as _; use shims::unix::linux::sync::futex; use shims::unix::sync::EvalContextExt as _; use shims::unix::thread::EvalContextExt as _; @@ -42,6 +43,42 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let result = this.sync_file_range(fd, offset, nbytes, flags)?; this.write_scalar(result, dest)?; } + "epoll_create1" => { + let [flag] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.epoll_create1(flag)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "epoll_ctl" => { + let [epfd, op, fd, event] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.epoll_ctl(epfd, op, fd, event)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "epoll_wait" => { + let [epfd, events, maxevents, timeout] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.epoll_wait(epfd, events, maxevents, timeout)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "eventfd" => { + let [val, flag] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + let result = this.eventfd(val, flag)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "socketpair" => { + let [domain, type_, protocol, sv] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let result = this.socketpair(domain, type_, protocol, sv)?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } + "__libc_current_sigrtmax" => { + let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + + let result = this.libc_current_sigrtmax()?; + this.write_scalar(Scalar::from_i32(result), dest)?; + } // Time related shims "clock_gettime" => { diff --git a/src/shims/unix/linux/fs.rs b/src/shims/unix/linux/fs.rs new file mode 100644 index 0000000000..fad35d6c7e --- /dev/null +++ b/src/shims/unix/linux/fs.rs @@ -0,0 +1,167 @@ +use rustc_middle::ty::ScalarInt; + +use crate::{machine::SIGRTMAX, *}; +use epoll::{Epoll, EpollEvent}; +use event::Event; +use socketpair::SocketPair; + +use shims::unix::fs::EvalContextExtPrivate; + +pub mod epoll; +pub mod event; +pub mod socketpair; + +impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {} +pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { + fn epoll_create1(&mut self, flags: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let flags = this.read_scalar(flags)?.to_i32()?; + + let epoll_cloexec = this.eval_libc_i32("EPOLL_CLOEXEC")?; + // FIXME handle cloexec + let _fd_cloexec = this.eval_libc_i32("FD_CLOEXEC")?; + if flags == epoll_cloexec { + // FIXME set close on exec, FD_CLOEXEC + } else if flags != 0 { + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + return Ok(-1); + } + + let fh = &mut this.machine.file_handler; + #[allow(clippy::box_default)] + let fd = fh.insert_fd(Box::new(Epoll::default())); + Ok(fd) + } + + fn epoll_ctl( + &mut self, + epfd: &OpTy<'tcx, Provenance>, + op: &OpTy<'tcx, Provenance>, + fd: &OpTy<'tcx, Provenance>, + event: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let values = this.read_scalar(op)?.to_i32()?; + let epfd = this.read_scalar(epfd)?.to_i32()?; + let fd = this.read_scalar(fd)?.to_i32()?; + + let epoll_ctl_add = this.eval_libc_i32("EPOLL_CTL_ADD")?; + let epoll_ctl_mod = this.eval_libc_i32("EPOLL_CTL_MOD")?; + let epoll_ctl_del = this.eval_libc_i32("EPOLL_CTL_DEL")?; + + if values == epoll_ctl_add || values == epoll_ctl_mod { + let event = this.deref_operand(event)?; + + let events = this.mplace_field(&event, 0)?; + let events = this.read_scalar(&events.into())?.to_u32()?; + let data = this.mplace_field(&event, 1)?; + let data = this.read_scalar(&data.into())?.to_u64()?; + let event = EpollEvent { events, data }; + + if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) { + let epfd = epfd.as_epoll_handle()?; + + epfd.file_descriptors.insert(fd, event); + Ok(0) + } else { + this.handle_not_found() + } + } else if values == epoll_ctl_del { + if let Some(epfd) = this.machine.file_handler.handles.get_mut(&epfd) { + let epfd = epfd.as_epoll_handle()?; + + epfd.file_descriptors.remove(&fd); + Ok(0) + } else { + this.handle_not_found() + } + } else { + let einval = this.eval_libc("EINVAL")?; + this.set_last_error(einval)?; + Ok(-1) + } + } + + fn epoll_wait( + &mut self, + epfd: &OpTy<'tcx, Provenance>, + events: &OpTy<'tcx, Provenance>, + maxevents: &OpTy<'tcx, Provenance>, + timeout: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let _epfd = this.read_scalar(epfd)?.to_i32()?; + let _events = this.read_scalar(events)?; + let maxevents = this.read_scalar(maxevents)?.to_i32()?; + let _timeout = this.read_scalar(timeout)?.to_i32()?; + + if maxevents <= 0 { + throw_ub_format!("maxevents must be greater than 0"); + } + + throw_ub_format!("closed due to not writing"); + } + + fn eventfd( + &mut self, + val: &OpTy<'tcx, Provenance>, + flags: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let val = this.read_scalar(val)?.to_u32()?; + let flags = this.read_scalar(flags)?.to_i32()?; + + let efd_cloexec = this.eval_libc_i32("EFD_CLOEXEC")?; + let efd_nonblock = this.eval_libc_i32("EFD_NONBLOCK")?; + let efd_semaphore = this.eval_libc_i32("EFD_SEMAPHORE")?; + + if flags & (efd_cloexec | efd_nonblock | efd_semaphore) == 0 { + throw_unsup_format!("{flags} is unsupported"); + } + // FIXME handle the cloexec and nonblock flags + if flags & efd_cloexec != 0 {} + if flags & efd_nonblock != 0 {} + if flags & efd_semaphore != 0 { + throw_unsup_format!("EFD_SEMAPHORE is unsupported"); + } + + let fh = &mut this.machine.file_handler; + let fd = fh.insert_fd(Box::new(Event { val })); + Ok(fd) + } + + fn socketpair( + &mut self, + _domain: &OpTy<'tcx, Provenance>, + type_: &OpTy<'tcx, Provenance>, + _protocol: &OpTy<'tcx, Provenance>, + sv: &OpTy<'tcx, Provenance>, + ) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + + let _flags = this.read_scalar(type_)?.to_i32()?; + let sv = this.deref_operand(sv)?; + + let fh = &mut this.machine.file_handler; + let sv0 = fh.insert_fd(Box::new(SocketPair)); + let sv0 = ScalarInt::try_from_int(sv0, sv.layout.size).unwrap(); + let sv1 = fh.insert_fd(Box::new(SocketPair)); + let sv1 = ScalarInt::try_from_int(sv1, sv.layout.size).unwrap(); + + this.write_scalar(sv0, &sv.into())?; + this.write_scalar(sv1, &sv.offset(sv.layout.size, sv.layout, this)?.into())?; + + Ok(0) + } + + fn libc_current_sigrtmax(&mut self) -> InterpResult<'tcx, i32> { + let _this = self.eval_context_mut(); + + Ok(SIGRTMAX) + } +} diff --git a/src/shims/unix/linux/fs/epoll.rs b/src/shims/unix/linux/fs/epoll.rs new file mode 100644 index 0000000000..d7eee0752f --- /dev/null +++ b/src/shims/unix/linux/fs/epoll.rs @@ -0,0 +1,42 @@ +use crate::*; + +use crate::shims::unix::fs::FileDescriptor; + +use rustc_data_structures::fx::FxHashMap; +use std::io; + +#[derive(Clone, Debug, Default)] +pub struct Epoll { + pub file_descriptors: FxHashMap, +} + +#[derive(Clone, Debug)] +pub struct EpollEvent { + pub events: u32, + pub data: u64, +} + +impl FileDescriptor for Epoll { + fn name(&self) -> &'static str { + "epoll" + } + + fn as_epoll_handle<'tcx>(&mut self) -> InterpResult<'tcx, &mut Epoll> { + Ok(self) + } + + fn dup<'tcx>(&mut self) -> io::Result> { + Ok(Box::new(self.clone())) + } + + fn is_tty(&self) -> bool { + false + } + + fn close<'tcx>( + self: Box, + _communicate_allowed: bool, + ) -> InterpResult<'tcx, io::Result> { + Ok(Ok(0)) + } +} diff --git a/src/shims/unix/linux/fs/event.rs b/src/shims/unix/linux/fs/event.rs new file mode 100644 index 0000000000..1241576701 --- /dev/null +++ b/src/shims/unix/linux/fs/event.rs @@ -0,0 +1,39 @@ +use crate::shims::unix::fs::FileDescriptor; + +use rustc_const_eval::interpret::InterpResult; + +use std::io; + +#[derive(Debug)] +pub struct Event { + pub val: u32, +} + +impl FileDescriptor for Event { + fn name(&self) -> &'static str { + "event" + } + + fn dup<'tcx>(&mut self) -> io::Result> { + Ok(Box::new(Event { val: self.val })) + } + + fn is_tty(&self) -> bool { + false + } + + fn write<'tcx>( + &self, + _communicate_allowed: bool, + bytes: &[u8], + ) -> InterpResult<'tcx, io::Result> { + throw_unsup_format!("cannot write to {}, {:?}", self.name(), bytes); + } + + fn close<'tcx>( + self: Box, + _communicate_allowed: bool, + ) -> InterpResult<'tcx, io::Result> { + Ok(Ok(0)) + } +} diff --git a/src/shims/unix/linux/fs/socketpair.rs b/src/shims/unix/linux/fs/socketpair.rs new file mode 100644 index 0000000000..c9c75b3295 --- /dev/null +++ b/src/shims/unix/linux/fs/socketpair.rs @@ -0,0 +1,29 @@ +use crate::*; + +use crate::shims::unix::fs::FileDescriptor; + +use std::io; + +#[derive(Debug)] +pub struct SocketPair; + +impl FileDescriptor for SocketPair { + fn name(&self) -> &'static str { + "socketpair" + } + + fn dup<'tcx>(&mut self) -> io::Result> { + Ok(Box::new(SocketPair)) + } + + fn is_tty(&self) -> bool { + false + } + + fn close<'tcx>( + self: Box, + _communicate_allowed: bool, + ) -> InterpResult<'tcx, io::Result> { + Ok(Ok(0)) + } +} diff --git a/src/shims/unix/linux/mod.rs b/src/shims/unix/linux/mod.rs index 498eb57c57..162dfd0e11 100644 --- a/src/shims/unix/linux/mod.rs +++ b/src/shims/unix/linux/mod.rs @@ -1,3 +1,4 @@ pub mod dlsym; pub mod foreign_items; +pub mod fs; pub mod sync; diff --git a/tests/fail/crates/tokio_mvp.rs b/tests/fail/crates/tokio_mvp.rs deleted file mode 100644 index 7cb42c09a9..0000000000 --- a/tests/fail/crates/tokio_mvp.rs +++ /dev/null @@ -1,7 +0,0 @@ -//@compile-flags: -Zmiri-disable-isolation -//@error-pattern: can't call foreign function: epoll_create1 -//@normalize-stderr-test: " = note: inside .*\n" -> "" -//@only-target-linux: the errors differ too much between platforms - -#[tokio::main] -async fn main() {} diff --git a/tests/fail/crates/tokio_mvp.stderr b/tests/fail/crates/tokio_mvp.stderr deleted file mode 100644 index 016081d90a..0000000000 --- a/tests/fail/crates/tokio_mvp.stderr +++ /dev/null @@ -1,19 +0,0 @@ -error: unsupported operation: can't call foreign function: epoll_create1 - --> CARGO_REGISTRY/.../epoll.rs:LL:CC - | -LL | syscall!(epoll_create1(flag)).map(|ep| Selector { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ can't call foreign function: epoll_create1 - | - = help: this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support - = note: BACKTRACE: -note: inside `main` at $DIR/tokio_mvp.rs:LL:CC - --> $DIR/tokio_mvp.rs:LL:CC - | -LL | #[tokio::main] - | ^^^^^^^^^^^^^^ - = note: this error originates in the macro `syscall` which comes from the expansion of the attribute macro `tokio::main` (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/tests/pass-dep/tokio_mvp.rs b/tests/pass-dep/tokio_mvp.rs new file mode 100644 index 0000000000..79b501aed8 --- /dev/null +++ b/tests/pass-dep/tokio_mvp.rs @@ -0,0 +1,9 @@ +//@compile-flags: -Zmiri-disable-isolation -Zmiri-permissive-provenance +//@only-target-linux: the errors differ too much between platforms + +#[cfg(not(target_arch = "x86_64"))] +fn main() {} + +#[tokio::main] +#[cfg(target_arch = "x86_64")] +async fn main() {}