Skip to content

Commit 59d6b3c

Browse files
committed
Merge #566
566: Add WaitStatus::PtraceSyscall for use with PTRACE_O_TRACESYSGOOD r=asomers The recommended way to trace syscalls with ptrace is to set the PTRACE_O_TRACESYSGOOD option, to distinguish syscall stops from receiving an actual SIGTRAP. In C, this would cause WSTOPSIG to return SIGTRAP | 0x80, but nix wants to parse that as an actual signal. Add another wait status type for syscall stops (in the language of the ptrace(2) manpage, "PTRACE_EVENT stops" and "Syscall-stops" are different things), and mask out bit 0x80 from signals before trying to parse it. Closes #550
2 parents 26c35c0 + d292984 commit 59d6b3c

File tree

3 files changed

+110
-2
lines changed

3 files changed

+110
-2
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2626
- Added `cfmakeraw`, `cfsetspeed`, and `tcgetsid`. ([#527](https://github.com/nix-rust/nix/pull/527))
2727
- Added "bad none", "bad write_ptr", "bad write_int", and "bad readwrite" variants to the `ioctl!`
2828
macro. ([#670](https://github.com/nix-rust/nix/pull/670))
29+
- On Linux and Android, added support for receiving `PTRACE_O_TRACESYSGOOD`
30+
events from `wait` and `waitpid` using `WaitStatus::PtraceSyscall`
31+
([#566](https://github.com/nix-rust/nix/pull/566)).
2932

3033
### Changed
3134
- Changed `ioctl!(write ...)` into `ioctl!(write_ptr ...)` and `ioctl!(write_int ..)` variants

src/sys/wait.rs

+57-2
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,56 @@ libc_bitflags!(
4040
target_os = "android"))]
4141
const WSTOPPED: WaitPidFlag = WUNTRACED;
4242

43+
/// Possible return values from `wait()` or `waitpid()`.
44+
///
45+
/// Each status (other than `StillAlive`) describes a state transition
46+
/// in a child process `Pid`, such as the process exiting or stopping,
47+
/// plus additional data about the transition if any.
48+
///
49+
/// Note that there are two Linux-specific enum variants, `PtraceEvent`
50+
/// and `PtraceSyscall`. Portable code should avoid exhaustively
51+
/// matching on `WaitStatus`.
4352
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
4453
pub enum WaitStatus {
54+
/// The process exited normally (as with `exit()` or returning from
55+
/// `main`) with the given exit code. This case matches the C macro
56+
/// `WIFEXITED(status)`; the second field is `WEXITSTATUS(status)`.
4557
Exited(Pid, i8),
58+
/// The process was killed by the given signal. The third field
59+
/// indicates whether the signal generated a core dump. This case
60+
/// matches the C macro `WIFSIGNALED(status)`; the last two fields
61+
/// correspond to `WTERMSIG(status)` and `WCOREDUMP(status)`.
4662
Signaled(Pid, Signal, bool),
63+
/// The process is alive, but was stopped by the given signal. This
64+
/// is only reported if `WaitPidFlag::WUNTRACED` was passed. This
65+
/// case matches the C macro `WIFSTOPPED(status)`; the second field
66+
/// is `WSTOPSIG(status)`.
4767
Stopped(Pid, Signal),
68+
/// The traced process was stopped by a `PTRACE_EVENT_*` event. See
69+
/// [`nix::sys::ptrace`] and [`ptrace`(2)] for more information. All
70+
/// currently-defined events use `SIGTRAP` as the signal; the third
71+
/// field is the `PTRACE_EVENT_*` value of the event.
72+
///
73+
/// [`nix::sys::ptrace`]: ../ptrace/index.html
74+
/// [`ptrace`(2)]: http://man7.org/linux/man-pages/man2/ptrace.2.html
4875
#[cfg(any(target_os = "linux", target_os = "android"))]
4976
PtraceEvent(Pid, Signal, c_int),
77+
/// The traced process was stopped by execution of a system call,
78+
/// and `PTRACE_O_TRACESYSGOOD` is in effect. See [`ptrace`(2)] for
79+
/// more information.
80+
///
81+
/// [`ptrace`(2)]: http://man7.org/linux/man-pages/man2/ptrace.2.html
82+
#[cfg(any(target_os = "linux", target_os = "android"))]
83+
PtraceSyscall(Pid),
84+
/// The process was previously stopped but has resumed execution
85+
/// after receiving a `SIGCONT` signal. This is only reported if
86+
/// `WaitPidFlag::WCONTINUED` was passed. This case matches the C
87+
/// macro `WIFCONTINUED(status)`.
5088
Continued(Pid),
89+
/// There are currently no state changes to report in any awaited
90+
/// child process. This is only returned if `WaitPidFlag::WNOHANG`
91+
/// was used (otherwise `wait()` or `waitpid()` would block until
92+
/// there was something to report).
5193
StillAlive
5294
}
5395

@@ -56,6 +98,7 @@ pub enum WaitStatus {
5698
mod status {
5799
use sys::signal::Signal;
58100
use libc::c_int;
101+
use libc::SIGTRAP;
59102

60103
pub fn exited(status: i32) -> bool {
61104
(status & 0x7F) == 0
@@ -82,7 +125,17 @@ mod status {
82125
}
83126

84127
pub fn stop_signal(status: i32) -> Signal {
85-
Signal::from_c_int((status & 0xFF00) >> 8).unwrap()
128+
// Keep only 7 bits of the signal: the high bit
129+
// is used to indicate syscall stops, below.
130+
Signal::from_c_int((status & 0x7F00) >> 8).unwrap()
131+
}
132+
133+
pub fn syscall_stop(status: i32) -> bool {
134+
// From ptrace(2), setting PTRACE_O_TRACESYSGOOD has the effect
135+
// of delivering SIGTRAP | 0x80 as the signal number for syscall
136+
// stops. This allows easily distinguishing syscall stops from
137+
// genuine SIGTRAP signals.
138+
((status & 0xFF00) >> 8) == SIGTRAP | 0x80
86139
}
87140

88141
pub fn stop_additional(status: i32) -> c_int {
@@ -196,7 +249,9 @@ fn decode(pid : Pid, status: i32) -> WaitStatus {
196249
if #[cfg(any(target_os = "linux", target_os = "android"))] {
197250
fn decode_stopped(pid: Pid, status: i32) -> WaitStatus {
198251
let status_additional = status::stop_additional(status);
199-
if status_additional == 0 {
252+
if status::syscall_stop(status) {
253+
WaitStatus::PtraceSyscall(pid)
254+
} else if status_additional == 0 {
200255
WaitStatus::Stopped(pid, status::stop_signal(status))
201256
} else {
202257
WaitStatus::PtraceEvent(pid, status::stop_signal(status), status::stop_additional(status))

test/sys/test_wait.rs

+50
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,53 @@ fn test_wait_exit() {
3434
Err(_) => panic!("Error: Fork Failed")
3535
}
3636
}
37+
38+
#[cfg(any(target_os = "linux", target_os = "android"))]
39+
// FIXME: qemu-user doesn't implement ptrace on most arches
40+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
41+
mod ptrace {
42+
use nix::sys::ptrace::*;
43+
use nix::sys::ptrace::ptrace::*;
44+
use nix::sys::signal::*;
45+
use nix::sys::wait::*;
46+
use nix::unistd::*;
47+
use nix::unistd::ForkResult::*;
48+
use std::{ptr, process};
49+
50+
fn ptrace_child() -> ! {
51+
let _ = ptrace(PTRACE_TRACEME, Pid::from_raw(0), ptr::null_mut(), ptr::null_mut());
52+
// As recommended by ptrace(2), raise SIGTRAP to pause the child
53+
// until the parent is ready to continue
54+
let _ = raise(SIGTRAP);
55+
process::exit(0)
56+
}
57+
58+
fn ptrace_parent(child: Pid) {
59+
// Wait for the raised SIGTRAP
60+
assert_eq!(waitpid(child, None), Ok(WaitStatus::Stopped(child, SIGTRAP)));
61+
// We want to test a syscall stop and a PTRACE_EVENT stop
62+
assert!(ptrace_setoptions(child, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXIT).is_ok());
63+
64+
// First, stop on the next system call, which will be exit()
65+
assert!(ptrace(PTRACE_SYSCALL, child, ptr::null_mut(), ptr::null_mut()).is_ok());
66+
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceSyscall(child)));
67+
// Then get the ptrace event for the process exiting
68+
assert!(ptrace(PTRACE_CONT, child, ptr::null_mut(), ptr::null_mut()).is_ok());
69+
assert_eq!(waitpid(child, None), Ok(WaitStatus::PtraceEvent(child, SIGTRAP, PTRACE_EVENT_EXIT)));
70+
// Finally get the normal wait() result, now that the process has exited
71+
assert!(ptrace(PTRACE_CONT, child, ptr::null_mut(), ptr::null_mut()).is_ok());
72+
assert_eq!(waitpid(child, None), Ok(WaitStatus::Exited(child, 0)));
73+
}
74+
75+
#[test]
76+
fn test_wait_ptrace() {
77+
#[allow(unused_variables)]
78+
let m = ::FORK_MTX.lock().expect("Mutex got poisoned by another test");
79+
80+
match fork() {
81+
Ok(Child) => ptrace_child(),
82+
Ok(Parent { child }) => ptrace_parent(child),
83+
Err(_) => panic!("Error: Fork Failed")
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)