Skip to content

Commit d61174e

Browse files
Auto merge of #115746 - tgross35:unnamed-threads-panic-message, r=<try>
Print thread ID in panic message `panic!` does not print any identifying information for threads that are unnamed. However, in many cases, the thread ID can be determined. This changes the panic message from something like this: thread '<unnamed>' panicked at src/main.rs:3:5: explicit panic To something like this: thread '<unnamed>' (12345) panicked at src/main.rs:3:5: explicit panic Stack overflow messages are updated as well. This change applies to both named and unnamed threads. The ID printed is the OS integer thread ID rather than the Rust thread ID, which should also be what debuggers print. try-job: `*various*` try-job: `x86_64-msvc*`
2 parents 64c81fd + fd78223 commit d61174e

File tree

130 files changed

+535
-174
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

130 files changed

+535
-174
lines changed

library/std/src/panicking.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ fn default_hook(info: &PanicHookInfo<'_>) {
269269

270270
thread::with_current_name(|name| {
271271
let name = name.unwrap_or("<unnamed>");
272+
let tid = thread::current_os_id();
272273

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

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

283284
if write_msg(&mut cursor).is_ok() {

library/std/src/sys/pal/hermit/thread.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ impl Thread {
103103
}
104104
}
105105

106+
pub(crate) fn current_os_id() -> Option<u64> {
107+
None
108+
}
109+
106110
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
107111
unsafe { Ok(NonZero::new_unchecked(hermit_abi::available_parallelism())) }
108112
}

library/std/src/sys/pal/itron/thread.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,10 @@ unsafe fn terminate_and_delete_current_task() -> ! {
349349
unsafe { crate::hint::unreachable_unchecked() };
350350
}
351351

352+
pub(crate) fn current_os_id() -> Option<u64> {
353+
None
354+
}
355+
352356
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
353357
super::unsupported()
354358
}

library/std/src/sys/pal/sgx/thread.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ impl Thread {
137137
}
138138
}
139139

140+
pub(crate) fn current_os_id() -> Option<u64> {
141+
None
142+
}
143+
140144
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
141145
unsupported()
142146
}

library/std/src/sys/pal/teeos/thread.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ impl Drop for Thread {
132132
}
133133
}
134134

135+
pub(crate) fn current_os_id() -> Option<u64> {
136+
None
137+
}
138+
135139
// Note: Both `sched_getaffinity` and `sysconf` are available but not functional on
136140
// teeos, so this function always returns an Error!
137141
pub fn available_parallelism() -> io::Result<NonZero<usize>> {

library/std/src/sys/pal/uefi/thread.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ impl Thread {
4444
}
4545
}
4646

47+
pub(crate) fn current_os_id() -> Option<u64> {
48+
None
49+
}
50+
4751
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
4852
// UEFI is single threaded
4953
Ok(NonZero::new(1).unwrap())

library/std/src/sys/pal/unix/stack_overflow.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,8 @@ mod imp {
120120
&& thread_info.guard_page_range.contains(&fault_addr)
121121
{
122122
let name = thread_info.thread_name.as_deref().unwrap_or("<unknown>");
123-
rtprintpanic!("\nthread '{name}' has overflowed its stack\n");
123+
let tid = crate::thread::current_os_id();
124+
rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n");
124125
rtabort!("stack overflow");
125126
}
126127
})
@@ -696,7 +697,8 @@ mod imp {
696697
if code == c::EXCEPTION_STACK_OVERFLOW {
697698
crate::thread::with_current_name(|name| {
698699
let name = name.unwrap_or("<unknown>");
699-
rtprintpanic!("\nthread '{name}' has overflowed its stack\n");
700+
let tid = crate::thread::current_os_id();
701+
rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n");
700702
});
701703
}
702704
c::EXCEPTION_CONTINUE_SEARCH

library/std/src/sys/pal/unix/thread.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,44 @@ impl Drop for Thread {
318318
}
319319
}
320320

