Skip to content

Print thread ID in panic message #115746

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion library/std/src/panicking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ fn default_hook(info: &PanicHookInfo<'_>) {

thread::with_current_name(|name| {
let name = name.unwrap_or("<unnamed>");
let tid = thread::current_os_id();

// Try to write the panic message to a buffer first to prevent other concurrent outputs
// interleaving with it.
Expand All @@ -277,7 +278,7 @@ fn default_hook(info: &PanicHookInfo<'_>) {

let write_msg = |dst: &mut dyn crate::io::Write| {
// We add a newline to ensure the panic message appears at the start of a line.
writeln!(dst, "\nthread '{name}' panicked at {location}:\n{msg}")
writeln!(dst, "\nthread '{name}' ({tid}) panicked at {location}:\n{msg}")
};

if write_msg(&mut cursor).is_ok() {
Expand Down
4 changes: 4 additions & 0 deletions library/std/src/sys/pal/hermit/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ impl Thread {
}
}

pub(crate) fn current_os_id() -> Option<u64> {
None
}

pub fn available_parallelism() -> io::Result<NonZero<usize>> {
unsafe { Ok(NonZero::new_unchecked(hermit_abi::available_parallelism())) }
}
4 changes: 4 additions & 0 deletions library/std/src/sys/pal/itron/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,10 @@ unsafe fn terminate_and_delete_current_task() -> ! {
unsafe { crate::hint::unreachable_unchecked() };
}

pub(crate) fn current_os_id() -> Option<u64> {
None
}

pub fn available_parallelism() -> io::Result<NonZero<usize>> {
super::unsupported()
}
4 changes: 4 additions & 0 deletions library/std/src/sys/pal/sgx/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ impl Thread {
}
}

pub(crate) fn current_os_id() -> Option<u64> {
None
}

pub fn available_parallelism() -> io::Result<NonZero<usize>> {
unsupported()
}
4 changes: 4 additions & 0 deletions library/std/src/sys/pal/teeos/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ impl Drop for Thread {
}
}

pub(crate) fn current_os_id() -> Option<u64> {
None
}

// Note: Both `sched_getaffinity` and `sysconf` are available but not functional on
// teeos, so this function always returns an Error!
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
Expand Down
4 changes: 4 additions & 0 deletions library/std/src/sys/pal/uefi/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ impl Thread {
}
}

pub(crate) fn current_os_id() -> Option<u64> {
None
}

pub fn available_parallelism() -> io::Result<NonZero<usize>> {
// UEFI is single threaded
Ok(NonZero::new(1).unwrap())
Expand Down
6 changes: 4 additions & 2 deletions library/std/src/sys/pal/unix/stack_overflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ mod imp {
&& thread_info.guard_page_range.contains(&fault_addr)
{
let name = thread_info.thread_name.as_deref().unwrap_or("<unknown>");
rtprintpanic!("\nthread '{name}' has overflowed its stack\n");
let tid = crate::thread::current_os_id();
rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n");
rtabort!("stack overflow");
}
})
Expand Down Expand Up @@ -696,7 +697,8 @@ mod imp {
if code == c::EXCEPTION_STACK_OVERFLOW {
crate::thread::with_current_name(|name| {
let name = name.unwrap_or("<unknown>");
rtprintpanic!("\nthread '{name}' has overflowed its stack\n");
let tid = crate::thread::current_os_id();
rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n");
});
}
c::EXCEPTION_CONTINUE_SEARCH
Expand Down
38 changes: 38 additions & 0 deletions library/std/src/sys/pal/unix/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,44 @@ impl Drop for Thread {
}
}

pub(crate) fn current_os_id() -> Option<u64> {
// Most Unix platforms have a way to query an integer ID of the current thread, all with
// slightly different spellings.
cfg_if::cfg_if! {
// Most platforms have a function returning a `pid_t` or int, which is an `i32`.
if #[cfg(any(target_os = "android", target_os = "linux", target_os = "nto"))] {
// SAFETY: FFI call with no preconditions.
let id: libc::pid_t = unsafe { libc::gettid() };
Some(id as u64)
} else if #[cfg(target_os = "openbsd")] {
// SAFETY: FFI call with no preconditions.
let id: libc::pid_t = unsafe { libc::getthrid() };
Some(id as u64)
} else if #[cfg(target_os = "freebsd")] {
// SAFETY: FFI call with no preconditions.
let id: libc::c_int = unsafe { libc::pthread_getthreadid_np() };
Some(id as u64)
} else if #[cfg(target_os = "netbsd")] {
// SAFETY: FFI call with no preconditions.
let id: libc::lwpid_t = unsafe { libc::_lwp_self() };
Some(id as u64)
} else if #[cfg(target_vendor = "apple")] {
// Apple allows querying arbitrary thread IDs, `thread=NULL` queries the current thread.
let mut id = 0u64;
// SAFETY: `thread_id` is a valid pointer, no other preconditions.
let status: libc::c_int = unsafe { libc::pthread_threadid_np(0, &mut id) };
if status == 0 {
Some(id)
} else {
None
}
} else {
// Other platforms don't have an OS thread ID or don't have a way to access it.
None
}
}
}

