Skip to content

Commit 4f79c0e

Browse files
committed
Added support for prctl handling thread names
1 parent 4636ddc commit 4f79c0e

14 files changed

+185
-7
lines changed

ci/ci.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ case $HOST_TARGET in
154154
TEST_TARGET=i686-unknown-freebsd run_tests_minimal $BASIC $UNIX time hashmap random threadname pthread fs libc-pipe
155155
TEST_TARGET=x86_64-unknown-illumos run_tests_minimal $BASIC $UNIX time hashmap random thread sync available-parallelism tls libc-pipe
156156
TEST_TARGET=x86_64-pc-solaris run_tests_minimal $BASIC $UNIX time hashmap random thread sync available-parallelism tls libc-pipe
157-
TEST_TARGET=aarch64-linux-android run_tests_minimal $BASIC $UNIX time hashmap pthread --skip threadname
157+
TEST_TARGET=aarch64-linux-android run_tests_minimal $BASIC $UNIX time hashmap threadname pthread
158158
TEST_TARGET=wasm32-wasip2 run_tests_minimal $BASIC wasm
159159
TEST_TARGET=wasm32-unknown-unknown run_tests_minimal no_std empty_main wasm # this target doesn't really have std
160160
TEST_TARGET=thumbv7em-none-eabihf run_tests_minimal no_std

src/shims/unix/android/foreign_items.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use rustc_span::Symbol;
22
use rustc_target::spec::abi::Abi;
33

4+
use crate::shims::unix::android::thread::prctl;
45
use crate::*;
56

67
pub fn is_dyn_sym(_name: &str) -> bool {
@@ -25,6 +26,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
2526
this.write_scalar(errno_place.to_ref(this).to_scalar(), dest)?;
2627
}
2728

29+
// Threading
30+
"prctl" => prctl(this, link_name, abi, args, dest)?,
31+
2832
_ => return interp_ok(EmulateItemResult::NotSupported),
2933
}
3034
interp_ok(EmulateItemResult::NeedsReturn)

src/shims/unix/android/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod foreign_items;
2+
pub mod thread;

src/shims/unix/android/thread.rs

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use rustc_span::Symbol;
2+
use rustc_target::abi::Size;
3+
use rustc_target::spec::abi::Abi;
4+
5+
use crate::helpers::check_min_arg_count;
6+
use crate::shims::unix::thread::EvalContextExt as _;
7+
use crate::*;
8+
9+
const TASK_COMM_LEN: usize = 16;
10+
11+
pub fn prctl<'tcx>(
12+
this: &mut MiriInterpCx<'tcx>,
13+
link_name: Symbol,
14+
abi: Abi,
15+
args: &[OpTy<'tcx>],
16+
dest: &MPlaceTy<'tcx>,
17+
) -> InterpResult<'tcx> {
18+
// We do not use `check_shim` here because `prctl` is variadic. The argument
19+
// count is checked bellow.
20+
this.check_abi_and_shim_symbol_clash(abi, Abi::C { unwind: false }, link_name)?;
21+
22+
// FIXME: Use constants once https://github.com/rust-lang/libc/pull/3941 backported to the 0.2 branch.
23+
let pr_set_name = 15;
24+
let pr_get_name = 16;
25+
26+
let [op] = check_min_arg_count("prctl", args)?;
27+
let res = match this.read_scalar(op)?.to_i32()? {
28+
op if op == pr_set_name => {
29+
let [_, name] = check_min_arg_count("prctl(PR_SET_NAME, ...)", args)?;
30+
let name = this.read_scalar(name)?;
31+
let thread = this.pthread_self()?;
32+
// The Linux kernel silently truncates long names.
33+
// https://www.man7.org/linux/man-pages/man2/PR_SET_NAME.2const.html
34+
let res =
35+
this.pthread_setname_np(thread, name, TASK_COMM_LEN, /* truncate */ true)?;
36+
assert!(res);
37+
Scalar::from_u32(0)
38+
}
39+
op if op == pr_get_name => {
40+
let [_, name] = check_min_arg_count("prctl(PR_GET_NAME, ...)", args)?;
41+
let name = this.read_scalar(name)?;
42+
let thread = this.pthread_self()?;
43+
let len = Scalar::from_target_usize(TASK_COMM_LEN as u64, this);
44+
this.check_ptr_access(
45+
name.to_pointer(this)?,
46+
Size::from_bytes(TASK_COMM_LEN),
47+
CheckInAllocMsg::MemoryAccessTest,
48+
)?;
49+
let res = this.pthread_getname_np(thread, name, len, /* truncate*/ false)?;
50+
assert!(res);
51+
Scalar::from_u32(0)
52+
}
53+
op => throw_unsup_format!("Miri does not support `prctl` syscall with op={}", op),
54+
};
55+
this.write_scalar(res, dest)?;
56+
interp_ok(())
57+
}

