Skip to content

Commit afb6f33

Browse files
committed
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. These should be useful in general but should also help support printing the OS thread ID in panic messages [1]. [1]: #115746
1 parent 31d0d21 commit afb6f33

File tree

7 files changed

+239
-7
lines changed

7 files changed

+239
-7
lines changed

src/tools/miri/src/shims/env.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
110110
}
111111
}
112112

113+
/// Get the process identifier.
113114
fn get_pid(&self) -> u32 {
114115
let this = self.eval_context_ref();
115116
if this.machine.communicate() { std::process::id() } else { 1000 }
116117
}
118+
119+
/// Get an "OS" thread ID for the current thread.
120+
fn get_current_tid(&self) -> u32 {
121+
self.get_tid(self.eval_context_ref().machine.threads.active_thread())
122+
}
123+
124+
/// Get an "OS" thread ID for any thread.
125+
fn get_tid(&self, thread: ThreadId) -> u32 {
126+
let this = self.eval_context_ref();
127+
let index = thread.to_u32();
128+
let target_os = &this.tcx.sess.target.os;
129+
if target_os == "linux" || target_os == "netbsd" {
130+
// On Linux, the main thread has PID == TID so we uphold this. NetBSD also appears
131+
// to exhibit the same behavior, though I can't find a citation.
132+
this.get_pid().strict_add(index)
133+
} else {
134+
// Other platforms do not document any relationship between PID and TID.
135+
index
136+
}
137+
}
117138
}

src/tools/miri/src/shims/unix/env.rs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use rustc_data_structures::fx::FxHashMap;
77
use rustc_index::IndexVec;
88
use rustc_middle::ty::Ty;
99
use rustc_middle::ty::layout::LayoutOf;
10+
use rustc_span::Symbol;
1011

1112
use crate::*;
1213

@@ -275,15 +276,56 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
275276
interp_ok(Scalar::from_u32(this.get_pid()))
276277
}
277278

278-
fn linux_gettid(&mut self) -> InterpResult<'tcx, Scalar> {
279+
/// The `gettid`-like function for Unix platforms that take no parameters and return a 32-bit
280+
/// integer. It is not always named "gettid".
281+
fn unix_gettid(&mut self, link_name: Symbol) -> InterpResult<'tcx, Scalar> {
279282
let this = self.eval_context_ref();
280-
this.assert_target_os("linux", "gettid");
283+
let target_os = &self.eval_context_ref().tcx.sess.target.os;
284+
285+
// Various platforms have a similar function with different names.
286+
match (target_os.as_ref(), link_name.as_str()) {
287+
("linux" | "android", "gettid") => (),
288+
("openbsd", "getthrid") => (),
289+
("freebsd", "pthread_getthreadid_np") => (),
290+
("netbsd", "_lwp_self") => (),
291+
(_, name) => panic!("`{name}` is not supported on {target_os}"),
292+
};
293+
294+
// For most platforms the return type is an `i32`, but some are unsigned. The TID
295+
// will always be positive so we don't need to differentiate.
296+
interp_ok(Scalar::from_u32(this.get_current_tid()))
297+
}
281298

282-
let index = this.machine.threads.active_thread().to_u32();
299+
/// The Apple-specific `int pthread_threadid_np(pthread_t thread, uint64_t *thread_id)`, which
300+
/// allows querying the ID for arbitrary threads.
301+
fn apple_pthread_threadip_np(
302+
&mut self,
303+
thread_op: &OpTy<'tcx>,
304+
tid_op: &OpTy<'tcx>,
305+
) -> InterpResult<'tcx, Scalar> {
306+
let this = self.eval_context_mut();
307+
308+
let target_vendor = &this.tcx.sess.target.vendor;
309+
assert_eq!(
310+
target_vendor, "apple",
311+
"`pthread_threadid_np` is not supported on target vendor {target_vendor}",
312+
);
313+
314+
let thread = this.read_scalar(thread_op)?.to_int(this.libc_ty_layout("pthread_t").size)?;
315+
let thread = if thread == 0 {
316+
// Null thread ID indicates that we are querying the active thread.
317+
this.machine.threads.active_thread()
318+
} else {
319+
let Ok(thread) = this.thread_id_try_from(thread) else {
320+
return interp_ok(this.eval_libc("ESRCH"));
321+
};
322+
thread
323+
};
283324