321+
pub(crate) fn current_os_id() -> Option<u64> {
322+
// Most Unix platforms have a way to query an integer ID of the current thread, all with
323+
// slightly different spellings.
324+
cfg_if::cfg_if! {
325+
// Most platforms have a function returning a `pid_t` or int, which is an `i32`.
326+
if #[cfg(any(target_os = "android", target_os = "linux", target_os = "nto"))] {
327+
// SAFETY: FFI call with no preconditions.
328+
let id: libc::pid_t = unsafe { libc::gettid() };
329+
Some(id as u64)
330+
} else if #[cfg(target_os = "openbsd")] {
331+
// SAFETY: FFI call with no preconditions.
332+
let id: libc::pid_t = unsafe { libc::getthrid() };
333+
Some(id as u64)
334+
} else if #[cfg(target_os = "freebsd")] {
335+
// SAFETY: FFI call with no preconditions.
336+
let id: libc::c_int = unsafe { libc::pthread_getthreadid_np() };
337+
Some(id as u64)
338+
} else if #[cfg(target_os = "netbsd")] {
339+
// SAFETY: FFI call with no preconditions.
340+
let id: libc::lwpid_t = unsafe { libc::_lwp_self() };
341+
Some(id as u64)
342+
} else if #[cfg(target_vendor = "apple")] {
343+
// Apple allows querying arbitrary thread IDs, `thread=NULL` queries the current thread.
344+
let mut id = 0u64;
345+
// SAFETY: `thread_id` is a valid pointer, no other preconditions.
346+
let status: libc::c_int = unsafe { libc::pthread_threadid_np(0, &mut id) };
347+
if status == 0 {
348+
Some(id)
349+
} else {
350+
None
351+
}
352+
} else {
353+
// Other platforms don't have an OS thread ID or don't have a way to access it.
354+
None
355+
}
356+
}
357+
}
358+
321359
#[cfg(any(
322360
target_os = "linux",
323361
target_os = "nto",

library/std/src/sys/pal/unsupported/thread.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ impl Thread {
3131
}
3232
}
3333

34+
pub(crate) fn current_os_id() -> Option<u64> {
35+
None
36+
}
37+
3438
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
3539
unsupported()
3640
}

library/std/src/sys/pal/wasi/thread.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ impl Thread {
186186
}
187187
}
188188