src/shims/unix/freebsd/foreign_items.rs

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
2929
this.read_scalar(thread)?,
3030
this.read_scalar(name)?,
3131
max_len,
32+
/* truncate */ false,
3233
)?;
3334
}
3435
"pthread_get_name_np" => {

src/shims/unix/linux/foreign_items.rs

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
8484
this.read_scalar(thread)?,
8585
this.read_scalar(name)?,
8686
TASK_COMM_LEN,
87+
/* truncate */ false,
8788
)?;
8889
let res = if res { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") };
8990
this.write_scalar(res, dest)?;

src/shims/unix/macos/foreign_items.rs

+1
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
181181
thread,
182182
this.read_scalar(name)?,
183183
this.eval_libc("MAXTHREADNAMESIZE").to_target_usize(this)?.try_into().unwrap(),
184+
/* truncate */ false,
184185
)? {
185186
Scalar::from_u32(0)
186187
} else {

src/shims/unix/solarish/foreign_items.rs

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
3030
this.read_scalar(thread)?,
3131
this.read_scalar(name)?,
3232
max_len,
33+
/* truncate */ false,
3334
)?;
3435
let res = if res { Scalar::from_u32(0) } else { this.eval_libc("ERANGE") };
3536
this.write_scalar(res, dest)?;

src/shims/unix/thread.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,29 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
6464
}
6565

6666
/// Set the name of the specified thread. If the name including the null terminator
67-
/// is longer than `name_max_len`, then `false` is returned.
67+
/// is longer or equals to `name_max_len`, then if `truncate` is set the truncated name
68+
/// is used as the thread name, otherwise `false` is returned.
6869
fn pthread_setname_np(
6970
&mut self,
7071
thread: Scalar,
7172
name: Scalar,
7273
name_max_len: usize,
74+
truncate: bool,
7375
) -> InterpResult<'tcx, bool> {
7476
let this = self.eval_context_mut();
7577

7678
let thread = thread.to_int(this.libc_ty_layout("pthread_t").size)?;
7779
let thread = ThreadId::try_from(thread).unwrap();
7880
let name = name.to_pointer(this)?;
79-
let name = this.read_c_str(name)?.to_owned();
81+
let mut name = this.read_c_str(name)?.to_owned();
8082

8183
// Comparing with `>=` to account for null terminator.
8284
if name.len() >= name_max_len {
83-
return interp_ok(false);
85+
if truncate {
86+
name.truncate(name_max_len.saturating_sub(1));
87+
} else {
88+
return interp_ok(false);
89+
}
8490
}
8591

8692
this.set_thread_name(thread, name);