284-
// Compute a TID for this thread, ensuring that the main thread has PID == TID.
285-
let tid = this.get_pid().strict_add(index);
325+
let tid_dest = this.deref_pointer_as(tid_op, this.machine.layouts.u64)?;
326+
this.write_int(this.get_tid(thread), &tid_dest)?;
286327

287-
interp_ok(Scalar::from_u32(tid))
328+
// Never an error if we only ever check the current thread.
329+
interp_ok(Scalar::from_u32(0))
288330
}
289331
}

src/tools/miri/src/shims/unix/freebsd/foreign_items.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
5656
};
5757
this.write_scalar(res, dest)?;
5858
}
59+
"pthread_getthreadid_np" => {
60+
let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
61+
let result = this.unix_gettid(link_name)?;
62+
this.write_scalar(result, dest)?;
63+
}
5964

6065
"cpuset_getaffinity" => {
6166
// The "same" kind of api as `sched_getaffinity` but more fine grained control for FreeBSD specifically.

src/tools/miri/src/shims/unix/linux/foreign_items.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
117117
}
118118
"gettid" => {
119119
let [] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
120-
let result = this.linux_gettid()?;
120+
let result = this.unix_gettid(link_name)?;
121121
this.write_scalar(result, dest)?;
122122
}
123123

src/tools/miri/src/shims/unix/macos/foreign_items.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,11 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
222222
};
223223
this.write_scalar(res, dest)?;
224224
}
225+
"pthread_threadid_np" => {
226+
let [thread, tid_ptr] = this.check_shim(abi, CanonAbi::C, link_name, args)?;
227+
let res = this.apple_pthread_threadip_np(thread, tid_ptr)?;
228+
this.write_scalar(res, dest)?;
229+
}
225230

226231
// Synchronization primitives
227232
"os_sync_wait_on_address" => {

src/tools/miri/src/shims/windows/foreign_items.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
630630
this.write_scalar(name, &name_ptr)?;
631631
this.write_scalar(res, dest)?;
632632
}
633+
"GetThreadId" => {
634+
let [handle] = this.check_shim(abi, sys_conv, link_name, args)?;
635+
let handle = this.read_handle(handle, "GetThreadId")?;
636+
637+
let thread = match handle {
638+
Handle::Thread(thread) => thread,
639+
Handle::Pseudo(PseudoHandle::CurrentThread) => this.active_thread(),
640+
_ => this.invalid_handle("GetThreadDescription")?,
641+
};
642+
643+
this.write_scalar(Scalar::from_u32(this.get_tid(thread)), dest)?;
644+
}
633645

