Skip to content

Commit b31dd9c

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. 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]: rust-lang/rust#115746 Squashed commit from Ralf: try_from_scalar: extend comment
1 parent 205f63e commit b31dd9c

File tree

14 files changed

+312
-12
lines changed

14 files changed

+312
-12
lines changed

src/helpers.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1337,7 +1337,6 @@ where
13371337

13381338
/// Check that the number of varargs is at least the minimum what we expect.
13391339
/// Fixed args should not be included.
1340-
/// Use `check_vararg_fixed_arg_count` to extract the varargs slice from full function arguments.
13411340
pub fn check_min_vararg_count<'a, 'tcx, const N: usize>(
13421341
name: &'a str,
13431342
args: &'a [OpTy<'tcx>],

src/shims/env.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,30 @@ 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+
let this = self.eval_context_ref();
122+
self.get_tid(this.machine.threads.active_thread())
123+
}
124+
125+
/// Get an "OS" thread ID for any thread.
126+
fn get_tid(&self, thread: ThreadId) -> u32 {
127+
let this = self.eval_context_ref();
128+
let index = thread.to_u32();
129+
let target_os = &this.tcx.sess.target.os;
130+
if target_os == "linux" || target_os == "netbsd" {
131+
// On Linux, the main thread has PID == TID so we uphold this. NetBSD also appears
132+
// to exhibit the same behavior, though I can't find a citation.
133+
this.get_pid().strict_add(index)
134+
} else {
135+
// Other platforms do not display any relationship between PID and TID.
136+
index
137+
}
138+
}
117139
}

src/shims/extern_static.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ impl<'tcx> MiriMachine<'tcx> {
6666
ecx,
6767
&["__cxa_thread_atexit_impl", "__clock_gettime64"],
6868
)?;
69-
Self::weak_symbol_extern_statics(ecx, &["getrandom", "statx"])?;
69+
Self::weak_symbol_extern_statics(ecx, &["getrandom", "gettid", "statx"])?;
7070
}
7171
"freebsd" => {
7272
Self::null_ptr_extern_statics(ecx, &["__cxa_thread_atexit_impl"])?;

src/shims/unix/env.rs

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -274,15 +274,52 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
274274
interp_ok(Scalar::from_u32(this.get_pid()))
275275
}
276276

277-
fn linux_gettid(&mut self) -> InterpResult<'tcx, Scalar> {
277+
/// The `gettid`-like function for Unix platforms that take no parameters and return a 32-bit
278+
/// integer. It is not always named "gettid".
279+
fn unix_gettid(&mut self, link_name: &str) -> InterpResult<'tcx, Scalar> {
278280
let this = self.eval_context_ref();
279-
this.assert_target_os("linux", "gettid");
281+
this.assert_target_os_is_unix(link_name);
280282

281-
let index = this.machine.threads.active_thread().to_u32();
283+
// For most platforms the return type is an `i32`, but some are unsigned. The TID
284+
// will always be positive so we don't need to differentiate.
285+
interp_ok(Scalar::from_u32(this.get_current_tid()))
286+
}
287+
288+
/// The Apple-specific `int pthread_threadid_np(pthread_t thread, uint64_t *thread_id)`, which
289+
/// allows querying the ID for arbitrary threads, identified by their pthread_t.
290+
///
291+
/// API documentation: <https://www.manpagez.com/man/3/pthread_threadid_np/>.
292+
fn apple_pthread_threadip_np(
293+
&mut self,
294+
thread_op: &OpTy<'tcx>,
295+
tid_op: &OpTy<'tcx>,
296+
) -> InterpResult<'tcx, Scalar> {
297+
let this = self.eval_context_mut();
298+
this.assert_target_os("macos", "pthread_threadip_np");
299+
300+
let tid_dest = this.read_pointer(tid_op)?;
301+
if this.ptr_is_null(tid_dest)? {
302+
// If NULL is passed, an error is immediately returned
303+
return interp_ok(this.eval_libc("EINVAL"));
304+
}
305+
306+
let thread = this.read_scalar(thread_op)?.to_int(this.libc_ty_layout("pthread_t").size)?;
307+
let thread = if thread == 0 {
308+
// Null thread ID indicates that we are querying the active thread.
309+
this.machine.threads.active_thread()
310+
} else {
311+
// Our pthread_t is just the raw ThreadId.
312+
let Ok(thread) = this.thread_id_try_from(thread) else {
313+
return interp_ok(this.eval_libc("ESRCH"));
314+
};
315+
thread
316+
};
282317

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

286-
interp_ok(Scalar::from_u32(tid))
322+
// Possible errors have been handled, return success.
323+
interp_ok(Scalar::from_u32(0))
287324
}
288325
}

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.as_str())?;
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/shims/unix/linux/foreign_items.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::*;
1818
const TASK_COMM_LEN: u64 = 16;
1919

2020
pub fn is_dyn_sym(name: &str) -> bool {
21-
matches!(name, "statx")
21+
matches!(name, "gettid" | "statx")
2222
}
2323

2424
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@@ -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.as_str())?;
121121
this.write_scalar(result, dest)?;
122122
}
123123

src/shims/unix/linux_like/syscall.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use rustc_span::Symbol;
44
use rustc_target::callconv::FnAbi;
55

66
use crate::helpers::check_min_vararg_count;
7+
use crate::shims::unix::env::EvalContextExt;
78
use crate::shims::unix::linux_like::eventfd::EvalContextExt as _;
89
use crate::shims::unix::linux_like::sync::futex;
910
use crate::*;
@@ -24,6 +25,7 @@ pub fn syscall<'tcx>(
2425
let sys_getrandom = ecx.eval_libc("SYS_getrandom").to_target_usize(ecx)?;
2526
let sys_futex = ecx.eval_libc("SYS_futex").to_target_usize(ecx)?;
2627
let sys_eventfd2 = ecx.eval_libc("SYS_eventfd2").to_target_usize(ecx)?;
28+
let sys_gettid = ecx.eval_libc("SYS_gettid").to_target_usize(ecx)?;
2729

2830
match ecx.read_target_usize(op)? {
2931
// `libc::syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK)`
@@ -53,6 +55,10 @@ pub fn syscall<'tcx>(
5355
let result = ecx.eventfd(initval, flags)?;
5456
ecx.write_int(result.to_i32()?, dest)?;
5557
}
58+
num if num == sys_gettid => {
59+
let result = ecx.unix_gettid("SYS_gettid")?;
60+
ecx.write_int(result.to_u32()?, dest)?;
61+
}
5662
num => {
5763
throw_unsup_format!("syscall: unsupported syscall number {num}");
5864
}

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/shims/windows/foreign_items.rs

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

633650
// Miscellaneous
634651
"ExitProcess" => {

src/shims/windows/handle.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ impl Handle {
166166
/// Structurally invalid handles return [`HandleError::InvalidHandle`].
167167
/// If the handle is structurally valid but semantically invalid, e.g. a for non-existent thread
168168
/// ID, returns [`HandleError::ThreadNotFound`].
169+
///
170+
/// This function is deliberately private; shims should always use `read_handle`.
171+
/// That enforces handle validity even when Windows does not: for now, we argue invalid
172+
/// handles are always a bug and programmers likely want to know about them.
169173
fn try_from_scalar<'tcx>(
170174
handle: Scalar,
171175
cx: &MiriInterpCx<'tcx>,

0 commit comments

Comments
 (0)