test_dependencies/Cargo.lock

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
//! Ensure we report UB when the buffer is smaller than 16 bytes (even if the thread
2+
//! name would fit in the smaller buffer).
3+
//@only-target: android # Miri supports prctl for Android only
4+
5+
fn main() {
6+
let mut buf = vec![0u8; 15];
7+
unsafe {
8+
libc::prctl(libc::PR_GET_NAME, buf.as_mut_ptr().cast::<libc::c_char>()); //~ ERROR: memory access failed: expected a pointer to 16 bytes of memory, but got alloc952 which is only 15 bytes from the end of the allocation
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
error: Undefined Behavior: memory access failed: expected a pointer to 16 bytes of memory, but got ALLOC which is only 15 bytes from the end of the allocation
2+
--> tests/fail-dep/libc/prctl-threadname.rs:LL:CC
3+
|
4+
LL | libc::prctl(libc::PR_GET_NAME, buf.as_mut_ptr().cast::<libc::c_char>());
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: expected a pointer to 16 bytes of memory, but got ALLOC which is only 15 bytes from the end of the allocation
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
help: ALLOC was allocated here:
10+
--> tests/fail-dep/libc/prctl-threadname.rs:LL:CC
11+
|
12+
LL | let mut buf = vec![0u8; 15];
13+
| ^^^^^^^^^^^^^
14+
= note: BACKTRACE (of the first span):
15+
= note: inside `main` at tests/fail-dep/libc/prctl-threadname.rs:LL:CC
16+
= note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info)
17+
18+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
19+
20+
error: aborting due to 1 previous error
21+
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//@only-target: android # Miri supports prctl for Android only
2+
use std::ffi::{CStr, CString};
3+
use std::thread;
4+
5+
// The Linux kernel all names 16 bytes long including the null terminator.
6+
const MAX_THREAD_NAME_LEN: usize = 16;
7+
8+
fn main() {
9+
// The short name should be shorter than 16 bytes which POSIX promises
10+
// for thread names. The length includes a null terminator.
11+
let short_name = "test_named".to_owned();
12+
let long_name = std::iter::once("test_named_thread_truncation")
13+
.chain(std::iter::repeat(" yada").take(100))
14+
.collect::<String>();
15+
16+
fn set_thread_name(name: &CStr) -> i32 {
17+
unsafe { libc::prctl(libc::PR_SET_NAME, name.as_ptr().cast::<libc::c_char>()) }
18+
}
19+
20+
fn get_thread_name(name: &mut [u8]) -> i32 {
21+
assert!(name.len() >= MAX_THREAD_NAME_LEN);
22+
unsafe { libc::prctl(libc::PR_GET_NAME, name.as_mut_ptr().cast::<libc::c_char>()) }
23+
}
24+
25+
// Set name via Rust API, get it via prctl.
26+
let long_name2 = long_name.clone();
27+
thread::Builder::new()
28+
.name(long_name.clone())
29+
.spawn(move || {
30+
let mut buf = vec![0u8; MAX_THREAD_NAME_LEN];
31+
assert_eq!(get_thread_name(&mut buf), 0);
32+
let cstr = CStr::from_bytes_until_nul(&buf).unwrap();
33+
let truncated_name = &long_name2[..long_name2.len().min(MAX_THREAD_NAME_LEN - 1)];
34+
assert_eq!(cstr.to_bytes(), truncated_name.as_bytes());
35+
})
36+
.unwrap()
37+
.join()
38+
.unwrap();
39+
40+
// Set name via prctl and get it again (short name).
41+
thread::Builder::new()
42+
.spawn(move || {
43+
// Set short thread name.
44+
let cstr = CString::new(short_name.clone()).unwrap();
45+
assert!(cstr.to_bytes_with_nul().len() <= MAX_THREAD_NAME_LEN); // this should fit
46+
assert_eq!(set_thread_name(&cstr), 0);
47+
48+
let mut buf = vec![0u8; MAX_THREAD_NAME_LEN];
49+
assert_eq!(get_thread_name(&mut buf), 0);
50+
let cstr = CStr::from_bytes_until_nul(&buf).unwrap();
51+
assert_eq!(cstr.to_bytes(), short_name.as_bytes());
52+
})
53+
.unwrap()
54+
.join()
55+
.unwrap();
56+
57+
// Set name via prctl and get it again (long name).
58+
thread::Builder::new()
59+
.spawn(move || {
60+
// Set full thread name.
61+
let cstr = CString::new(long_name.clone()).unwrap();
62+
assert!(cstr.to_bytes_with_nul().len() > MAX_THREAD_NAME_LEN);
63+
// Names are truncated by the Linux kernel.
64+
assert_eq!(set_thread_name(&cstr), 0);
65+
66+
let mut buf = vec![0u8; MAX_THREAD_NAME_LEN];
67+
assert_eq!(get_thread_name(&mut buf), 0);
68+
let cstr = CStr::from_bytes_until_nul(&buf).unwrap();
69+
assert_eq!(cstr.to_bytes(), &long_name.as_bytes()[..(MAX_THREAD_NAME_LEN - 1)]);
70+
})
71+
.unwrap()
72+
.join()
73+
.unwrap();
74+
}

tests/pass-dep/libc/pthread-threadname.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//@ignore-target: windows # No pthreads on Windows
2+
//@ignore-target: android # No pthread_{get,set}_name on Android
23
use std::ffi::{CStr, CString};
34
use std::thread;
45

0 commit comments

Comments
 (0)