#[cfg(any(
target_os = "linux",
target_os = "nto",
Expand Down
4 changes: 4 additions & 0 deletions library/std/src/sys/pal/unsupported/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ impl Thread {
}
}

pub(crate) fn current_os_id() -> Option<u64> {
None
}

pub fn available_parallelism() -> io::Result<NonZero<usize>> {
unsupported()
}
4 changes: 4 additions & 0 deletions library/std/src/sys/pal/wasi/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ impl Thread {
}
}

pub(crate) fn current_os_id() -> Option<u64> {
None
}

pub fn available_parallelism() -> io::Result<NonZero<usize>> {
cfg_if::cfg_if! {
if #[cfg(target_feature = "atomics")] {
Expand Down
1 change: 1 addition & 0 deletions library/std/src/sys/pal/windows/c/bindings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2177,6 +2177,7 @@ GetSystemInfo
GetSystemTimeAsFileTime
GetSystemTimePreciseAsFileTime
GetTempPathW
GetThreadId
GetUserProfileDirectoryW
GetWindowsDirectoryW
HANDLE
Expand Down
1 change: 1 addition & 0 deletions library/std/src/sys/pal/windows/c/windows_sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ windows_targets::link!("kernel32.dll" "system" fn GetSystemInfo(lpsysteminfo : *
windows_targets::link!("kernel32.dll" "system" fn GetSystemTimeAsFileTime(lpsystemtimeasfiletime : *mut FILETIME));
windows_targets::link!("kernel32.dll" "system" fn GetSystemTimePreciseAsFileTime(lpsystemtimeasfiletime : *mut FILETIME));
windows_targets::link!("kernel32.dll" "system" fn GetTempPathW(nbufferlength : u32, lpbuffer : PWSTR) -> u32);
windows_targets::link!("kernel32.dll" "system" fn GetThreadId(thread : HANDLE) -> u32);
windows_targets::link!("userenv.dll" "system" fn GetUserProfileDirectoryW(htoken : HANDLE, lpprofiledir : PWSTR, lpcchsize : *mut u32) -> BOOL);
windows_targets::link!("kernel32.dll" "system" fn GetWindowsDirectoryW(lpbuffer : PWSTR, usize : u32) -> u32);
windows_targets::link!("kernel32.dll" "system" fn InitOnceBeginInitialize(lpinitonce : *mut INIT_ONCE, dwflags : u32, fpending : *mut BOOL, lpcontext : *mut *mut core::ffi::c_void) -> BOOL);
Expand Down
3 changes: 2 additions & 1 deletion library/std/src/sys/pal/windows/stack_overflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POIN
if code == c::EXCEPTION_STACK_OVERFLOW {
thread::with_current_name(|name| {
let name = name.unwrap_or("<unknown>");
rtprintpanic!("\nthread '{name}' has overflowed its stack\n");
let tid = thread::current_os_id();
rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n");
});
}
c::EXCEPTION_CONTINUE_SEARCH
Expand Down
8 changes: 8 additions & 0 deletions library/std/src/sys/pal/windows/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ impl Thread {
}
}

pub(crate) fn current_os_id() -> Option<u64> {
// SAFETY: FFI call with no preconditions.
let id: u32 = unsafe { c::GetThreadId(c::GetCurrentThread()) };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am going to change this to GetCurrentThreadId that does both steps, but not until the Miri shims merge so I can stop manually syncing changes.


// A return value of 0 indicates failed lookup.
if id == 0 { None } else { Some(id.into()) }
}

pub fn available_parallelism() -> io::Result<NonZero<usize>> {
let res = unsafe {
let mut sysinfo: c::SYSTEM_INFO = crate::mem::zeroed();
Expand Down
4 changes: 4 additions & 0 deletions library/std/src/sys/pal/xous/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ impl Thread {
}
}

pub(crate) fn current_os_id() -> Option<u64> {
None
}

pub fn available_parallelism() -> io::Result<NonZero<usize>> {
// We're unicore right now.
Ok(unsafe { NonZero::new_unchecked(1) })
Expand Down
12 changes: 11 additions & 1 deletion library/std/src/thread/current.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{Thread, ThreadId};
use super::{Thread, ThreadId, imp};
use crate::mem::ManuallyDrop;
use crate::ptr;
use crate::sys::thread_local::local_pointer;
Expand Down Expand Up @@ -148,6 +148,16 @@ pub(crate) fn current_id() -> ThreadId {
id::get_or_init()
}

/// Gets the OS thread ID of the thread that invokes it, if available. If not, return the Rust
/// thread ID.
///
/// We use a `u64` to fit either integer or pointer IDs on all platforms without excess `cfg`. This
/// is a "best effort" approach for diagnostics and is allowed to fall back to a non-OS ID (such as
/// the Rust thread ID) or a non-unique ID (such as a PID) if the thread ID cannot be retrieved.
pub(crate) fn current_os_id() -> u64 {
imp::current_os_id().unwrap_or_else(|| current_id().as_u64().get())
}

/// Gets a reference to the handle of the thread that invokes it, if the handle
/// has been initialized.
pub(super) fn try_with_current<F, R>(f: F) -> R
Expand Down
2 changes: 1 addition & 1 deletion library/std/src/thread/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ mod current;

#[stable(feature = "rust1", since = "1.0.0")]
pub use current::current;
pub(crate) use current::{current_id, current_or_unnamed, drop_current};
pub(crate) use current::{current_id, current_or_unnamed, current_os_id, drop_current};
use current::{set_current, try_with_current};

mod spawnhook;
Expand Down
7 changes: 7 additions & 0 deletions library/std/src/thread/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,13 @@ fn test_thread_id_not_equal() {
assert!(thread::current().id() != spawned_id);
}

#[test]
fn test_thread_os_id_not_equal() {
let spawned_id = thread::spawn(|| thread::current_os_id()).join().unwrap();
let current_id = thread::current_os_id();
assert!(current_id != spawned_id);
}

#[test]
fn test_scoped_threads_drop_result_before_join() {
let actually_finished = &AtomicBool::new(false);
Expand Down
5 changes: 5 additions & 0 deletions src/tools/compiletest/src/runtest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2413,6 +2413,11 @@ impl<'test> TestCx<'test> {
})
.into_owned();

// Normalize thread IDs in panic messages
normalized = static_regex!(r"thread '(?P<name>.*?)' \(\d+\) panicked")
.replace_all(&normalized, "thread '$name' ($$TID) panicked")
.into_owned();

normalized = normalized.replace("\t", "\\t"); // makes tabs visible

// Remove test annotations like `//~ ERROR text` from the output,
Expand Down
21 changes: 21 additions & 0 deletions src/tools/miri/src/shims/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,29 @@ 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 {
self.get_tid(self.eval_context_ref().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 document any relationship between PID and TID.
index
}
}
}
54 changes: 48 additions & 6 deletions src/tools/miri/src/shims/unix/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use rustc_data_structures::fx::FxHashMap;
use rustc_index::IndexVec;
use rustc_middle::ty::Ty;
use rustc_middle::ty::layout::LayoutOf;
use rustc_span::Symbol;

use crate::*;

Expand Down Expand Up @@ -275,15 +276,56 @@ 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: Symbol) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_ref();
this.assert_target_os("linux", "gettid");
let target_os = &self.eval_context_ref().tcx.sess.target.os;

// Various platforms have a similar function with different names.
match (target_os.as_ref(), link_name.as_str()) {
("linux" | "android", "gettid") => (),
("openbsd", "getthrid") => (),
("freebsd", "pthread_getthreadid_np") => (),
("netbsd", "_lwp_self") => (),
(_, name) => panic!("`{name}` is not supported on {target_os}"),
};

// 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()))
}

let index = this.machine.threads.active_thread().to_u32();
/// The Apple-specific `int pthread_threadid_np(pthread_t thread, uint64_t *thread_id)`, which
/// allows querying the ID for arbitrary threads.
fn apple_pthread_threadip_np(
&mut self,
thread_op: &OpTy<'tcx>,
tid_op: &OpTy<'tcx>,
) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_mut();

let target_vendor = &this.tcx.sess.target.vendor;
assert_eq!(
target_vendor, "apple",
"`pthread_threadid_np` is not supported on target vendor {target_vendor}",
);

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 {
let Ok(thread) = this.thread_id_try_from(thread) else {
return interp_ok(this.eval_libc("ESRCH"));
};
thread
};

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

interp_ok(Scalar::from_u32(tid))
// Never an error if we only ever check the current thread.
interp_ok(Scalar::from_u32(0))
}
}
5 changes: 5 additions & 0 deletions src/tools/miri/src/shims/unix/freebsd/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
this.write_scalar(result, dest)?;
}

"cpuset_getaffinity" => {
// The "same" kind of api as `sched_getaffinity` but more fine grained control for FreeBSD specifically.
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/src/shims/unix/linux/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
this.write_scalar(result, dest)?;
}

Expand Down
Loading
Loading