Add shims for gettid-esque functions
Various platforms provide a function to return the current OS thread ID, but they all use a slightly different name. Add shims for these functions for Apple, FreeBSD, and Windows, with tests to account for those and a few more platforms that are not yet supported by Miri. The syscall and extern symbol is included as well on Linux. These should be useful in general but will also help support printing the OS thread ID in panic messages [1]. [1]: https://github.com/rust-lang/rust/pull/115746 Squashed commit from Ralf: try_from_scalar: extend comment
This commit is contained in:
parent
85357d1e5c
commit
d69b8e2b48
14 changed files with 312 additions and 12 deletions
|
|
@ -1337,7 +1337,6 @@ where
|
|||
|
||||
/// Check that the number of varargs is at least the minimum what we expect.
|
||||
/// Fixed args should not be included.
|
||||
/// Use `check_vararg_fixed_arg_count` to extract the varargs slice from full function arguments.
|
||||
pub fn check_min_vararg_count<'a, 'tcx, const N: usize>(
|
||||
name: &'a str,
|
||||
args: &'a [OpTy<'tcx>],
|
||||
|
|
|
|||
|
|
@ -110,8 +110,30 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the process identifier.
|
||||
fn get_pid(&self) -> u32 {
|
||||
let this = self.eval_context_ref();
|
||||
if this.machine.communicate() { std::process::id() } else { 1000 }
|
||||
}
|
||||
|
||||
/// Get an "OS" thread ID for the current thread.
|
||||
fn get_current_tid(&self) -> u32 {
|
||||
let this = self.eval_context_ref();
|
||||
self.get_tid(this.machine.threads.active_thread())
|
||||
}
|
||||
|
||||
/// Get an "OS" thread ID for any thread.
|
||||
fn get_tid(&self, thread: ThreadId) -> u32 {
|
||||
let this = self.eval_context_ref();
|
||||
let index = thread.to_u32();
|
||||
let target_os = &this.tcx.sess.target.os;
|
||||
if target_os == "linux" || target_os == "netbsd" {
|
||||
// On Linux, the main thread has PID == TID so we uphold this. NetBSD also appears
|
||||
// to exhibit the same behavior, though I can't find a citation.
|
||||
this.get_pid().strict_add(index)
|
||||
} else {
|
||||
// Other platforms do not display any relationship between PID and TID.
|
||||
index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ impl<'tcx> MiriMachine<'tcx> {
|
|||
ecx,
|
||||
&["__cxa_thread_atexit_impl", "__clock_gettime64"],
|
||||
)?;
|
||||
Self::weak_symbol_extern_statics(ecx, &["getrandom", "statx"])?;
|
||||
Self::weak_symbol_extern_statics(ecx, &["getrandom", "gettid", "statx"])?;
|
||||
}
|
||||
"freebsd" => {
|
||||
Self::null_ptr_extern_statics(ecx, &["__cxa_thread_atexit_impl"])?;
|
||||
|
|
|
|||
|
|
@ -274,15 +274,52 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
interp_ok(Scalar::from_u32(this.get_pid()))
|
||||
}
|
||||
|
||||
fn linux_gettid(&mut self) -> InterpResult<'tcx, Scalar> {
|
||||
/// The `gettid`-like function for Unix platforms that take no parameters and return a 32-bit
|
||||
/// integer. It is not always named "gettid".
|
||||
fn unix_gettid(&mut self, link_name: &str) -> InterpResult<'tcx, Scalar> {
|
||||
let this = self.eval_context_ref();
|
||||
this.assert_target_os("linux", "gettid");
|
||||
this.assert_target_os_is_unix(link_name);
|
||||
|
||||
let index = this.machine.threads.active_thread().to_u32();
|
||||
// For most platforms the return type is an `i32`, but some are unsigned. The TID
|
||||
// will always be positive so we don't need to differentiate.
|
||||
interp_ok(Scalar::from_u32(this.get_current_tid()))
|
||||
}
|
||||
|
||||
// Compute a TID for this thread, ensuring that the main thread has PID == TID.
|
||||
let tid = this.get_pid().strict_add(index);
|
||||
/// The Apple-specific `int pthread_threadid_np(pthread_t thread, uint64_t *thread_id)`, which
|
||||
/// allows querying the ID for arbitrary threads, identified by their pthread_t.
|
||||
///
|
||||
/// API documentation: <https://www.manpagez.com/man/3/pthread_threadid_np/>.
|
||||
fn apple_pthread_threadip_np(
|
||||
&mut self,
|
||||
thread_op: &OpTy<'tcx>,
|
||||
tid_op: &OpTy<'tcx>,
|
||||
) -> InterpResult<'tcx, Scalar> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os("macos", "pthread_threadip_np");
|
||||
|
||||
interp_ok(Scalar::from_u32(tid))
|
||||
let tid_dest = this.read_pointer(tid_op)?;
|
||||
if this.ptr_is_null(tid_dest)? {
|
||||
// If NULL is passed, an error is immediately returned
|
||||
return interp_ok(this.eval_libc("EINVAL"));
|
||||
}
|
||||
|
||||
let thread = this.read_scalar(thread_op)?.to_int(this.libc_ty_layout("pthread_t").size)?;
|
||||
let thread = if thread == 0 {
|
||||
// Null thread ID indicates that we are querying the active thread.
|
||||
this.machine.threads.active_thread()
|
||||
} else {
|
||||
// Our pthread_t is just the raw ThreadId.
|
||||
let Ok(thread) = this.thread_id_try_from(thread) else {
|
||||
return interp_ok(this.eval_libc("ESRCH"));
|
||||
};
|
||||
thread
|
||||
};
|
||||
|
||||
let tid = this.get_tid(thread);
|
||||
let tid_dest = this.deref_pointer_as(tid_op, this.machine.layouts.u64)?;
|
||||
this.write_int(tid, &tid_dest)?;
|
||||
|
||||
// Possible errors have been handled, return success.
|
||||
interp_ok(Scalar::from_u32(0))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
};
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
"pthread_getthreadid_np" => {
|
||||
let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
|
||||
let result = this.unix_gettid(link_name.as_str())?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
|
||||
"cpuset_getaffinity" => {
|
||||
// The "same" kind of api as `sched_getaffinity` but more fine grained control for FreeBSD specifically.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use crate::*;
|
|||
const TASK_COMM_LEN: u64 = 16;
|
||||
|
||||
pub fn is_dyn_sym(name: &str) -> bool {
|
||||
matches!(name, "statx")
|
||||
matches!(name, "gettid" | "statx")
|
||||
}
|
||||
|
||||
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
|
||||
|
|
@ -117,7 +117,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
}
|
||||
"gettid" => {
|
||||
let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
|
||||
let result = this.linux_gettid()?;
|
||||
let result = this.unix_gettid(link_name.as_str())?;
|
||||
this.write_scalar(result, dest)?;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use rustc_span::Symbol;
|
|||
use rustc_target::callconv::FnAbi;
|
||||
|
||||
use crate::helpers::check_min_vararg_count;
|
||||
use crate::shims::unix::env::EvalContextExt;
|
||||
use crate::shims::unix::linux_like::eventfd::EvalContextExt as _;
|
||||
use crate::shims::unix::linux_like::sync::futex;
|
||||
use crate::*;
|
||||
|
|
@ -24,6 +25,7 @@ pub fn syscall<'tcx>(
|
|||
let sys_getrandom = ecx.eval_libc("SYS_getrandom").to_target_usize(ecx)?;
|
||||
let sys_futex = ecx.eval_libc("SYS_futex").to_target_usize(ecx)?;
|
||||
let sys_eventfd2 = ecx.eval_libc("SYS_eventfd2").to_target_usize(ecx)?;
|
||||
let sys_gettid = ecx.eval_libc("SYS_gettid").to_target_usize(ecx)?;
|
||||
|
||||
match ecx.read_target_usize(op)? {
|
||||
// `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)`
|
||||
|
|
@ -53,6 +55,10 @@ pub fn syscall<'tcx>(
|
|||
let result = ecx.eventfd(initval, flags)?;
|
||||
ecx.write_int(result.to_i32()?, dest)?;
|
||||
}
|
||||
num if num == sys_gettid => {
|
||||
let result = ecx.unix_gettid("SYS_gettid")?;
|
||||
ecx.write_int(result.to_u32()?, dest)?;
|
||||
}
|
||||
num => {
|
||||
throw_unsup_format!("syscall: unsupported syscall number {num}");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -222,6 +222,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
};
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
"pthread_threadid_np" => {
|
||||
let [thread, tid_ptr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
|
||||
let res = this.apple_pthread_threadip_np(thread, tid_ptr)?;
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
|
||||
// Synchronization primitives
|
||||
"os_sync_wait_on_address" => {
|
||||
|
|
|
|||
|
|
@ -629,6 +629,23 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
|
|||
this.write_scalar(name, &name_ptr)?;
|
||||
this.write_scalar(res, dest)?;
|
||||
}
|
||||
"GetThreadId" => {
|
||||
let [handle] = this.check_shim(abi, sys_conv, link_name, args)?;
|
||||
let handle = this.read_handle(handle, "GetThreadId")?;
|
||||
let thread = match handle {
|
||||
Handle::Thread(thread) => thread,
|
||||
Handle::Pseudo(PseudoHandle::CurrentThread) => this.active_thread(),
|
||||
_ => this.invalid_handle("GetThreadDescription")?,
|
||||
};
|
||||
let tid = this.get_tid(thread);
|
||||
this.write_scalar(Scalar::from_u32(tid), dest)?;
|
||||
}
|
||||
"GetCurrentThreadId" => {
|
||||
let [] = this.check_shim(abi, sys_conv, link_name, args)?;
|
||||
let thread = this.active_thread();
|
||||
let tid = this.get_tid(thread);
|
||||
this.write_scalar(Scalar::from_u32(tid), dest)?;
|
||||
}
|
||||
|
||||
// Miscellaneous
|
||||
"ExitProcess" => {
|
||||
|
|
|
|||
|
|
@ -166,6 +166,10 @@ impl Handle {
|
|||
/// Structurally invalid handles return [`HandleError::InvalidHandle`].
|
||||
/// If the handle is structurally valid but semantically invalid, e.g. a for non-existent thread
|
||||
/// ID, returns [`HandleError::ThreadNotFound`].
|
||||
///
|
||||
/// This function is deliberately private; shims should always use `read_handle`.
|
||||
/// That enforces handle validity even when Windows does not: for now, we argue invalid
|
||||
/// handles are always a bug and programmers likely want to know about them.
|
||||
fn try_from_scalar<'tcx>(
|
||||
handle: Scalar,
|
||||
cx: &MiriInterpCx<'tcx>,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
//! Ensure we error if thread functions are called with invalid handles
|
||||
//@only-target: windows # testing Windows API
|
||||
|
||||
use windows_sys::Win32::System::Threading::GetThreadId;
|
||||
|
||||
fn main() {
|
||||
let _tid = unsafe { GetThreadId(std::ptr::dangling_mut()) };
|
||||
//~^ ERROR: invalid handle
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
error: abnormal termination: invalid handle 1 passed to GetThreadId
|
||||
--> tests/fail-dep/concurrency/windows_thread_invalid.rs:LL:CC
|
||||
|
|
||||
LL | let _tid = unsafe { GetThreadId(std::ptr::dangling_mut()) };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ abnormal termination occurred here
|
||||
|
|
||||
= note: BACKTRACE:
|
||||
= note: inside `main` at tests/fail-dep/concurrency/windows_thread_invalid.rs:LL:CC
|
||||
|
||||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
|
||||
|
||||
error: aborting due to 1 previous error
|
||||
|
||||
183
src/tools/miri/tests/pass-dep/shims/gettid.rs
Normal file
183
src/tools/miri/tests/pass-dep/shims/gettid.rs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
//! Test for `gettid` and similar functions for retrieving an OS thread ID.
|
||||
//@ revisions: with_isolation without_isolation
|
||||
//@ [without_isolation] compile-flags: -Zmiri-disable-isolation
|
||||
|
||||
#![feature(linkage)]
|
||||
|
||||
fn gettid() -> u64 {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(target_os = "android", target_os = "linux"))] {
|
||||
gettid_linux_like()
|
||||
} else if #[cfg(target_os = "nto")] {
|
||||
unsafe { libc::gettid() as u64 }
|
||||
} else if #[cfg(target_os = "openbsd")] {
|
||||
unsafe { libc::getthrid() as u64 }
|
||||
} else if #[cfg(target_os = "freebsd")] {
|
||||
unsafe { libc::pthread_getthreadid_np() as u64 }
|
||||
} else if #[cfg(target_os = "netbsd")] {
|
||||
unsafe { libc::_lwp_self() as u64 }
|
||||
} else if #[cfg(any(target_os = "solaris", target_os = "illumos"))] {
|
||||
// On Solaris and Illumos, the `pthread_t` is the OS TID.
|
||||
unsafe { libc::pthread_self() as u64 }
|
||||
} else if #[cfg(target_vendor = "apple")] {
|
||||
let mut id = 0u64;
|
||||
let status: libc::c_int = unsafe { libc::pthread_threadid_np(0, &mut id) };
|
||||
assert_eq!(status, 0);
|
||||
id
|
||||
} else if #[cfg(windows)] {
|
||||
use windows_sys::Win32::System::Threading::GetCurrentThreadId;
|
||||
unsafe { GetCurrentThreadId() as u64 }
|
||||
} else {
|
||||
compile_error!("platform has no gettid")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test the libc function, the syscall, and the extern symbol.
|
||||
#[cfg(any(target_os = "android", target_os = "linux"))]
|
||||
fn gettid_linux_like() -> u64 {
|
||||
unsafe extern "C" {
|
||||
#[linkage = "extern_weak"]
|
||||
static gettid: Option<unsafe extern "C" fn() -> libc::pid_t>;
|
||||
}
|
||||
|
||||
let from_libc = unsafe { libc::gettid() as u64 };
|
||||
let from_sys = unsafe { libc::syscall(libc::SYS_gettid) as u64 };
|
||||
let from_static = unsafe { gettid.unwrap()() as u64 };
|
||||
|
||||
assert_eq!(from_libc, from_sys);
|
||||
assert_eq!(from_libc, from_static);
|
||||
|
||||
from_libc
|
||||
}
|
||||
|
||||
/// Specific platforms can query the tid of arbitrary threads from a `pthread_t` / `HANDLE`
|
||||
#[cfg(any(target_vendor = "apple", windows))]
|
||||
mod queried {
|
||||
use std::ffi::c_void;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::{ptr, thread};
|
||||
|
||||
use super::*;
|
||||
|
||||
static SPAWNED_TID: AtomicU64 = AtomicU64::new(0);
|
||||
static CAN_JOIN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Save this thread's TID, give the spawning thread a chance to query it separately before
|
||||
/// being joined.
|
||||
fn thread_body() {
|
||||
SPAWNED_TID.store(gettid(), Ordering::Relaxed);
|
||||
|
||||
// Spin until the main thread has a chance to read this thread's ID
|
||||
while !CAN_JOIN.load(Ordering::Relaxed) {
|
||||
thread::yield_now();
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a thread, query then return its TID.
|
||||
#[cfg(target_vendor = "apple")]
|
||||
fn spawn_update_join() -> u64 {
|
||||
extern "C" fn thread_start(_data: *mut c_void) -> *mut c_void {
|
||||
thread_body();
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
let mut t: libc::pthread_t = 0;
|
||||
let mut spawned_tid_from_handle = 0u64;
|
||||
|
||||
unsafe {
|
||||
let res = libc::pthread_create(&mut t, ptr::null(), thread_start, ptr::null_mut());
|
||||
assert_eq!(res, 0, "failed to spawn thread");
|
||||
|
||||
let res = libc::pthread_threadid_np(t, &mut spawned_tid_from_handle);
|
||||
assert_eq!(res, 0, "failed to query thread ID");
|
||||
CAN_JOIN.store(true, Ordering::Relaxed);
|
||||
|
||||
let res = libc::pthread_join(t, ptr::null_mut());
|
||||
assert_eq!(res, 0, "failed to join thread");
|
||||
|
||||
// Apple also has two documented return values for invalid threads and null pointers
|
||||
let res = libc::pthread_threadid_np(libc::pthread_t::MAX, &mut 0);
|
||||
assert_eq!(res, libc::ESRCH, "expected ESRCH for invalid TID");
|
||||
let res = libc::pthread_threadid_np(0, ptr::null_mut());
|
||||
assert_eq!(res, libc::EINVAL, "invalid EINVAL for a null pointer");
|
||||
}
|
||||
|
||||
spawned_tid_from_handle
|
||||
}
|
||||
|
||||
/// Spawn a thread, query then return its TID.
|
||||
#[cfg(windows)]
|
||||
fn spawn_update_join() -> u64 {
|
||||
use windows_sys::Win32::Foundation::WAIT_FAILED;
|
||||
use windows_sys::Win32::System::Threading::{
|
||||
CreateThread, GetThreadId, INFINITE, WaitForSingleObject,
|
||||
};
|
||||
|
||||
extern "system" fn thread_start(_data: *mut c_void) -> u32 {
|
||||
thread_body();
|
||||
0
|
||||
}
|
||||
|
||||
let spawned_tid_from_handle;
|
||||
let mut tid_at_spawn = 0u32;
|
||||
|
||||
unsafe {
|
||||
let handle =
|
||||
CreateThread(ptr::null(), 0, Some(thread_start), ptr::null(), 0, &mut tid_at_spawn);
|
||||
assert!(!handle.is_null(), "failed to spawn thread");
|
||||
|
||||
spawned_tid_from_handle = GetThreadId(handle);
|
||||
assert_ne!(spawned_tid_from_handle, 0, "failed to query thread ID");
|
||||
CAN_JOIN.store(true, Ordering::Relaxed);
|
||||
|
||||
let res = WaitForSingleObject(handle, INFINITE);
|
||||
assert_ne!(res, WAIT_FAILED, "failed to join thread");
|
||||
}
|
||||
|
||||
// Windows also indirectly returns the TID from `CreateThread`, ensure that matches up.
|
||||
assert_eq!(spawned_tid_from_handle, tid_at_spawn);
|
||||
|
||||
spawned_tid_from_handle.into()
|
||||
}
|
||||
|
||||
pub fn check() {
|
||||
let spawned_tid_from_handle = spawn_update_join();
|
||||
let spawned_tid_from_self = SPAWNED_TID.load(Ordering::Relaxed);
|
||||
let current_tid = gettid();
|
||||
|
||||
// Ensure that we got a different thread ID.
|
||||
assert_ne!(spawned_tid_from_handle, current_tid);
|
||||
|
||||
// Ensure that we get the same result from `gettid` and from querying a thread's handle
|
||||
assert_eq!(spawned_tid_from_handle, spawned_tid_from_self);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let tid = gettid();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
assert_ne!(gettid(), tid);
|
||||
});
|
||||
|
||||
// Test that in isolation mode a deterministic value will be returned.
|
||||
// The value is not important, we only care that whatever the value is,
|
||||
// won't change from execution to execution.
|
||||
if cfg!(with_isolation) {
|
||||
if cfg!(target_os = "linux") {
|
||||
// Linux starts the TID at the PID, which is 1000.
|
||||
assert_eq!(tid, 1000);
|
||||
} else {
|
||||
// Other platforms start counting from 0.
|
||||
assert_eq!(tid, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// On Linux and NetBSD, the first TID is the PID.
|
||||
#[cfg(any(target_os = "linux", target_os = "netbsd"))]
|
||||
assert_eq!(tid, unsafe { libc::getpid() } as u64);
|
||||
|
||||
#[cfg(any(target_vendor = "apple", windows))]
|
||||
queried::check();
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
//@compile-flags: -Zmiri-track-alloc-id=19 -Zmiri-track-alloc-accesses -Cpanic=abort
|
||||
//@normalize-stderr-test: "id 19" -> "id $$ALLOC"
|
||||
//@compile-flags: -Zmiri-track-alloc-id=21 -Zmiri-track-alloc-accesses -Cpanic=abort
|
||||
//@normalize-stderr-test: "id 21" -> "id $$ALLOC"
|
||||
//@only-target: linux # alloc IDs differ between OSes (due to extern static allocations)
|
||||
|
||||
extern "Rust" {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue