diff --git a/README.md b/README.md index 6b8d8ed1f8..565c1d8b42 100644 --- a/README.md +++ b/README.md @@ -237,6 +237,7 @@ degree documented below): - `solaris` / `illumos`: maintained by @devnexen. Supports the entire test suite. - `freebsd`: maintained by @YohDeadfall and @LorrensP-2158466. Supports the entire test suite. - `android`: **maintainer wanted**. Supports the entire test suite. + - `netbsd`: maintained by @joboet. Supports the entire test suite. - For targets on other operating systems, Miri might fail before even reaching the `main` function. However, even for targets that we do support, the degree of support for accessing platform APIs @@ -662,6 +663,7 @@ Violations of [Stacked Borrows] found that are likely bugs (but Stacked Borrows * [`VecDeque::iter_mut` creating overlapping mutable references](https://github.com/rust-lang/rust/issues/74029) * [Various standard library aliasing issues involving raw pointers](https://github.com/rust-lang/rust/pull/78602) * [`<[T]>::copy_within` using a loan after invalidating it](https://github.com/rust-lang/rust/pull/85610) +* [`libc` had a wrong definition of `_lwp_park`, causing `std` to pass an immutable pointer because of coercion](https://github.com/rust-lang/libc/pull/5169) ## Scientific papers employing Miri diff --git a/src/concurrency/sync.rs b/src/concurrency/sync.rs index de2e0d8e23..fa774baaa8 100644 --- a/src/concurrency/sync.rs +++ b/src/concurrency/sync.rs @@ -245,6 +245,24 @@ struct FutexWaiter { bitset: u32, } +/// The set of threads that should wake immediately from `thread_park`. +#[derive(Default, Clone, Debug)] +pub struct ThreadTokens { + /// The set of thread tokens, each being associated with the vclock of the + /// thread that added the token to establish a happens-before edge between + /// `thread_unpark` and `thread_park`. + tokens: FxHashMap>, +} + +/// The result of a `thread_park`. +pub enum ParkResult { + /// The token was already made available, and hence the thread should continue + /// running. + Already, + /// The thread was put to sleep. + Parked, +} + // Private extension trait for local helper methods impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {} pub(super) trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { @@ -830,4 +848,72 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(woken) } + + /// Parks the current thread, to be unparked by `thread_unpark`. + /// + /// If `thread_unpark` is called before `thread_park`, this call will return + /// `ParkResult::Already` and `callback` is never invoked. Otherwise, the + /// current thread is blocked, and `callback` will be invoked upon the next + /// call to `thread_unpark`. + fn thread_park( + &mut self, + deadline: Option, + callback: DynUnblockCallback<'tcx>, + ) -> InterpResult<'tcx, ParkResult> { + let this = self.eval_context_mut(); + let thread_id = this.active_thread(); + + if let Some(vclock) = this.machine.thread_tokens.tokens.remove(&thread_id) { + if let Some(vclock) = vclock { + this.acquire_clock(&vclock)?; + } + + interp_ok(ParkResult::Already) + } else { + this.block_thread(BlockReason::Park, deadline, + callback!( + @capture<'tcx> { + callback: DynUnblockCallback<'tcx>, + } + |this, unblock: UnblockKind| { + match unblock { + UnblockKind::Ready => { + let thread = this.active_thread(); + let token = this.machine.thread_tokens.tokens.remove(&thread).expect(" + can only be unblocked by thread_unpark, which sets a token + "); + + if let Some(vclock) = token { + this.acquire_clock(&vclock)?; + } + }, + UnblockKind::TimedOut => {} + } + + callback.call(this, unblock) + } + )); + interp_ok(ParkResult::Parked) + } + } + + /// Unparks `thread`. + /// + /// If `thread` is blocked through `thread_unpark`, then unblock it. Otherwise + /// add the threads token to the thread token set, so that the next `thread_park` + /// returns immediately. + fn thread_unpark(&mut self, thread: ThreadId) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let mut vclock = None; + this.release_clock(|c| vclock = Some(c.clone()))?; + this.machine.thread_tokens.tokens.insert(thread, vclock); + + if this.machine.threads.thread_ref(thread).is_blocked_on(&BlockReason::Park) { + // Only unblock threads that are currently parked. + this.unblock_thread(thread, BlockReason::Park)?; + } + + interp_ok(()) + } } diff --git a/src/concurrency/thread.rs b/src/concurrency/thread.rs index 7d9001e73b..468c1d1a47 100644 --- a/src/concurrency/thread.rs +++ b/src/concurrency/thread.rs @@ -105,6 +105,8 @@ pub enum BlockReason { RwLock, /// Blocked on a Futex variable. Futex, + /// Blocked by a thread park operation. + Park, /// Blocked on an InitOnce. InitOnce, /// Blocked on epoll. @@ -228,6 +230,11 @@ impl<'tcx> Thread<'tcx> { self.state.is_enabled() } + /// Return whether this thread is blocked for `reason`. + pub fn is_blocked_on(&self, reason: &BlockReason) -> bool { + self.state.is_blocked_on(reason) + } + /// Get the name of the current thread for display purposes; will include thread ID if not set. fn thread_display_name(&self, id: ThreadId) -> String { if let Some(ref thread_name) = self.thread_name { diff --git a/src/helpers.rs b/src/helpers.rs index 730c7d9fac..9f64b95bf8 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -648,6 +648,26 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(()) } + /// Helper function used inside shims of foreign functions to check that an + /// alias name for a function is used on all `target_oses`, but not anywhere + /// else. It returns a hopefully helpful error message if this is not the case. + fn check_alias_used_on( + &self, + alias: &str, + target_oses: &[Os], + name: Symbol, + ) -> InterpResult<'tcx> { + let target_os = &self.eval_context_ref().tcx.sess.target.os; + let in_list = target_oses.contains(target_os); + let alias_used = name.as_str() == alias; + match (alias_used, in_list) { + (true, true) => interp_ok(()), + (false, false) => interp_ok(()), + (true, false) => throw_unsup_format!("`{name}` is not supported on {target_os}"), + (false, true) => throw_unsup_format!("`{name}` is named `{alias}` on {target_os}"), + } + } + /// Helper function used inside the shims of foreign functions to assert that the target OS /// is part of the UNIX family. It panics showing a message with the `name` of the foreign function /// if this is not the case. diff --git a/src/machine.rs b/src/machine.rs index 80f392363b..1ec2c9e71e 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -36,7 +36,7 @@ use rustc_target::spec::{Arch, Os}; use crate::alloc_addresses::EvalContextExt; use crate::concurrency::cpu_affinity::{self, CpuAffinityMask}; use crate::concurrency::data_race::{self, NaReadType, NaWriteType}; -use crate::concurrency::sync::SyncObj; +use crate::concurrency::sync::{SyncObj, ThreadTokens}; use crate::concurrency::{ AllocDataRaceHandler, GenmcCtx, GenmcEvalContextExt as _, GlobalDataRaceHandler, weak_memory, }; @@ -541,6 +541,9 @@ pub struct MiriMachine<'tcx> { /// The set of threads. pub(crate) threads: ThreadManager<'tcx>, + /// The list of threads that should wake immediately from a `thread_park`. + pub(crate) thread_tokens: ThreadTokens, + /// Handles blocking I/O and polling for completion. pub(crate) blocking_io: BlockingIoManager, @@ -774,6 +777,7 @@ impl<'tcx> MiriMachine<'tcx> { dirs: Default::default(), layouts, threads, + thread_tokens: Default::default(), thread_cpu_affinity, blocking_io, static_roots: Vec::new(), @@ -1020,6 +1024,7 @@ impl VisitProvenance for MiriMachine<'_> { #[rustfmt::skip] let MiriMachine { threads, + thread_tokens: _, thread_cpu_affinity: _, tls, env_vars, diff --git a/src/provenance_gc.rs b/src/provenance_gc.rs index c292f764d6..29a6ba6894 100644 --- a/src/provenance_gc.rs +++ b/src/provenance_gc.rs @@ -19,7 +19,7 @@ macro_rules! no_provenance { )+ } } -no_provenance!(i8 i16 i32 i64 isize u8 u16 u32 u64 usize bool ThreadId Deadline); +no_provenance!(i8 i16 i32 i64 isize u8 u16 u32 u64 usize bool ThreadId Deadline Instant); impl VisitProvenance for &'static str { fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {} diff --git a/src/shims/time.rs b/src/shims/time.rs index acc0d5bc88..6b8f8e82f6 100644 --- a/src/shims/time.rs +++ b/src/shims/time.rs @@ -5,6 +5,7 @@ use std::time::{Duration, SystemTime}; use chrono::{DateTime, Datelike, Offset, Timelike, Utc}; use chrono_tz::Tz; +use rustc_middle::ty::ScalarInt; use rustc_target::spec::Os; use crate::*; @@ -483,6 +484,31 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { }) } + /// Formats `duration` as a `timespec` and writes it to `tp`. + /// + /// This will stop the machine if the duration is too large to fit in a + /// `timespec` (which should never happen for realistic durations). + fn write_timespec(&mut self, duration: Duration, tp: &MPlaceTy<'tcx>) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let sec_field = this.project_field_named(tp, "tv_sec")?; + let Some(secs) = i64::try_from(duration.as_secs()) + .ok() + .and_then(|secs| ScalarInt::try_from_int(secs, sec_field.layout.size)) + else { + throw_machine_stop!(TerminationInfo::Abort( + "tried to write a very large duration".to_owned() + )); + }; + this.write_scalar(secs, &sec_field)?; + + let nsec_field = this.project_field_named(tp, "tv_nsec")?; + let nsecs = Scalar::from_uint(duration.subsec_nanos(), nsec_field.layout.size); + this.write_scalar(nsecs, &nsec_field)?; + + interp_ok(()) + } + /// Parse a `timeval` struct and return it as a [`Duration`]. It returns [`None`] /// if the value in the `timeval` struct is invalid. Some libc functions will return /// EINVAL in this case. diff --git a/src/shims/unix/foreign_items.rs b/src/shims/unix/foreign_items.rs index c2ae65091e..3d1e923844 100644 --- a/src/shims/unix/foreign_items.rs +++ b/src/shims/unix/foreign_items.rs @@ -12,6 +12,7 @@ use self::shims::unix::android::foreign_items as android; use self::shims::unix::freebsd::foreign_items as freebsd; use self::shims::unix::linux::foreign_items as linux; use self::shims::unix::macos::foreign_items as macos; +use self::shims::unix::netbsd::foreign_items as netbsd; use self::shims::unix::solarish::foreign_items as solarish; use crate::concurrency::cpu_affinity::CpuAffinityMask; use crate::shims::alloc::EvalContextExt as _; @@ -34,6 +35,7 @@ pub fn is_dyn_sym(name: &str, target_os: &Os) -> bool { Os::FreeBsd => freebsd::is_dyn_sym(name), Os::Linux => linux::is_dyn_sym(name), Os::MacOs => macos::is_dyn_sym(name), + Os::NetBsd => netbsd::is_dyn_sym(name), Os::Solaris | Os::Illumos => solarish::is_dyn_sym(name), _ => false, }, @@ -49,7 +51,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let name = this.read_scalar(val)?.to_i32()?; // FIXME: Which of these are POSIX, and which are GNU/Linux? // At least the names seem to all also exist on macOS. - let sysconfs: &[(&str, fn(&MiriInterpCx<'_>) -> Scalar)] = &[ + let common_sysconfs: &[(&str, fn(&MiriInterpCx<'_>) -> Scalar)] = &[ ("_SC_PAGESIZE", |this| Scalar::from_int(this.machine.page_size, this.pointer_size())), ("_SC_PAGE_SIZE", |this| Scalar::from_int(this.machine.page_size, this.pointer_size())), ("_SC_NPROCESSORS_CONF", |this| { @@ -67,7 +69,22 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // The spec imposes a minimum of `_POSIX_OPEN_MAX` (20). ("_SC_OPEN_MAX", |this| Scalar::from_int(2_i32.pow(16), this.pointer_size())), ]; - for &(sysconf_name, value) in sysconfs { + + let os_sysconfs: &[(&str, fn(&MiriInterpCx<'_>) -> Scalar)] = match this.tcx.sess.target.os + { + // While this constant is in the UNIX standard, it isn't implemented on GNU/Linux. + // So, only support it where we use it. + // + // NetBSD uses the page size as minimum, see + // https://github.com/NetBSD/src/blob/a024c3d4c2b19510732467992412cf6e07ab0b6d/lib/libc/gen/sysconf.c#L414. + Os::NetBsd => + &[("_SC_THREAD_STACK_MIN", |this| { + Scalar::from_int(this.machine.page_size, this.pointer_size()) + })], + _ => &[], + }; + + for &(sysconf_name, value) in common_sysconfs.iter().chain(os_sysconfs) { let sysconf_name = this.eval_libc_i32(sysconf_name); if sysconf_name == name { return interp_ok(value(this)); @@ -132,7 +149,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.getenv(name)?; this.write_pointer(result, dest)?; } - "unsetenv" => { + "unsetenv" | "__unsetenv13" => { + this.check_alias_used_on("__unsetenv13", &[Os::NetBsd], link_name)?; + let [name] = this.check_shim_sig( shim_sig!(extern "C" fn(*const _) -> i32), link_name, @@ -343,7 +362,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "flock" => { // Currently this function does not exist on all Unixes, e.g. on Solaris. this.check_target_os( - &[Os::Linux, Os::Android, Os::FreeBsd, Os::MacOs, Os::Illumos], + &[Os::Linux, Os::Android, Os::FreeBsd, Os::MacOs, Os::Illumos, Os::NetBsd], link_name, )?; @@ -406,17 +425,23 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.linkat(oldfd, oldpath, newfd, newpath, flags)?; this.write_scalar(result, dest)?; } - "fstat" => { + "fstat" | "__fstat50" => { + this.check_alias_used_on("__fstat50", &[Os::NetBsd], link_name)?; + let [fd, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let result = this.fstat(fd, buf)?; this.write_scalar(result, dest)?; } - "lstat" => { + "lstat" | "__lstat50" => { + this.check_alias_used_on("__lstat50", &[Os::NetBsd], link_name)?; + let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let result = this.lstat(path, buf)?; this.write_scalar(result, dest)?; } - "stat" => { + "stat" | "__stat50" => { + this.check_alias_used_on("__stat50", &[Os::NetBsd], link_name)?; + let [path, buf] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let result = this.stat(path, buf)?; this.write_scalar(result, dest)?; @@ -474,7 +499,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.rmdir(path)?; this.write_scalar(result, dest)?; } - "opendir" => { + "opendir" | "__opendir30" => { + this.check_alias_used_on("__opendir30", &[Os::NetBsd], link_name)?; + let [name] = this.check_shim_sig( shim_sig!(extern "C" fn(*const _) -> *mut _), link_name, @@ -498,6 +525,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let [dirp] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; this.readdir(dirp, dest)?; } + "readdir_r" | "readdir_r$INODE64" | "__readdir_r30" => { + this.check_target_os(&[Os::MacOs, Os::NetBsd], link_name)?; + + this.check_alias_used_on("__readdir_r30", &[Os::NetBsd], link_name)?; + // FIXME: check that readdir_r$INODE64 is only used on Intel macOS. + + let [dirp, entry, result] = + this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + let result = this.readdir_r(dirp, entry, result)?; + this.write_scalar(result, dest)?; + } "lseek" => { // FIXME: This does not have a direct test (#3179). let [fd, offset, whence] = this.check_shim_sig( @@ -639,7 +677,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "pipe2" => { // Currently this function does not exist on all Unixes, e.g. on macOS. this.check_target_os( - &[Os::Linux, Os::Android, Os::FreeBsd, Os::Solaris, Os::Illumos], + &[Os::Linux, Os::Android, Os::FreeBsd, Os::Solaris, Os::Illumos, Os::NetBsd], link_name, )?; @@ -654,7 +692,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } // Network sockets - "socket" => { + "socket" | "__socket30" => { + this.check_alias_used_on("__socket30", &[Os::NetBsd], link_name)?; + let [domain, type_, protocol] = this.check_shim_sig( shim_sig!(extern "C" fn(i32, i32, i32) -> i32), link_name, @@ -821,7 +861,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.localtime_r(timep, result_op)?; this.write_pointer(result, dest)?; } - "clock_gettime" => { + "clock_gettime" | "__clock_gettime50" => { + this.check_alias_used_on("__clock_gettime50", &[Os::NetBsd], link_name)?; + let [clk_id, tp] = this.check_shim_sig( shim_sig!(extern "C" fn(libc::clockid_t, *mut _) -> i32), link_name, @@ -1116,7 +1158,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let res = this.pthread_self()?; this.write_scalar(res, dest)?; } - "sched_yield" => { + "sched_yield" | "__libc_thr_yield" => { + this.check_alias_used_on("__libc_thr_yield", &[Os::NetBsd], link_name)?; + // FIXME: This does not have a direct test (#3179). let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; this.sched_yield()?; @@ -1131,7 +1175,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { "clock_nanosleep" => { // Currently this function does not exist on all Unixes, e.g. on macOS. this.check_target_os( - &[Os::FreeBsd, Os::Linux, Os::Android, Os::Solaris, Os::Illumos], + &[Os::FreeBsd, Os::Linux, Os::Android, Os::Solaris, Os::Illumos, Os::NetBsd], link_name, )?; @@ -1269,10 +1313,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_null(dest)?; } "getentropy" => { - // This function is non-standard but exists with the same signature and behavior on - // Linux, macOS, FreeBSD and Solaris/Illumos. + // This function is in the POSIX standard, but does not exist + // everywhere yet. this.check_target_os( - &[Os::Linux, Os::MacOs, Os::FreeBsd, Os::Illumos, Os::Solaris, Os::Android], + &[ + Os::Linux, + Os::MacOs, + Os::FreeBsd, + Os::Illumos, + Os::Solaris, + Os::Android, + Os::NetBsd, + ], link_name, )?; @@ -1320,8 +1372,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { } "arc4random_buf" => { // This function is non-standard but exists with the same signature and - // same behavior (eg never fails) on FreeBSD and Solaris/Illumos. - this.check_target_os(&[Os::FreeBsd, Os::Illumos, Os::Solaris], link_name)?; + // same behavior (eg never fails) on FreeBSD, Solaris/Illumos and NetBSD. + this.check_target_os( + &[Os::FreeBsd, Os::Illumos, Os::Solaris, Os::NetBsd], + link_name, + )?; let [ptr, len] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; let ptr = this.read_pointer(ptr)?; @@ -1343,7 +1398,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // `_Unwind_RaiseException` impl in miri should work: // https://github.com/ARM-software/abi-aa/blob/main/ehabi32/ehabi32.rst this.check_target_os( - &[Os::Linux, Os::FreeBsd, Os::Illumos, Os::Solaris, Os::Android, Os::MacOs], + &[ + Os::Linux, + Os::FreeBsd, + Os::Illumos, + Os::Solaris, + Os::Android, + Os::MacOs, + Os::NetBsd, + ], link_name, )?; @@ -1415,8 +1478,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { this.write_null(dest)?; } - "getpwuid_r" | "__posix_getpwuid_r" if this.frame_in_std() => { + "getpwuid_r" | "__posix_getpwuid_r" | "__getpwuid_r50" if this.frame_in_std() => { // getpwuid_r is the standard name, __posix_getpwuid_r is used on solarish + this.check_alias_used_on( + "__posix_getpwuid_r", + &[Os::Illumos, Os::Solaris], + link_name, + )?; + this.check_alias_used_on("__getpwuid_r50", &[Os::NetBsd], link_name)?; + let [uid, pwd, buf, buflen, result] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; this.check_no_isolation("`getpwuid_r`")?; @@ -1472,6 +1542,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { macos::EvalContextExt::emulate_foreign_item_inner( this, link_name, abi, args, dest, ), + Os::NetBsd => + netbsd::EvalContextExt::emulate_foreign_item_inner( + this, link_name, abi, args, dest, + ), Os::Solaris | Os::Illumos => solarish::EvalContextExt::emulate_foreign_item_inner( this, link_name, abi, args, dest, diff --git a/src/shims/unix/fs.rs b/src/shims/unix/fs.rs index 659125fa33..2ced3dd983 100644 --- a/src/shims/unix/fs.rs +++ b/src/shims/unix/fs.rs @@ -241,6 +241,8 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { // which can be different between the libc used by std and the libc used by everyone else. let buf = this.deref_pointer(buf_op)?; + let netbsd = this.tcx.sess.target.os == Os::NetBsd; + this.write_int_fields_named( &[ ("st_dev", metadata.dev.unwrap_or(0).into()), @@ -251,11 +253,11 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { ("st_gid", metadata.gid.unwrap_or(0).into()), ("st_rdev", 0), ("st_atime", access_sec.into()), - ("st_atime_nsec", access_nsec.into()), + (if netbsd { "st_atimensec" } else { "st_atime_nsec" }, access_nsec.into()), ("st_mtime", modified_sec.into()), - ("st_mtime_nsec", modified_nsec.into()), + (if netbsd { "st_mtimensec" } else { "st_mtime_nsec" }, modified_nsec.into()), ("st_ctime", 0), - ("st_ctime_nsec", 0), + (if netbsd { "st_ctimensec" } else { "st_ctime_nsec" }, 0), ("st_size", metadata.size.into()), ("st_blocks", metadata.blocks.unwrap_or(0).into()), ("st_blksize", metadata.blksize.unwrap_or(0).into()), @@ -263,11 +265,14 @@ trait EvalContextExtPrivate<'tcx>: crate::MiriInterpCxExt<'tcx> { &buf, )?; - if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd) { + if matches!(&this.tcx.sess.target.os, Os::MacOs | Os::FreeBsd | Os::NetBsd) { this.write_int_fields_named( &[ ("st_birthtime", created_sec.into()), - ("st_birthtime_nsec", created_nsec.into()), + ( + if netbsd { "st_birthtimensec" } else { "st_birthtime_nsec" }, + created_nsec.into(), + ), ("st_flags", 0), ("st_gen", 0), ], @@ -652,7 +657,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if !matches!( &this.tcx.sess.target.os, - Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux + Os::MacOs + | Os::FreeBsd + | Os::Solaris + | Os::Illumos + | Os::Android + | Os::Linux + | Os::NetBsd ) { panic!("`stat` should not be called on {}", this.tcx.sess.target.os); } @@ -681,7 +692,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if !matches!( &this.tcx.sess.target.os, - Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Android | Os::Linux + Os::MacOs + | Os::FreeBsd + | Os::Solaris + | Os::Illumos + | Os::Android + | Os::Linux + | Os::NetBsd ) { panic!("`lstat` should not be called on {}", this.tcx.sess.target.os); } @@ -708,7 +725,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { if !matches!( &this.tcx.sess.target.os, - Os::MacOs | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::Linux | Os::Android + Os::MacOs + | Os::FreeBsd + | Os::Solaris + | Os::Illumos + | Os::Linux + | Os::Android + | Os::NetBsd ) { panic!("`fstat` should not be called on {}", this.tcx.sess.target.os); } @@ -1208,7 +1231,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { interp_ok(()) } - fn macos_readdir_r( + fn readdir_r( &mut self, dirp_op: &OpTy<'tcx>, entry_op: &OpTy<'tcx>, @@ -1216,7 +1239,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ) -> InterpResult<'tcx, Scalar> { let this = self.eval_context_mut(); - this.assert_target_os(Os::MacOs, "readdir_r"); + if !matches!(&this.tcx.sess.target.os, Os::MacOs | Os::NetBsd) { + panic!("`readdir_r` should not be called on {}", this.tcx.sess.target.os); + } let dirp = this.read_target_usize(dirp_op)?; let result_place = this.deref_pointer_as(result_op, this.machine.layouts.mut_raw_ptr)?; @@ -1238,7 +1263,8 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // The name is written with write_os_str_to_c_str, while the rest of the // dirent struct is written using write_int_fields. - // For reference, on macOS this looks like: + // For reference: + // On macOS: // pub struct dirent { // pub d_ino: u64, // pub d_seekoff: u64, @@ -1247,6 +1273,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // pub d_type: u8, // pub d_name: [c_char; 1024], // } + // + // On NetBSD: + // pub struct dirent { + // pub d_fileno: ino_t, + // pub d_reclen: u16, + // pub d_namlen: u16, + // pub d_type: u8, + // pub d_name: [c_char; 512], + //} let entry_place = this.deref_pointer_as(entry_op, this.libc_ty_layout("dirent"))?; @@ -1269,11 +1304,24 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { ("d_reclen", entry_place.layout.size.bytes().into()), ("d_namlen", file_name_buf_len.strict_sub(1).into()), ("d_type", dir_entry.d_type.into()), - ("d_ino", dir_entry.ino.into()), - ("d_seekoff", 0), ], &entry_place, )?; + + if this.tcx.sess.target.os == Os::MacOs { + this.write_int_fields_named( + &[("d_ino", dir_entry.ino.into()), ("d_seekoff", 0)], + &entry_place, + )?; + } else if this.tcx.sess.target.os == Os::NetBsd { + this.write_int_fields_named( + &[("d_fileno", dir_entry.ino.into())], + &entry_place, + )?; + } else { + panic!("remember to check for extra fields!"); + } + this.write_scalar(this.read_scalar(entry_op)?, &result_place)?; Scalar::from_i32(0) diff --git a/src/shims/unix/macos/foreign_items.rs b/src/shims/unix/macos/foreign_items.rs index d8146ab7b8..f7a61fb443 100644 --- a/src/shims/unix/macos/foreign_items.rs +++ b/src/shims/unix/macos/foreign_items.rs @@ -66,12 +66,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { let result = this.opendir(name)?; this.write_scalar(result, dest)?; } - "readdir_r" | "readdir_r$INODE64" => { - let [dirp, entry, result] = - this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; - let result = this.macos_readdir_r(dirp, entry, result)?; - this.write_scalar(result, dest)?; - } "realpath$DARWIN_EXTSN" => { let [path, resolved_path] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; diff --git a/src/shims/unix/mod.rs b/src/shims/unix/mod.rs index cae31d47c1..4e2615d7ee 100644 --- a/src/shims/unix/mod.rs +++ b/src/shims/unix/mod.rs @@ -15,6 +15,7 @@ mod freebsd; pub mod linux; mod linux_like; mod macos; +mod netbsd; mod solarish; // All the Unix-specific extension traits diff --git a/src/shims/unix/netbsd/foreign_items.rs b/src/shims/unix/netbsd/foreign_items.rs new file mode 100644 index 0000000000..c10210d340 --- /dev/null +++ b/src/shims/unix/netbsd/foreign_items.rs @@ -0,0 +1,101 @@ +use rustc_abi::CanonAbi; +use rustc_middle::ty::Ty; +use rustc_span::Symbol; +use rustc_target::callconv::FnAbi; + +use super::lwp::EvalContextExt as _; +use crate::shims::unix::ThreadNameResult; +use crate::shims::unix::thread::EvalContextExt as _; +use crate::*; + +// See https://github.com/NetBSD/src/blob/adc9c78bb5681db46effe4b421961463a5156f50/lib/libpthread/pthread.h#L281 +const PTHREAD_MAX_NAMELEN_NP: u64 = 32; + +pub fn is_dyn_sym(_name: &str) -> bool { + false +} + +impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} +pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + fn emulate_foreign_item_inner( + &mut self, + link_name: Symbol, + abi: &FnAbi<'tcx, Ty<'tcx>>, + args: &[OpTy<'tcx>], + dest: &MPlaceTy<'tcx>, + ) -> InterpResult<'tcx, EmulateItemResult> { + let this = self.eval_context_mut(); + match link_name.as_str() { + // Threading + "pthread_setname_np" => { + let [thread, template, arg] = + this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + + let template = this.read_pointer(template)?; + if this.read_c_str(template)? != b"%s" { + throw_unsup_format!( + "`pthread_setname_np` with a non-trivial template is not supported" + ); + } + + let res = match this.pthread_setname_np( + this.read_scalar(thread)?, + this.read_scalar(arg)?, + PTHREAD_MAX_NAMELEN_NP, + /* truncate */ false, + )? { + ThreadNameResult::Ok => Scalar::from_u32(0), + ThreadNameResult::NameTooLong => this.eval_libc("EINVAL"), + ThreadNameResult::ThreadNotFound => this.eval_libc("ESRCH"), + }; + this.write_scalar(res, dest)?; + } + "pthread_getname_np" => { + let [thread, name, len] = + this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + + let res = match this.pthread_getname_np( + this.read_scalar(thread)?, + this.read_scalar(name)?, + this.read_scalar(len)?, + /* truncate*/ true, + )? { + ThreadNameResult::Ok => Scalar::from_u32(0), + ThreadNameResult::NameTooLong => unreachable!(), + ThreadNameResult::ThreadNotFound => this.eval_libc("ESRCH"), + }; + this.write_scalar(res, dest)?; + } + "_lwp_self" => { + let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + let result = this.lwp_self()?; + this.write_scalar(result, dest)?; + } + "___lwp_park60" => { + let [clock_id, flags, ts, unpark, hint, unparkhint] = + this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + this.lwp_park(clock_id, flags, ts, unpark, hint, unparkhint, dest)?; + } + "_lwp_unpark" => { + let [lwp, hint] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + this.lwp_unpark(lwp, hint, dest)?; + } + + // Miscellaneous + "__errno" => { + let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + let errno_place = this.last_error_place()?; + this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?; + } + + // Incomplete shims that we "stub out" just to get pre-main initialization code to work. + // These shims are enabled only when the caller is in the standard library. + "_cpuset_create" if this.frame_in_std() => { + let [] = this.check_shim_sig_lenient(abi, CanonAbi::C, link_name, args)?; + this.write_null(dest)?; + } + _ => return interp_ok(EmulateItemResult::NotSupported), + } + interp_ok(EmulateItemResult::NeedsReturn) + } +} diff --git a/src/shims/unix/netbsd/lwp.rs b/src/shims/unix/netbsd/lwp.rs new file mode 100644 index 0000000000..e6e51a1310 --- /dev/null +++ b/src/shims/unix/netbsd/lwp.rs @@ -0,0 +1,139 @@ +use crate::concurrency::sync::ParkResult; +use crate::*; + +impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {} +pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { + fn lwp_self(&mut self) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + + let thread_id = this.active_thread(); + interp_ok(Scalar::from_u32(thread_id.to_u32())) + } + + /// Implements [`_lwp_park`]. + /// + /// [`_lwp_park`]: https://man.netbsd.org/_lwp_park.2 + fn lwp_park( + &mut self, + clock_id: &OpTy<'tcx>, + flags: &OpTy<'tcx>, + ts: &OpTy<'tcx>, + unpark: &OpTy<'tcx>, + hint: &OpTy<'tcx>, + unparkhint: &OpTy<'tcx>, + dest: &MPlaceTy<'tcx>, + ) -> InterpResult<'tcx> { + let this = self.eval_context_mut(); + + let clock_id = this.read_scalar(clock_id)?.to_i32()?; + let flags = this.read_scalar(flags)?.to_i32()?; + let ts = this.read_pointer(ts)?; + let _hint = this.read_pointer(hint)?; + let _unparkhint = this.read_pointer(unparkhint)?; + + if this.read_scalar(unpark)?.to_u32()? != 0 { + if !this.lwp_unpark(unpark, unparkhint, dest)? { + return interp_ok(()); + } + } + + let (timeout, write_remaining) = if this.ptr_is_null(ts)? { + (None, None) + } else { + if clock_id != this.eval_libc_i32("CLOCK_MONOTONIC") { + throw_unsup_format!("lwp_park: only CLOCK_MONOTONIC is currently supported"); + } + let clock = TimeoutClock::Monotonic; + + let ts = this.ptr_to_mplace(ts, this.libc_ty_layout("timespec")); + let Some(duration) = this.read_timespec(&ts)? else { + this.set_errno_and_return_neg1_i32(LibcError("EINVAL"))?; + return interp_ok(()); + }; + + let (style, write_remaining) = if flags == 0 { + // No flags set, the timespec should be interpreted as a duration + // to sleep for, i.e., a relative time. + let deadline = this.machine.monotonic_clock.now().add_lossy(duration); + (TimeoutStyle::Relative, Some((deadline, ts))) + } else if flags == this.eval_libc_i32("TIMER_ABSTIME") { + // Only flag TIMER_ABSTIME set, the timespec should be interpreted as + // an absolute time. + (TimeoutStyle::Absolute, None) + } else { + throw_unsup_format!( + "`lwp_park` unsupported flags {flags}, only no flags or \ + TIMER_ABSTIME is supported" + ) + }; + + (Some(this.machine.timeout(clock, style, duration)), write_remaining) + }; + + let res = { + let dest = dest.clone(); + this.thread_park( + timeout, + callback!( + @capture<'tcx> { + write_remaining: Option<(Instant, MPlaceTy<'tcx>)>, + ts: Pointer, + dest: MPlaceTy<'tcx>, + } + |this, unblock: UnblockKind| { + if let Some((deadline, return_ts)) = write_remaining { + let remaining = deadline.duration_since(this.machine.monotonic_clock.now()); + this.write_timespec(remaining, &return_ts)?; + } + + match unblock { + UnblockKind::Ready => { + this.write_scalar(Scalar::from_i32(0), &dest) + } + UnblockKind::TimedOut => { + this.set_errno_and_return_neg1(LibcError("ETIMEDOUT"), &dest) + } + } + } + ), + )? + }; + + match res { + ParkResult::Already => this.set_errno_and_return_neg1(LibcError("EALREADY"), dest)?, + ParkResult::Parked => { + // This thread is blocked. `dest` will be filled by the callback + // invoked when it is unblocked. + } + } + + interp_ok(()) + } + + /// Implements [`_lwp_unpark`]. + /// + /// [`_lwp_unpark`]: https://man.netbsd.org/_lwp_unpark.2 + fn lwp_unpark( + &mut self, + lwp: &OpTy<'tcx>, + hint: &OpTy<'tcx>, + dest: &MPlaceTy<'tcx>, + ) -> InterpResult<'tcx, bool> { + let this = self.eval_context_mut(); + + let lwp = this.read_scalar(lwp)?.to_u32()?; + let _hint = this.read_pointer(hint)?; + + let Ok(thread) = this.machine.threads.thread_id_try_from(lwp) else { + this.set_errno_and_return_neg1(LibcError("ESRCH"), dest)?; + return interp_ok(false); + }; + + // FIXME: this is a bit imprecise – `_lwp_park` is also used by NetBSD's + // pthread implementation, which will consume the thread token + // and then continue waiting. + this.thread_unpark(thread)?; + this.write_scalar(Scalar::from_i32(0), dest)?; + interp_ok(true) + } +} diff --git a/src/shims/unix/netbsd/mod.rs b/src/shims/unix/netbsd/mod.rs new file mode 100644 index 0000000000..b8c41c9e4d --- /dev/null +++ b/src/shims/unix/netbsd/mod.rs @@ -0,0 +1,2 @@ +pub mod foreign_items; +pub mod lwp; diff --git a/src/shims/unix/socket.rs b/src/shims/unix/socket.rs index ae882f8ff3..251c50a22b 100644 --- a/src/shims/unix/socket.rs +++ b/src/shims/unix/socket.rs @@ -264,8 +264,10 @@ impl UnixFileDescription for Socket { if op == fionbio { // On these OSes, Rust uses the ioctl, so we trust that it is reasonable and controls // the same internal flag as fcntl. - if !matches!(ecx.tcx.sess.target.os, Os::Linux | Os::Android | Os::MacOs | Os::FreeBsd) - { + if !matches!( + ecx.tcx.sess.target.os, + Os::Linux | Os::Android | Os::MacOs | Os::FreeBsd | Os::NetBsd + ) { // FIONBIO cannot be used to change the blocking mode of a socket on solarish targets: // // Since there might be more targets which do weird things with this option, we use @@ -321,10 +323,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // if there is anything left at the end, that's an unsupported flag. if matches!( this.tcx.sess.target.os, - Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos + Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::NetBsd ) { // SOCK_NONBLOCK and SOCK_CLOEXEC only exist on Linux, Android, FreeBSD, - // Solaris, and Illumos targets. + // Solaris, Illumos and NetBSD targets. let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK"); let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC"); if flags & sock_nonblock == sock_nonblock { @@ -542,10 +544,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // if there is anything left at the end, that's an unsupported flag. if matches!( this.tcx.sess.target.os, - Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos + Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::NetBsd ) { // SOCK_NONBLOCK and SOCK_CLOEXEC only exist on Linux, Android, FreeBSD, - // Solaris, and Illumos targets. + // Solaris, Illumos and NetBSD targets. let sock_nonblock = this.eval_libc_i32("SOCK_NONBLOCK"); let sock_cloexec = this.eval_libc_i32("SOCK_CLOEXEC"); if flags & sock_nonblock == sock_nonblock { @@ -744,10 +746,10 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { // if there is anything left at the end, that's an unsupported flag. if matches!( this.tcx.sess.target.os, - Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos + Os::Linux | Os::Android | Os::FreeBsd | Os::Solaris | Os::Illumos | Os::NetBsd ) { // MSG_NOSIGNAL and MSG_DONTWAIT only exist on Linux, Android, FreeBSD, - // Solaris, and Illumos targets. + // Solaris, Illumos and NetBSD targets. let msg_nosignal = this.eval_libc_i32("MSG_NOSIGNAL"); let msg_dontwait = this.eval_libc_i32("MSG_DONTWAIT"); if flags & msg_nosignal == msg_nosignal { diff --git a/src/shims/unix/sync.rs b/src/shims/unix/sync.rs index 4e351c1571..d1c9624194 100644 --- a/src/shims/unix/sync.rs +++ b/src/shims/unix/sync.rs @@ -35,7 +35,13 @@ const PTHREAD_INIT: u8 = 1; #[inline] fn mutexattr_kind_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, u64> { interp_ok(match &ecx.tcx.sess.target.os { - Os::Linux | Os::Illumos | Os::Solaris | Os::MacOs | Os::FreeBsd | Os::Android => 0, + Os::Linux + | Os::Illumos + | Os::Solaris + | Os::MacOs + | Os::FreeBsd + | Os::Android + | Os::NetBsd => 0, os => throw_unsup_format!("`pthread_mutexattr` is not supported on {os}"), }) } @@ -135,8 +141,8 @@ impl SyncObj for PthreadMutex { fn mutex_init_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, Size> { let offset = match &ecx.tcx.sess.target.os { Os::Linux | Os::Illumos | Os::Solaris | Os::FreeBsd | Os::Android => 0, - // macOS stores a signature in the first bytes, so we move to offset 4. - Os::MacOs => 4, + // macOS and NetBSD store a signature in the first bytes, so we move to offset 4. + Os::MacOs | Os::NetBsd => 4, os => throw_unsup_format!("`pthread_mutex` is not supported on {os}"), }; let offset = Size::from_bytes(offset); @@ -163,7 +169,7 @@ fn mutex_init_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, Size> check_static_initializer("PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP"); check_static_initializer("PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP"); } - Os::Illumos | Os::Solaris | Os::MacOs | Os::FreeBsd | Os::Android => { + Os::Illumos | Os::Solaris | Os::MacOs | Os::FreeBsd | Os::Android | Os::NetBsd => { // No non-standard initializers. } os => throw_unsup_format!("`pthread_mutex` is not supported on {os}"), @@ -331,7 +337,7 @@ where #[inline] fn condattr_clock_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, u64> { interp_ok(match &ecx.tcx.sess.target.os { - Os::Linux | Os::Illumos | Os::Solaris | Os::FreeBsd | Os::Android => 0, + Os::Linux | Os::Illumos | Os::Solaris | Os::FreeBsd | Os::Android | Os::NetBsd => 0, // macOS does not have a clock attribute. os => throw_unsup_format!("`pthread_condattr` clock field is not supported on {os}"), }) @@ -370,8 +376,8 @@ fn condattr_set_clock_id<'tcx>( fn cond_init_offset<'tcx>(ecx: &MiriInterpCx<'tcx>) -> InterpResult<'tcx, Size> { let offset = match &ecx.tcx.sess.target.os { Os::Linux | Os::Illumos | Os::Solaris | Os::FreeBsd | Os::Android => 0, - // macOS stores a signature in the first bytes, so we move to offset 4. - Os::MacOs => 4, + // macOS and NetBSD store a signature in the first bytes, so we move to offset 4. + Os::MacOs | Os::NetBsd => 4, os => throw_unsup_format!("`pthread_cond` is not supported on {os}"), }; let offset = Size::from_bytes(offset);