189+
pub(crate) fn current_os_id() -> Option<u64> {
190+
None
191+
}
192+
189193
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
190194
cfg_if::cfg_if! {
191195
if #[cfg(target_feature = "atomics")] {

library/std/src/sys/pal/windows/c/bindings.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2177,6 +2177,7 @@ GetSystemInfo
21772177
GetSystemTimeAsFileTime
21782178
GetSystemTimePreciseAsFileTime
21792179
GetTempPathW
2180+
GetThreadId
21802181
GetUserProfileDirectoryW
21812182
GetWindowsDirectoryW
21822183
HANDLE

library/std/src/sys/pal/windows/c/windows_sys.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ windows_targets::link!("kernel32.dll" "system" fn GetSystemInfo(lpsysteminfo : *
6161
windows_targets::link!("kernel32.dll" "system" fn GetSystemTimeAsFileTime(lpsystemtimeasfiletime : *mut FILETIME));
6262
windows_targets::link!("kernel32.dll" "system" fn GetSystemTimePreciseAsFileTime(lpsystemtimeasfiletime : *mut FILETIME));
6363
windows_targets::link!("kernel32.dll" "system" fn GetTempPathW(nbufferlength : u32, lpbuffer : PWSTR) -> u32);
64+
windows_targets::link!("kernel32.dll" "system" fn GetThreadId(thread : HANDLE) -> u32);
6465
windows_targets::link!("userenv.dll" "system" fn GetUserProfileDirectoryW(htoken : HANDLE, lpprofiledir : PWSTR, lpcchsize : *mut u32) -> BOOL);
6566
windows_targets::link!("kernel32.dll" "system" fn GetWindowsDirectoryW(lpbuffer : PWSTR, usize : u32) -> u32);
6667
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);

library/std/src/sys/pal/windows/stack_overflow.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ unsafe extern "system" fn vectored_handler(ExceptionInfo: *mut c::EXCEPTION_POIN
2020
if code == c::EXCEPTION_STACK_OVERFLOW {
2121
thread::with_current_name(|name| {
2222
let name = name.unwrap_or("<unknown>");
23-
rtprintpanic!("\nthread '{name}' has overflowed its stack\n");
23+
let tid = thread::current_os_id();
24+
rtprintpanic!("\nthread '{name}' ({tid}) has overflowed its stack\n");
2425
});
2526
}
2627
c::EXCEPTION_CONTINUE_SEARCH

library/std/src/sys/pal/windows/thread.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,14 @@ impl Thread {
115115
}
116116
}
117117

118+
pub(crate) fn current_os_id() -> Option<u64> {
119+
// SAFETY: FFI call with no preconditions.
120+
let id: u32 = unsafe { c::GetThreadId(c::GetCurrentThread()) };
121+
122+
// A return value of 0 indicates failed lookup.
123+
if id == 0 { None } else { Some(id.into()) }
124+
}
125+
118126
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
119127
let res = unsafe {
120128
let mut sysinfo: c::SYSTEM_INFO = crate::mem::zeroed();

library/std/src/sys/pal/xous/thread.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ impl Thread {
133133
}
134134
}
135135

136+
pub(crate) fn current_os_id() -> Option<u64> {
137+
None
138+
}
139+
136140
pub fn available_parallelism() -> io::Result<NonZero<usize>> {
137141
// We're unicore right now.
138142
Ok(unsafe { NonZero::new_unchecked(1) })

library/std/src/thread/current.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{Thread, ThreadId};
1+
use super::{Thread, ThreadId, imp};
22
use crate::mem::ManuallyDrop;
33
use crate::ptr;
44
use crate::sys::thread_local::local_pointer;
@@ -148,6 +148,16 @@ pub(crate) fn current_id() -> ThreadId {
148148
id::get_or_init()
149149
}
150150

151+
/// Gets the OS thread ID of the thread that invokes it, if available. If not, return the Rust
152+
/// thread ID.
153+
///
154+
/// We use a `u64` to fit either integer or pointer IDs on all platforms without excess `cfg`. This
155+
/// is a "best effort" approach for diagnostics and is allowed to fall back to a non-OS ID (such as
156+
/// the Rust thread ID) or a non-unique ID (such as a PID) if the thread ID cannot be retrieved.
157+
pub(crate) fn current_os_id() -> u64 {
158+
imp::current_os_id().unwrap_or_else(|| current_id().as_u64().get())
159+
}
160+
151161
/// Gets a reference to the handle of the thread that invokes it, if the handle
152162
/// has been initialized.
153163
pub(super) fn try_with_current<F, R>(f: F) -> R

library/std/src/thread/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ mod current;
183183

184184
#[stable(feature = "rust1", since = "1.0.0")]
185185
pub use current::current;
186-
pub(crate) use current::{current_id, current_or_unnamed, drop_current};
186+
pub(crate) use current::{current_id, current_or_unnamed, current_os_id, drop_current};
187187
use current::{set_current, try_with_current};
188188

189189
mod spawnhook;

library/std/src/thread/tests.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,13 @@ fn test_thread_id_not_equal() {
346346
assert!(thread::current().id() != spawned_id);
347347
}
348348

349+
#[test]
350+
fn test_thread_os_id_not_equal() {
351+
let spawned_id = thread::spawn(|| thread::current_os_id()).join().unwrap();
352+
let current_id = thread::current_os_id();
353+
assert!(current_id != spawned_id);
354+
}
355+
349356
#[test]
350357
fn test_scoped_threads_drop_result_before_join() {
351358
let actually_finished = &AtomicBool::new(false);

src/tools/compiletest/src/runtest.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2413,6 +2413,11 @@ impl<'test> TestCx<'test> {
24132413
})
24142414
.into_owned();
24152415

2416+
// Normalize thread IDs in panic messages
2417+
normalized = static_regex!(r"thread '(?P<name>.*?)' \(\d+\) panicked")
2418+
.replace_all(&normalized, "thread '$name' ($$TID) panicked")
2419+
.into_owned();
2420+
24162421
normalized = normalized.replace("\t", "\\t"); // makes tabs visible
24172422

24182423
// Remove test annotations like `//~ ERROR text` from the output,

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

0 commit comments

Comments
 (0)