634646
// Miscellaneous
635647
"ExitProcess" => {
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
//@ ignore-target: illumos solaris wasm
2+
//@ revisions: with_isolation without_isolation
3+
//@ [without_isolation] compile-flags: -Zmiri-disable-isolation
4+
5+
// This test is based on `getpid.rs`
6+
7+
fn gettid() -> u64 {
8+
cfg_if::cfg_if! {
9+
if #[cfg(any(target_os = "android", target_os = "linux", target_os = "nto"))] {
10+
(unsafe { libc::gettid() }) as u64
11+
} else if #[cfg(target_os = "openbsd")] {
12+
(unsafe { libc::getthrid() }) as u64
13+
} else if #[cfg(target_os = "freebsd")] {
14+
(unsafe { libc::pthread_getthreadid_np() }) as u64
15+
} else if #[cfg(target_os = "netbsd")] {
16+
(unsafe { libc::_lwp_self() }) as u64
17+
} else if #[cfg(target_vendor = "apple")] {
18+
let mut id = 0u64;
19+
let status: libc::c_int = unsafe { libc::pthread_threadid_np(0, &mut id) };
20+
assert_eq!(status, 0);
21+
id
22+
} else if #[cfg(windows)] {
23+
use windows_sys::Win32::System::Threading::{GetCurrentThread, GetThreadId};
24+
(unsafe { GetThreadId(GetCurrentThread()) }) as u64
25+
} else {
26+
compile_error!("platform has no gettid")
27+
}
28+
}
29+
}
30+
31+
/// Specific platforms can query the tid of arbitrary threads; test that here.
32+
#[cfg(any(target_vendor = "apple", windows))]
33+
mod queried {
34+
use std::ffi::c_void;
35+
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
36+
use std::{ptr, thread, time};
37+
38+
use super::*;
39+
40+
static SPAWNED_TID: AtomicU64 = AtomicU64::new(0);
41+
static CAN_JOIN: AtomicBool = AtomicBool::new(false);
42+
43+
#[cfg(unix)]
44+
extern "C" fn thread_start(_data: *mut c_void) -> *mut c_void {
45+
thread_body();
46+
ptr::null_mut()
47+
}
48+
49+
#[cfg(windows)]
50+
extern "system" fn thread_start(_data: *mut c_void) -> u32 {
51+
thread_body();
52+
0
53+
}
54+
55+
fn thread_body() {
56+
SPAWNED_TID.store(gettid(), Ordering::Relaxed);
57+
let sleep_duration = time::Duration::from_millis(10);
58+
59+
// Spin until the main thread has a chance to read this thread's ID
60+
while !CAN_JOIN.load(Ordering::Relaxed) {
61+
thread::sleep(sleep_duration);
62+
}
63+
}
64+
65+
#[cfg(unix)]
66+
fn spawn_update_join() -> u64 {
67+
let mut t: libc::pthread_t = 0;
68+
let mut spawned_tid_from_handle = 0u64;
69+
70+
unsafe {
71+
let res = libc::pthread_create(&mut t, ptr::null(), thread_start, ptr::null_mut());
72+
assert_eq!(res, 0);
73+
74+
let res = libc::pthread_threadid_np(t, &mut spawned_tid_from_handle);
75+
assert_eq!(res, 0);
76+
CAN_JOIN.store(true, Ordering::Relaxed);
77+
78+
let res = libc::pthread_join(t, ptr::null_mut());
79+
assert_eq!(res, 0);
80+
}
81+
82+
spawned_tid_from_handle
83+
}
84+
85+
#[cfg(windows)]
86+
fn spawn_update_join() -> u64 {
87+
use windows_sys::Win32::Foundation::WAIT_FAILED;
88+
use windows_sys::Win32::System::Threading::{
89+
CreateThread, GetThreadId, INFINITE, WaitForSingleObject,
90+
};
91+
92+
let spawned_tid_from_handle;
93+
let mut tid_at_spawn = 0u32;
94+
95+
unsafe {
96+
let handle =
97+
CreateThread(ptr::null(), 0, Some(thread_start), ptr::null(), 0, &mut tid_at_spawn);
98+
assert!(!handle.is_null());
99+
100+
spawned_tid_from_handle = GetThreadId(handle);
101+
assert_ne!(spawned_tid_from_handle, 0);
102+
CAN_JOIN.store(true, Ordering::Relaxed);
103+
104+
let res = WaitForSingleObject(handle, INFINITE);
105+
assert_ne!(res, WAIT_FAILED);
106+
}
107+
108+
assert_eq!(spawned_tid_from_handle, tid_at_spawn);
109+
110+
spawned_tid_from_handle.into()
111+
}
112+
113+
pub fn check() {
114+
let spawned_tid_from_handle = spawn_update_join();
115+
assert_ne!(spawned_tid_from_handle, 0);
116+
assert_ne!(spawned_tid_from_handle, gettid());
117+
assert_eq!(spawned_tid_from_handle, SPAWNED_TID.load(Ordering::Relaxed));
118+
}
119+
}
120+
121+
fn main() {
122+
let tid = gettid();
123+
124+
std::thread::spawn(move || {
125+
assert_ne!(gettid(), tid);
126+
});
127+
128+
// Test that in isolation mode a deterministic value will be returned.
129+
// The value is not important, we only care that whatever the value is,
130+
// won't change from execution to execution.
131+
if cfg!(with_isolation) {
132+
if cfg!(target_os = "linux") {
133+
// Linux starts the TID at the PID, which is 1000.
134+
assert_eq!(tid, 1000);
135+
} else {
136+
// Other platforms start counting from 0.
137+
assert_eq!(tid, 0);
138+
}
139+
}
140+
141+
// On Linux and NetBSD, the first TID is the PID.
142+
#[cfg(any(target_os = "linux", target_os = "netbsd"))]
143+
assert_eq!(tid, unsafe { libc::getpid() } as u64);
144+
145+
#[cfg(any(target_vendor = "apple", windows))]
146+
queried::check();
147+
}

0 commit comments

Comments
 (0)